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