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