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