Fix a segfault occuring when opening some collection files.
[geeqie.git] / src / collect-io.c
1 /*
2  * Geeqie
3  * (C) 2004 John Ellis
4  * Copyright (C) 2008 The Geeqie Team
5  *
6  * Author: John Ellis
7  *
8  * This software is released under the GNU General Public License (GNU GPL).
9  * Please read the included file COPYING for more information.
10  * This software comes with no warranty of any kind, use at your own risk!
11  */
12
13
14 #include "main.h"
15 #include "collect-io.h"
16
17 #include "collect.h"
18 #include "filedata.h"
19 #include "layout_util.h"
20 #include "rcfile.h"
21 #include "secure_save.h"
22 #include "thumb.h"
23 #include "ui_fileops.h"
24
25
26 #define GQ_COLLECTION_MARKER "#" GQ_APPNAME
27
28 #define GQ_COLLECTION_FAIL_MIN     300
29 #define GQ_COLLECTION_FAIL_PERCENT 98
30 #define GQ_COLLECTION_READ_BUFSIZE 4096
31
32 typedef struct _CollectManagerEntry CollectManagerEntry;
33
34 static void collection_load_thumb_step(CollectionData *cd);
35 static gint collection_save_private(CollectionData *cd, const gchar *path);
36
37 static CollectManagerEntry *collect_manager_get_entry(const gchar *path);
38 static void collect_manager_entry_reset(CollectManagerEntry *entry);
39 static gint collect_manager_process_action(CollectManagerEntry *entry, gchar **path_ptr);
40
41
42 static gint scan_geometry(gchar *buffer, gint *x, gint *y, gint *w, gint *h)
43 {
44         gint nx, ny, nw, nh;
45
46         if (sscanf(buffer, "%d %d %d %d", &nx, &ny, &nw, &nh) != 4) return FALSE;
47
48         *x = nx;
49         *y = ny;
50         *w = nw;
51         *h = nh;
52
53         return TRUE;
54 }
55
56 static gint collection_load_private(CollectionData *cd, const gchar *path, CollectionLoadFlags flags)
57 {
58         gchar s_buf[GQ_COLLECTION_READ_BUFSIZE];
59         FILE *f;
60         gchar *pathl;
61         gint limit_failures = TRUE;
62         gint success = TRUE;
63         gint has_official_header = FALSE;
64         gint has_geometry_header = FALSE;
65         gint has_gqview_header   = FALSE;
66         gint need_header         = TRUE;
67         guint total = 0;
68         guint fail = 0;
69         gboolean changed = FALSE;
70         CollectManagerEntry *entry = NULL;
71         guint flush = !!(flags & COLLECTION_LOAD_FLUSH);
72         guint append = !!(flags & COLLECTION_LOAD_APPEND);
73         guint only_geometry = !!(flags & COLLECTION_LOAD_GEOMETRY);
74
75         if (!only_geometry)
76                 {
77                 collection_load_stop(cd);
78
79                 if (flush)
80                         collect_manager_flush();
81                 else
82                         entry = collect_manager_get_entry(path);
83
84                 if (!append)
85                         {
86                         collection_list_free(cd->list);
87                         cd->list = NULL;
88                         }
89                 }
90
91         if (!path && !cd->path) return FALSE;
92
93         if (!path) path = cd->path;
94
95         DEBUG_1("collection load: append=%d flush=%d only_geometry=%d path=%s",
96                           append, flush, only_geometry, path);
97
98         /* load it */
99         pathl = path_from_utf8(path);
100         f = fopen(pathl, "r");
101         g_free(pathl);
102         if (!f)
103                 {
104                 log_printf("Failed to open collection file: \"%s\"\n", path);
105                 return FALSE;
106                 }
107
108         while (fgets(s_buf, sizeof(s_buf), f))
109                 {
110                 gchar *buf;
111                 gchar *p = s_buf;
112
113                 /* Skip whitespaces and empty lines */
114                 while (*p && g_ascii_isspace(*p)) p++;
115                 if (*p == '\n' || *p == '\r') continue;
116
117                 /* Parse comments */
118                 if (*p == '#')
119                         {
120                         if (!need_header) continue;
121                         if (g_ascii_strncasecmp(p, GQ_COLLECTION_MARKER, strlen(GQ_COLLECTION_MARKER)) == 0)
122                                 {
123                                 /* Looks like an official collection, allow unchecked input.
124                                  * All this does is allow adding files that may not exist,
125                                  * which is needed for the collection manager to work.
126                                  * Also unofficial files abort after too many invalid entries.
127                                  */
128                                 has_official_header = TRUE;
129                                 limit_failures = FALSE;
130                                 }
131                         else if (strncmp(p, "#geometry:", 10 ) == 0 &&
132                                  scan_geometry(p + 10, &cd->window_x, &cd->window_y, &cd->window_w, &cd->window_h))
133                                 {
134                                 has_geometry_header = TRUE;
135                                 cd->window_read = TRUE;
136                                 if (only_geometry) break;
137                                 }
138                         else if (g_ascii_strncasecmp(p, "#GQview collection", strlen("#GQview collection")) == 0)
139                                 {
140                                 /* As 2008/04/15 there is no difference between our collection file format
141                                  * and GQview 2.1.5 collection file format so ignore failures as well. */
142                                 has_gqview_header = TRUE;
143                                 limit_failures = FALSE;
144                                 }
145                         need_header = (!has_official_header && !has_gqview_header) || !has_geometry_header;
146                         continue;
147                         }
148
149                 if (only_geometry) continue;
150
151                 /* Read filenames */
152                 buf = quoted_value(p, NULL);
153                 if (buf)
154                         {
155                         gint valid;
156
157                         if (!flush)
158                                 changed |= collect_manager_process_action(entry, &buf);
159
160                         valid = (buf[0] == G_DIR_SEPARATOR && collection_add_check(cd, file_data_new_simple(buf), FALSE, TRUE));
161                         if (!valid) DEBUG_1("collection invalid file: %s", buf);
162                         g_free(buf);
163
164                         total++;
165                         if (!valid)
166                                 {
167                                 fail++;
168                                 if (limit_failures &&
169                                     fail > GQ_COLLECTION_FAIL_MIN &&
170                                     fail * 100 / total > GQ_COLLECTION_FAIL_PERCENT)
171                                         {
172                                         log_printf("%d invalid filenames in unofficial collection file, closing: %s\n", fail, path);
173                                         success = FALSE;
174                                         break;
175                                         }
176                                 }
177                         }
178                 }
179
180         DEBUG_1("collection files: total = %d fail = %d official=%d gqview=%d geometry=%d",
181                           total, fail, has_official_header, has_gqview_header, has_geometry_header);
182
183         fclose(f);
184         if (only_geometry) return has_geometry_header;
185
186         if (!flush)
187                 {
188                 gchar *buf = NULL;
189                 while (collect_manager_process_action(entry, &buf))
190                         {
191                         collection_add_check(cd, file_data_new_simple(buf), FALSE, TRUE);
192                         changed = TRUE;
193                         g_free(buf);
194                         buf = NULL;
195                         }
196                 }
197
198         cd->list = collection_list_sort(cd->list, cd->sort_method);
199
200         if (!flush && changed && success)
201                 collection_save_private(cd, path);
202
203         if (!flush)
204                 collect_manager_entry_reset(entry);
205
206         if (!append) cd->changed = FALSE;
207
208         return success;
209 }
210
211 gint collection_load(CollectionData *cd, const gchar *path, CollectionLoadFlags flags)
212 {
213         if (collection_load_private(cd, path, flags | COLLECTION_LOAD_FLUSH))
214                 {
215                 layout_recent_add_path(cd->path);
216                 return TRUE;
217                 }
218
219         return FALSE;
220 }
221
222 static void collection_load_thumb_do(CollectionData *cd)
223 {
224         GdkPixbuf *pixbuf;
225
226         if (!cd->thumb_loader || !g_list_find(cd->list, cd->thumb_info)) return;
227
228         pixbuf = thumb_loader_get_pixbuf(cd->thumb_loader);
229         collection_info_set_thumb(cd->thumb_info, pixbuf);
230         g_object_unref(pixbuf);
231
232         if (cd->info_updated_func) cd->info_updated_func(cd, cd->thumb_info, cd->info_updated_data);
233 }
234
235 static void collection_load_thumb_error_cb(ThumbLoader *tl, gpointer data)
236 {
237         CollectionData *cd = data;
238
239         collection_load_thumb_do(cd);
240         collection_load_thumb_step(cd);
241 }
242
243 static void collection_load_thumb_done_cb(ThumbLoader *tl, gpointer data)
244 {
245         CollectionData *cd = data;
246
247         collection_load_thumb_do(cd);
248         collection_load_thumb_step(cd);
249 }
250
251 static void collection_load_thumb_step(CollectionData *cd)
252 {
253         GList *work;
254         CollectInfo *ci;
255
256         if (!cd->list)
257                 {
258                 collection_load_stop(cd);
259                 return;
260                 }
261
262         work = cd->list;
263         ci = work->data;
264         work = work->next;
265         /* find first unloaded thumb */
266         while (work && ci->pixbuf)
267                 {
268                 ci = work->data;
269                 work = work->next;
270                 }
271
272         if (!ci || ci->pixbuf)
273                 {
274                 /* done */
275                 collection_load_stop(cd);
276
277                 /* send a NULL CollectInfo to notify end */
278                 if (cd->info_updated_func) cd->info_updated_func(cd, NULL, cd->info_updated_data);
279
280                 return;
281                 }
282
283         /* setup loader and call it */
284         cd->thumb_info = ci;
285         thumb_loader_free(cd->thumb_loader);
286         cd->thumb_loader = thumb_loader_new(options->thumbnails.max_width, options->thumbnails.max_height);
287         thumb_loader_set_callbacks(cd->thumb_loader,
288                                    collection_load_thumb_done_cb,
289                                    collection_load_thumb_error_cb,
290                                    NULL,
291                                    cd);
292
293         /* start it */
294         if (!thumb_loader_start(cd->thumb_loader, ci->fd))
295                 {
296                 /* error, handle it, do next */
297                 DEBUG_1("error loading thumb for %s", ci->fd->path);
298                 collection_load_thumb_do(cd);
299                 collection_load_thumb_step(cd);
300                 }
301 }
302
303 void collection_load_thumb_idle(CollectionData *cd)
304 {
305         if (!cd->thumb_loader) collection_load_thumb_step(cd);
306 }
307
308 gint collection_load_begin(CollectionData *cd, const gchar *path, CollectionLoadFlags flags)
309 {
310         if (!collection_load(cd, path, flags)) return FALSE;
311
312         collection_load_thumb_idle(cd);
313
314         return TRUE;
315 }
316
317 void collection_load_stop(CollectionData *cd)
318 {
319         if (!cd->thumb_loader) return;
320
321         thumb_loader_free(cd->thumb_loader);
322         cd->thumb_loader = NULL;
323 }
324
325 static gint collection_save_private(CollectionData *cd, const gchar *path)
326 {
327         SecureSaveInfo *ssi;
328         GList *work;
329         gchar *pathl;
330
331         if (!path && !cd->path) return FALSE;
332
333         if (!path)
334                 {
335                 path = cd->path;
336                 }
337
338
339         pathl = path_from_utf8(path);
340         ssi = secure_open(pathl);
341         g_free(pathl);
342         if (!ssi)
343                 {
344                 log_printf(_("failed to open collection (write) \"%s\"\n"), path);
345                 return FALSE;
346                 }
347
348         secure_fprintf(ssi, "%s collection\n", GQ_COLLECTION_MARKER);
349         secure_fprintf(ssi, "#created with %s version %s\n", GQ_APPNAME, VERSION);
350
351         collection_update_geometry(cd);
352         if (cd->window_read)
353                 {
354                 secure_fprintf(ssi, "#geometry: %d %d %d %d\n", cd->window_x, cd->window_y, cd->window_w, cd->window_h);
355                 }
356
357         work = cd->list;
358         while (work && secsave_errno == SS_ERR_NONE)
359                 {
360                 CollectInfo *ci = work->data;
361                 secure_fprintf(ssi, "\"%s\"\n", ci->fd->path);
362                 work = work->next;
363                 }
364
365         secure_fprintf(ssi, "#end\n");
366
367         if (secure_close(ssi))
368                 {
369                 log_printf(_("error saving collection file: %s\nerror: %s\n"), path,
370                             secsave_strerror(secsave_errno));
371                 return FALSE;
372                 }
373
374         if (!cd->path || strcmp(path, cd->path) != 0)
375                 {
376                 gchar *buf = cd->path;
377                 cd->path = g_strdup(path);
378                 path = cd->path;
379                 g_free(buf);
380
381                 g_free(cd->name);
382                 cd->name = g_strdup(filename_from_path(cd->path));
383
384                 collection_path_changed(cd);
385                 }
386
387         cd->changed = FALSE;
388
389         return TRUE;
390 }
391
392 gint collection_save(CollectionData *cd, const gchar *path)
393 {
394         if (collection_save_private(cd, path))
395                 {
396                 layout_recent_add_path(cd->path);
397                 return TRUE;
398                 }
399
400         return FALSE;
401 }
402
403 gint collection_load_only_geometry(CollectionData *cd, const gchar *path)
404 {
405         return collection_load(cd, path, COLLECTION_LOAD_GEOMETRY);
406 }
407
408
409 /*
410  *-------------------------------------------------------------------
411  * collection manager
412  *-------------------------------------------------------------------
413  */
414
415 #define COLLECT_MANAGER_ACTIONS_PER_IDLE 1000
416 #define COLLECT_MANAGER_FLUSH_DELAY      10000
417
418 struct _CollectManagerEntry
419 {
420         gchar *path;
421         GList *add_list;
422         GHashTable *oldpath_hash;
423         GHashTable *newpath_hash;
424         gboolean empty;
425 };
426
427 typedef enum {
428         COLLECTION_MANAGER_UPDATE,
429         COLLECTION_MANAGER_ADD,
430         COLLECTION_MANAGER_REMOVE
431 } CollectManagerType;
432
433 typedef struct _CollectManagerAction CollectManagerAction;
434 struct _CollectManagerAction
435 {
436         gchar *oldpath;
437         gchar *newpath;
438
439         CollectManagerType type;
440
441         gint ref;
442 };
443
444
445 static GList *collection_manager_entry_list = NULL;
446 static GList *collection_manager_action_list = NULL;
447 static GList *collection_manager_action_tail = NULL;
448 static gint collection_manager_timer_id = -1;
449
450
451 static CollectManagerAction *collect_manager_action_new(const gchar *oldpath, const gchar *newpath,
452                                                         CollectManagerType type)
453 {
454         CollectManagerAction *action;
455
456         action = g_new0(CollectManagerAction, 1);
457         action->ref = 1;
458
459         action->oldpath = g_strdup(oldpath);
460         action->newpath = g_strdup(newpath);
461
462         action->type = type;
463
464         return action;
465 }
466
467 static void collect_manager_action_ref(CollectManagerAction *action)
468 {
469         action->ref++;
470 }
471
472 static void collect_manager_action_unref(CollectManagerAction *action)
473 {
474         action->ref--;
475
476         if (action->ref > 0) return;
477
478         g_free(action->oldpath);
479         g_free(action->newpath);
480         g_free(action);
481 }
482
483 static void collect_manager_entry_free_data(CollectManagerEntry *entry)
484 {
485         GList *work;
486
487         work = entry->add_list;
488         while (work)
489                 {
490                 CollectManagerAction *action;
491
492                 action = work->data;
493                 work = work->next;
494
495                 collect_manager_action_unref(action);
496                 }
497         g_list_free(entry->add_list);
498         g_hash_table_destroy(entry->oldpath_hash);
499         g_hash_table_destroy(entry->newpath_hash);
500 }
501
502 static void collect_manager_entry_init_data(CollectManagerEntry *entry)
503 {
504         entry->add_list = NULL;
505         entry->oldpath_hash = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify) collect_manager_action_unref);
506         entry->newpath_hash = g_hash_table_new(g_str_hash, g_str_equal);
507         entry->empty = TRUE;
508
509 }
510
511 static CollectManagerEntry *collect_manager_entry_new(const gchar *path)
512 {
513         CollectManagerEntry *entry;
514
515         entry = g_new0(CollectManagerEntry, 1);
516         entry->path = g_strdup(path);
517         collect_manager_entry_init_data(entry);
518
519         collection_manager_entry_list = g_list_append(collection_manager_entry_list, entry);
520
521         return entry;
522 }
523
524
525 static void collect_manager_entry_free(CollectManagerEntry *entry)
526 {
527         collection_manager_entry_list = g_list_remove(collection_manager_entry_list, entry);
528
529         collect_manager_entry_free_data(entry);
530
531         g_free(entry->path);
532         g_free(entry);
533 }
534
535 static void collect_manager_entry_reset(CollectManagerEntry *entry)
536 {
537         collect_manager_entry_free_data(entry);
538         collect_manager_entry_init_data(entry);
539 }
540
541 static CollectManagerEntry *collect_manager_get_entry(const gchar *path)
542 {
543         GList *work;
544
545         work = collection_manager_entry_list;
546         while (work)
547                 {
548                 CollectManagerEntry *entry;
549
550                 entry = work->data;
551                 work = work->next;
552                 if (strcmp(entry->path, path) == 0)
553                         {
554                         return entry;
555                         }
556                 }
557         return NULL;
558
559 }
560
561 static void collect_manager_entry_add_action(CollectManagerEntry *entry, CollectManagerAction *action)
562 {
563
564         CollectManagerAction *orig_action;
565
566         entry->empty = FALSE;
567
568         if (action->oldpath == NULL)
569                 {
570                 /* add file */
571                 if (action->newpath == NULL)
572                         {
573                         return;
574                         }
575
576                 orig_action = g_hash_table_lookup(entry->newpath_hash, action->newpath);
577                 if (orig_action)
578                         {
579                         /* target already exists */
580                         log_printf("collection manager failed to add another action for target %s in collection %s\n",
581                                 action->newpath, entry->path);
582                         return;
583                         }
584                 entry->add_list = g_list_append(entry->add_list, action);
585                 g_hash_table_insert(entry->newpath_hash, action->newpath, action);
586                 collect_manager_action_ref(action);
587                 return;
588                 }
589
590         orig_action = g_hash_table_lookup(entry->newpath_hash, action->oldpath);
591         if (orig_action)
592                 {
593                 /* new action with the same file */
594                 CollectManagerAction *new_action = collect_manager_action_new(orig_action->oldpath, action->newpath, action->type);
595
596                 if (new_action->oldpath)
597                         {
598                         g_hash_table_steal(entry->oldpath_hash, orig_action->oldpath);
599                         g_hash_table_insert(entry->oldpath_hash, new_action->oldpath, new_action);
600                         }
601                 else
602                         {
603                         GList *work = g_list_find(entry->add_list, orig_action);
604                         work->data = new_action;
605                         }
606
607                 g_hash_table_steal(entry->newpath_hash, orig_action->newpath);
608                 if (new_action->newpath)
609                         {
610                         g_hash_table_insert(entry->newpath_hash, new_action->newpath, new_action);
611                         }
612                 collect_manager_action_unref(orig_action);
613                 return;
614                 }
615
616
617         orig_action = g_hash_table_lookup(entry->oldpath_hash, action->oldpath);
618         if (orig_action)
619                 {
620                 /* another action for the same source, ignore */
621                 log_printf("collection manager failed to add another action for source %s in collection %s\n",
622                         action->oldpath, entry->path);
623                 return;
624                 }
625
626         g_hash_table_insert(entry->oldpath_hash, action->oldpath, action);
627         if (action->newpath)
628                 {
629                 g_hash_table_insert(entry->newpath_hash, action->newpath, action);
630                 }
631         collect_manager_action_ref(action);
632 }
633
634 static gint collect_manager_process_action(CollectManagerEntry *entry, gchar **path_ptr)
635 {
636         gchar *path = *path_ptr;
637         CollectManagerAction *action;
638
639         if (path == NULL)
640                 {
641                 /* get new files */
642                 if (entry->add_list)
643                         {
644                         action = entry->add_list->data;
645                         g_assert(action->oldpath == NULL);
646                         entry->add_list = g_list_remove(entry->add_list, action);
647                         path = g_strdup(action->newpath);
648                         g_hash_table_remove(entry->newpath_hash, path);
649                         collect_manager_action_unref(action);
650                         }
651                 *path_ptr = path;
652                 return (path != NULL);
653                 }
654
655         action = g_hash_table_lookup(entry->oldpath_hash, path);
656
657         if (action)
658                 {
659                 g_free(path);
660                 path = g_strdup(action->newpath);
661                 *path_ptr = path;
662                 return TRUE;
663                 }
664
665         return FALSE; /* no change */
666 }
667
668 static void collect_manager_refresh(void)
669 {
670         GList *list;
671         GList *work;
672         gchar *base;
673         FileData *dir_fd;
674
675         base = g_build_filename(homedir(), GQ_RC_DIR_COLLECTIONS, NULL);
676         dir_fd = file_data_new_simple(base);
677         filelist_read(dir_fd, &list, NULL);
678         file_data_unref(dir_fd);
679         g_free(base);
680
681         work = collection_manager_entry_list;
682         while (work && list)
683                 {
684                 CollectManagerEntry *entry;
685                 GList *list_step;
686
687                 entry = work->data;
688                 work = work->next;
689
690                 list_step = list;
691                 while (list_step && entry)
692                         {
693                         FileData *fd;
694
695                         fd = list_step->data;
696                         list_step = list_step->next;
697
698                         if (strcmp(fd->path, entry->path) == 0)
699                                 {
700                                 list = g_list_remove(list, fd);
701                                 file_data_unref(fd);
702
703                                 entry = NULL;
704                                 }
705                         else
706                                 {
707                                 collect_manager_entry_free(entry);
708                                 }
709                         }
710                 }
711
712         work = list;
713         while (work)
714                 {
715                 FileData *fd;
716
717                 fd = work->data;
718                 work = work->next;
719
720                 collect_manager_entry_new(fd->path);
721                 }
722
723         filelist_free(list);
724 }
725
726 static void collect_manager_process_actions(gint max)
727 {
728         if (collection_manager_action_list) DEBUG_1("collection manager processing actions");
729         
730         while (collection_manager_action_list != NULL && max > 0)
731                 {
732                 CollectManagerAction *action;
733                 GList *work;
734
735                 action = collection_manager_action_list->data;
736                 work = collection_manager_entry_list;
737                 while (work)
738                         {
739                         CollectManagerEntry *entry;
740
741                         entry = work->data;
742                         work = work->next;
743
744                         if (action->type == COLLECTION_MANAGER_UPDATE)
745                                 {
746                                 collect_manager_entry_add_action(entry, action);
747                                 }
748                         else if (action->oldpath && action->newpath &&
749                                  strcmp(action->newpath, entry->path) == 0)
750                                 {
751                                 /* convert action to standard add format */
752                                 g_free(action->newpath);
753                                 if (action->type == COLLECTION_MANAGER_ADD)
754                                         {
755                                         action->newpath = action->oldpath;
756                                         action->oldpath = NULL;
757                                         }
758                                 else if (action->type == COLLECTION_MANAGER_REMOVE)
759                                         {
760                                         action->newpath = NULL;
761                                         }
762                                 collect_manager_entry_add_action(entry, action);
763                                 }
764
765                         max--;
766                         }
767
768                 if (action->type != COLLECTION_MANAGER_UPDATE &&
769                     action->oldpath && action->newpath)
770                         {
771                         log_printf("collection manager failed to %s %s for collection %s\n",
772                                 (action->type == COLLECTION_MANAGER_ADD) ? "add" : "remove",
773                                 action->oldpath, action->newpath);
774                         }
775
776                 if (collection_manager_action_tail == collection_manager_action_list)
777                         {
778                         collection_manager_action_tail = NULL;
779                         }
780                 collection_manager_action_list = g_list_remove(collection_manager_action_list, action);
781                 collect_manager_action_unref(action);
782                 }
783 }
784
785 static gint collect_manager_process_entry(CollectManagerEntry *entry)
786 {
787         CollectionData *cd;
788         gint success;
789
790         if (entry->empty) return FALSE;
791
792         cd = collection_new(entry->path);
793         success = collection_load_private(cd, entry->path, COLLECTION_LOAD_NONE);
794
795         collection_unref(cd);
796
797         return TRUE;
798 }
799
800 static gint collect_manager_process_entry_list(void)
801 {
802         GList *work;
803
804         work = collection_manager_entry_list;
805         while (work)
806                 {
807                 CollectManagerEntry *entry;
808
809                 entry = work->data;
810                 work = work->next;
811                 if (collect_manager_process_entry(entry)) return TRUE;
812                 }
813
814         return FALSE;
815 }
816
817
818
819 static gint collect_manager_process_cb(gpointer data)
820 {
821         if (collection_manager_action_list) collect_manager_refresh();
822         collect_manager_process_actions(COLLECT_MANAGER_ACTIONS_PER_IDLE);
823         if (collection_manager_action_list) return TRUE;
824
825         if (collect_manager_process_entry_list()) return TRUE;
826
827         DEBUG_1("collection manager is up to date");
828         return FALSE;
829 }
830
831 static gint collect_manager_timer_cb(gpointer data)
832 {
833         DEBUG_1("collection manager timer expired");
834
835         g_idle_add_full(G_PRIORITY_LOW, collect_manager_process_cb, NULL, NULL);
836
837         collection_manager_timer_id = -1;
838         return FALSE;
839 }
840
841 static void collect_manager_timer_push(gint stop)
842 {
843         if (collection_manager_timer_id != -1)
844                 {
845                 if (!stop) return;
846
847                 g_source_remove(collection_manager_timer_id);
848                 collection_manager_timer_id = -1;
849                 }
850
851         if (!stop)
852                 {
853                 collection_manager_timer_id = g_timeout_add(COLLECT_MANAGER_FLUSH_DELAY,
854                                                             collect_manager_timer_cb, NULL);
855                 DEBUG_1("collection manager timer started");
856                 }
857 }
858
859 static void collect_manager_add_action(CollectManagerAction *action)
860 {
861         if (!action) return;
862
863         /* we keep track of the list's tail to keep this a n(1) operation */
864
865         if (collection_manager_action_tail)
866                 {
867                 collection_manager_action_tail = g_list_append(collection_manager_action_tail, action);
868                 collection_manager_action_tail = collection_manager_action_tail->next;
869                 }
870         else
871                 {
872                 collection_manager_action_list = g_list_append(collection_manager_action_list, action);
873                 collection_manager_action_tail = collection_manager_action_list;
874                 }
875
876         collect_manager_timer_push(FALSE);
877 }
878
879 void collect_manager_moved(FileData *fd)
880 {
881         CollectManagerAction *action;
882         const gchar *oldpath = fd->change->source;
883         const gchar *newpath = fd->change->dest;
884
885         action = collect_manager_action_new(oldpath, newpath, COLLECTION_MANAGER_UPDATE);
886         collect_manager_add_action(action);
887 }
888
889 void collect_manager_add(FileData *fd, const gchar *collection)
890 {
891         CollectManagerAction *action;
892         CollectWindow *cw;
893
894         if (!fd || !collection) return;
895
896         cw = collection_window_find_by_path(collection);
897         if (cw)
898                 {
899                 if (collection_list_find_fd(cw->cd->list, fd) == NULL)
900                         {
901                         collection_add(cw->cd, fd, FALSE);
902                         }
903                 return;
904                 }
905
906         action = collect_manager_action_new(fd->path, collection, COLLECTION_MANAGER_ADD);
907         collect_manager_add_action(action);
908 }
909
910 void collect_manager_remove(FileData *fd, const gchar *collection)
911 {
912         CollectManagerAction *action;
913         CollectWindow *cw;
914
915         if (!fd || !collection) return;
916
917         cw = collection_window_find_by_path(collection);
918         if (cw)
919                 {
920                 while (collection_remove(cw->cd, fd));
921                 return;
922                 }
923
924         action = collect_manager_action_new(fd->path, collection, COLLECTION_MANAGER_REMOVE);
925         collect_manager_add_action(action);
926 }
927
928 void collect_manager_flush(void)
929 {
930         collect_manager_timer_push(TRUE);
931
932         DEBUG_1("collection manager flushing");
933         while (collect_manager_process_cb(NULL));
934 }
935
936 void collect_manager_notify_cb(FileData *fd, NotifyType type, gpointer data)
937 {
938
939         if (type != NOTIFY_TYPE_CHANGE || !fd->change) return;
940         
941         switch(fd->change->type)
942                 {
943                 case FILEDATA_CHANGE_MOVE:
944                         collect_manager_moved(fd);
945                         break;
946                 case FILEDATA_CHANGE_COPY:
947                         break;
948                 case FILEDATA_CHANGE_RENAME:
949                         collect_manager_moved(fd);
950                         break;
951                 case FILEDATA_CHANGE_DELETE:
952                         break;
953                 case FILEDATA_CHANGE_UNSPECIFIED:
954                         break;
955                 }
956
957 }