Fix clang-tidy: readability-non-const-parameter (4)
[geeqie.git] / src / cache-maint.cc
1 /*
2  * Copyright (C) 2006 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 "cache-maint.h"
24
25 #include "cache-loader.h"
26 #include "filedata.h"
27 #include "layout.h"
28 #include "misc.h"
29 #include "pixbuf-util.h"
30 #include "thumb.h"
31 #include "thumb-standard.h"
32 #include "ui-fileops.h"
33 #include "ui-misc.h"
34 #include "ui-tabcomp.h"
35 #include "ui-utildlg.h"
36 #include "window.h"
37
38
39 struct CMData
40 {
41         GList *list;
42         GList *done_list;
43         guint idle_id; /* event source id */
44         GenericDialog *gd;
45         GtkWidget *entry;
46         GtkWidget *spinner;
47         GtkWidget *button_stop;
48         GtkWidget *button_close;
49         gboolean clear;
50         gboolean metadata;
51         gboolean remote;
52 };
53
54 #define PURGE_DIALOG_WIDTH 400
55
56 /*
57  *-----------------------------------------------------------------------------
58  * Command line cache maintenance program functions *-----------------------------------------------------------------------------
59  */
60 static gchar *cache_maintenance_path = nullptr;
61 static GtkStatusIcon *status_icon;
62
63 static void cache_maintenance_sim_stop_cb(gpointer)
64 {
65         exit(EXIT_SUCCESS);
66 }
67
68 static void cache_maintenance_render_stop_cb(gpointer)
69 {
70         gtk_status_icon_set_tooltip_text(status_icon, _("Geeqie: Creating sim data..."));
71         cache_manager_sim_remote(cache_maintenance_path, TRUE, reinterpret_cast<GDestroyNotify *>(cache_maintenance_sim_stop_cb));
72 }
73
74 static void cache_maintenance_clean_stop_cb(gpointer)
75 {
76         gtk_status_icon_set_tooltip_text(status_icon, _("Geeqie: Creating thumbs..."));
77         cache_manager_render_remote(cache_maintenance_path, TRUE, options->thumbnails.cache_into_dirs, reinterpret_cast<GDestroyNotify *>(cache_maintenance_render_stop_cb));
78 }
79
80 static void cache_maintenance_user_cancel_cb()
81 {
82         exit(EXIT_FAILURE);
83 }
84
85 static void cache_maintenance_status_icon_activate_cb(GtkStatusIcon *, gpointer)
86 {
87         GtkWidget *menu;
88         GtkWidget *item;
89
90         menu = gtk_menu_new();
91
92         item = gtk_menu_item_new_with_label(_("Exit Geeqie Cache Maintenance"));
93
94         g_signal_connect(G_OBJECT(item), "activate", cache_maintenance_user_cancel_cb, item);
95         gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
96         gtk_widget_show(item);
97
98         /* take ownership of menu */
99         g_object_ref_sink(G_OBJECT(menu));
100
101         gtk_menu_popup_at_pointer(GTK_MENU(menu), nullptr);
102 }
103
104 void cache_maintenance(const gchar *path)
105 {
106         cache_maintenance_path = g_strdup(path);
107
108         status_icon = gtk_status_icon_new_from_pixbuf(pixbuf_inline(PIXBUF_INLINE_ICON));
109         gtk_status_icon_set_tooltip_text(status_icon, _("Geeqie: Cleaning thumbs..."));
110         gtk_status_icon_set_visible(status_icon, TRUE);
111         g_signal_connect(G_OBJECT(status_icon), "activate", G_CALLBACK(cache_maintenance_status_icon_activate_cb), NULL);
112
113         cache_maintain_home_remote(FALSE, FALSE, reinterpret_cast<GDestroyNotify *>(cache_maintenance_clean_stop_cb));
114 }
115
116 /*
117  *-------------------------------------------------------------------
118  * cache maintenance
119  *-------------------------------------------------------------------
120  */
121
122 static gchar *extension_find_dot(gchar *path)
123 {
124         gchar *dot = nullptr;
125
126         if (!path) return nullptr;
127
128         while (*path != '\0')
129                 {
130                 if (*path == '.') dot = path;
131                 path++;
132                 }
133
134         return dot;
135 }
136
137 static gboolean isempty(const gchar *path)
138 {
139         DIR *dp;
140         struct dirent *dir;
141         gchar *pathl;
142
143         pathl = path_from_utf8(path);
144         dp = opendir(pathl);
145         g_free(pathl);
146         if (!dp) return FALSE;
147
148         while ((dir = readdir(dp)) != nullptr)
149                 {
150                 gchar *name = dir->d_name;
151
152                 if (name[0] != '.' || (name[1] != '\0' && (name[1] != '.' || name[2] != '\0')) )
153                         {
154                         closedir(dp);
155                         return FALSE;
156                         }
157                 }
158
159         closedir(dp);
160         return TRUE;
161 }
162
163 static void cache_maintain_home_close(CMData *cm)
164 {
165         if (cm->idle_id) g_source_remove(cm->idle_id);
166         if (cm->gd) generic_dialog_close(cm->gd);
167         filelist_free(cm->list);
168         g_list_free(cm->done_list);
169         g_free(cm);
170 }
171
172 static void cache_maintain_home_stop(CMData *cm)
173 {
174         if (cm->idle_id)
175                 {
176                 g_source_remove(cm->idle_id);
177                 cm->idle_id = 0;
178                 }
179
180         if (!cm->remote)
181                 {
182                 gq_gtk_entry_set_text(GTK_ENTRY(cm->entry), _("done"));
183                 gtk_spinner_stop(GTK_SPINNER(cm->spinner));
184
185                 gtk_widget_set_sensitive(cm->button_stop, FALSE);
186                 gtk_widget_set_sensitive(cm->button_close, TRUE);
187                 }
188 }
189
190 static gboolean cache_maintain_home_cb(gpointer data)
191 {
192         auto cm = static_cast<CMData *>(data);
193         GList *dlist = nullptr;
194         GList *list = nullptr;
195         FileData *fd;
196         gboolean just_done = FALSE;
197         gboolean still_have_a_file = TRUE;
198         gsize base_length;
199         const gchar *cache_folder;
200         gboolean filter_disable;
201
202         if (cm->metadata)
203                 {
204                 cache_folder = get_metadata_cache_dir();
205                 }
206         else
207                 {
208                 cache_folder = get_thumbnails_cache_dir();
209                 }
210
211         base_length = strlen(cache_folder);
212
213         if (!cm->list)
214                 {
215                 DEBUG_1("purge chk done.");
216                 cm->idle_id = 0;
217                 cache_maintain_home_stop(cm);
218                 return G_SOURCE_REMOVE;
219                 }
220
221         fd = static_cast<FileData *>(cm->list->data);
222
223         DEBUG_1("purge chk (%d) \"%s\"", (cm->clear && !cm->metadata), fd->path);
224
225 /**
226  * It is necessary to disable the file filter when clearing the cache,
227  * otherwise the .sim (file similarity) files are not deleted.
228  */
229         filter_disable = options->file_filter.disable;
230         options->file_filter.disable = TRUE;
231
232         if (g_list_find(cm->done_list, fd) == nullptr)
233                 {
234                 cm->done_list = g_list_prepend(cm->done_list, fd);
235
236                 if (filelist_read(fd, &list, &dlist))
237                         {
238                         GList *work;
239
240                         just_done = TRUE;
241                         still_have_a_file = FALSE;
242
243                         work = list;
244                         while (work)
245                                 {
246                                 auto fd_list = static_cast<FileData *>(work->data);
247                                 gchar *path_buf = g_strdup(fd_list->path);
248                                 gchar *dot;
249
250                                 dot = extension_find_dot(path_buf);
251
252                                 if (dot) *dot = '\0';
253                                 if ((!cm->metadata && cm->clear) ||
254                                     (strlen(path_buf) > base_length && !isfile(path_buf + base_length)) )
255                                         {
256                                         if (dot) *dot = '.';
257                                         if (!unlink_file(path_buf)) log_printf("failed to delete:%s\n", path_buf);
258                                         }
259                                 else
260                                         {
261                                         still_have_a_file = TRUE;
262                                         }
263                                 g_free(path_buf);
264                                 work = work->next;
265                                 }
266                         }
267                 }
268         options->file_filter.disable = filter_disable;
269
270         filelist_free(list);
271
272         cm->list = g_list_concat(dlist, cm->list);
273
274         if (cm->list && g_list_find(cm->done_list, cm->list->data) != nullptr)
275                 {
276                 /* check if the dir is empty */
277
278                 if (cm->list->data == fd && just_done)
279                         {
280                         if (!still_have_a_file && !dlist && cm->list->next && !rmdir_utf8(fd->path))
281                                 {
282                                 log_printf("Unable to delete dir: %s\n", fd->path);
283                                 }
284                         }
285                 else
286                         {
287                         /* must re-check for an empty dir */
288                         if (isempty(fd->path) && cm->list->next && !rmdir_utf8(fd->path))
289                                 {
290                                 log_printf("Unable to delete dir: %s\n", fd->path);
291                                 }
292                         }
293
294                 fd = static_cast<FileData *>(cm->list->data);
295                 cm->done_list = g_list_remove(cm->done_list, fd);
296                 cm->list = g_list_remove(cm->list, fd);
297                 file_data_unref(fd);
298                 }
299
300         if (cm->list && !cm->remote)
301                 {
302                 const gchar *buf;
303
304                 fd = static_cast<FileData *>(cm->list->data);
305                 if (strlen(fd->path) > base_length)
306                         {
307                         buf = fd->path + base_length;
308                         }
309                 else
310                         {
311                         buf = "...";
312                         }
313                 gq_gtk_entry_set_text(GTK_ENTRY(cm->entry), buf);
314                 }
315
316         return G_SOURCE_CONTINUE;
317 }
318
319 static void cache_maintain_home_close_cb(GenericDialog *, gpointer data)
320 {
321         auto cm = static_cast<CMData *>(data);
322
323         if (!gtk_widget_get_sensitive(cm->button_close)) return;
324
325         cache_maintain_home_close(cm);
326 }
327
328 static void cache_maintain_home_stop_cb(GenericDialog *, gpointer data)
329 {
330         auto cm = static_cast<CMData *>(data);
331
332         cache_maintain_home_stop(cm);
333 }
334
335 /* sorry for complexity (cm->done_list), but need it to remove empty dirs */
336 void cache_maintain_home(gboolean metadata, gboolean clear, GtkWidget *parent)
337 {
338         CMData *cm;
339         GList *dlist;
340         FileData *dir_fd;
341         const gchar *msg;
342         const gchar *cache_folder;
343         GtkWidget *hbox;
344
345         if (metadata)
346                 {
347                 cache_folder = get_metadata_cache_dir();
348                 }
349         else
350                 {
351                 cache_folder = get_thumbnails_cache_dir();
352                 }
353
354         dir_fd = file_data_new_dir(cache_folder);
355         if (!filelist_read(dir_fd, nullptr, &dlist))
356                 {
357                 file_data_unref(dir_fd);
358                 return;
359                 }
360
361         dlist = g_list_append(dlist, dir_fd);
362
363         cm = g_new0(CMData, 1);
364         cm->list = dlist;
365         cm->done_list = nullptr;
366         cm->clear = clear;
367         cm->metadata = metadata;
368         cm->remote = FALSE;
369
370         if (metadata)
371                 {
372                 msg = _("Removing old metadata...");
373                 }
374         else if (clear)
375                 {
376                 msg = _("Clearing cached thumbnails...");
377                 }
378         else
379                 {
380                 msg = _("Removing old thumbnails...");
381                 }
382
383         cm->gd = generic_dialog_new(_("Maintenance"),
384                                     "main_maintenance",
385                                     parent, FALSE,
386                                     nullptr, cm);
387         cm->gd->cancel_cb = cache_maintain_home_close_cb;
388         cm->button_close = generic_dialog_add_button(cm->gd, GQ_ICON_CLOSE, _("Close"),
389                                                      cache_maintain_home_close_cb, FALSE);
390         gtk_widget_set_sensitive(cm->button_close, FALSE);
391         cm->button_stop = generic_dialog_add_button(cm->gd, GQ_ICON_STOP, _("Stop"),
392                                                     cache_maintain_home_stop_cb, FALSE);
393
394         generic_dialog_add_message(cm->gd, nullptr, msg, nullptr, FALSE);
395         gtk_window_set_default_size(GTK_WINDOW(cm->gd->dialog), PURGE_DIALOG_WIDTH, -1);
396
397         hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
398         gq_gtk_box_pack_start(GTK_BOX(cm->gd->vbox), hbox, FALSE, FALSE, 5);
399         gtk_widget_show(hbox);
400
401         cm->entry = gtk_entry_new();
402         gtk_widget_set_can_focus(cm->entry, FALSE);
403         gtk_editable_set_editable(GTK_EDITABLE(cm->entry), FALSE);
404         gq_gtk_box_pack_start(GTK_BOX(hbox), cm->entry, TRUE, TRUE, 0);
405         gtk_widget_show(cm->entry);
406
407         cm->spinner = gtk_spinner_new();
408         gtk_spinner_start(GTK_SPINNER(cm->spinner));
409         gq_gtk_box_pack_start(GTK_BOX(hbox), cm->spinner, FALSE, FALSE, 0);
410         gtk_widget_show(cm->spinner);
411
412         gtk_widget_show(cm->gd->dialog);
413
414         cm->idle_id = g_idle_add(cache_maintain_home_cb, cm);
415 }
416
417 /**
418  * @brief Clears or culls cached data
419  * @param metadata TRUE - work on metadata cache, FALSE - work on thumbnail cache
420  * @param clear TRUE - clear cache, FALSE - delete orphaned cached items
421  * @param func Function called when idle loop function terminates
422  *
423  *
424  */
425 void cache_maintain_home_remote(gboolean metadata, gboolean clear, GDestroyNotify *func)
426 {
427         CMData *cm;
428         GList *dlist;
429         FileData *dir_fd;
430         const gchar *cache_folder;
431
432         if (metadata)
433                 {
434                 cache_folder = get_metadata_cache_dir();
435                 }
436         else
437                 {
438                 cache_folder = get_thumbnails_cache_dir();
439                 }
440
441         dir_fd = file_data_new_dir(cache_folder);
442         if (!filelist_read(dir_fd, nullptr, &dlist))
443                 {
444                 file_data_unref(dir_fd);
445                 return;
446                 }
447
448         dlist = g_list_append(dlist, dir_fd);
449
450         cm = g_new0(CMData, 1);
451         cm->list = dlist;
452         cm->done_list = nullptr;
453         cm->clear = clear;
454         cm->metadata = metadata;
455         cm->remote = TRUE;
456
457         cm->idle_id = g_idle_add_full(G_PRIORITY_LOW, cache_maintain_home_cb, cm, reinterpret_cast<GDestroyNotify>(func));
458 }
459
460 static void cache_file_move(const gchar *src, const gchar *dest)
461 {
462         if (!dest || !src || !isfile(src)) return;
463
464         if (!move_file(src, dest))
465                 {
466                 DEBUG_1("Failed to move cache file \"%s\" to \"%s\"", src, dest);
467                 /* we remove it anyway - it's stale */
468                 unlink_file(src);
469                 }
470 }
471
472 static void cache_maint_moved(FileData *fd)
473 {
474         gchar *base;
475         mode_t mode = 0755;
476         const gchar *src = fd->change->source;
477         const gchar *dest = fd->change->dest;
478
479         if (!src || !dest) return;
480
481         base = cache_get_location(CACHE_TYPE_THUMB, dest, FALSE, &mode);
482         if (recursive_mkdir_if_not_exists(base, mode))
483                 {
484                 gchar *buf;
485                 gchar *d;
486
487                 buf = cache_find_location(CACHE_TYPE_THUMB, src);
488                 d = cache_get_location(CACHE_TYPE_THUMB, dest, TRUE, nullptr);
489                 cache_file_move(buf, d);
490                 g_free(d);
491                 g_free(buf);
492
493                 buf = cache_find_location(CACHE_TYPE_SIM, src);
494                 d = cache_get_location(CACHE_TYPE_SIM, dest, TRUE, nullptr);
495                 cache_file_move(buf, d);
496                 g_free(d);
497                 g_free(buf);
498                 }
499         else
500                 {
501                 log_printf("Failed to create cache dir for move %s\n", base);
502                 }
503         g_free(base);
504
505         base = cache_get_location(CACHE_TYPE_METADATA, dest, FALSE, &mode);
506         if (recursive_mkdir_if_not_exists(base, mode))
507                 {
508                 gchar *buf;
509                 gchar *d;
510
511                 buf = cache_find_location(CACHE_TYPE_METADATA, src);
512                 d = cache_get_location(CACHE_TYPE_METADATA, dest, TRUE, nullptr);
513                 cache_file_move(buf, d);
514                 g_free(d);
515                 g_free(buf);
516                 }
517         g_free(base);
518
519         if (options->thumbnails.enable_caching && options->thumbnails.spec_standard)
520                 thumb_std_maint_moved(src, dest);
521 }
522
523 static void cache_file_remove(const gchar *path)
524 {
525         if (path && isfile(path) && !unlink_file(path))
526                 {
527                 DEBUG_1("Failed to remove cache file %s", path);
528                 }
529 }
530
531 static void cache_maint_removed(FileData *fd)
532 {
533         gchar *buf;
534
535         buf = cache_find_location(CACHE_TYPE_THUMB, fd->path);
536         cache_file_remove(buf);
537         g_free(buf);
538
539         buf = cache_find_location(CACHE_TYPE_SIM, fd->path);
540         cache_file_remove(buf);
541         g_free(buf);
542
543         buf = cache_find_location(CACHE_TYPE_METADATA, fd->path);
544         cache_file_remove(buf);
545         g_free(buf);
546
547         if (options->thumbnails.enable_caching && options->thumbnails.spec_standard)
548                 thumb_std_maint_removed(fd->path);
549 }
550
551 static void cache_maint_copied(FileData *fd)
552 {
553         gchar *dest_base;
554         gchar *src_cache;
555         mode_t mode = 0755;
556
557         src_cache = cache_find_location(CACHE_TYPE_METADATA, fd->change->source);
558         if (!src_cache) return;
559
560         dest_base = cache_get_location(CACHE_TYPE_METADATA, fd->change->dest, FALSE, &mode);
561         if (recursive_mkdir_if_not_exists(dest_base, mode))
562                 {
563                 gchar *path;
564
565                 path = cache_get_location(CACHE_TYPE_METADATA, fd->change->dest, TRUE, nullptr);
566                 if (!copy_file(src_cache, path))
567                         {
568                         DEBUG_1("failed to copy metadata %s to %s", src_cache, path);
569                         }
570                 g_free(path);
571                 }
572
573         g_free(dest_base);
574         g_free(src_cache);
575 }
576
577 void cache_notify_cb(FileData *fd, NotifyType type, gpointer)
578 {
579         if (!(type & NOTIFY_CHANGE) || !fd->change) return;
580
581         DEBUG_1("Notify cache_maint: %s %04x", fd->path, type);
582         switch (fd->change->type)
583                 {
584                 case FILEDATA_CHANGE_MOVE:
585                 case FILEDATA_CHANGE_RENAME:
586                         cache_maint_moved(fd);
587                         break;
588                 case FILEDATA_CHANGE_COPY:
589                         cache_maint_copied(fd);
590                         break;
591                 case FILEDATA_CHANGE_DELETE:
592                         cache_maint_removed(fd);
593                         break;
594                 case FILEDATA_CHANGE_UNSPECIFIED:
595                 case FILEDATA_CHANGE_WRITE_METADATA:
596                         break;
597                 }
598 }
599
600
601 /*
602  *-------------------------------------------------------------------
603  * new cache maintenance utilities
604  *-------------------------------------------------------------------
605  */
606
607 struct CacheManager
608 {
609         GenericDialog *dialog;
610         GtkWidget *folder_entry;
611         GtkWidget *progress;
612
613         GList *list_todo;
614
615         gint count_total;
616         gint count_done;
617 };
618
619 struct CacheOpsData
620 {
621         GenericDialog *gd;
622         ThumbLoaderStd *tl;
623         CacheLoader *cl;
624         GDestroyNotify *destroy_func; /* Used by the command line prog. functions */
625
626         GList *list;
627         GList *list_dir;
628
629         gint days;
630         gboolean clear;
631
632         GtkWidget *button_close;
633         GtkWidget *button_stop;
634         GtkWidget *button_start;
635         GtkWidget *progress;
636         GtkWidget *progress_bar;
637         GtkWidget *spinner;
638
639         GtkWidget *group;
640         GtkWidget *entry;
641
642         gint count_total;
643         gint count_done;
644
645         gboolean local;
646         gboolean recurse;
647
648         gboolean remote;
649
650         guint idle_id; /* event source id */
651 };
652
653 static void cache_manager_render_reset(CacheOpsData *cd)
654 {
655         filelist_free(cd->list);
656         cd->list = nullptr;
657
658         filelist_free(cd->list_dir);
659         cd->list_dir = nullptr;
660
661         thumb_loader_free(reinterpret_cast<ThumbLoader *>(cd->tl));
662         cd->tl = nullptr;
663 }
664
665 static void cache_manager_render_close_cb(GenericDialog *, gpointer data)
666 {
667         auto cd = static_cast<CacheOpsData *>(data);
668
669         if (!gtk_widget_get_sensitive(cd->button_close)) return;
670
671         cache_manager_render_reset(cd);
672         generic_dialog_close(cd->gd);
673         g_free(cd);
674 }
675
676 static void cache_manager_render_finish(CacheOpsData *cd)
677 {
678         cache_manager_render_reset(cd);
679         if (!cd->remote)
680                 {
681                 gq_gtk_entry_set_text(GTK_ENTRY(cd->progress), _("done"));
682                 gtk_spinner_stop(GTK_SPINNER(cd->spinner));
683
684                 gtk_widget_set_sensitive(cd->group, TRUE);
685                 gtk_widget_set_sensitive(cd->button_start, TRUE);
686                 gtk_widget_set_sensitive(cd->button_stop, FALSE);
687                 gtk_widget_set_sensitive(cd->button_close, TRUE);
688                 }
689 }
690
691 static void cache_manager_render_stop_cb(GenericDialog *, gpointer data)
692 {
693         auto cd = static_cast<CacheOpsData *>(data);
694
695         gq_gtk_entry_set_text(GTK_ENTRY(cd->progress), _("stopped"));
696         cache_manager_render_finish(cd);
697 }
698
699 static void cache_manager_render_folder(CacheOpsData *cd, FileData *dir_fd)
700 {
701         GList *list_d = nullptr;
702         GList *list_f = nullptr;
703
704         if (cd->recurse)
705                 {
706                 filelist_read(dir_fd, &list_f, &list_d);
707                 }
708         else
709                 {
710                 filelist_read(dir_fd, &list_f, nullptr);
711                 }
712
713         list_f = filelist_filter(list_f, FALSE);
714         list_d = filelist_filter(list_d, TRUE);
715
716         cd->list = g_list_concat(list_f, cd->list);
717         cd->list_dir = g_list_concat(list_d, cd->list_dir);
718 }
719
720 static gboolean cache_manager_render_file(CacheOpsData *cd);
721
722 static void cache_manager_render_thumb_done_cb(ThumbLoader *, gpointer data)
723 {
724         auto cd = static_cast<CacheOpsData *>(data);
725
726         thumb_loader_free(reinterpret_cast<ThumbLoader *>(cd->tl));
727         cd->tl = nullptr;
728
729         while (cache_manager_render_file(cd));
730 }
731
732 static gboolean cache_manager_render_file(CacheOpsData *cd)
733 {
734         if (cd->list)
735                 {
736                 FileData *fd;
737                 gint success;
738
739                 fd = static_cast<FileData *>(cd->list->data);
740                 cd->list = g_list_remove(cd->list, fd);
741
742                 cd->tl = reinterpret_cast<ThumbLoaderStd *>(thumb_loader_new(options->thumbnails.max_width, options->thumbnails.max_height));
743                 thumb_loader_set_callbacks(reinterpret_cast<ThumbLoader *>(cd->tl),
744                                            cache_manager_render_thumb_done_cb,
745                                            cache_manager_render_thumb_done_cb,
746                                            nullptr, cd);
747                 thumb_loader_set_cache(reinterpret_cast<ThumbLoader *>(cd->tl), TRUE, cd->local, TRUE);
748                 success = thumb_loader_start(reinterpret_cast<ThumbLoader *>(cd->tl), fd);
749                 if (success)
750                         {
751                         if (!cd->remote)
752                                 {
753                                 gq_gtk_entry_set_text(GTK_ENTRY(cd->progress), fd->path);
754                                 cd->count_done = cd->count_done + 1;
755                                 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(cd->progress_bar), static_cast<gdouble>(cd->count_done) / cd->count_total);
756                                 }
757                         }
758                 else
759                         {
760                         thumb_loader_free(reinterpret_cast<ThumbLoader *>(cd->tl));
761                         cd->tl = nullptr;
762                         }
763
764                 file_data_unref(fd);
765
766                 return (!success);
767                 }
768         if (cd->list_dir)
769                 {
770                 FileData *fd;
771
772                 fd = static_cast<FileData *>(cd->list_dir->data);
773                 cd->list_dir = g_list_remove(cd->list_dir, fd);
774
775                 cache_manager_render_folder(cd, fd);
776
777                 file_data_unref(fd);
778
779                 return TRUE;
780                 }
781
782         if (!cd->remote)
783                 {
784                 gq_gtk_entry_set_text(GTK_ENTRY(cd->progress), _("done"));
785                 }
786         cache_manager_render_finish(cd);
787
788         if (cd->destroy_func)
789                 {
790                 g_idle_add(reinterpret_cast<GSourceFunc>(cd->destroy_func), nullptr);
791                 }
792
793         return FALSE;
794 }
795
796 static void cache_manager_render_start_cb(GenericDialog *, gpointer data)
797 {
798         auto cd = static_cast<CacheOpsData *>(data);
799         gchar *path;
800         GList *list_total = nullptr;
801
802         if(!cd->remote)
803                 {
804                 if (cd->list || !gtk_widget_get_sensitive(cd->button_start)) return;
805                 }
806
807         path = remove_trailing_slash((gq_gtk_entry_get_text(GTK_ENTRY(cd->entry))));
808         parse_out_relatives(path);
809
810         if (!isdir(path))
811                 {
812                 if (!cd->remote)
813                         {
814                         warning_dialog(_("Invalid folder"),
815                         _("The specified folder can not be found."),
816                         GQ_ICON_DIALOG_WARNING, cd->gd->dialog);
817                         }
818                 else
819                         {
820                         log_printf("The specified folder can not be found: %s\n", path);
821                         }
822                 }
823         else
824                 {
825                 FileData *dir_fd;
826                 if(!cd->remote)
827                         {
828                         gtk_widget_set_sensitive(cd->group, FALSE);
829                         gtk_widget_set_sensitive(cd->button_start, FALSE);
830                         gtk_widget_set_sensitive(cd->button_stop, TRUE);
831                         gtk_widget_set_sensitive(cd->button_close, FALSE);
832
833                         gtk_spinner_start(GTK_SPINNER(cd->spinner));
834                         }
835                 dir_fd = file_data_new_dir(path);
836                 cache_manager_render_folder(cd, dir_fd);
837                 list_total = filelist_recursive(dir_fd);
838                 cd->count_total = g_list_length(list_total);
839                 file_data_unref(dir_fd);
840                 g_list_free(list_total);
841                 cd->count_done = 0;
842
843                 while (cache_manager_render_file(cd));
844                 }
845
846         g_free(path);
847 }
848
849 static void cache_manager_render_start_render_remote(CacheOpsData *cd, const gchar *user_path)
850 {
851         gchar *path;
852
853         path = remove_trailing_slash(user_path);
854         parse_out_relatives(path);
855
856         if (!isdir(path))
857                 {
858                 log_printf("The specified folder can not be found: %s\n", path);
859                 }
860         else
861                 {
862                 FileData *dir_fd;
863
864                 dir_fd = file_data_new_dir(path);
865                 cache_manager_render_folder(cd, dir_fd);
866                 file_data_unref(dir_fd);
867                 while (cache_manager_render_file(cd));
868                 }
869
870         g_free(path);
871 }
872
873 static void cache_manager_render_dialog(GtkWidget *widget, const gchar *path)
874 {
875         CacheOpsData *cd;
876         GtkWidget *hbox;
877         GtkWidget *label;
878         GtkWidget *button;
879
880         cd = g_new0(CacheOpsData, 1);
881         cd->remote = FALSE;
882
883         cd->gd = generic_dialog_new(_("Create thumbnails"),
884                                     "create_thumbnails",
885                                     widget, FALSE,
886                                     nullptr, cd);
887         gtk_window_set_default_size(GTK_WINDOW(cd->gd->dialog), PURGE_DIALOG_WIDTH, -1);
888         cd->gd->cancel_cb = cache_manager_render_close_cb;
889         cd->button_close = generic_dialog_add_button(cd->gd, GQ_ICON_CLOSE, _("Close"),
890                                                      cache_manager_render_close_cb, FALSE);
891         cd->button_start = generic_dialog_add_button(cd->gd, GQ_ICON_OK, _("S_tart"),
892                                                      cache_manager_render_start_cb, FALSE);
893         cd->button_stop = generic_dialog_add_button(cd->gd, GQ_ICON_STOP, _("Stop"),
894                                                     cache_manager_render_stop_cb, FALSE);
895         gtk_widget_set_sensitive(cd->button_stop, FALSE);
896
897         generic_dialog_add_message(cd->gd, nullptr, _("Create thumbnails"), nullptr, FALSE);
898
899         hbox = pref_box_new(cd->gd->vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, 0);
900         pref_spacer(hbox, PREF_PAD_INDENT);
901         cd->group = pref_box_new(hbox, TRUE, GTK_ORIENTATION_VERTICAL, PREF_PAD_GAP);
902
903         hbox = pref_box_new(cd->group, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
904         pref_label_new(hbox, _("Folder:"));
905
906         label = tab_completion_new(&cd->entry, path, nullptr, nullptr, nullptr, nullptr);
907         tab_completion_add_select_button(cd->entry,_("Select folder") , TRUE);
908         gq_gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
909         gtk_widget_show(label);
910
911         pref_checkbox_new_int(cd->group, _("Include subfolders"), FALSE, &cd->recurse);
912         button = pref_checkbox_new_int(cd->group, _("Store thumbnails local to source images"), FALSE, &cd->local);
913         gtk_widget_set_sensitive(button, options->thumbnails.spec_standard);
914
915         pref_line(cd->gd->vbox, PREF_PAD_SPACE);
916         hbox = pref_box_new(cd->gd->vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
917
918         cd->progress = gtk_entry_new();
919         gtk_widget_set_can_focus(cd->progress, FALSE);
920         gtk_editable_set_editable(GTK_EDITABLE(cd->progress), FALSE);
921         gq_gtk_entry_set_text(GTK_ENTRY(cd->progress), _("click start to begin"));
922         gq_gtk_box_pack_start(GTK_BOX(hbox), cd->progress, TRUE, TRUE, 0);
923         gtk_widget_show(cd->progress);
924
925         cd->progress_bar = gtk_progress_bar_new();
926         gq_gtk_box_pack_start(GTK_BOX(cd->gd->vbox), cd->progress_bar, TRUE, TRUE, 0);
927         gtk_widget_show(cd->progress_bar);
928
929         cd->spinner = gtk_spinner_new();
930         gq_gtk_box_pack_start(GTK_BOX(hbox), cd->spinner, FALSE, FALSE, 0);
931         gtk_widget_show(cd->spinner);
932
933         cd->list = nullptr;
934
935         gtk_widget_show(cd->gd->dialog);
936 }
937
938 /**
939  * @brief Create thumbnails
940  * @param path Path to image folder
941  * @param recurse
942  * @param local Create thumbnails in same folder as images
943  * @param func Function called when idle loop function terminates
944  *
945  *
946  */
947 void cache_manager_render_remote(const gchar *path, gboolean recurse, gboolean local, GDestroyNotify *func)
948 {
949         CacheOpsData *cd;
950
951         cd = g_new0(CacheOpsData, 1);
952         cd->recurse = recurse;
953         cd->local = local;
954         cd->remote = TRUE;
955         cd->destroy_func = func;
956
957         cache_manager_render_start_render_remote(cd, path);
958 }
959
960 static void cache_manager_standard_clean_close_cb(GenericDialog *, gpointer data)
961 {
962         auto cd = static_cast<CacheOpsData *>(data);
963
964         if (!gtk_widget_get_sensitive(cd->button_close)) return;
965
966         generic_dialog_close(cd->gd);
967
968         thumb_loader_std_thumb_file_validate_cancel(cd->tl);
969         filelist_free(cd->list);
970         g_free(cd);
971 }
972
973 static void cache_manager_standard_clean_done(CacheOpsData *cd)
974 {
975         if (!cd->remote)
976                 {
977                 gtk_widget_set_sensitive(cd->button_stop, FALSE);
978                 gtk_widget_set_sensitive(cd->button_close, TRUE);
979
980                 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(cd->progress), 1.0);
981                 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(cd->progress), _("done"));
982                 }
983         if (cd->idle_id)
984                 {
985                 g_source_remove(cd->idle_id);
986                 cd->idle_id = 0;
987                 }
988
989         thumb_loader_std_thumb_file_validate_cancel(cd->tl);
990         cd->tl = nullptr;
991
992         filelist_free(cd->list);
993         cd->list = nullptr;
994 }
995
996 static void cache_manager_standard_clean_stop_cb(GenericDialog *, gpointer data)
997 {
998         auto cd = static_cast<CacheOpsData *>(data);
999
1000         cache_manager_standard_clean_done(cd);
1001 }
1002
1003 static gint cache_manager_standard_clean_clear_cb(gpointer data)
1004 {
1005         auto cd = static_cast<CacheOpsData *>(data);
1006
1007         if (cd->list)
1008                 {
1009                 FileData *next_fd;
1010
1011                 next_fd = static_cast<FileData *>(cd->list->data);
1012                 cd->list = g_list_remove(cd->list, next_fd);
1013
1014                 DEBUG_1("thumb removed: %s", next_fd->path);
1015
1016                 unlink_file(next_fd->path);
1017                 file_data_unref(next_fd);
1018
1019                 cd->count_done++;
1020                 if (!cd->remote)
1021                         {
1022                         if (cd->count_total != 0)
1023                                 {
1024                                 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(cd->progress),
1025                                                               static_cast<gdouble>(cd->count_done) / cd->count_total);
1026                                 }
1027                         }
1028
1029                 return G_SOURCE_CONTINUE;
1030                 }
1031
1032         cd->idle_id = 0;
1033         cache_manager_standard_clean_done(cd);
1034         return G_SOURCE_REMOVE;
1035 }
1036
1037 static void cache_manager_standard_clean_valid_cb(const gchar *path, gboolean valid, gpointer data)
1038 {
1039         auto cd = static_cast<CacheOpsData *>(data);
1040
1041         if (path)
1042                 {
1043                 if (!valid)
1044                         {
1045                         DEBUG_1("thumb cleaned: %s", path);
1046                         unlink_file(path);
1047                         }
1048
1049                 cd->count_done++;
1050                 if (!cd->remote)
1051                         {
1052                         if (cd->count_total != 0)
1053                                 {
1054                                 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(cd->progress),
1055                                                               static_cast<gdouble>(cd->count_done) / cd->count_total);
1056                                 }
1057                         }
1058                 }
1059
1060         cd->tl = nullptr;
1061         if (cd->list)
1062                 {
1063                 FileData *next_fd;
1064
1065                 next_fd = static_cast<FileData *>(cd->list->data);
1066                 cd->list = g_list_remove(cd->list, next_fd);
1067
1068                 cd->tl = thumb_loader_std_thumb_file_validate(next_fd->path, cd->days,
1069                                                               cache_manager_standard_clean_valid_cb, cd);
1070                 file_data_unref(next_fd);
1071                 }
1072         else
1073                 {
1074                 cache_manager_standard_clean_done(cd);
1075                 }
1076 }
1077
1078 static void cache_manager_standard_clean_start(GenericDialog *, gpointer data)
1079 {
1080         auto cd = static_cast<CacheOpsData *>(data);
1081         GList *list;
1082         gchar *path;
1083         FileData *dir_fd;
1084
1085         if (!cd->remote)
1086         {
1087                 if (cd->list || !gtk_widget_get_sensitive(cd->button_start)) return;
1088
1089                 gtk_widget_set_sensitive(cd->button_start, FALSE);
1090                 gtk_widget_set_sensitive(cd->button_stop, TRUE);
1091                 gtk_widget_set_sensitive(cd->button_close, FALSE);
1092
1093                 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(cd->progress), _("running..."));
1094         }
1095
1096         path = g_build_filename(get_thumbnails_standard_cache_dir(), THUMB_FOLDER_NORMAL, NULL);
1097         dir_fd = file_data_new_dir(path);
1098         filelist_read(dir_fd, &list, nullptr);
1099         cd->list = list;
1100         file_data_unref(dir_fd);
1101         g_free(path);
1102
1103         path = g_build_filename(get_thumbnails_standard_cache_dir(), THUMB_FOLDER_LARGE, NULL);
1104         dir_fd = file_data_new_dir(path);
1105         filelist_read(dir_fd, &list, nullptr);
1106         cd->list = g_list_concat(cd->list, list);
1107         file_data_unref(dir_fd);
1108         g_free(path);
1109
1110         path = g_build_filename(get_thumbnails_standard_cache_dir(), THUMB_FOLDER_FAIL, NULL);
1111         dir_fd = file_data_new_dir(path);
1112         filelist_read(dir_fd, &list, nullptr);
1113         cd->list = g_list_concat(cd->list, list);
1114         file_data_unref(dir_fd);
1115         g_free(path);
1116
1117         cd->count_total = g_list_length(cd->list);
1118         cd->count_done = 0;
1119
1120         /* start iterating */
1121         if (cd->clear)
1122                 {
1123                 cd->idle_id = g_idle_add(cache_manager_standard_clean_clear_cb, cd);
1124                 }
1125         else
1126                 {
1127                 cache_manager_standard_clean_valid_cb(nullptr, TRUE, cd);
1128                 }
1129 }
1130
1131 static void cache_manager_standard_clean_start_cb(GenericDialog *gd, gpointer data)
1132 {
1133         cache_manager_standard_clean_start(gd, data);
1134 }
1135
1136 static void cache_manager_standard_process(GtkWidget *widget, gboolean clear)
1137 {
1138         CacheOpsData *cd;
1139         const gchar *icon_name;
1140         const gchar *msg;
1141
1142         cd = g_new0(CacheOpsData, 1);
1143         cd->clear = clear;
1144         cd->remote = FALSE;
1145
1146         if (clear)
1147                 {
1148                 icon_name = GQ_ICON_DELETE;
1149                 msg = _("Clearing thumbnails...");
1150                 }
1151         else
1152                 {
1153                 icon_name = GQ_ICON_CLEAR;
1154                 msg = _("Removing old thumbnails...");
1155                 }
1156
1157         cd->gd = generic_dialog_new(_("Maintenance"),
1158                                     "standard_maintenance",
1159                                     widget, FALSE,
1160                                     nullptr, cd);
1161         cd->gd->cancel_cb = cache_manager_standard_clean_close_cb;
1162         cd->button_close = generic_dialog_add_button(cd->gd, GQ_ICON_CLOSE, _("Close"),
1163                                                      cache_manager_standard_clean_close_cb, FALSE);
1164         cd->button_start = generic_dialog_add_button(cd->gd, GQ_ICON_OK, _("S_tart"),
1165                                                      cache_manager_standard_clean_start_cb, FALSE);
1166         cd->button_stop = generic_dialog_add_button(cd->gd, GQ_ICON_STOP, _("Stop"),
1167                                                     cache_manager_standard_clean_stop_cb, FALSE);
1168         gtk_widget_set_sensitive(cd->button_stop, FALSE);
1169
1170         generic_dialog_add_message(cd->gd, icon_name, msg, nullptr, FALSE);
1171
1172         cd->progress = gtk_progress_bar_new();
1173         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(cd->progress), _("click start to begin"));
1174         gtk_progress_bar_set_show_text(GTK_PROGRESS_BAR(cd->progress), TRUE);
1175         gq_gtk_box_pack_start(GTK_BOX(cd->gd->vbox), cd->progress, FALSE, FALSE, 0);
1176         gtk_widget_show(cd->progress);
1177
1178         cd->days = 30;
1179         cd->tl = nullptr;
1180         cd->idle_id = 0;
1181
1182         gtk_widget_show(cd->gd->dialog);
1183 }
1184
1185 void cache_manager_standard_process_remote(gboolean clear)
1186 {
1187         CacheOpsData *cd;
1188
1189         cd = g_new0(CacheOpsData, 1);
1190         cd->clear = clear;
1191         cd->days = 30;
1192         cd->tl = nullptr;
1193         cd->idle_id = 0;
1194         cd->remote = TRUE;
1195
1196         cache_manager_standard_clean_start(nullptr, cd);
1197 }
1198
1199 static void cache_manager_standard_clean_cb(GtkWidget *widget, gpointer)
1200 {
1201         cache_manager_standard_process(widget, FALSE);
1202 }
1203
1204 static void cache_manager_standard_clear_cb(GtkWidget *widget, gpointer)
1205 {
1206         cache_manager_standard_process(widget, TRUE);
1207 }
1208
1209
1210 static void cache_manager_main_clean_cb(GtkWidget *widget, gpointer)
1211 {
1212         cache_maintain_home(FALSE, FALSE, widget);
1213 }
1214
1215
1216 static void dummy_cancel_cb(GenericDialog *, gpointer)
1217 {
1218         /* no op, only so cancel button appears */
1219 }
1220
1221 static void cache_manager_main_clear_ok_cb(GenericDialog *, gpointer)
1222 {
1223         cache_maintain_home(FALSE, TRUE, nullptr);
1224 }
1225
1226 void cache_manager_main_clear_confirm(GtkWidget *parent)
1227 {
1228         GenericDialog *gd;
1229
1230         gd = generic_dialog_new(_("Clear cache"),
1231                                 "clear_cache", parent, TRUE,
1232                                 dummy_cancel_cb, nullptr);
1233         generic_dialog_add_message(gd, GQ_ICON_DIALOG_QUESTION, _("Clear cache"),
1234                                    _("This will remove all thumbnails and sim. files\nthat have been saved to disk, continue?"), TRUE);
1235         generic_dialog_add_button(gd, GQ_ICON_OK, "OK", cache_manager_main_clear_ok_cb, TRUE);
1236
1237         gtk_widget_show(gd->dialog);
1238 }
1239
1240 static void cache_manager_main_clear_cb(GtkWidget *widget, gpointer)
1241 {
1242         cache_manager_main_clear_confirm(widget);
1243 }
1244
1245 static void cache_manager_render_cb(GtkWidget *widget, gpointer)
1246 {
1247         const gchar *path = layout_get_path(nullptr);
1248
1249         if (!path || !*path) path = homedir();
1250         cache_manager_render_dialog(widget, path);
1251 }
1252
1253 static void cache_manager_metadata_clean_cb(GtkWidget *widget, gpointer)
1254 {
1255         cache_maintain_home(TRUE, FALSE, widget);
1256 }
1257
1258
1259 static CacheManager *cache_manager = nullptr;
1260
1261 static void cache_manager_close_cb(GenericDialog *gd, gpointer)
1262 {
1263         generic_dialog_close(gd);
1264
1265         g_free(cache_manager);
1266         cache_manager = nullptr;
1267 }
1268
1269 static void cache_manager_help_cb(GenericDialog *, gpointer)
1270 {
1271         help_window_show("GuideReferenceManagement.html");
1272 }
1273
1274 static GtkWidget *cache_manager_location_label(GtkWidget *group, const gchar *subdir)
1275 {
1276         GtkWidget *label;
1277         gchar *buf;
1278
1279         buf = g_strdup_printf(_("Location: %s"), subdir);
1280         label = pref_label_new(group, buf);
1281         g_free(buf);
1282         gtk_label_set_xalign(GTK_LABEL(label), 0.0);
1283         gtk_label_set_yalign(GTK_LABEL(label), 0.5);
1284
1285         return label;
1286 }
1287
1288 static gboolean cache_manager_sim_file(CacheOpsData *cd);
1289
1290 static void cache_manager_sim_reset(CacheOpsData *cd)
1291 {
1292         filelist_free(cd->list);
1293         cd->list = nullptr;
1294
1295         filelist_free(cd->list_dir);
1296         cd->list_dir = nullptr;
1297
1298         cache_loader_free(cd->cl);
1299         cd->cl = nullptr;
1300 }
1301
1302 static void cache_manager_sim_close_cb(GenericDialog *, gpointer data)
1303 {
1304         auto cd = static_cast<CacheOpsData *>(data);
1305
1306         if (!gtk_widget_get_sensitive(cd->button_close)) return;
1307
1308         cache_manager_sim_reset(cd);
1309         generic_dialog_close(cd->gd);
1310         g_free(cd);
1311 }
1312
1313 static void cache_manager_sim_finish(CacheOpsData *cd)
1314 {
1315         cache_manager_sim_reset(cd);
1316         if (!cd->remote)
1317                 {
1318                 gtk_spinner_stop(GTK_SPINNER(cd->spinner));
1319
1320                 gtk_widget_set_sensitive(cd->group, TRUE);
1321                 gtk_widget_set_sensitive(cd->button_start, TRUE);
1322                 gtk_widget_set_sensitive(cd->button_stop, FALSE);
1323                 gtk_widget_set_sensitive(cd->button_close, TRUE);
1324                 }
1325 }
1326
1327 static void cache_manager_sim_stop_cb(GenericDialog *, gpointer data)
1328 {
1329         auto cd = static_cast<CacheOpsData *>(data);
1330
1331         gq_gtk_entry_set_text(GTK_ENTRY(cd->progress), _("stopped"));
1332         cache_manager_sim_finish(cd);
1333 }
1334
1335 static void cache_manager_sim_folder(CacheOpsData *cd, FileData *dir_fd)
1336 {
1337         GList *list_d = nullptr;
1338         GList *list_f = nullptr;
1339
1340         if (cd->recurse)
1341                 {
1342                 filelist_read(dir_fd, &list_f, &list_d);
1343                 }
1344         else
1345                 {
1346                 filelist_read(dir_fd, &list_f, nullptr);
1347                 }
1348
1349         list_f = filelist_filter(list_f, FALSE);
1350         list_d = filelist_filter(list_d, TRUE);
1351
1352         cd->list = g_list_concat(list_f, cd->list);
1353         cd->list_dir = g_list_concat(list_d, cd->list_dir);
1354 }
1355
1356 static void cache_manager_sim_file_done_cb(CacheLoader *, gint, gpointer data)
1357 {
1358         auto cd = static_cast<CacheOpsData *>(data);
1359
1360         cache_loader_free(cd->cl);
1361         cd->cl = nullptr;
1362
1363         while (cache_manager_sim_file(cd));
1364 }
1365
1366 static void cache_manager_sim_start_sim_remote(CacheOpsData *cd, const gchar *user_path)
1367 {
1368         gchar *path;
1369
1370         path = remove_trailing_slash(user_path);
1371         parse_out_relatives(path);
1372
1373         if (!isdir(path))
1374                 {
1375                 log_printf("The specified folder can not be found: %s\n", path);
1376                 }
1377         else
1378                 {
1379                 FileData *dir_fd;
1380
1381                 dir_fd = file_data_new_dir(path);
1382                 cache_manager_sim_folder(cd, dir_fd);
1383                 file_data_unref(dir_fd);
1384                 while (cache_manager_sim_file(cd));
1385                 }
1386
1387         g_free(path);
1388 }
1389
1390 /**
1391  * @brief Generate .sim files
1392  * @param path Path to image folder
1393  * @param recurse
1394  * @param func Function called when idle loop function terminates
1395  *
1396  *
1397  */
1398 void cache_manager_sim_remote(const gchar *path, gboolean recurse, GDestroyNotify *func)
1399 {
1400         CacheOpsData *cd;
1401
1402         cd = g_new0(CacheOpsData, 1);
1403         cd->recurse = recurse;
1404         cd->remote = TRUE;
1405         cd->destroy_func = func;
1406
1407         cache_manager_sim_start_sim_remote(cd, path);
1408 }
1409
1410 static gboolean cache_manager_sim_file(CacheOpsData *cd)
1411 {
1412         CacheDataType load_mask;
1413
1414         if (cd->list)
1415                 {
1416                 FileData *fd;
1417                 fd = static_cast<FileData *>(cd->list->data);
1418                 cd->list = g_list_remove(cd->list, fd);
1419
1420                 load_mask = static_cast<CacheDataType>(CACHE_LOADER_DIMENSIONS | CACHE_LOADER_DATE | CACHE_LOADER_MD5SUM | CACHE_LOADER_SIMILARITY);
1421                 cd->cl = cache_loader_new(fd, load_mask, (cache_manager_sim_file_done_cb), cd);
1422
1423                 if (!cd->remote)
1424                         {
1425                         gq_gtk_entry_set_text(GTK_ENTRY(cd->progress), fd->path);
1426                         }
1427
1428                 file_data_unref(fd);
1429                 cd->count_done = cd->count_done + 1;
1430                 if (!cd->remote)
1431                         {
1432                         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(cd->progress_bar), static_cast<gdouble>(cd->count_done) / cd->count_total);
1433                         }
1434
1435                 return FALSE;
1436                 }
1437         if (cd->list_dir)
1438                 {
1439                 FileData *fd;
1440
1441                 fd = static_cast<FileData *>(cd->list_dir->data);
1442                 cd->list_dir = g_list_remove(cd->list_dir, fd);
1443
1444                 cache_manager_sim_folder(cd, fd);
1445                 file_data_unref(fd);
1446
1447                 return TRUE;
1448                 }
1449
1450                 if (!cd->remote)
1451                         {
1452                         gq_gtk_entry_set_text(GTK_ENTRY(cd->progress), _("done"));
1453                         }
1454
1455         cache_manager_sim_finish(cd);
1456
1457         if (cd->destroy_func)
1458                 {
1459                 g_idle_add(reinterpret_cast<GSourceFunc>(cd->destroy_func), nullptr);
1460                 }
1461
1462         return FALSE;
1463 }
1464
1465 static void cache_manager_sim_start_cb(GenericDialog *, gpointer data)
1466 {
1467         auto cd = static_cast<CacheOpsData *>(data);
1468         gchar *path;
1469         GList *list_total = nullptr;
1470
1471         if (!cd->remote)
1472                 {
1473                 if (cd->list || !gtk_widget_get_sensitive(cd->button_start)) return;
1474                 }
1475
1476         path = remove_trailing_slash((gq_gtk_entry_get_text(GTK_ENTRY(cd->entry))));
1477         parse_out_relatives(path);
1478
1479         if (!isdir(path))
1480                 {
1481                 if (!cd->remote)
1482                         {
1483                         warning_dialog(_("Invalid folder"),
1484                         _("The specified folder can not be found."),
1485                         GQ_ICON_DIALOG_WARNING, cd->gd->dialog);
1486                         }
1487                 else
1488                         {
1489                         log_printf("The specified folder can not be found: %s\n", path);
1490                         }
1491                 }
1492         else
1493                 {
1494                 FileData *dir_fd;
1495                 if(!cd->remote)
1496                         {
1497                         gtk_widget_set_sensitive(cd->group, FALSE);
1498                         gtk_widget_set_sensitive(cd->button_start, FALSE);
1499                         gtk_widget_set_sensitive(cd->button_stop, TRUE);
1500                         gtk_widget_set_sensitive(cd->button_close, FALSE);
1501
1502                         gtk_spinner_start(GTK_SPINNER(cd->spinner));
1503                         }
1504                 dir_fd = file_data_new_dir(path);
1505                 cache_manager_sim_folder(cd, dir_fd);
1506                 list_total = filelist_recursive(dir_fd);
1507                 cd->count_total = g_list_length(list_total);
1508                 file_data_unref(dir_fd);
1509                 g_list_free(list_total);
1510                 cd->count_done = 0;
1511
1512                 while (cache_manager_sim_file(static_cast<CacheOpsData *>(cd)));
1513                 }
1514
1515         g_free(path);
1516 }
1517
1518 static void cache_manager_sim_load_dialog(GtkWidget *widget, const gchar *path)
1519 {
1520         CacheOpsData *cd;
1521         GtkWidget *hbox;
1522         GtkWidget *label;
1523
1524         cd = g_new0(CacheOpsData, 1);
1525         cd->remote = FALSE;
1526         cd->recurse = TRUE;
1527
1528         cd->gd = generic_dialog_new(_("Create sim. files"), "create_sim_files", widget, FALSE, nullptr, cd);
1529         gtk_window_set_default_size(GTK_WINDOW(cd->gd->dialog), PURGE_DIALOG_WIDTH, -1);
1530         cd->gd->cancel_cb = cache_manager_sim_close_cb;
1531         cd->button_close = generic_dialog_add_button(cd->gd, GQ_ICON_CLOSE, _("Close"),
1532                                                      cache_manager_sim_close_cb, FALSE);
1533         cd->button_start = generic_dialog_add_button(cd->gd, GQ_ICON_OK, _("S_tart"),
1534                                                      cache_manager_sim_start_cb, FALSE);
1535         cd->button_stop = generic_dialog_add_button(cd->gd, GQ_ICON_STOP, _("Stop"),
1536                                                     cache_manager_sim_stop_cb, FALSE);
1537         gtk_widget_set_sensitive(cd->button_stop, FALSE);
1538
1539         generic_dialog_add_message(cd->gd, nullptr, _("Create sim. files recursively"), nullptr, FALSE);
1540
1541         hbox = pref_box_new(cd->gd->vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, 0);
1542         pref_spacer(hbox, PREF_PAD_INDENT);
1543         cd->group = pref_box_new(hbox, TRUE, GTK_ORIENTATION_VERTICAL, PREF_PAD_GAP);
1544
1545         hbox = pref_box_new(cd->group, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
1546         pref_label_new(hbox, _("Folder:"));
1547
1548         label = tab_completion_new(&cd->entry, path, nullptr, nullptr, nullptr, nullptr);
1549         tab_completion_add_select_button(cd->entry,_("Select folder") , TRUE);
1550         gq_gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
1551         gtk_widget_show(label);
1552
1553         pref_line(cd->gd->vbox, PREF_PAD_SPACE);
1554         hbox = pref_box_new(cd->gd->vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
1555
1556         cd->progress = gtk_entry_new();
1557         gtk_widget_set_can_focus(cd->progress, FALSE);
1558         gtk_editable_set_editable(GTK_EDITABLE(cd->progress), FALSE);
1559         gq_gtk_entry_set_text(GTK_ENTRY(cd->progress), _("click start to begin"));
1560         gq_gtk_box_pack_start(GTK_BOX(hbox), cd->progress, TRUE, TRUE, 0);
1561         gtk_widget_show(cd->progress);
1562
1563         cd->progress_bar = gtk_progress_bar_new();
1564         gq_gtk_box_pack_start(GTK_BOX(cd->gd->vbox), cd->progress_bar, TRUE, TRUE, 0);
1565         gtk_widget_show(cd->progress_bar);
1566
1567         cd->spinner = gtk_spinner_new();
1568         gq_gtk_box_pack_start(GTK_BOX(hbox), cd->spinner, FALSE, FALSE, 0);
1569         gtk_widget_show(cd->spinner);
1570
1571         cd->list = nullptr;
1572
1573         gtk_widget_show(cd->gd->dialog);
1574 }
1575
1576 static void cache_manager_sim_load_cb(GtkWidget *widget, gpointer)
1577 {
1578         const gchar *path = layout_get_path(nullptr);
1579
1580         if (!path || !*path) path = homedir();
1581         cache_manager_sim_load_dialog(widget, path);
1582 }
1583
1584 static void cache_manager_cache_maintenance_close_cb(GenericDialog *, gpointer data)
1585 {
1586         auto cd = static_cast<CacheOpsData *>(data);
1587
1588         if (!gtk_widget_get_sensitive(cd->button_close)) return;
1589
1590         cache_manager_sim_reset(cd);
1591         generic_dialog_close(cd->gd);
1592         g_free(cd);
1593 }
1594
1595 static void cache_manager_cache_maintenance_start_cb(GenericDialog *, gpointer data)
1596 {
1597         auto cd = static_cast<CacheOpsData *>(data);
1598         gchar *path;
1599         gchar *cmd_line;
1600
1601         if (!cd->remote)
1602                 {
1603                 if (cd->list || !gtk_widget_get_sensitive(cd->button_start)) return;
1604                 }
1605
1606         path = remove_trailing_slash((gq_gtk_entry_get_text(GTK_ENTRY(cd->entry))));
1607         parse_out_relatives(path);
1608
1609         if (!isdir(path))
1610                 {
1611                 if (!cd->remote)
1612                         {
1613                         warning_dialog(_("Invalid folder"),
1614                         _("The specified folder can not be found."),
1615                         GQ_ICON_DIALOG_WARNING, cd->gd->dialog);
1616                         }
1617                 else
1618                         {
1619                         log_printf("The specified folder can not be found: \"%s\"\n", path);
1620                         }
1621                 }
1622         else
1623                 {
1624                 cmd_line = g_strdup_printf("%s --cache-maintenance \"%s\"", gq_executable_path, path);
1625
1626                 g_spawn_command_line_async(cmd_line, nullptr);
1627
1628                 g_free(cmd_line);
1629                 generic_dialog_close(cd->gd);
1630                 cache_manager_sim_reset(cd);
1631                 g_free(cd);
1632                 }
1633
1634         g_free(path);
1635 }
1636
1637 static void cache_manager_cache_maintenance_load_dialog(GtkWidget *widget, const gchar *path)
1638 {
1639         CacheOpsData *cd;
1640         GtkWidget *hbox;
1641         GtkWidget *label;
1642
1643         cd = g_new0(CacheOpsData, 1);
1644         cd->remote = FALSE;
1645         cd->recurse = TRUE;
1646
1647         cd->gd = generic_dialog_new(_("Background cache maintenance"), "background_cache_maintenance", widget, FALSE, nullptr, cd);
1648         gtk_window_set_default_size(GTK_WINDOW(cd->gd->dialog), PURGE_DIALOG_WIDTH, -1);
1649         cd->gd->cancel_cb = cache_manager_cache_maintenance_close_cb;
1650         cd->button_close = generic_dialog_add_button(cd->gd, GQ_ICON_CLOSE, _("Close"),
1651                                                      cache_manager_cache_maintenance_close_cb, FALSE);
1652         cd->button_start = generic_dialog_add_button(cd->gd, GQ_ICON_OK, _("S_tart"),
1653                                                      cache_manager_cache_maintenance_start_cb, FALSE);
1654
1655         generic_dialog_add_message(cd->gd, nullptr, _("Recursively delete orphaned thumbnails\nand .sim files, and create new\nthumbnails and .sim files"), nullptr, FALSE);
1656
1657         hbox = pref_box_new(cd->gd->vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, 0);
1658         pref_spacer(hbox, PREF_PAD_INDENT);
1659         cd->group = pref_box_new(hbox, TRUE, GTK_ORIENTATION_VERTICAL, PREF_PAD_GAP);
1660
1661         hbox = pref_box_new(cd->group, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
1662         pref_label_new(hbox, _("Folder:"));
1663
1664         label = tab_completion_new(&cd->entry, path, nullptr, nullptr, nullptr, nullptr);
1665         tab_completion_add_select_button(cd->entry,_("Select folder") , TRUE);
1666         gq_gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
1667         gtk_widget_show(label);
1668
1669         cd->list = nullptr;
1670
1671         gtk_widget_show(cd->gd->dialog);
1672 }
1673
1674 static void cache_manager_cache_maintenance_load_cb(GtkWidget *widget, gpointer)
1675 {
1676         const gchar *path = layout_get_path(nullptr);
1677
1678         if (!path || !*path) path = homedir();
1679         cache_manager_cache_maintenance_load_dialog(widget, path);
1680 }
1681
1682 void cache_manager_show()
1683 {
1684         GenericDialog *gd;
1685         GtkWidget *group;
1686         GtkWidget *button;
1687         GtkWidget *table;
1688         GtkSizeGroup *sizegroup;
1689         gchar *path;
1690
1691         if (cache_manager)
1692                 {
1693                 gtk_window_present(GTK_WINDOW(cache_manager->dialog->dialog));
1694                 return;
1695                 }
1696
1697         cache_manager = g_new0(CacheManager, 1);
1698
1699         cache_manager->dialog = generic_dialog_new(_("Cache Maintenance"),
1700                                                    "cache_manager",
1701                                                    nullptr, FALSE,
1702                                                    nullptr, cache_manager);
1703         gd = cache_manager->dialog;
1704
1705         gd->cancel_cb = cache_manager_close_cb;
1706         generic_dialog_add_button(gd, GQ_ICON_CLOSE, _("Close"),
1707                                   cache_manager_close_cb, FALSE);
1708         generic_dialog_add_button(gd, GQ_ICON_HELP, _("Help"),
1709                                   cache_manager_help_cb, FALSE);
1710
1711         generic_dialog_add_message(gd, nullptr, _("Cache and Data Maintenance"), nullptr, FALSE);
1712
1713         sizegroup = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
1714
1715         group = pref_group_new(gd->vbox, FALSE, _("Geeqie thumbnail and sim. cache"), GTK_ORIENTATION_VERTICAL);
1716
1717         cache_manager_location_label(group, get_thumbnails_cache_dir());
1718
1719         table = pref_table_new(group, 2, 2, FALSE, FALSE);
1720
1721         button = pref_table_button(table, 0, 0, GQ_ICON_CLEAR, _("Clean up"),
1722                                    G_CALLBACK(cache_manager_main_clean_cb), cache_manager);
1723         gtk_size_group_add_widget(sizegroup, button);
1724         pref_table_label(table, 1, 0, _("Remove orphaned or outdated thumbnails and sim. files."), GTK_ALIGN_START);
1725
1726         button = pref_table_button(table, 0, 1, GQ_ICON_DELETE, _("Clear cache"),
1727                                    G_CALLBACK(cache_manager_main_clear_cb), cache_manager);
1728         gtk_size_group_add_widget(sizegroup, button);
1729         pref_table_label(table, 1, 1, _("Delete all cached data."), GTK_ALIGN_START);
1730
1731
1732         group = pref_group_new(gd->vbox, FALSE, _("Shared thumbnail cache"), GTK_ORIENTATION_VERTICAL);
1733
1734         path = g_build_filename(get_thumbnails_standard_cache_dir(), NULL);
1735         cache_manager_location_label(group, path);
1736         g_free(path);
1737
1738         table = pref_table_new(group, 2, 2, FALSE, FALSE);
1739
1740         button = pref_table_button(table, 0, 0, GQ_ICON_CLEAR, _("Clean up"),
1741                                    G_CALLBACK(cache_manager_standard_clean_cb), cache_manager);
1742         gtk_size_group_add_widget(sizegroup, button);
1743         pref_table_label(table, 1, 0, _("Remove orphaned or outdated thumbnails."), GTK_ALIGN_START);
1744
1745         button = pref_table_button(table, 0, 1, GQ_ICON_DELETE, _("Clear cache"),
1746                                    G_CALLBACK(cache_manager_standard_clear_cb), cache_manager);
1747         gtk_size_group_add_widget(sizegroup, button);
1748         pref_table_label(table, 1, 1, _("Delete all cached thumbnails."), GTK_ALIGN_START);
1749
1750         group = pref_group_new(gd->vbox, FALSE, _("Create thumbnails"), GTK_ORIENTATION_VERTICAL);
1751
1752         table = pref_table_new(group, 2, 1, FALSE, FALSE);
1753
1754         button = pref_table_button(table, 0, 1, GQ_ICON_RUN, _("Render"),
1755                                    G_CALLBACK(cache_manager_render_cb), cache_manager);
1756         gtk_size_group_add_widget(sizegroup, button);
1757         pref_table_label(table, 1, 1, _("Render thumbnails for a specific folder."), GTK_ALIGN_START);
1758         gtk_widget_set_sensitive(group, options->thumbnails.enable_caching);
1759
1760         group = pref_group_new(gd->vbox, FALSE, _("File similarity cache"), GTK_ORIENTATION_VERTICAL);
1761
1762         table = pref_table_new(group, 3, 2, FALSE, FALSE);
1763
1764         button = pref_table_button(table, 0, 0, GQ_ICON_RUN, _("Create"),
1765                                    G_CALLBACK(cache_manager_sim_load_cb), cache_manager);
1766         gtk_size_group_add_widget(sizegroup, button);
1767         pref_table_label(table, 1, 0, _("Create sim. files recursively."), GTK_ALIGN_START);
1768         gtk_widget_set_sensitive(group, options->thumbnails.enable_caching);
1769
1770         group = pref_group_new(gd->vbox, FALSE, _("Metadata"), GTK_ORIENTATION_VERTICAL);
1771
1772         cache_manager_location_label(group, get_metadata_cache_dir());
1773
1774         table = pref_table_new(group, 2, 1, FALSE, FALSE);
1775
1776         button = pref_table_button(table, 0, 0, GQ_ICON_CLEAR, _("Clean up"),
1777                                    G_CALLBACK(cache_manager_metadata_clean_cb), cache_manager);
1778         gtk_size_group_add_widget(sizegroup, button);
1779         pref_table_label(table, 1, 0, _("Remove orphaned keywords and comments."), GTK_ALIGN_START);
1780
1781         group = pref_group_new(gd->vbox, FALSE, _("Background cache maintenance"), GTK_ORIENTATION_VERTICAL);
1782
1783         table = pref_table_new(group, 3, 2, FALSE, FALSE);
1784
1785         button = pref_table_button(table, 0, 0, GQ_ICON_RUN, _("Select"),
1786                                    G_CALLBACK(cache_manager_cache_maintenance_load_cb), cache_manager);
1787         gtk_size_group_add_widget(sizegroup, button);
1788         pref_table_label(table, 1, 0, _("Run cache maintenance as a background job."), GTK_ALIGN_START);
1789         gtk_widget_set_sensitive(group, options->thumbnails.enable_caching);
1790
1791         gtk_widget_show(cache_manager->dialog->dialog);
1792 }
1793 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */