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