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