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