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