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