Move debug macros from main.h to new debug.h.
[geeqie.git] / src / collect-io.c
1 /*
2  * Geeqie
3  * (C) 2004 John Ellis
4  * Copyright (C) 2008 The Geeqie Team
5  *
6  * Author: John Ellis
7  *
8  * This software is released under the GNU General Public License (GNU GPL).
9  * Please read the included file COPYING for more information.
10  * This software comes with no warranty of any kind, use at your own risk!
11  */
12
13
14 #include "main.h"
15 #include "collect-io.h"
16
17 #include "collect.h"
18 #include "debug.h"
19 #include "filelist.h"
20 #include "layout_util.h"
21 #include "rcfile.h"
22 #include "secure_save.h"
23 #include "thumb.h"
24 #include "ui_fileops.h"
25
26
27 #define GQ_COLLECTION_MARKER "#" GQ_APPNAME
28
29 #define GQ_COLLECTION_FAIL_MIN     300
30 #define GQ_COLLECTION_FAIL_PERCENT 98
31 #define GQ_COLLECTION_READ_BUFSIZE 4096
32
33 typedef struct _CollectManagerEntry CollectManagerEntry;
34
35 static void collection_load_thumb_step(CollectionData *cd);
36 static gint collection_save_private(CollectionData *cd, const gchar *path);
37
38 static CollectManagerEntry *collect_manager_get_entry(const gchar *path);
39 static void collect_manager_entry_reset(CollectManagerEntry *entry);
40 static gint collect_manager_process_action(CollectManagerEntry *entry, gchar **path_ptr);
41
42
43 static gint scan_geometry(gchar *buffer, gint *x, gint *y, gint *w, gint *h)
44 {
45         gint nx, ny, nw, nh;
46
47         if(sscanf(buffer, "%d %d %d %d", &nx, &ny, &nw, &nh) != 4) return FALSE;
48
49         *x = nx;
50         *y = ny;
51         *w = nw;
52         *h = nh;
53
54         return TRUE;
55 }
56
57 static gint collection_load_private(CollectionData *cd, const gchar *path, CollectionLoadFlags flags)
58 {
59         gchar s_buf[GQ_COLLECTION_READ_BUFSIZE];
60         FILE *f;
61         gchar *pathl;
62         gint limit_failures = TRUE;
63         gint success = TRUE;
64         gint has_official_header = FALSE;
65         gint has_geometry_header = FALSE;
66         gint has_gqview_header   = FALSE;
67         gint need_header         = TRUE;
68         guint total = 0;
69         guint fail = 0;
70         gboolean changed = FALSE;
71         CollectManagerEntry *entry = NULL;
72         guint flush = !!(flags & COLLECTION_LOAD_FLUSH);
73         guint append = !!(flags & COLLECTION_LOAD_APPEND);
74         guint only_geometry = !!(flags & COLLECTION_LOAD_GEOMETRY);
75
76         if (!only_geometry)
77                 {
78                 collection_load_stop(cd);
79
80                 if (flush)
81                         collect_manager_flush();
82                 else
83                         entry = collect_manager_get_entry(path);
84
85                 if (!append)
86                         {
87                         collection_list_free(cd->list);
88                         cd->list = NULL;
89                         }
90                 }
91
92         if (!path && !cd->path) return FALSE;
93
94         if (!path) path = cd->path;
95
96         DEBUG_1("collection load: append=%d flush=%d only_geometry=%d path=%s",
97                           append, flush, only_geometry, path);
98
99         /* load it */
100         pathl = path_from_utf8(path);
101         f = fopen(pathl, "r");
102         g_free(pathl);
103         if (!f)
104                 {
105                 printf("Failed to open collection file: \"%s\"\n", path);
106                 return FALSE;
107                 }
108
109         while (fgets(s_buf, sizeof(s_buf), f))
110                 {
111                 gchar *buf;
112                 gchar *p = s_buf;
113
114                 /* Skip whitespaces and empty lines */
115                 while (*p && g_ascii_isspace(*p)) p++;
116                 if (*p == '\n' || *p == '\r') continue;
117
118                 /* Parse comments */
119                 if (*p == '#')
120                         {
121                         if (!need_header) continue;
122                         if (strncasecmp(p, GQ_COLLECTION_MARKER, strlen(GQ_COLLECTION_MARKER)) == 0)
123                                 {
124                                 /* Looks like an official collection, allow unchecked input.
125                                  * All this does is allow adding files that may not exist,
126                                  * which is needed for the collection manager to work.
127                                  * Also unofficial files abort after too many invalid entries.
128                                  */
129                                 has_official_header = TRUE;
130                                 limit_failures = FALSE;
131                                 }
132                         else if (strncmp(p, "#geometry:", 10 ) == 0 &&
133                                  scan_geometry(p + 10, &cd->window_x, &cd->window_y, &cd->window_w, &cd->window_h))
134                                 {
135                                 has_geometry_header = TRUE;
136                                 cd->window_read = TRUE;
137                                 if (only_geometry) break;
138                                 }
139                         else if (strncasecmp(p, "#GQview collection", strlen("#GQview collection")) == 0)
140                                 {
141                                 /* As 2008/04/15 there is no difference between our collection file format
142                                  * and GQview 2.1.5 collection file format so ignore failures as well. */
143                                 has_gqview_header = TRUE;
144                                 limit_failures = FALSE;
145                                 }
146                         need_header = (!has_official_header && !has_gqview_header) || !has_geometry_header;
147                         continue;
148                         }
149
150                 /* Read filenames */
151                 buf = quoted_value(p, NULL);
152                 if (buf)
153                         {
154                         gint valid;
155
156                         if (!flush)
157                                 changed |= collect_manager_process_action(entry, &buf);
158
159                         valid = (buf[0] == '/' && collection_add_check(cd, file_data_new_simple(buf), FALSE, TRUE));
160                         if (!valid) DEBUG_1("collection invalid file: %s", buf);
161                         g_free(buf);
162
163                         total++;
164                         if (!valid)
165                                 {
166                                 fail++;
167                                 if (limit_failures &&
168                                     fail > GQ_COLLECTION_FAIL_MIN &&
169                                     fail * 100 / total > GQ_COLLECTION_FAIL_PERCENT)
170                                         {
171                                         printf("%d invalid filenames in unofficial collection file, closing: %s\n", fail, path);
172                                         success = FALSE;
173                                         break;
174                                         }
175                                 }
176                         }
177                 }
178
179         DEBUG_1("collection files: total = %d fail = %d official=%d gqview=%d geometry=%d",
180                           total, fail, has_official_header, has_gqview_header, has_geometry_header);
181
182         fclose(f);
183         if (only_geometry) return has_geometry_header;
184
185         if (!flush)
186                 {
187                 gchar *buf = NULL;
188                 while (collect_manager_process_action(entry, &buf))
189                         {
190                         collection_add_check(cd, file_data_new_simple(buf), FALSE, TRUE);
191                         changed = TRUE;
192                         g_free(buf);
193                         buf = NULL;
194                         }
195                 }
196
197         cd->list = collection_list_sort(cd->list, cd->sort_method);
198
199         if (!flush && changed && success)
200                 collection_save_private(cd, path);
201
202         if (!flush)
203                 collect_manager_entry_reset(entry);
204
205         if (!append) cd->changed = FALSE;
206
207         return success;
208 }
209
210 gint collection_load(CollectionData *cd, const gchar *path, CollectionLoadFlags flags)
211 {
212         if (collection_load_private(cd, path, flags | COLLECTION_LOAD_FLUSH))
213                 {
214                 layout_recent_add_path(cd->path);
215                 return TRUE;
216                 }
217
218         return FALSE;
219 }
220
221 static void collection_load_thumb_do(CollectionData *cd)
222 {
223         GdkPixbuf *pixbuf;
224
225         if (!cd->thumb_loader || !g_list_find(cd->list, cd->thumb_info)) return;
226
227         pixbuf = thumb_loader_get_pixbuf(cd->thumb_loader, TRUE);
228         collection_info_set_thumb(cd->thumb_info, pixbuf);
229         g_object_unref(pixbuf);
230
231         if (cd->info_updated_func) cd->info_updated_func(cd, cd->thumb_info, cd->info_updated_data);
232 }
233
234 static void collection_load_thumb_error_cb(ThumbLoader *tl, gpointer data)
235 {
236         CollectionData *cd = data;
237
238         collection_load_thumb_do(cd);
239         collection_load_thumb_step(cd);
240 }
241
242 static void collection_load_thumb_done_cb(ThumbLoader *tl, gpointer data)
243 {
244         CollectionData *cd = data;
245
246         collection_load_thumb_do(cd);
247         collection_load_thumb_step(cd);
248 }
249
250 static void collection_load_thumb_step(CollectionData *cd)
251 {
252         GList *work;
253         CollectInfo *ci;
254
255         if (!cd->list)
256                 {
257                 collection_load_stop(cd);
258                 return;
259                 }
260
261         work = cd->list;
262         ci = work->data;
263         work = work->next;
264         /* find first unloaded thumb */
265         while (work && ci->pixbuf)
266                 {
267                 ci = work->data;
268                 work = work->next;
269                 }
270
271         if (!ci || ci->pixbuf)
272                 {
273                 /* done */
274                 collection_load_stop(cd);
275
276                 /* send a NULL CollectInfo to notify end */
277                 if (cd->info_updated_func) cd->info_updated_func(cd, NULL, cd->info_updated_data);
278
279                 return;
280                 }
281
282         /* setup loader and call it */
283         cd->thumb_info = ci;
284         thumb_loader_free(cd->thumb_loader);
285         cd->thumb_loader = thumb_loader_new(options->thumbnails.max_width, options->thumbnails.max_height);
286         thumb_loader_set_callbacks(cd->thumb_loader,
287                                    collection_load_thumb_done_cb,
288                                    collection_load_thumb_error_cb,
289                                    NULL,
290                                    cd);
291
292         /* start it */
293         if (!thumb_loader_start(cd->thumb_loader, ci->fd->path))
294                 {
295                 /* error, handle it, do next */
296                 DEBUG_1("error loading thumb for %s", ci->fd->path);
297                 collection_load_thumb_do(cd);
298                 collection_load_thumb_step(cd);
299                 }
300 }
301
302 void collection_load_thumb_idle(CollectionData *cd)
303 {
304         if (!cd->thumb_loader) collection_load_thumb_step(cd);
305 }
306
307 gint collection_load_begin(CollectionData *cd, const gchar *path, CollectionLoadFlags flags)
308 {
309         if (!collection_load(cd, path, flags)) return FALSE;
310
311         collection_load_thumb_idle(cd);
312
313         return TRUE;
314 }
315
316 void collection_load_stop(CollectionData *cd)
317 {
318         if (!cd->thumb_loader) return;
319
320         thumb_loader_free(cd->thumb_loader);
321         cd->thumb_loader = NULL;
322 }
323
324 static gint collection_save_private(CollectionData *cd, const gchar *path)
325 {
326         SecureSaveInfo *ssi;
327         GList *work;
328         gchar *pathl;
329
330         if (!path && !cd->path) return FALSE;
331
332         if (!path)
333                 {
334                 path = cd->path;
335                 }
336
337
338         pathl = path_from_utf8(path);
339         ssi = secure_open(pathl);
340         g_free(pathl);
341         if (!ssi)
342                 {
343                 printf_term(_("failed to open collection (write) \"%s\"\n"), path);
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                 printf_term(_("error saving collection file: %s\nerror: %s\n"), path,
369                             secsave_strerror(secsave_errno));
370                 return FALSE;
371                 }
372
373         if (!cd->path || strcmp(path, cd->path) != 0)
374                 {
375                 gchar *buf = cd->path;
376                 cd->path = g_strdup(path);
377                 path = cd->path;
378                 g_free(buf);
379
380                 g_free(cd->name);
381                 cd->name = g_strdup(filename_from_path(cd->path));
382
383                 collection_path_changed(cd);
384                 }
385
386         cd->changed = FALSE;
387
388         return TRUE;
389 }
390
391 gint collection_save(CollectionData *cd, const gchar *path)
392 {
393         if (collection_save_private(cd, path))
394                 {
395                 layout_recent_add_path(cd->path);
396                 return TRUE;
397                 }
398
399         return FALSE;
400 }
401
402 gint collection_load_only_geometry(CollectionData *cd, const gchar *path)
403 {
404         return collection_load(cd, path, COLLECTION_LOAD_GEOMETRY);
405 }
406
407
408 /*
409  *-------------------------------------------------------------------
410  * collection manager
411  *-------------------------------------------------------------------
412  */
413
414 #define COLLECT_MANAGER_ACTIONS_PER_IDLE 1000
415 #define COLLECT_MANAGER_FLUSH_DELAY      10000
416
417 struct _CollectManagerEntry
418 {
419         gchar *path;
420         GList *add_list;
421         GHashTable *oldpath_hash;
422         GHashTable *newpath_hash;
423         gboolean empty;
424 };
425
426 typedef enum {
427         COLLECTION_MANAGER_UPDATE,
428         COLLECTION_MANAGER_ADD,
429         COLLECTION_MANAGER_REMOVE
430 } CollectManagerType;
431
432 typedef struct _CollectManagerAction CollectManagerAction;
433 struct _CollectManagerAction
434 {
435         gchar *oldpath;
436         gchar *newpath;
437
438         CollectManagerType type;
439
440         gint ref;
441 };
442
443
444 static GList *collection_manager_entry_list = NULL;
445 static GList *collection_manager_action_list = NULL;
446 static GList *collection_manager_action_tail = NULL;
447 static gint collection_manager_timer_id = -1;
448
449
450 static CollectManagerAction *collect_manager_action_new(const gchar *oldpath, const gchar *newpath,
451                                                         CollectManagerType type)
452 {
453         CollectManagerAction *action;
454
455         action = g_new0(CollectManagerAction, 1);
456         action->ref = 1;
457
458         action->oldpath = g_strdup(oldpath);
459         action->newpath = g_strdup(newpath);
460
461         action->type = type;
462
463         return action;
464 }
465
466 static void collect_manager_action_ref(CollectManagerAction *action)
467 {
468         action->ref++;
469 }
470
471 static void collect_manager_action_unref(CollectManagerAction *action)
472 {
473         action->ref--;
474
475         if (action->ref > 0) return;
476
477         g_free(action->oldpath);
478         g_free(action->newpath);
479         g_free(action);
480 }
481
482 static void collect_manager_entry_free_data(CollectManagerEntry *entry)
483 {
484         GList *work;
485
486         work = entry->add_list;
487         while (work)
488                 {
489                 CollectManagerAction *action;
490
491                 action = work->data;
492                 work = work->next;
493
494                 collect_manager_action_unref(action);
495                 }
496         g_list_free(entry->add_list);
497         g_hash_table_destroy(entry->oldpath_hash);
498         g_hash_table_destroy(entry->newpath_hash);
499 }
500
501 static void collect_manager_entry_init_data(CollectManagerEntry *entry)
502 {
503         entry->add_list = NULL;
504         entry->oldpath_hash = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify) collect_manager_action_unref);
505         entry->newpath_hash = g_hash_table_new(g_str_hash, g_str_equal);
506         entry->empty = TRUE;
507
508 }
509
510 static CollectManagerEntry *collect_manager_entry_new(const gchar *path)
511 {
512         CollectManagerEntry *entry;
513
514         entry = g_new0(CollectManagerEntry, 1);
515         entry->path = g_strdup(path);
516         collect_manager_entry_init_data(entry);
517
518         collection_manager_entry_list = g_list_append(collection_manager_entry_list, entry);
519
520         return entry;
521 }
522
523
524 static void collect_manager_entry_free(CollectManagerEntry *entry)
525 {
526         collection_manager_entry_list = g_list_remove(collection_manager_entry_list, entry);
527
528         collect_manager_entry_free_data(entry);
529
530         g_free(entry->path);
531         g_free(entry);
532 }
533
534 static void collect_manager_entry_reset(CollectManagerEntry *entry)
535 {
536         collect_manager_entry_free_data(entry);
537         collect_manager_entry_init_data(entry);
538 }
539
540 static CollectManagerEntry *collect_manager_get_entry(const gchar *path)
541 {
542         GList *work;
543
544         work = collection_manager_entry_list;
545         while (work)
546                 {
547                 CollectManagerEntry *entry;
548
549                 entry = work->data;
550                 work = work->next;
551                 if (strcmp(entry->path, path) == 0)
552                         {
553                         return entry;
554                         }
555                 }
556         return NULL;
557
558 }
559
560 static void collect_manager_entry_add_action(CollectManagerEntry *entry, CollectManagerAction *action)
561 {
562
563         CollectManagerAction *orig_action;
564
565         entry->empty = FALSE;
566
567         if (action->oldpath == NULL)
568                 {
569                 /* add file */
570                 if (action->newpath == NULL)
571                         {
572                         return;
573                         }
574
575                 orig_action = g_hash_table_lookup(entry->newpath_hash, action->newpath);
576                 if (orig_action)
577                         {
578                         /* target already exists */
579                         printf("collection manager failed to add another action for target %s in collection %s\n",
580                                 action->newpath, entry->path);
581                         return;
582                         }
583                 entry->add_list = g_list_append(entry->add_list, action);
584                 g_hash_table_insert(entry->newpath_hash, action->newpath, action);
585                 collect_manager_action_ref(action);
586                 return;
587                 }
588
589         orig_action = g_hash_table_lookup(entry->newpath_hash, action->oldpath);
590         if (orig_action)
591                 {
592                 /* new action with the same file */
593                 CollectManagerAction *new_action = collect_manager_action_new(orig_action->oldpath, action->newpath, action->type);
594
595                 if (new_action->oldpath)
596                         {
597                         g_hash_table_steal(entry->oldpath_hash, orig_action->oldpath);
598                         g_hash_table_insert(entry->oldpath_hash, new_action->oldpath, new_action);
599                         }
600                 else
601                         {
602                         GList *work = g_list_find(entry->add_list, orig_action);
603                         work->data = new_action;
604                         }
605
606                 g_hash_table_steal(entry->newpath_hash, orig_action->newpath);
607                 if (new_action->newpath)
608                         {
609                         g_hash_table_insert(entry->newpath_hash, new_action->newpath, new_action);
610                         }
611                 collect_manager_action_unref(orig_action);
612                 return;
613                 }
614
615
616         orig_action = g_hash_table_lookup(entry->oldpath_hash, action->oldpath);
617         if (orig_action)
618                 {
619                 /* another action for the same source, ignore */
620                 printf("collection manager failed to add another action for source %s in collection %s\n",
621                         action->oldpath, entry->path);
622                 return;
623                 }
624
625         g_hash_table_insert(entry->oldpath_hash, action->oldpath, action);
626         if (action->newpath)
627                 {
628                 g_hash_table_insert(entry->newpath_hash, action->newpath, action);
629                 }
630         collect_manager_action_ref(action);
631 }
632
633 static gint collect_manager_process_action(CollectManagerEntry *entry, gchar **path_ptr)
634 {
635         gchar *path = *path_ptr;
636         CollectManagerAction *action;
637
638         if (path == NULL)
639                 {
640                 /* get new files */
641                 if (entry->add_list)
642                         {
643                         action = entry->add_list->data;
644                         g_assert(action->oldpath == NULL);
645                         entry->add_list = g_list_remove(entry->add_list, action);
646                         path = g_strdup(action->newpath);
647                         g_hash_table_remove(entry->newpath_hash, path);
648                         collect_manager_action_unref(action);
649                         }
650                 *path_ptr = path;
651                 return (path != NULL);
652                 }
653
654         action = g_hash_table_lookup(entry->oldpath_hash, path);
655
656         if (action)
657                 {
658                 g_free(path);
659                 path = g_strdup(action->newpath);
660                 *path_ptr = path;
661                 return TRUE;
662                 }
663
664         return FALSE; /* no change */
665 }
666
667 static void collect_manager_refresh(void)
668 {
669         GList *list = NULL;
670         GList *work;
671         gchar *base;
672
673         base = g_strconcat(homedir(), "/", GQ_RC_DIR_COLLECTIONS, NULL);
674         path_list(base, &list, NULL);
675         g_free(base);
676
677         work = collection_manager_entry_list;
678         while (work && list)
679                 {
680                 CollectManagerEntry *entry;
681                 GList *list_step;
682
683                 entry = work->data;
684                 work = work->next;
685
686                 list_step = list;
687                 while (list_step && entry)
688                         {
689                         gchar *path;
690
691                         path = list_step->data;
692                         list_step = list_step->next;
693
694                         if (strcmp(path, entry->path) == 0)
695                                 {
696                                 list = g_list_remove(list, path);
697                                 g_free(path);
698
699                                 entry = NULL;
700                                 }
701                         else
702                                 {
703                                 collect_manager_entry_free(entry);
704                                 }
705                         }
706                 }
707
708         work = list;
709         while (work)
710                 {
711                 gchar *path;
712
713                 path = work->data;
714                 work = work->next;
715
716                 collect_manager_entry_new(path);
717                 g_free(path);
718                 }
719
720         g_list_free(list);
721 }
722
723 static void collect_manager_process_actions(gint max)
724 {
725         if (collection_manager_action_list) DEBUG_1("collection manager processing actions");
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         DEBUG_1("collection manager is up to date");
825         return FALSE;
826 }
827
828 static gint collect_manager_timer_cb(gpointer data)
829 {
830         DEBUG_1("collection manager timer expired");
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                 DEBUG_1("collection manager timer started");
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         DEBUG_1("collection manager flushing");
930         while (collect_manager_process_cb(NULL));
931 }