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