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