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