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