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