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