Use functions to return directories instead of constants.
[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 "filedata.h"
19 #include "layout_util.h"
20 #include "misc.h"
21 #include "secure_save.h"
22 #include "thumb.h"
23 #include "ui_fileops.h"
24
25 #define GQ_COLLECTION_MARKER "#" GQ_APPNAME
26
27 #define GQ_COLLECTION_FAIL_MIN     300
28 #define GQ_COLLECTION_FAIL_PERCENT 98
29 #define GQ_COLLECTION_READ_BUFSIZE 4096
30
31 typedef struct _CollectManagerEntry CollectManagerEntry;
32
33 static void collection_load_thumb_step(CollectionData *cd);
34 static gint collection_save_private(CollectionData *cd, const gchar *path);
35
36 static CollectManagerEntry *collect_manager_get_entry(const gchar *path);
37 static void collect_manager_entry_reset(CollectManagerEntry *entry);
38 static gint collect_manager_process_action(CollectManagerEntry *entry, gchar **path_ptr);
39
40
41 static gint scan_geometry(gchar *buffer, gint *x, gint *y, gint *w, gint *h)
42 {
43         gint nx, ny, nw, nh;
44
45         if (sscanf(buffer, "%d %d %d %d", &nx, &ny, &nw, &nh) != 4) return FALSE;
46
47         *x = nx;
48         *y = ny;
49         *w = nw;
50         *h = nh;
51
52         return TRUE;
53 }
54
55 static gint collection_load_private(CollectionData *cd, const gchar *path, CollectionLoadFlags flags)
56 {
57         gchar s_buf[GQ_COLLECTION_READ_BUFSIZE];
58         FILE *f;
59         gchar *pathl;
60         gint limit_failures = TRUE;
61         gint success = TRUE;
62         gint has_official_header = FALSE;
63         gint has_geometry_header = FALSE;
64         gint has_gqview_header   = FALSE;
65         gint need_header         = TRUE;
66         guint total = 0;
67         guint fail = 0;
68         gboolean changed = FALSE;
69         CollectManagerEntry *entry = NULL;
70         guint flush = !!(flags & COLLECTION_LOAD_FLUSH);
71         guint append = !!(flags & COLLECTION_LOAD_APPEND);
72         guint only_geometry = !!(flags & COLLECTION_LOAD_GEOMETRY);
73
74         if (!only_geometry)
75                 {
76                 collection_load_stop(cd);
77
78                 if (flush)
79                         collect_manager_flush();
80                 else
81                         entry = collect_manager_get_entry(path);
82
83                 if (!append)
84                         {
85                         collection_list_free(cd->list);
86                         cd->list = NULL;
87                         }
88                 }
89
90         if (!path && !cd->path) return FALSE;
91
92         if (!path) path = cd->path;
93
94         DEBUG_1("collection load: append=%d flush=%d only_geometry=%d path=%s",
95                           append, flush, only_geometry, path);
96
97         /* load it */
98         pathl = path_from_utf8(path);
99         f = fopen(pathl, "r");
100         g_free(pathl);
101         if (!f)
102                 {
103                 log_printf("Failed to open collection file: \"%s\"\n", path);
104                 return FALSE;
105                 }
106
107         while (fgets(s_buf, sizeof(s_buf), f))
108                 {
109                 gchar *buf;
110                 gchar *p = s_buf;
111
112                 /* Skip whitespaces and empty lines */
113                 while (*p && g_ascii_isspace(*p)) p++;
114                 if (*p == '\n' || *p == '\r') continue;
115
116                 /* Parse comments */
117                 if (*p == '#')
118                         {
119                         if (!need_header) continue;
120                         if (g_ascii_strncasecmp(p, GQ_COLLECTION_MARKER, strlen(GQ_COLLECTION_MARKER)) == 0)
121                                 {
122                                 /* Looks like an official collection, allow unchecked input.
123                                  * All this does is allow adding files that may not exist,
124                                  * which is needed for the collection manager to work.
125                                  * Also unofficial files abort after too many invalid entries.
126                                  */
127                                 has_official_header = TRUE;
128                                 limit_failures = FALSE;
129                                 }
130                         else if (strncmp(p, "#geometry:", 10 ) == 0 &&
131                                  scan_geometry(p + 10, &cd->window_x, &cd->window_y, &cd->window_w, &cd->window_h))
132                                 {
133                                 has_geometry_header = TRUE;
134                                 cd->window_read = TRUE;
135                                 if (only_geometry) break;
136                                 }
137                         else if (g_ascii_strncasecmp(p, "#GQview collection", strlen("#GQview collection")) == 0)
138                                 {
139                                 /* As 2008/04/15 there is no difference between our collection file format
140                                  * and GQview 2.1.5 collection file format so ignore failures as well. */
141                                 has_gqview_header = TRUE;
142                                 limit_failures = FALSE;
143                                 }
144                         need_header = (!has_official_header && !has_gqview_header) || !has_geometry_header;
145                         continue;
146                         }
147
148                 if (only_geometry) continue;
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] == G_DIR_SEPARATOR && 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                                         log_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);
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))
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                 log_printf(_("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                 log_printf(_("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                         log_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                 log_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;
670         GList *work;
671         FileData *dir_fd;
672
673         dir_fd = file_data_new_simple(get_collections_dir());
674         filelist_read(dir_fd, &list, NULL);
675         file_data_unref(dir_fd);
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                         FileData *fd;
690
691                         fd = list_step->data;
692                         list_step = list_step->next;
693
694                         if (strcmp(fd->path, entry->path) == 0)
695                                 {
696                                 list = g_list_remove(list, fd);
697                                 file_data_unref(fd);
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                 FileData *fd;
712
713                 fd = work->data;
714                 work = work->next;
715
716                 collect_manager_entry_new(fd->path);
717                 }
718
719         filelist_free(list);
720 }
721
722 static void collect_manager_process_actions(gint max)
723 {
724         if (collection_manager_action_list) DEBUG_1("collection manager processing actions");
725         
726         while (collection_manager_action_list != NULL && max > 0)
727                 {
728                 CollectManagerAction *action;
729                 GList *work;
730
731                 action = collection_manager_action_list->data;
732                 work = collection_manager_entry_list;
733                 while (work)
734                         {
735                         CollectManagerEntry *entry;
736
737                         entry = work->data;
738                         work = work->next;
739
740                         if (action->type == COLLECTION_MANAGER_UPDATE)
741                                 {
742                                 collect_manager_entry_add_action(entry, action);
743                                 }
744                         else if (action->oldpath && action->newpath &&
745                                  strcmp(action->newpath, entry->path) == 0)
746                                 {
747                                 /* convert action to standard add format */
748                                 g_free(action->newpath);
749                                 if (action->type == COLLECTION_MANAGER_ADD)
750                                         {
751                                         action->newpath = action->oldpath;
752                                         action->oldpath = NULL;
753                                         }
754                                 else if (action->type == COLLECTION_MANAGER_REMOVE)
755                                         {
756                                         action->newpath = NULL;
757                                         }
758                                 collect_manager_entry_add_action(entry, action);
759                                 }
760
761                         max--;
762                         }
763
764                 if (action->type != COLLECTION_MANAGER_UPDATE &&
765                     action->oldpath && action->newpath)
766                         {
767                         log_printf("collection manager failed to %s %s for collection %s\n",
768                                 (action->type == COLLECTION_MANAGER_ADD) ? "add" : "remove",
769                                 action->oldpath, action->newpath);
770                         }
771
772                 if (collection_manager_action_tail == collection_manager_action_list)
773                         {
774                         collection_manager_action_tail = NULL;
775                         }
776                 collection_manager_action_list = g_list_remove(collection_manager_action_list, action);
777                 collect_manager_action_unref(action);
778                 }
779 }
780
781 static gint collect_manager_process_entry(CollectManagerEntry *entry)
782 {
783         CollectionData *cd;
784         gint success;
785
786         if (entry->empty) return FALSE;
787
788         cd = collection_new(entry->path);
789         success = collection_load_private(cd, entry->path, COLLECTION_LOAD_NONE);
790
791         collection_unref(cd);
792
793         return TRUE;
794 }
795
796 static gint collect_manager_process_entry_list(void)
797 {
798         GList *work;
799
800         work = collection_manager_entry_list;
801         while (work)
802                 {
803                 CollectManagerEntry *entry;
804
805                 entry = work->data;
806                 work = work->next;
807                 if (collect_manager_process_entry(entry)) return TRUE;
808                 }
809
810         return FALSE;
811 }
812
813
814
815 static gint collect_manager_process_cb(gpointer data)
816 {
817         if (collection_manager_action_list) collect_manager_refresh();
818         collect_manager_process_actions(COLLECT_MANAGER_ACTIONS_PER_IDLE);
819         if (collection_manager_action_list) return TRUE;
820
821         if (collect_manager_process_entry_list()) return TRUE;
822
823         DEBUG_1("collection manager is up to date");
824         return FALSE;
825 }
826
827 static gint collect_manager_timer_cb(gpointer data)
828 {
829         DEBUG_1("collection manager timer expired");
830
831         g_idle_add_full(G_PRIORITY_LOW, collect_manager_process_cb, NULL, NULL);
832
833         collection_manager_timer_id = -1;
834         return FALSE;
835 }
836
837 static void collect_manager_timer_push(gint stop)
838 {
839         if (collection_manager_timer_id != -1)
840                 {
841                 if (!stop) return;
842
843                 g_source_remove(collection_manager_timer_id);
844                 collection_manager_timer_id = -1;
845                 }
846
847         if (!stop)
848                 {
849                 collection_manager_timer_id = g_timeout_add(COLLECT_MANAGER_FLUSH_DELAY,
850                                                             collect_manager_timer_cb, NULL);
851                 DEBUG_1("collection manager timer started");
852                 }
853 }
854
855 static void collect_manager_add_action(CollectManagerAction *action)
856 {
857         if (!action) return;
858
859         /* we keep track of the list's tail to keep this a n(1) operation */
860
861         if (collection_manager_action_tail)
862                 {
863                 collection_manager_action_tail = g_list_append(collection_manager_action_tail, action);
864                 collection_manager_action_tail = collection_manager_action_tail->next;
865                 }
866         else
867                 {
868                 collection_manager_action_list = g_list_append(collection_manager_action_list, action);
869                 collection_manager_action_tail = collection_manager_action_list;
870                 }
871
872         collect_manager_timer_push(FALSE);
873 }
874
875 void collect_manager_moved(FileData *fd)
876 {
877         CollectManagerAction *action;
878         const gchar *oldpath = fd->change->source;
879         const gchar *newpath = fd->change->dest;
880
881         action = collect_manager_action_new(oldpath, newpath, COLLECTION_MANAGER_UPDATE);
882         collect_manager_add_action(action);
883 }
884
885 void collect_manager_add(FileData *fd, const gchar *collection)
886 {
887         CollectManagerAction *action;
888         CollectWindow *cw;
889
890         if (!fd || !collection) return;
891
892         cw = collection_window_find_by_path(collection);
893         if (cw)
894                 {
895                 if (collection_list_find_fd(cw->cd->list, fd) == NULL)
896                         {
897                         collection_add(cw->cd, fd, FALSE);
898                         }
899                 return;
900                 }
901
902         action = collect_manager_action_new(fd->path, collection, COLLECTION_MANAGER_ADD);
903         collect_manager_add_action(action);
904 }
905
906 void collect_manager_remove(FileData *fd, const gchar *collection)
907 {
908         CollectManagerAction *action;
909         CollectWindow *cw;
910
911         if (!fd || !collection) return;
912
913         cw = collection_window_find_by_path(collection);
914         if (cw)
915                 {
916                 while (collection_remove(cw->cd, fd));
917                 return;
918                 }
919
920         action = collect_manager_action_new(fd->path, collection, COLLECTION_MANAGER_REMOVE);
921         collect_manager_add_action(action);
922 }
923
924 void collect_manager_flush(void)
925 {
926         collect_manager_timer_push(TRUE);
927
928         DEBUG_1("collection manager flushing");
929         while (collect_manager_process_cb(NULL));
930 }
931
932 void collect_manager_notify_cb(FileData *fd, NotifyType type, gpointer data)
933 {
934
935         if (type != NOTIFY_TYPE_CHANGE || !fd->change) return;
936         
937         switch(fd->change->type)
938                 {
939                 case FILEDATA_CHANGE_MOVE:
940                         collect_manager_moved(fd);
941                         break;
942                 case FILEDATA_CHANGE_COPY:
943                         break;
944                 case FILEDATA_CHANGE_RENAME:
945                         collect_manager_moved(fd);
946                         break;
947                 case FILEDATA_CHANGE_DELETE:
948                         break;
949                 case FILEDATA_CHANGE_UNSPECIFIED:
950                         break;
951                 }
952
953 }
954 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */