Bug fix: Change all .desktop files to RDNS style
[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 struct 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 namespace
48 {
49
50 gboolean scan_geometry(gchar *buffer, GdkRectangle &window)
51 {
52         gint nx, ny, nw, nh;
53
54         if (sscanf(buffer, "%d %d %d %d", &nx, &ny, &nw, &nh) != 4) return FALSE;
55
56         window.x = nx;
57         window.y = ny;
58         window.width = nw;
59         window.height = nh;
60
61         return TRUE;
62 }
63
64 }
65
66 static gboolean collection_load_private(CollectionData *cd, const gchar *path, CollectionLoadFlags flags)
67 {
68         gchar s_buf[GQ_COLLECTION_READ_BUFSIZE];
69         FILE *f;
70         gchar *pathl;
71         gboolean limit_failures = TRUE;
72         gboolean success = TRUE;
73         gboolean has_official_header = FALSE;
74         gboolean has_geometry_header = FALSE;
75         gboolean has_gqview_header   = FALSE;
76         gboolean need_header     = TRUE;
77         guint total = 0;
78         guint fail = 0;
79         gboolean changed = FALSE;
80         CollectManagerEntry *entry = nullptr;
81         guint flush = !!(flags & COLLECTION_LOAD_FLUSH);
82         guint append = !!(flags & COLLECTION_LOAD_APPEND);
83         guint only_geometry = !!(flags & COLLECTION_LOAD_GEOMETRY);
84         gboolean reading_extended_filename = FALSE;
85         gchar *buffer2;
86
87         if (!only_geometry)
88                 {
89                 collection_load_stop(cd);
90
91                 if (flush)
92                         collect_manager_flush();
93                 else
94                         entry = collect_manager_get_entry(path);
95
96                 if (!append)
97                         {
98                         g_list_free_full(cd->list, reinterpret_cast<GDestroyNotify>(collection_info_free));
99                         cd->list = nullptr;
100                         }
101                 }
102
103         if (!path && !cd->path) return FALSE;
104
105         if (!path) path = cd->path;
106
107         pathl = path_from_utf8(path);
108
109         DEBUG_1("collection load: append=%d flush=%d only_geometry=%d path=%s",
110                           append, flush, only_geometry, pathl);
111
112         /* load it */
113         f = fopen(pathl, "r");
114         g_free(pathl);
115         if (!f)
116                 {
117                 log_printf("Failed to open collection file: \"%s\"\n", path);
118                 return FALSE;
119                 }
120
121         GString *extended_filename_buffer = g_string_new(nullptr);
122         while (fgets(s_buf, sizeof(s_buf), f))
123                 {
124                 gchar *buf;
125                 gchar *p = s_buf;
126
127                 if (!reading_extended_filename)
128                         {
129                         /* Skip whitespaces and empty lines */
130                         while (*p && g_ascii_isspace(*p)) p++;
131                         if (*p == '\n' || *p == '\r') continue;
132
133                         /* Parse comments */
134                         if (*p == '#')
135                                 {
136                                 if (!need_header) continue;
137                                 if (g_ascii_strncasecmp(p, GQ_COLLECTION_MARKER, strlen(GQ_COLLECTION_MARKER)) == 0)
138                                         {
139                                         /* Looks like an official collection, allow unchecked input.
140                                          * All this does is allow adding files that may not exist,
141                                          * which is needed for the collection manager to work.
142                                          * Also unofficial files abort after too many invalid entries.
143                                          */
144                                         has_official_header = TRUE;
145                                         limit_failures = FALSE;
146                                         }
147                                 else if (strncmp(p, "#geometry:", 10 ) == 0 &&
148                                          scan_geometry(p + 10, cd->window))
149                                         {
150                                         has_geometry_header = TRUE;
151                                         cd->window_read = TRUE;
152                                         if (only_geometry) break;
153                                         }
154                                 else if (g_ascii_strncasecmp(p, "#GQview collection", strlen("#GQview collection")) == 0)
155                                         {
156                                         /* As 2008/04/15 there is no difference between our collection file format
157                                          * and GQview 2.1.5 collection file format so ignore failures as well. */
158                                         has_gqview_header = TRUE;
159                                         limit_failures = FALSE;
160                                         }
161                                 need_header = (!has_official_header && !has_gqview_header) || !has_geometry_header;
162                                 continue;
163                                 }
164
165                         if (only_geometry) continue;
166                         }
167
168                 /* Read filenames */
169                 /** @todo This is not safe! */
170                 /* Updated: anything within double quotes is considered a filename */
171                 if (!reading_extended_filename)
172                         {
173                         /* not yet reading filename */
174                         while (*p && *p != '"') p++;
175                         if (*p) p++;
176                         /* start of filename read */
177                         buf = p;
178                         while (*p && *p != '"') p++;
179                         if (p[0] != '"')
180                                 {
181                                 /* first part of extended filename */
182                                 g_string_append(extended_filename_buffer, buf);
183                                 reading_extended_filename = TRUE;
184                                 continue;
185                                 }
186                         }
187                 else
188                         {
189                         buf = p;
190                         while (*p && *p != '"') p++;
191                         if (p[0] != '"')
192                                 {
193                                 /* end of extended filename still not found */
194                                 g_string_append(extended_filename_buffer, buf);
195                                 continue;
196                                 }
197                         else
198                                 {
199                                 /* end of extended filename found */
200                                 g_string_append_len(extended_filename_buffer, buf, p - buf);
201                                 reading_extended_filename = FALSE;
202                                 }
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 #define COLLECT_MANAGER_ACTIONS_PER_IDLE 1000
481 #define COLLECT_MANAGER_FLUSH_DELAY      10000
482
483 struct CollectManagerEntry
484 {
485         gchar *path;
486         GList *add_list;
487         GHashTable *oldpath_hash;
488         GHashTable *newpath_hash;
489         gboolean empty;
490 };
491
492 enum CollectManagerType {
493         COLLECTION_MANAGER_UPDATE,
494         COLLECTION_MANAGER_ADD,
495         COLLECTION_MANAGER_REMOVE
496 };
497
498 struct CollectManagerAction
499 {
500         gchar *oldpath;
501         gchar *newpath;
502
503         CollectManagerType type;
504
505         gint ref;
506 };
507
508
509 static GList *collection_manager_entry_list = nullptr;
510 static GList *collection_manager_action_list = nullptr;
511 static GList *collection_manager_action_tail = nullptr;
512 static guint collection_manager_timer_id = 0; /* event source id */
513
514
515 static CollectManagerAction *collect_manager_action_new(const gchar *oldpath, const gchar *newpath,
516                                                         CollectManagerType type)
517 {
518         CollectManagerAction *action;
519
520         action = g_new0(CollectManagerAction, 1);
521         action->ref = 1;
522
523         action->oldpath = g_strdup(oldpath);
524         action->newpath = g_strdup(newpath);
525
526         action->type = type;
527
528         return action;
529 }
530
531 static void collect_manager_action_ref(CollectManagerAction *action)
532 {
533         action->ref++;
534 }
535
536 static void collect_manager_action_unref(CollectManagerAction *action)
537 {
538         action->ref--;
539
540         if (action->ref > 0) return;
541
542         g_free(action->oldpath);
543         g_free(action->newpath);
544         g_free(action);
545 }
546
547 static void collect_manager_entry_free_data(CollectManagerEntry *entry)
548 {
549         g_list_free_full(entry->add_list, reinterpret_cast<GDestroyNotify>(collect_manager_action_unref));
550         if (g_hash_table_size(entry->oldpath_hash) > 0)
551                 g_hash_table_destroy(entry->oldpath_hash);
552         else
553                 g_hash_table_unref(entry->oldpath_hash);
554         if (g_hash_table_size(entry->newpath_hash) > 0)
555                 g_hash_table_destroy(entry->newpath_hash);
556         else
557                 g_hash_table_unref(entry->newpath_hash);
558 }
559
560 static void collect_manager_entry_init_data(CollectManagerEntry *entry)
561 {
562         entry->add_list = nullptr;
563         entry->oldpath_hash = g_hash_table_new_full(g_str_hash, g_str_equal, nullptr, reinterpret_cast<GDestroyNotify>(collect_manager_action_unref));
564         entry->newpath_hash = g_hash_table_new(g_str_hash, g_str_equal);
565         entry->empty = TRUE;
566
567 }
568
569 static CollectManagerEntry *collect_manager_entry_new(const gchar *path)
570 {
571         CollectManagerEntry *entry;
572
573         entry = g_new0(CollectManagerEntry, 1);
574         entry->path = g_strdup(path);
575         collect_manager_entry_init_data(entry);
576
577         collection_manager_entry_list = g_list_append(collection_manager_entry_list, entry);
578
579         return entry;
580 }
581
582
583 static void collect_manager_entry_free(CollectManagerEntry *entry)
584 {
585         collection_manager_entry_list = g_list_remove(collection_manager_entry_list, entry);
586
587         collect_manager_entry_free_data(entry);
588
589         g_free(entry->path);
590         g_free(entry);
591 }
592
593 static void collect_manager_entry_reset(CollectManagerEntry *entry)
594 {
595         collect_manager_entry_free_data(entry);
596         collect_manager_entry_init_data(entry);
597 }
598
599 static CollectManagerEntry *collect_manager_get_entry(const gchar *path)
600 {
601         GList *work;
602
603         work = collection_manager_entry_list;
604         while (work)
605                 {
606                 CollectManagerEntry *entry;
607
608                 entry = static_cast<CollectManagerEntry *>(work->data);
609                 work = work->next;
610                 if (strcmp(entry->path, path) == 0)
611                         {
612                         return entry;
613                         }
614                 }
615         return nullptr;
616
617 }
618
619 static void collect_manager_entry_add_action(CollectManagerEntry *entry, CollectManagerAction *action)
620 {
621
622         CollectManagerAction *orig_action;
623
624         entry->empty = FALSE;
625
626         if (action->oldpath == nullptr)
627                 {
628                 /* add file */
629                 if (action->newpath == nullptr)
630                         {
631                         return;
632                         }
633
634                 orig_action = static_cast<CollectManagerAction *>(g_hash_table_lookup(entry->newpath_hash, action->newpath));
635                 if (orig_action)
636                         {
637                         /* target already exists */
638                         log_printf("collection manager failed to add another action for target %s in collection %s\n",
639                                 action->newpath, entry->path);
640                         return;
641                         }
642                 entry->add_list = g_list_append(entry->add_list, action);
643                 g_hash_table_insert(entry->newpath_hash, action->newpath, action);
644                 collect_manager_action_ref(action);
645                 return;
646                 }
647
648         orig_action = static_cast<CollectManagerAction *>(g_hash_table_lookup(entry->newpath_hash, action->oldpath));
649         if (orig_action)
650                 {
651                 /* new action with the same file */
652                 CollectManagerAction *new_action = collect_manager_action_new(orig_action->oldpath, action->newpath, action->type);
653
654                 if (new_action->oldpath)
655                         {
656                         g_hash_table_steal(entry->oldpath_hash, orig_action->oldpath);
657                         g_hash_table_insert(entry->oldpath_hash, new_action->oldpath, new_action);
658                         }
659                 else
660                         {
661                         GList *work = g_list_find(entry->add_list, orig_action);
662                         work->data = new_action;
663                         }
664
665                 g_hash_table_steal(entry->newpath_hash, orig_action->newpath);
666                 if (new_action->newpath)
667                         {
668                         g_hash_table_insert(entry->newpath_hash, new_action->newpath, new_action);
669                         }
670                 collect_manager_action_unref(orig_action);
671                 return;
672                 }
673
674
675         orig_action = static_cast<CollectManagerAction *>(g_hash_table_lookup(entry->oldpath_hash, action->oldpath));
676         if (orig_action)
677                 {
678                 /* another action for the same source, ignore */
679                 log_printf("collection manager failed to add another action for source %s in collection %s\n",
680                         action->oldpath, entry->path);
681                 return;
682                 }
683
684         g_hash_table_insert(entry->oldpath_hash, action->oldpath, action);
685         if (action->newpath)
686                 {
687                 g_hash_table_insert(entry->newpath_hash, action->newpath, action);
688                 }
689         collect_manager_action_ref(action);
690 }
691
692 static gboolean collect_manager_process_action(CollectManagerEntry *entry, gchar **path_ptr)
693 {
694         gchar *path = *path_ptr;
695         CollectManagerAction *action;
696
697         if (path == nullptr)
698                 {
699                 /* get new files */
700                 if (entry->add_list)
701                         {
702                         action = static_cast<CollectManagerAction *>(entry->add_list->data);
703                         g_assert(action->oldpath == nullptr);
704                         entry->add_list = g_list_remove(entry->add_list, action);
705                         path = g_strdup(action->newpath);
706                         g_hash_table_remove(entry->newpath_hash, path);
707                         collect_manager_action_unref(action);
708                         }
709                 *path_ptr = path;
710                 return (path != nullptr);
711                 }
712
713         action = static_cast<CollectManagerAction *>(g_hash_table_lookup(entry->oldpath_hash, path));
714
715         if (action)
716                 {
717                 strcpy(*path_ptr, action->newpath);
718                 return TRUE;
719                 }
720
721         return FALSE; /* no change */
722 }
723
724 static void collect_manager_refresh()
725 {
726         GList *list;
727         GList *work;
728         FileData *dir_fd;
729
730         dir_fd = file_data_new_dir(get_collections_dir());
731         filelist_read(dir_fd, &list, nullptr);
732         file_data_unref(dir_fd);
733
734         work = collection_manager_entry_list;
735         while (work && list)
736                 {
737                 CollectManagerEntry *entry;
738                 GList *list_step;
739
740                 entry = static_cast<CollectManagerEntry *>(work->data);
741                 work = work->next;
742
743                 list_step = list;
744                 while (list_step && entry)
745                         {
746                         FileData *fd;
747
748                         fd = static_cast<FileData *>(list_step->data);
749                         list_step = list_step->next;
750
751                         if (strcmp(fd->path, entry->path) == 0)
752                                 {
753                                 list = g_list_remove(list, fd);
754                                 file_data_unref(fd);
755
756                                 entry = nullptr;
757                                 }
758                         else
759                                 {
760                                 collect_manager_entry_free(entry);
761
762                                 entry = nullptr;
763                                 }
764                         }
765                 }
766
767         work = list;
768         while (work)
769                 {
770                 FileData *fd;
771
772                 fd = static_cast<FileData *>(work->data);
773                 work = work->next;
774
775                 collect_manager_entry_new(fd->path);
776                 }
777
778         filelist_free(list);
779 }
780
781 static void collect_manager_process_actions(gint max)
782 {
783         if (collection_manager_action_list) DEBUG_1("collection manager processing actions");
784
785         while (collection_manager_action_list != nullptr && max > 0)
786                 {
787                 CollectManagerAction *action;
788                 GList *work;
789
790                 action = static_cast<CollectManagerAction *>(collection_manager_action_list->data);
791                 work = collection_manager_entry_list;
792                 while (work)
793                         {
794                         CollectManagerEntry *entry;
795
796                         entry = static_cast<CollectManagerEntry *>(work->data);
797                         work = work->next;
798
799                         if (action->type == COLLECTION_MANAGER_UPDATE)
800                                 {
801                                 collect_manager_entry_add_action(entry, action);
802                                 }
803                         else if (action->oldpath && action->newpath &&
804                                  strcmp(action->newpath, entry->path) == 0)
805                                 {
806                                 /* convert action to standard add format */
807                                 g_free(action->newpath);
808                                 if (action->type == COLLECTION_MANAGER_ADD)
809                                         {
810                                         action->newpath = action->oldpath;
811                                         action->oldpath = nullptr;
812                                         }
813                                 else if (action->type == COLLECTION_MANAGER_REMOVE)
814                                         {
815                                         action->newpath = nullptr;
816                                         }
817                                 collect_manager_entry_add_action(entry, action);
818                                 }
819
820                         max--;
821                         }
822
823                 if (action->type != COLLECTION_MANAGER_UPDATE &&
824                     action->oldpath && action->newpath)
825                         {
826                         log_printf("collection manager failed to %s %s for collection %s\n",
827                                 (action->type == COLLECTION_MANAGER_ADD) ? "add" : "remove",
828                                 action->oldpath, action->newpath);
829                         }
830
831                 if (collection_manager_action_tail == collection_manager_action_list)
832                         {
833                         collection_manager_action_tail = nullptr;
834                         }
835                 collection_manager_action_list = g_list_remove(collection_manager_action_list, action);
836                 collect_manager_action_unref(action);
837                 }
838 }
839
840 static gboolean collect_manager_process_entry(CollectManagerEntry *entry)
841 {
842         CollectionData *cd;
843
844         if (entry->empty) return FALSE;
845
846         cd = collection_new(entry->path);
847         (void) collection_load_private(cd, entry->path, COLLECTION_LOAD_NONE);
848
849         collection_unref(cd);
850
851         return TRUE;
852 }
853
854 static gboolean collect_manager_process_entry_list()
855 {
856         GList *work;
857
858         work = collection_manager_entry_list;
859         while (work)
860                 {
861                 CollectManagerEntry *entry;
862
863                 entry = static_cast<CollectManagerEntry *>(work->data);
864                 work = work->next;
865                 if (collect_manager_process_entry(entry)) return TRUE;
866                 }
867
868         return FALSE;
869 }
870
871
872
873 static gboolean collect_manager_process_cb(gpointer)
874 {
875         if (collection_manager_action_list) collect_manager_refresh();
876         collect_manager_process_actions(COLLECT_MANAGER_ACTIONS_PER_IDLE);
877         if (collection_manager_action_list) return G_SOURCE_CONTINUE;
878
879         if (collect_manager_process_entry_list()) return G_SOURCE_CONTINUE;
880
881         DEBUG_1("collection manager is up to date");
882         return G_SOURCE_REMOVE;
883 }
884
885 static gboolean collect_manager_timer_cb(gpointer)
886 {
887         DEBUG_1("collection manager timer expired");
888
889         g_idle_add_full(G_PRIORITY_LOW, collect_manager_process_cb, nullptr, nullptr);
890
891         collection_manager_timer_id = 0;
892         return FALSE;
893 }
894
895 static void collect_manager_timer_push(gint stop)
896 {
897         if (collection_manager_timer_id)
898                 {
899                 if (!stop) return;
900
901                 g_source_remove(collection_manager_timer_id);
902                 collection_manager_timer_id = 0;
903                 }
904
905         if (!stop)
906                 {
907                 collection_manager_timer_id = g_timeout_add(COLLECT_MANAGER_FLUSH_DELAY,
908                                                             collect_manager_timer_cb, nullptr);
909                 DEBUG_1("collection manager timer started");
910                 }
911 }
912
913 static void collect_manager_add_action(CollectManagerAction *action)
914 {
915         if (!action) return;
916
917         /* we keep track of the list's tail to keep this a n(1) operation */
918
919         if (collection_manager_action_tail)
920                 {
921                 collection_manager_action_tail = g_list_append(collection_manager_action_tail, action);
922                 collection_manager_action_tail = collection_manager_action_tail->next;
923                 }
924         else
925                 {
926                 collection_manager_action_list = g_list_append(collection_manager_action_list, action);
927                 collection_manager_action_tail = collection_manager_action_list;
928                 }
929
930         collect_manager_timer_push(FALSE);
931 }
932
933 void collect_manager_moved(FileData *fd)
934 {
935         CollectManagerAction *action;
936         const gchar *oldpath = fd->change->source;
937         const gchar *newpath = fd->change->dest;
938
939         action = collect_manager_action_new(oldpath, newpath, COLLECTION_MANAGER_UPDATE);
940         collect_manager_add_action(action);
941 }
942
943 void collect_manager_add(FileData *fd, const gchar *collection)
944 {
945         CollectManagerAction *action;
946         CollectWindow *cw;
947
948         if (!fd || !collection) return;
949
950         cw = collection_window_find_by_path(collection);
951         if (cw)
952                 {
953                 if (collection_list_find_fd(cw->cd->list, fd) == nullptr)
954                         {
955                         collection_add(cw->cd, fd, FALSE);
956                         }
957                 return;
958                 }
959
960         action = collect_manager_action_new(fd->path, collection, COLLECTION_MANAGER_ADD);
961         collect_manager_add_action(action);
962 }
963
964 void collect_manager_remove(FileData *fd, const gchar *collection)
965 {
966         CollectManagerAction *action;
967         CollectWindow *cw;
968
969         if (!fd || !collection) return;
970
971         cw = collection_window_find_by_path(collection);
972         if (cw)
973                 {
974                 while (collection_remove(cw->cd, fd));
975                 return;
976                 }
977
978         action = collect_manager_action_new(fd->path, collection, COLLECTION_MANAGER_REMOVE);
979         collect_manager_add_action(action);
980 }
981
982 void collect_manager_flush()
983 {
984         collect_manager_timer_push(TRUE);
985
986         DEBUG_1("collection manager flushing");
987         while (collect_manager_process_cb(nullptr));
988 }
989
990 void collect_manager_notify_cb(FileData *fd, NotifyType type, gpointer)
991 {
992         if (!(type & NOTIFY_CHANGE) || !fd->change) return;
993
994         DEBUG_1("Notify collect_manager: %s %04x", fd->path, type);
995         switch (fd->change->type)
996                 {
997                 case FILEDATA_CHANGE_MOVE:
998                         collect_manager_moved(fd);
999                         break;
1000                 case FILEDATA_CHANGE_COPY:
1001                         break;
1002                 case FILEDATA_CHANGE_RENAME:
1003                         collect_manager_moved(fd);
1004                         break;
1005                 case FILEDATA_CHANGE_DELETE:
1006                 case FILEDATA_CHANGE_UNSPECIFIED:
1007                 case FILEDATA_CHANGE_WRITE_METADATA:
1008                         break;
1009                 }
1010 }
1011
1012 static gint collection_manager_sort_cb(gconstpointer a, gconstpointer b)
1013 {
1014         auto char_a = static_cast<const gchar *>(a);
1015         auto char_b = static_cast<const gchar *>(b);
1016
1017         return g_strcmp0(char_a, char_b);
1018 }
1019
1020 /**
1021  * @brief Creates sorted list of collections
1022  * @param[out] names_exc sorted list of collections names excluding extension
1023  * @param[out] names_inc sorted list of collections names including extension
1024  * @param[out] paths sorted list of collection paths
1025  *
1026  * Lists of type gchar.
1027  * Used lists must be freed with string_list_free()
1028  */
1029 void collect_manager_list(GList **names_exc, GList **names_inc, GList **paths)
1030 {
1031         FileData *dir_fd;
1032         GList *list = nullptr;
1033         gchar *name;
1034         FileData *fd;
1035         gchar *filename;
1036
1037         if (names_exc == nullptr && names_inc == nullptr && paths == nullptr)
1038                 {
1039                 return;
1040                 }
1041
1042         dir_fd = file_data_new_dir((get_collections_dir()));
1043
1044         filelist_read(dir_fd, &list, nullptr);
1045
1046         while (list)
1047                 {
1048                 fd = static_cast<FileData *>(list->data);
1049                 filename = g_strdup(filename_from_path(fd->path));
1050
1051                 if (file_extension_match(filename, GQ_COLLECTION_EXT))
1052                         {
1053                         name = remove_extension_from_path(filename);
1054
1055                         if (names_exc != nullptr)
1056                                 {
1057                                 *names_exc = g_list_insert_sorted(*names_exc, g_strdup(name),
1058                                                                                         collection_manager_sort_cb);
1059                                 *names_exc = g_list_first(*names_exc);
1060                                 }
1061                         if (names_inc != nullptr)
1062                                 {
1063                                 *names_inc = g_list_insert_sorted(*names_inc,filename,
1064                                                                                         collection_manager_sort_cb);
1065                                 *names_inc = g_list_first(*names_inc);
1066                                 }
1067                         if (paths != nullptr)
1068                                 {
1069                                 *paths = g_list_insert_sorted(*paths, g_strdup(fd->path),
1070                                                                                         collection_manager_sort_cb);
1071                                 *paths = g_list_first(*paths);
1072                                 }
1073                         g_free(name);
1074                         }
1075                 list = list->next;
1076                 g_free(filename);
1077                 }
1078
1079         filelist_free(list);
1080 }
1081 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */