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