Documentation: Use G_SOURCE_CONTINUE and G_SOURCE_REMOVE
[geeqie.git] / src / collect-io.cc
1 /*
2  * Copyright (C) 2004 John Ellis
3  * Copyright (C) 2008 - 2016 The Geeqie Team
4  *
5  * Author: John Ellis
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21
22 #include "main.h"
23 #include "collect-io.h"
24
25 #include "collect.h"
26 #include "filedata.h"
27 #include "layout-util.h"
28 #include "secure-save.h"
29 #include "thumb.h"
30 #include "ui-fileops.h"
31
32 #define GQ_COLLECTION_MARKER "#" GQ_APPNAME
33
34 #define GQ_COLLECTION_FAIL_MIN     300
35 #define GQ_COLLECTION_FAIL_PERCENT 98
36 #define GQ_COLLECTION_READ_BUFSIZE 4096
37
38 typedef struct _CollectManagerEntry CollectManagerEntry;
39
40 static void collection_load_thumb_step(CollectionData *cd);
41 static gboolean collection_save_private(CollectionData *cd, const gchar *path);
42
43 static CollectManagerEntry *collect_manager_get_entry(const gchar *path);
44 static void collect_manager_entry_reset(CollectManagerEntry *entry);
45 static gint collect_manager_process_action(CollectManagerEntry *entry, gchar **path_ptr);
46
47
48 static gboolean scan_geometry(gchar *buffer, gint *x, gint *y, gint *w, gint *h)
49 {
50         gint nx, ny, nw, nh;
51
52         if (sscanf(buffer, "%d %d %d %d", &nx, &ny, &nw, &nh) != 4) return FALSE;
53
54         *x = nx;
55         *y = ny;
56         *w = nw;
57         *h = nh;
58
59         return TRUE;
60 }
61
62 static gboolean collection_load_private(CollectionData *cd, const gchar *path, CollectionLoadFlags flags)
63 {
64         gchar s_buf[GQ_COLLECTION_READ_BUFSIZE];
65         FILE *f;
66         gchar *pathl;
67         gboolean limit_failures = TRUE;
68         gboolean success = TRUE;
69         gboolean has_official_header = FALSE;
70         gboolean has_geometry_header = FALSE;
71         gboolean has_gqview_header   = FALSE;
72         gboolean need_header     = TRUE;
73         guint total = 0;
74         guint fail = 0;
75         gboolean changed = FALSE;
76         CollectManagerEntry *entry = NULL;
77         guint flush = !!(flags & COLLECTION_LOAD_FLUSH);
78         guint append = !!(flags & COLLECTION_LOAD_APPEND);
79         guint only_geometry = !!(flags & COLLECTION_LOAD_GEOMETRY);
80         gboolean reading_extended_filename = FALSE;
81         GString *extended_filename_buffer = g_string_new(NULL);
82         gchar *buffer2;
83
84         if (!only_geometry)
85                 {
86                 collection_load_stop(cd);
87
88                 if (flush)
89                         collect_manager_flush();
90                 else
91                         entry = collect_manager_get_entry(path);
92
93                 if (!append)
94                         {
95                         collection_list_free(cd->list);
96                         cd->list = NULL;
97                         }
98                 }
99
100         if (!path && !cd->path) return FALSE;
101
102         if (!path) path = cd->path;
103
104         pathl = path_from_utf8(path);
105
106         DEBUG_1("collection load: append=%d flush=%d only_geometry=%d path=%s",
107                           append, flush, only_geometry, pathl);
108
109         /* load it */
110         f = fopen(pathl, "r");
111         g_free(pathl);
112         if (!f)
113                 {
114                 log_printf("Failed to open collection file: \"%s\"\n", path);
115                 return FALSE;
116                 }
117
118         while (fgets(s_buf, sizeof(s_buf), f))
119                 {
120                 gchar *buf;
121                 gchar *p = s_buf;
122
123                 if (!reading_extended_filename)
124                         {
125                         /* Skip whitespaces and empty lines */
126                         while (*p && g_ascii_isspace(*p)) p++;
127                         if (*p == '\n' || *p == '\r') continue;
128
129                         /* Parse comments */
130                         if (*p == '#')
131                                 {
132                                 if (!need_header) continue;
133                                 if (g_ascii_strncasecmp(p, GQ_COLLECTION_MARKER, strlen(GQ_COLLECTION_MARKER)) == 0)
134                                         {
135                                         /* Looks like an official collection, allow unchecked input.
136                                          * All this does is allow adding files that may not exist,
137                                          * which is needed for the collection manager to work.
138                                          * Also unofficial files abort after too many invalid entries.
139                                          */
140                                         has_official_header = TRUE;
141                                         limit_failures = FALSE;
142                                         }
143                                 else if (strncmp(p, "#geometry:", 10 ) == 0 &&
144                                          scan_geometry(p + 10, &cd->window_x, &cd->window_y, &cd->window_w, &cd->window_h))
145                                         {
146                                         has_geometry_header = TRUE;
147                                         cd->window_read = TRUE;
148                                         if (only_geometry) break;
149                                         }
150                                 else if (g_ascii_strncasecmp(p, "#GQview collection", strlen("#GQview collection")) == 0)
151                                         {
152                                         /* As 2008/04/15 there is no difference between our collection file format
153                                          * and GQview 2.1.5 collection file format so ignore failures as well. */
154                                         has_gqview_header = TRUE;
155                                         limit_failures = FALSE;
156                                         }
157                                 need_header = (!has_official_header && !has_gqview_header) || !has_geometry_header;
158                                 continue;
159                                 }
160
161                         if (only_geometry) continue;
162                         }
163
164                 /* Read filenames */
165                 /** @todo This is not safe! */
166                 /* Updated: anything within double quotes is considered a filename */
167                 if (!reading_extended_filename)
168                         {
169                         /* not yet reading filename */
170                         while (*p && *p != '"') p++;
171                         if (*p) p++;
172                         /* start of filename read */
173                         buf = p;
174                         while (*p && *p != '"') p++;
175                         if (p[0] != '"')
176                                 {
177                                 /* first part of extended filename */
178                                 g_string_append(extended_filename_buffer, buf);
179                                 reading_extended_filename = TRUE;
180                                 continue;
181                                 }
182                         }
183                 else
184                         {
185                         buf = p;
186                         while (*p && *p != '"') p++;
187                         if (p[0] != '"')
188                                 {
189                                 /* end of extended filename still not found */
190                                 g_string_append(extended_filename_buffer, buf);
191                                 continue;
192                                 }
193                         else
194                                 {
195                                 /* end of extended filename found */
196                                 g_string_append_len(extended_filename_buffer, buf, p - buf);
197                                 reading_extended_filename = FALSE;
198                                 }
199                         }
200
201                 if (strlen(extended_filename_buffer->str) > 0)
202                         {
203                         buffer2 = g_strdup(extended_filename_buffer->str);
204                         g_string_erase(extended_filename_buffer, 0, -1);
205                         }
206                 else
207                         {
208                         *p = 0;
209                         buffer2 = g_strdup(buf);
210                         }
211
212                 if (*buffer2)
213                         {
214                         gboolean valid;
215
216                         if (!flush)
217                                 changed |= collect_manager_process_action(entry, &buffer2);
218
219                         valid = (buffer2[0] == G_DIR_SEPARATOR && collection_add_check(cd, file_data_new_simple(buffer2), FALSE, TRUE));
220                         if (!valid) DEBUG_1("collection invalid file: %s", buffer2);
221
222                         total++;
223                         if (!valid)
224                                 {
225                                 fail++;
226                                 if (limit_failures &&
227                                     fail > GQ_COLLECTION_FAIL_MIN &&
228                                     fail * 100 / total > GQ_COLLECTION_FAIL_PERCENT)
229                                         {
230                                         log_printf("%d invalid filenames in unofficial collection file, closing: %s\n", fail, path);
231                                         success = FALSE;
232                                         break;
233                                         }
234                                 }
235                         }
236                 g_free(buffer2);
237                 }
238
239         g_string_free(extended_filename_buffer, TRUE);
240
241         DEBUG_1("collection files: total = %d fail = %d official=%d gqview=%d geometry=%d",
242                           total, fail, has_official_header, has_gqview_header, has_geometry_header);
243
244         fclose(f);
245         if (only_geometry) return has_geometry_header;
246
247         if (!flush)
248                 {
249                 gchar *buf = NULL;
250                 while (collect_manager_process_action(entry, &buf))
251                         {
252                         collection_add_check(cd, file_data_new_group(buf), FALSE, TRUE);
253                         changed = TRUE;
254                         g_free(buf);
255                         buf = NULL;
256                         }
257                 }
258
259         cd->list = collection_list_sort(cd->list, cd->sort_method);
260
261         if (!flush && changed && success)
262                 collection_save_private(cd, path);
263
264         if (!flush)
265                 collect_manager_entry_reset(entry);
266
267         if (!append) cd->changed = FALSE;
268
269         return success;
270 }
271
272 gboolean collection_load(CollectionData *cd, const gchar *path, CollectionLoadFlags flags)
273 {
274         if (collection_load_private(cd, path, static_cast<CollectionLoadFlags>(flags | COLLECTION_LOAD_FLUSH)))
275                 {
276                 layout_recent_add_path(cd->path);
277                 return TRUE;
278                 }
279
280         return FALSE;
281 }
282
283 static void collection_load_thumb_do(CollectionData *cd)
284 {
285         GdkPixbuf *pixbuf;
286
287         if (!cd->thumb_loader || !g_list_find(cd->list, cd->thumb_info)) return;
288
289         pixbuf = thumb_loader_get_pixbuf(cd->thumb_loader);
290         collection_info_set_thumb(cd->thumb_info, pixbuf);
291         g_object_unref(pixbuf);
292
293         if (cd->info_updated_func) cd->info_updated_func(cd, cd->thumb_info, cd->info_updated_data);
294 }
295
296 static void collection_load_thumb_error_cb(ThumbLoader *UNUSED(tl), gpointer data)
297 {
298         CollectionData *cd = static_cast<CollectionData *>(data);
299
300         collection_load_thumb_do(cd);
301         collection_load_thumb_step(cd);
302 }
303
304 static void collection_load_thumb_done_cb(ThumbLoader *UNUSED(tl), gpointer data)
305 {
306         CollectionData *cd = static_cast<CollectionData *>(data);
307
308         collection_load_thumb_do(cd);
309         collection_load_thumb_step(cd);
310 }
311
312 static void collection_load_thumb_step(CollectionData *cd)
313 {
314         GList *work;
315         CollectInfo *ci;
316
317         if (!cd->list)
318                 {
319                 collection_load_stop(cd);
320                 return;
321                 }
322
323         work = cd->list;
324         ci = static_cast<CollectInfo *>(work->data);
325         work = work->next;
326         /* find first unloaded thumb */
327         while (work && ci->pixbuf)
328                 {
329                 ci = static_cast<CollectInfo *>(work->data);
330                 work = work->next;
331                 }
332
333         if (!ci || ci->pixbuf)
334                 {
335                 /* done */
336                 collection_load_stop(cd);
337
338                 /* send a NULL CollectInfo to notify end */
339                 if (cd->info_updated_func) cd->info_updated_func(cd, NULL, cd->info_updated_data);
340
341                 return;
342                 }
343
344         /* setup loader and call it */
345         cd->thumb_info = ci;
346         thumb_loader_free(cd->thumb_loader);
347         cd->thumb_loader = thumb_loader_new(options->thumbnails.max_width, options->thumbnails.max_height);
348         thumb_loader_set_callbacks(cd->thumb_loader,
349                                    collection_load_thumb_done_cb,
350                                    collection_load_thumb_error_cb,
351                                    NULL,
352                                    cd);
353
354         /* start it */
355         if (!thumb_loader_start(cd->thumb_loader, ci->fd))
356                 {
357                 /* error, handle it, do next */
358                 DEBUG_1("error loading thumb for %s", ci->fd->path);
359                 collection_load_thumb_do(cd);
360                 collection_load_thumb_step(cd);
361                 }
362 }
363
364 void collection_load_thumb_idle(CollectionData *cd)
365 {
366         if (!cd->thumb_loader) collection_load_thumb_step(cd);
367 }
368
369 gboolean collection_load_begin(CollectionData *cd, const gchar *path, CollectionLoadFlags flags)
370 {
371         if (!collection_load(cd, path, flags)) return FALSE;
372
373         collection_load_thumb_idle(cd);
374
375         return TRUE;
376 }
377
378 void collection_load_stop(CollectionData *cd)
379 {
380         if (!cd->thumb_loader) return;
381
382         thumb_loader_free(cd->thumb_loader);
383         cd->thumb_loader = NULL;
384 }
385
386 static gboolean collection_save_private(CollectionData *cd, const gchar *path)
387 {
388         SecureSaveInfo *ssi;
389         GList *work;
390         gchar *pathl;
391
392         if (!path && !cd->path) return FALSE;
393
394         if (!path)
395                 {
396                 path = cd->path;
397                 }
398
399
400         pathl = path_from_utf8(path);
401         ssi = secure_open(pathl);
402         g_free(pathl);
403         if (!ssi)
404                 {
405                 log_printf(_("failed to open collection (write) \"%s\"\n"), path);
406                 return FALSE;
407                 }
408
409         secure_fprintf(ssi, "%s collection\n", GQ_COLLECTION_MARKER);
410         secure_fprintf(ssi, "#created with %s version %s\n", GQ_APPNAME, VERSION);
411
412         collection_update_geometry(cd);
413         if (cd->window_read)
414                 {
415                 secure_fprintf(ssi, "#geometry: %d %d %d %d\n", cd->window_x, cd->window_y, cd->window_w, cd->window_h);
416                 }
417
418         work = cd->list;
419         while (work && secsave_errno == SS_ERR_NONE)
420                 {
421                 CollectInfo *ci = static_cast<CollectInfo *>(work->data);
422                 secure_fprintf(ssi, "\"%s\"\n", ci->fd->path);
423                 work = work->next;
424                 }
425
426         secure_fprintf(ssi, "#end\n");
427
428         if (secure_close(ssi))
429                 {
430                 log_printf(_("error saving collection file: %s\nerror: %s\n"), path,
431                             secsave_strerror(secsave_errno));
432                 return FALSE;
433                 }
434
435         if (!cd->path || strcmp(path, cd->path) != 0)
436                 {
437                 gchar *buf = cd->path;
438                 cd->path = g_strdup(path);
439                 path = cd->path;
440                 g_free(buf);
441
442                 g_free(cd->name);
443                 cd->name = g_strdup(filename_from_path(cd->path));
444
445                 collection_path_changed(cd);
446                 }
447
448         cd->changed = FALSE;
449
450         return TRUE;
451 }
452
453 gboolean collection_save(CollectionData *cd, const gchar *path)
454 {
455         if (collection_save_private(cd, path))
456                 {
457                 layout_recent_add_path(cd->path);
458                 return TRUE;
459                 }
460
461         return FALSE;
462 }
463
464 gboolean collection_load_only_geometry(CollectionData *cd, const gchar *path)
465 {
466         return collection_load(cd, path, COLLECTION_LOAD_GEOMETRY);
467 }
468
469
470 /*
471  *-------------------------------------------------------------------
472  * collection manager
473  *-------------------------------------------------------------------
474  */
475
476 #define COLLECT_MANAGER_ACTIONS_PER_IDLE 1000
477 #define COLLECT_MANAGER_FLUSH_DELAY      10000
478
479 struct _CollectManagerEntry
480 {
481         gchar *path;
482         GList *add_list;
483         GHashTable *oldpath_hash;
484         GHashTable *newpath_hash;
485         gboolean empty;
486 };
487
488 typedef enum {
489         COLLECTION_MANAGER_UPDATE,
490         COLLECTION_MANAGER_ADD,
491         COLLECTION_MANAGER_REMOVE
492 } CollectManagerType;
493
494 typedef struct _CollectManagerAction CollectManagerAction;
495 struct _CollectManagerAction
496 {
497         gchar *oldpath;
498         gchar *newpath;
499
500         CollectManagerType type;
501
502         gint ref;
503 };
504
505
506 static GList *collection_manager_entry_list = NULL;
507 static GList *collection_manager_action_list = NULL;
508 static GList *collection_manager_action_tail = NULL;
509 static guint collection_manager_timer_id = 0; /* event source id */
510
511
512 static CollectManagerAction *collect_manager_action_new(const gchar *oldpath, const gchar *newpath,
513                                                         CollectManagerType type)
514 {
515         CollectManagerAction *action;
516
517         action = g_new0(CollectManagerAction, 1);
518         action->ref = 1;
519
520         action->oldpath = g_strdup(oldpath);
521         action->newpath = g_strdup(newpath);
522
523         action->type = type;
524
525         return action;
526 }
527
528 static void collect_manager_action_ref(CollectManagerAction *action)
529 {
530         action->ref++;
531 }
532
533 static void collect_manager_action_unref(CollectManagerAction *action)
534 {
535         action->ref--;
536
537         if (action->ref > 0) return;
538
539         g_free(action->oldpath);
540         g_free(action->newpath);
541         g_free(action);
542 }
543
544 static void collect_manager_entry_free_data(CollectManagerEntry *entry)
545 {
546         GList *work;
547
548         work = entry->add_list;
549         while (work)
550                 {
551                 CollectManagerAction *action;
552
553                 action = static_cast<CollectManagerAction *>(work->data);
554                 work = work->next;
555
556                 collect_manager_action_unref(action);
557                 }
558         g_list_free(entry->add_list);
559         if (g_hash_table_size(entry->oldpath_hash) > 0)
560                 g_hash_table_destroy(entry->oldpath_hash);
561         else
562                 g_hash_table_unref(entry->oldpath_hash);
563         if (g_hash_table_size(entry->newpath_hash) > 0)
564                 g_hash_table_destroy(entry->newpath_hash);
565         else
566                 g_hash_table_unref(entry->newpath_hash);
567 }
568
569 static void collect_manager_entry_init_data(CollectManagerEntry *entry)
570 {
571         entry->add_list = NULL;
572         entry->oldpath_hash = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify) collect_manager_action_unref);
573         entry->newpath_hash = g_hash_table_new(g_str_hash, g_str_equal);
574         entry->empty = TRUE;
575
576 }
577
578 static CollectManagerEntry *collect_manager_entry_new(const gchar *path)
579 {
580         CollectManagerEntry *entry;
581
582         entry = g_new0(CollectManagerEntry, 1);
583         entry->path = g_strdup(path);
584         collect_manager_entry_init_data(entry);
585
586         collection_manager_entry_list = g_list_append(collection_manager_entry_list, entry);
587
588         return entry;
589 }
590
591
592 static void collect_manager_entry_free(CollectManagerEntry *entry)
593 {
594         collection_manager_entry_list = g_list_remove(collection_manager_entry_list, entry);
595
596         collect_manager_entry_free_data(entry);
597
598         g_free(entry->path);
599         g_free(entry);
600 }
601
602 static void collect_manager_entry_reset(CollectManagerEntry *entry)
603 {
604         collect_manager_entry_free_data(entry);
605         collect_manager_entry_init_data(entry);
606 }
607
608 static CollectManagerEntry *collect_manager_get_entry(const gchar *path)
609 {
610         GList *work;
611
612         work = collection_manager_entry_list;
613         while (work)
614                 {
615                 CollectManagerEntry *entry;
616
617                 entry = static_cast<CollectManagerEntry *>(work->data);
618                 work = work->next;
619                 if (strcmp(entry->path, path) == 0)
620                         {
621                         return entry;
622                         }
623                 }
624         return NULL;
625
626 }
627
628 static void collect_manager_entry_add_action(CollectManagerEntry *entry, CollectManagerAction *action)
629 {
630
631         CollectManagerAction *orig_action;
632
633         entry->empty = FALSE;
634
635         if (action->oldpath == NULL)
636                 {
637                 /* add file */
638                 if (action->newpath == NULL)
639                         {
640                         return;
641                         }
642
643                 orig_action = static_cast<CollectManagerAction *>(g_hash_table_lookup(entry->newpath_hash, action->newpath));
644                 if (orig_action)
645                         {
646                         /* target already exists */
647                         log_printf("collection manager failed to add another action for target %s in collection %s\n",
648                                 action->newpath, entry->path);
649                         return;
650                         }
651                 entry->add_list = g_list_append(entry->add_list, action);
652                 g_hash_table_insert(entry->newpath_hash, action->newpath, action);
653                 collect_manager_action_ref(action);
654                 return;
655                 }
656
657         orig_action = static_cast<CollectManagerAction *>(g_hash_table_lookup(entry->newpath_hash, action->oldpath));
658         if (orig_action)
659                 {
660                 /* new action with the same file */
661                 CollectManagerAction *new_action = collect_manager_action_new(orig_action->oldpath, action->newpath, action->type);
662
663                 if (new_action->oldpath)
664                         {
665                         g_hash_table_steal(entry->oldpath_hash, orig_action->oldpath);
666                         g_hash_table_insert(entry->oldpath_hash, new_action->oldpath, new_action);
667                         }
668                 else
669                         {
670                         GList *work = g_list_find(entry->add_list, orig_action);
671                         work->data = new_action;
672                         }
673
674                 g_hash_table_steal(entry->newpath_hash, orig_action->newpath);
675                 if (new_action->newpath)
676                         {
677                         g_hash_table_insert(entry->newpath_hash, new_action->newpath, new_action);
678                         }
679                 collect_manager_action_unref(orig_action);
680                 return;
681                 }
682
683
684         orig_action = static_cast<CollectManagerAction *>(g_hash_table_lookup(entry->oldpath_hash, action->oldpath));
685         if (orig_action)
686                 {
687                 /* another action for the same source, ignore */
688                 log_printf("collection manager failed to add another action for source %s in collection %s\n",
689                         action->oldpath, entry->path);
690                 return;
691                 }
692
693         g_hash_table_insert(entry->oldpath_hash, action->oldpath, action);
694         if (action->newpath)
695                 {
696                 g_hash_table_insert(entry->newpath_hash, action->newpath, action);
697                 }
698         collect_manager_action_ref(action);
699 }
700
701 static gboolean collect_manager_process_action(CollectManagerEntry *entry, gchar **path_ptr)
702 {
703         gchar *path = *path_ptr;
704         CollectManagerAction *action;
705
706         if (path == NULL)
707                 {
708                 /* get new files */
709                 if (entry->add_list)
710                         {
711                         action = static_cast<CollectManagerAction *>(entry->add_list->data);
712                         g_assert(action->oldpath == NULL);
713                         entry->add_list = g_list_remove(entry->add_list, action);
714                         path = g_strdup(action->newpath);
715                         g_hash_table_remove(entry->newpath_hash, path);
716                         collect_manager_action_unref(action);
717                         }
718                 *path_ptr = path;
719                 return (path != NULL);
720                 }
721
722         action = static_cast<CollectManagerAction *>(g_hash_table_lookup(entry->oldpath_hash, path));
723
724         if (action)
725                 {
726                 strcpy(*path_ptr, action->newpath);
727                 return TRUE;
728                 }
729
730         return FALSE; /* no change */
731 }
732
733 static void collect_manager_refresh(void)
734 {
735         GList *list;
736         GList *work;
737         FileData *dir_fd;
738
739         dir_fd = file_data_new_dir(get_collections_dir());
740         filelist_read(dir_fd, &list, NULL);
741         file_data_unref(dir_fd);
742
743         work = collection_manager_entry_list;
744         while (work && list)
745                 {
746                 CollectManagerEntry *entry;
747                 GList *list_step;
748
749                 entry = static_cast<CollectManagerEntry *>(work->data);
750                 work = work->next;
751
752                 list_step = list;
753                 while (list_step && entry)
754                         {
755                         FileData *fd;
756
757                         fd = static_cast<FileData *>(list_step->data);
758                         list_step = list_step->next;
759
760                         if (strcmp(fd->path, entry->path) == 0)
761                                 {
762                                 list = g_list_remove(list, fd);
763                                 file_data_unref(fd);
764
765                                 entry = NULL;
766                                 }
767                         else
768                                 {
769                                 collect_manager_entry_free(entry);
770
771                                 entry = NULL;
772                                 }
773                         }
774                 }
775
776         work = list;
777         while (work)
778                 {
779                 FileData *fd;
780
781                 fd = static_cast<FileData *>(work->data);
782                 work = work->next;
783
784                 collect_manager_entry_new(fd->path);
785                 }
786
787         filelist_free(list);
788 }
789
790 static void collect_manager_process_actions(gint max)
791 {
792         if (collection_manager_action_list) DEBUG_1("collection manager processing actions");
793
794         while (collection_manager_action_list != NULL && max > 0)
795                 {
796                 CollectManagerAction *action;
797                 GList *work;
798
799                 action = static_cast<CollectManagerAction *>(collection_manager_action_list->data);
800                 work = collection_manager_entry_list;
801                 while (work)
802                         {
803                         CollectManagerEntry *entry;
804
805                         entry = static_cast<CollectManagerEntry *>(work->data);
806                         work = work->next;
807
808                         if (action->type == COLLECTION_MANAGER_UPDATE)
809                                 {
810                                 collect_manager_entry_add_action(entry, action);
811                                 }
812                         else if (action->oldpath && action->newpath &&
813                                  strcmp(action->newpath, entry->path) == 0)
814                                 {
815                                 /* convert action to standard add format */
816                                 g_free(action->newpath);
817                                 if (action->type == COLLECTION_MANAGER_ADD)
818                                         {
819                                         action->newpath = action->oldpath;
820                                         action->oldpath = NULL;
821                                         }
822                                 else if (action->type == COLLECTION_MANAGER_REMOVE)
823                                         {
824                                         action->newpath = NULL;
825                                         }
826                                 collect_manager_entry_add_action(entry, action);
827                                 }
828
829                         max--;
830                         }
831
832                 if (action->type != COLLECTION_MANAGER_UPDATE &&
833                     action->oldpath && action->newpath)
834                         {
835                         log_printf("collection manager failed to %s %s for collection %s\n",
836                                 (action->type == COLLECTION_MANAGER_ADD) ? "add" : "remove",
837                                 action->oldpath, action->newpath);
838                         }
839
840                 if (collection_manager_action_tail == collection_manager_action_list)
841                         {
842                         collection_manager_action_tail = NULL;
843                         }
844                 collection_manager_action_list = g_list_remove(collection_manager_action_list, action);
845                 collect_manager_action_unref(action);
846                 }
847 }
848
849 static gboolean collect_manager_process_entry(CollectManagerEntry *entry)
850 {
851         CollectionData *cd;
852
853         if (entry->empty) return FALSE;
854
855         cd = collection_new(entry->path);
856         (void) collection_load_private(cd, entry->path, COLLECTION_LOAD_NONE);
857
858         collection_unref(cd);
859
860         return TRUE;
861 }
862
863 static gboolean collect_manager_process_entry_list(void)
864 {
865         GList *work;
866
867         work = collection_manager_entry_list;
868         while (work)
869                 {
870                 CollectManagerEntry *entry;
871
872                 entry = static_cast<CollectManagerEntry *>(work->data);
873                 work = work->next;
874                 if (collect_manager_process_entry(entry)) return TRUE;
875                 }
876
877         return FALSE;
878 }
879
880
881
882 static gboolean collect_manager_process_cb(gpointer UNUSED(data))
883 {
884         if (collection_manager_action_list) collect_manager_refresh();
885         collect_manager_process_actions(COLLECT_MANAGER_ACTIONS_PER_IDLE);
886         if (collection_manager_action_list) return G_SOURCE_CONTINUE;
887
888         if (collect_manager_process_entry_list()) return G_SOURCE_CONTINUE;
889
890         DEBUG_1("collection manager is up to date");
891         return G_SOURCE_REMOVE;
892 }
893
894 static gboolean collect_manager_timer_cb(gpointer UNUSED(data))
895 {
896         DEBUG_1("collection manager timer expired");
897
898         g_idle_add_full(G_PRIORITY_LOW, collect_manager_process_cb, NULL, NULL);
899
900         collection_manager_timer_id = 0;
901         return FALSE;
902 }
903
904 static void collect_manager_timer_push(gint stop)
905 {
906         if (collection_manager_timer_id)
907                 {
908                 if (!stop) return;
909
910                 g_source_remove(collection_manager_timer_id);
911                 collection_manager_timer_id = 0;
912                 }
913
914         if (!stop)
915                 {
916                 collection_manager_timer_id = g_timeout_add(COLLECT_MANAGER_FLUSH_DELAY,
917                                                             collect_manager_timer_cb, NULL);
918                 DEBUG_1("collection manager timer started");
919                 }
920 }
921
922 static void collect_manager_add_action(CollectManagerAction *action)
923 {
924         if (!action) return;
925
926         /* we keep track of the list's tail to keep this a n(1) operation */
927
928         if (collection_manager_action_tail)
929                 {
930                 collection_manager_action_tail = g_list_append(collection_manager_action_tail, action);
931                 collection_manager_action_tail = collection_manager_action_tail->next;
932                 }
933         else
934                 {
935                 collection_manager_action_list = g_list_append(collection_manager_action_list, action);
936                 collection_manager_action_tail = collection_manager_action_list;
937                 }
938
939         collect_manager_timer_push(FALSE);
940 }
941
942 void collect_manager_moved(FileData *fd)
943 {
944         CollectManagerAction *action;
945         const gchar *oldpath = fd->change->source;
946         const gchar *newpath = fd->change->dest;
947
948         action = collect_manager_action_new(oldpath, newpath, COLLECTION_MANAGER_UPDATE);
949         collect_manager_add_action(action);
950 }
951
952 void collect_manager_add(FileData *fd, const gchar *collection)
953 {
954         CollectManagerAction *action;
955         CollectWindow *cw;
956
957         if (!fd || !collection) return;
958
959         cw = collection_window_find_by_path(collection);
960         if (cw)
961                 {
962                 if (collection_list_find_fd(cw->cd->list, fd) == NULL)
963                         {
964                         collection_add(cw->cd, fd, FALSE);
965                         }
966                 return;
967                 }
968
969         action = collect_manager_action_new(fd->path, collection, COLLECTION_MANAGER_ADD);
970         collect_manager_add_action(action);
971 }
972
973 void collect_manager_remove(FileData *fd, const gchar *collection)
974 {
975         CollectManagerAction *action;
976         CollectWindow *cw;
977
978         if (!fd || !collection) return;
979
980         cw = collection_window_find_by_path(collection);
981         if (cw)
982                 {
983                 while (collection_remove(cw->cd, fd));
984                 return;
985                 }
986
987         action = collect_manager_action_new(fd->path, collection, COLLECTION_MANAGER_REMOVE);
988         collect_manager_add_action(action);
989 }
990
991 void collect_manager_flush(void)
992 {
993         collect_manager_timer_push(TRUE);
994
995         DEBUG_1("collection manager flushing");
996         while (collect_manager_process_cb(NULL));
997 }
998
999 void collect_manager_notify_cb(FileData *fd, NotifyType type, gpointer UNUSED(data))
1000 {
1001         if (!(type & NOTIFY_CHANGE) || !fd->change) return;
1002
1003         DEBUG_1("Notify collect_manager: %s %04x", fd->path, type);
1004         switch (fd->change->type)
1005                 {
1006                 case FILEDATA_CHANGE_MOVE:
1007                         collect_manager_moved(fd);
1008                         break;
1009                 case FILEDATA_CHANGE_COPY:
1010                         break;
1011                 case FILEDATA_CHANGE_RENAME:
1012                         collect_manager_moved(fd);
1013                         break;
1014                 case FILEDATA_CHANGE_DELETE:
1015                 case FILEDATA_CHANGE_UNSPECIFIED:
1016                 case FILEDATA_CHANGE_WRITE_METADATA:
1017                         break;
1018                 }
1019 }
1020
1021 static gint collection_manager_sort_cb(gconstpointer a, gconstpointer b)
1022 {
1023         const gchar *char_a = static_cast<const gchar *>(a);
1024         const gchar *char_b = static_cast<const gchar *>(b);
1025
1026         return g_strcmp0(char_a, char_b);
1027 }
1028  
1029 /**
1030  * @brief Creates sorted list of collections
1031  * @param[out] names_exc sorted list of collections names excluding extension
1032  * @param[out] names_inc sorted list of collections names including extension
1033  * @param[out] paths sorted list of collection paths
1034  * 
1035  * Lists of type gchar.
1036  * Used lists must be freed with string_list_free()
1037  */
1038 void collect_manager_list(GList **names_exc, GList **names_inc, GList **paths)
1039 {
1040         FileData *dir_fd;
1041         GList *list = NULL;
1042         gchar *name;
1043         FileData *fd;
1044         gchar *filename;
1045
1046         if (names_exc == NULL && names_inc == NULL && paths == NULL)
1047                 {
1048                 return;
1049                 }
1050
1051         dir_fd = file_data_new_dir((get_collections_dir()));
1052
1053         filelist_read(dir_fd, &list, NULL);
1054
1055         while (list)
1056                 {
1057                 fd = static_cast<FileData *>(list->data);
1058                 filename = g_strdup(filename_from_path((gchar *)fd->path));
1059
1060                 if (file_extension_match(filename, GQ_COLLECTION_EXT))
1061                         {
1062                         name = remove_extension_from_path(filename);
1063
1064                         if (names_exc != NULL)
1065                                 {
1066                                 *names_exc = g_list_insert_sorted(*names_exc, g_strdup(name),
1067                                                                                         collection_manager_sort_cb);
1068                                 *names_exc = g_list_first(*names_exc);
1069                                 }
1070                         if (names_inc != NULL)
1071                                 {
1072                                 *names_inc = g_list_insert_sorted(*names_inc,filename,
1073                                                                                         collection_manager_sort_cb);
1074                                 *names_inc = g_list_first(*names_inc);
1075                                 }
1076                         if (paths != NULL)
1077                                 {
1078                                 *paths = g_list_insert_sorted(*paths,fd->path,
1079                                                                                         collection_manager_sort_cb);
1080                                 *paths = g_list_first(*paths);
1081                                 }
1082                         g_free(name);
1083                         }
1084                 list = list->next;
1085                 g_free(filename);
1086                 }
1087
1088         filelist_free(list);
1089 }
1090 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */