GTK2 no longer supported
[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 UNUSED(data))
66 {
67         exit(EXIT_SUCCESS);
68 }
69
70 static void cache_maintenance_render_stop_cb(gpointer UNUSED(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 UNUSED(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 *UNUSED(status), gpointer UNUSED(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 *UNUSED(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 *UNUSED(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_box_new(GTK_ORIENTATION_HORIZONTAL, 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 UNUSED(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 *UNUSED(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 *UNUSED(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 *UNUSED(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 *UNUSED(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 *UNUSED(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 *UNUSED(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 *UNUSED(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         gtk_progress_bar_set_show_text(GTK_PROGRESS_BAR(cd->progress), TRUE);
1178         gtk_box_pack_start(GTK_BOX(cd->gd->vbox), cd->progress, FALSE, FALSE, 0);
1179         gtk_widget_show(cd->progress);
1180
1181         cd->days = 30;
1182         cd->tl = NULL;
1183         cd->idle_id = 0;
1184
1185         gtk_widget_show(cd->gd->dialog);
1186 }
1187
1188 void cache_manager_standard_process_remote(gboolean clear)
1189 {
1190         CacheOpsData *cd;
1191
1192         cd = g_new0(CacheOpsData, 1);
1193         cd->clear = clear;
1194         cd->days = 30;
1195         cd->tl = NULL;
1196         cd->idle_id = 0;
1197         cd->remote = TRUE;
1198
1199         cache_manager_standard_clean_start(NULL, cd);
1200 }
1201
1202 static void cache_manager_standard_clean_cb(GtkWidget *widget, gpointer UNUSED(data))
1203 {
1204         cache_manager_standard_process(widget, FALSE);
1205 }
1206
1207 static void cache_manager_standard_clear_cb(GtkWidget *widget, gpointer UNUSED(data))
1208 {
1209         cache_manager_standard_process(widget, TRUE);
1210 }
1211
1212
1213 static void cache_manager_main_clean_cb(GtkWidget *widget, gpointer UNUSED(data))
1214 {
1215         cache_maintain_home(FALSE, FALSE, widget);
1216 }
1217
1218
1219 static void dummy_cancel_cb(GenericDialog *UNUSED(gd), gpointer UNUSED(data))
1220 {
1221         /* no op, only so cancel button appears */
1222 }
1223
1224 static void cache_manager_main_clear_ok_cb(GenericDialog *UNUSED(gd), gpointer UNUSED(data))
1225 {
1226         cache_maintain_home(FALSE, TRUE, NULL);
1227 }
1228
1229 void cache_manager_main_clear_confirm(GtkWidget *parent)
1230 {
1231         GenericDialog *gd;
1232
1233         gd = generic_dialog_new(_("Clear cache"),
1234                                 "clear_cache", parent, TRUE,
1235                                 dummy_cancel_cb, NULL);
1236         generic_dialog_add_message(gd, GTK_STOCK_DIALOG_QUESTION, _("Clear cache"),
1237                                    _("This will remove all thumbnails and sim. files\nthat have been saved to disk, continue?"), TRUE);
1238         generic_dialog_add_button(gd, GTK_STOCK_OK, NULL, cache_manager_main_clear_ok_cb, TRUE);
1239
1240         gtk_widget_show(gd->dialog);
1241 }
1242
1243 static void cache_manager_main_clear_cb(GtkWidget *widget, gpointer UNUSED(data))
1244 {
1245         cache_manager_main_clear_confirm(widget);
1246 }
1247
1248 static void cache_manager_render_cb(GtkWidget *widget, gpointer UNUSED(data))
1249 {
1250         const gchar *path = layout_get_path(NULL);
1251
1252         if (!path || !*path) path = homedir();
1253         cache_manager_render_dialog(widget, path);
1254 }
1255
1256 static void cache_manager_metadata_clean_cb(GtkWidget *widget, gpointer UNUSED(data))
1257 {
1258         cache_maintain_home(TRUE, FALSE, widget);
1259 }
1260
1261
1262 static CacheManager *cache_manager = NULL;
1263
1264 static void cache_manager_close_cb(GenericDialog *gd, gpointer UNUSED(data))
1265 {
1266         generic_dialog_close(gd);
1267
1268         g_free(cache_manager);
1269         cache_manager = NULL;
1270 }
1271
1272 static void cache_manager_help_cb(GenericDialog *UNUSED(gd), gpointer UNUSED(data))
1273 {
1274         help_window_show("GuideReferenceManagement.html");
1275 }
1276
1277 static GtkWidget *cache_manager_location_label(GtkWidget *group, const gchar *subdir)
1278 {
1279         GtkWidget *label;
1280         gchar *buf;
1281
1282         buf = g_strdup_printf(_("Location: %s"), subdir);
1283         label = pref_label_new(group, buf);
1284         g_free(buf);
1285         gtk_label_set_xalign(GTK_LABEL(label), 0.0);
1286         gtk_label_set_yalign(GTK_LABEL(label), 0.5);
1287
1288         return label;
1289 }
1290
1291 static gboolean cache_manager_sim_file(CacheOpsData *cd);
1292
1293 static void cache_manager_sim_reset(CacheOpsData *cd)
1294 {
1295         filelist_free(cd->list);
1296         cd->list = NULL;
1297
1298         filelist_free(cd->list_dir);
1299         cd->list_dir = NULL;
1300
1301         cache_loader_free((CacheLoader *)cd->cl);
1302         cd->cl = NULL;
1303 }
1304
1305 static void cache_manager_sim_close_cb(GenericDialog *UNUSED(fd), gpointer data)
1306 {
1307         CacheOpsData *cd = data;
1308
1309         if (!gtk_widget_get_sensitive(cd->button_close)) return;
1310
1311         cache_manager_sim_reset(cd);
1312         generic_dialog_close(cd->gd);
1313         g_free(cd);
1314 }
1315
1316 static void cache_manager_sim_finish(CacheOpsData *cd)
1317 {
1318         cache_manager_sim_reset(cd);
1319         if (!cd->remote)
1320                 {
1321                 spinner_set_interval(cd->spinner, -1);
1322
1323                 gtk_widget_set_sensitive(cd->group, TRUE);
1324                 gtk_widget_set_sensitive(cd->button_start, TRUE);
1325                 gtk_widget_set_sensitive(cd->button_stop, FALSE);
1326                 gtk_widget_set_sensitive(cd->button_close, TRUE);
1327                 }
1328 }
1329
1330 static void cache_manager_sim_stop_cb(GenericDialog *UNUSED(fd), gpointer data)
1331 {
1332         CacheOpsData *cd = data;
1333
1334         gtk_entry_set_text(GTK_ENTRY(cd->progress), _("stopped"));
1335         cache_manager_sim_finish(cd);
1336 }
1337
1338 static void cache_manager_sim_folder(CacheOpsData *cd, FileData *dir_fd)
1339 {
1340         GList *list_d = NULL;
1341         GList *list_f = NULL;
1342
1343         if (cd->recurse)
1344                 {
1345                 filelist_read(dir_fd, &list_f, &list_d);
1346                 }
1347         else
1348                 {
1349                 filelist_read(dir_fd, &list_f, NULL);
1350                 }
1351
1352         list_f = filelist_filter(list_f, FALSE);
1353         list_d = filelist_filter(list_d, TRUE);
1354
1355         cd->list = g_list_concat(list_f, cd->list);
1356         cd->list_dir = g_list_concat(list_d, cd->list_dir);
1357 }
1358
1359 static void cache_manager_sim_file_done_cb(CacheLoader *UNUSED(cl), gint UNUSED(error), gpointer data)
1360 {
1361         CacheOpsData *cd = data;
1362
1363         cache_loader_free((CacheLoader *)cd->cl);
1364         cd->cl = NULL;
1365
1366         while (cache_manager_sim_file(cd));
1367 }
1368
1369 static void cache_manager_sim_start_sim_remote(CacheOpsData *cd, const gchar *user_path)
1370 {
1371         gchar *path;
1372
1373         path = remove_trailing_slash(user_path);
1374         parse_out_relatives(path);
1375
1376         if (!isdir(path))
1377                 {
1378                 log_printf("The specified folder can not be found: %s\n", path);
1379                 }
1380         else
1381                 {
1382                 FileData *dir_fd;
1383
1384                 dir_fd = file_data_new_dir(path);
1385                 cache_manager_sim_folder(cd, dir_fd);
1386                 file_data_unref(dir_fd);
1387                 while (cache_manager_sim_file(cd));
1388                 }
1389
1390         g_free(path);
1391 }
1392
1393 /**
1394  * @brief Generate .sim files
1395  * @param path Path to image folder
1396  * @param recurse 
1397  * @param func Function called when idle loop function terminates
1398  * 
1399  * 
1400  */
1401 void cache_manager_sim_remote(const gchar *path, gboolean recurse, GDestroyNotify *func)
1402 {
1403         CacheOpsData *cd;
1404
1405         cd = g_new0(CacheOpsData, 1);
1406         cd->recurse = recurse;
1407         cd->remote = TRUE;
1408         cd->destroy_func = func;
1409
1410         cache_manager_sim_start_sim_remote(cd, path);
1411 }
1412
1413 static gboolean cache_manager_sim_file(CacheOpsData *cd)
1414 {
1415         CacheDataType load_mask;
1416
1417         if (cd->list)
1418                 {
1419                 FileData *fd;
1420                 fd = cd->list->data;
1421                 cd->list = g_list_remove(cd->list, fd);
1422
1423                 load_mask = CACHE_LOADER_DIMENSIONS | CACHE_LOADER_DATE | CACHE_LOADER_MD5SUM | CACHE_LOADER_SIMILARITY;
1424                 cd->cl = (CacheLoader *)cache_loader_new(fd, load_mask, (cache_manager_sim_file_done_cb), cd);
1425
1426                 if (!cd->remote)
1427                         {
1428                         gtk_entry_set_text(GTK_ENTRY(cd->progress), fd->path);
1429                         }
1430
1431                 file_data_unref(fd);
1432                 cd->count_done = cd->count_done + 1;
1433                 if (!cd->remote)
1434                         {
1435                         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(cd->progress_bar), (gdouble)cd->count_done / cd->count_total);
1436                         }
1437
1438                 return FALSE;
1439                 }
1440         else if (cd->list_dir)
1441                 {
1442                 FileData *fd;
1443
1444                 fd = cd->list_dir->data;
1445                 cd->list_dir = g_list_remove(cd->list_dir, fd);
1446
1447                 cache_manager_sim_folder((CacheOpsData *)cd, fd);
1448                 file_data_unref(fd);
1449
1450                 return TRUE;
1451                 }
1452
1453                 if (!cd->remote)
1454                         {
1455                         gtk_entry_set_text(GTK_ENTRY(cd->progress), _("done"));
1456                         }
1457
1458         cache_manager_sim_finish((CacheOpsData *)cd);
1459
1460         if (cd->destroy_func)
1461                 {
1462                 g_idle_add((GSourceFunc)cd->destroy_func, NULL);
1463                 }
1464
1465         return FALSE;
1466 }
1467
1468 static void cache_manager_sim_start_cb(GenericDialog *UNUSED(fd), gpointer data)
1469 {
1470         CacheOpsData *cd = data;
1471         gchar *path;
1472         GList *list_total = NULL;
1473
1474         if (!cd->remote)
1475                 {
1476                 if (cd->list || !gtk_widget_get_sensitive(cd->button_start)) return;
1477                 }
1478
1479         path = remove_trailing_slash((gtk_entry_get_text(GTK_ENTRY(cd->entry))));
1480         parse_out_relatives(path);
1481
1482         if (!isdir(path))
1483                 {
1484                 if (!cd->remote)
1485                         {
1486                         warning_dialog(_("Invalid folder"),
1487                         _("The specified folder can not be found."),
1488                         GTK_STOCK_DIALOG_WARNING, cd->gd->dialog);
1489                         }
1490                 else
1491                         {
1492                         log_printf("The specified folder can not be found: %s\n", path);
1493                         }
1494                 }
1495         else
1496                 {
1497                 FileData *dir_fd;
1498                 if(!cd->remote)
1499                         {
1500                         gtk_widget_set_sensitive(cd->group, FALSE);
1501                         gtk_widget_set_sensitive(cd->button_start, FALSE);
1502                         gtk_widget_set_sensitive(cd->button_stop, TRUE);
1503                         gtk_widget_set_sensitive(cd->button_close, FALSE);
1504
1505                         spinner_set_interval(cd->spinner, SPINNER_SPEED);
1506                         }
1507                 dir_fd = file_data_new_dir(path);
1508                 cache_manager_sim_folder(cd, dir_fd);
1509                 list_total = filelist_recursive(dir_fd);
1510                 cd->count_total = g_list_length(list_total);
1511                 file_data_unref(dir_fd);
1512                 g_list_free(list_total);
1513                 cd->count_done = 0;
1514
1515                 while (cache_manager_sim_file((CacheOpsData *)cd));
1516                 }
1517
1518         g_free(path);
1519 }
1520
1521 static void cache_manager_sim_load_dialog(GtkWidget *widget, const gchar *path)
1522 {
1523         CacheOpsData *cd;
1524         GtkWidget *hbox;
1525         GtkWidget *label;
1526
1527         cd = g_new0(CacheOpsData, 1);
1528         cd->remote = FALSE;
1529         cd->recurse = TRUE;
1530
1531         cd->gd = generic_dialog_new(_("Create sim. files"), "create_sim_files", widget, FALSE, NULL, cd);
1532         gtk_window_set_default_size(GTK_WINDOW(cd->gd->dialog), PURGE_DIALOG_WIDTH, -1);
1533         cd->gd->cancel_cb = cache_manager_sim_close_cb;
1534         cd->button_close = generic_dialog_add_button(cd->gd, GTK_STOCK_CLOSE, NULL,
1535                                                      cache_manager_sim_close_cb, FALSE);
1536         cd->button_start = generic_dialog_add_button(cd->gd, GTK_STOCK_OK, _("S_tart"),
1537                                                      cache_manager_sim_start_cb, FALSE);
1538         cd->button_stop = generic_dialog_add_button(cd->gd, GTK_STOCK_STOP, NULL,
1539                                                     cache_manager_sim_stop_cb, FALSE);
1540         gtk_widget_set_sensitive(cd->button_stop, FALSE);
1541
1542         generic_dialog_add_message(cd->gd, NULL, _("Create sim. files recursively"), NULL, FALSE);
1543
1544         hbox = pref_box_new(cd->gd->vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, 0);
1545         pref_spacer(hbox, PREF_PAD_INDENT);
1546         cd->group = pref_box_new(hbox, TRUE, GTK_ORIENTATION_VERTICAL, PREF_PAD_GAP);
1547
1548         hbox = pref_box_new(cd->group, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
1549         pref_label_new(hbox, _("Folder:"));
1550
1551         label = tab_completion_new(&cd->entry, path, NULL, NULL, NULL, NULL);
1552         tab_completion_add_select_button(cd->entry,_("Select folder") , TRUE);
1553         gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
1554         gtk_widget_show(label);
1555
1556         pref_line(cd->gd->vbox, PREF_PAD_SPACE);
1557         hbox = pref_box_new(cd->gd->vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
1558
1559         cd->progress = gtk_entry_new();
1560         gtk_widget_set_can_focus(cd->progress, FALSE);
1561         gtk_editable_set_editable(GTK_EDITABLE(cd->progress), FALSE);
1562         gtk_entry_set_text(GTK_ENTRY(cd->progress), _("click start to begin"));
1563         gtk_box_pack_start(GTK_BOX(hbox), cd->progress, TRUE, TRUE, 0);
1564         gtk_widget_show(cd->progress);
1565
1566         cd->progress_bar = gtk_progress_bar_new();
1567         gtk_box_pack_start(GTK_BOX(cd->gd->vbox), cd->progress_bar, TRUE, TRUE, 0);
1568         gtk_widget_show(cd->progress_bar);
1569
1570         cd->spinner = spinner_new(NULL, -1);
1571         gtk_box_pack_start(GTK_BOX(hbox), cd->spinner, FALSE, FALSE, 0);
1572         gtk_widget_show(cd->spinner);
1573
1574         cd->list = NULL;
1575
1576         gtk_widget_show(cd->gd->dialog);
1577 }
1578
1579 static void cache_manager_sim_load_cb(GtkWidget *widget, gpointer UNUSED(data))
1580 {
1581         const gchar *path = layout_get_path(NULL);
1582
1583         if (!path || !*path) path = homedir();
1584         cache_manager_sim_load_dialog(widget, path);
1585 }
1586
1587 static void cache_manager_cache_maintenance_close_cb(GenericDialog *UNUSED(fd), gpointer data)
1588 {
1589         CacheOpsData *cd = data;
1590
1591         if (!gtk_widget_get_sensitive(cd->button_close)) return;
1592
1593         cache_manager_sim_reset(cd);
1594         generic_dialog_close(cd->gd);
1595         g_free(cd);
1596 }
1597
1598 static void cache_manager_cache_maintenance_start_cb(GenericDialog *UNUSED(fd), gpointer data)
1599 {
1600         CacheOpsData *cd = data;
1601         gchar *path;
1602         gchar *cmd_line;
1603
1604         if (!cd->remote)
1605                 {
1606                 if (cd->list || !gtk_widget_get_sensitive(cd->button_start)) return;
1607                 }
1608
1609         path = remove_trailing_slash((gtk_entry_get_text(GTK_ENTRY(cd->entry))));
1610         parse_out_relatives(path);
1611
1612         if (!isdir(path))
1613                 {
1614                 if (!cd->remote)
1615                         {
1616                         warning_dialog(_("Invalid folder"),
1617                         _("The specified folder can not be found."),
1618                         GTK_STOCK_DIALOG_WARNING, cd->gd->dialog);
1619                         }
1620                 else
1621                         {
1622                         log_printf("The specified folder can not be found: \"%s\"\n", path);
1623                         }
1624                 }
1625         else
1626                 {
1627                 cmd_line = g_strdup_printf("%s --cache-maintenance \"%s\"", gq_executable_path, path);
1628
1629                 g_spawn_command_line_async(cmd_line, NULL);
1630
1631                 g_free(cmd_line);
1632                 generic_dialog_close(cd->gd);
1633                 cache_manager_sim_reset(cd);
1634                 g_free(cd);
1635                 }
1636
1637         g_free(path);
1638 }
1639
1640 static void cache_manager_cache_maintenance_load_dialog(GtkWidget *widget, const gchar *path)
1641 {
1642         CacheOpsData *cd;
1643         GtkWidget *hbox;
1644         GtkWidget *label;
1645
1646         cd = g_new0(CacheOpsData, 1);
1647         cd->remote = FALSE;
1648         cd->recurse = TRUE;
1649
1650         cd->gd = generic_dialog_new(_("Background cache maintenance"), "background_cache_maintenance", widget, FALSE, NULL, cd);
1651         gtk_window_set_default_size(GTK_WINDOW(cd->gd->dialog), PURGE_DIALOG_WIDTH, -1);
1652         cd->gd->cancel_cb = cache_manager_cache_maintenance_close_cb;
1653         cd->button_close = generic_dialog_add_button(cd->gd, GTK_STOCK_CLOSE, NULL,
1654                                                      cache_manager_cache_maintenance_close_cb, FALSE);
1655         cd->button_start = generic_dialog_add_button(cd->gd, GTK_STOCK_OK, _("S_tart"),
1656                                                      cache_manager_cache_maintenance_start_cb, FALSE);
1657
1658         generic_dialog_add_message(cd->gd, NULL, _("Recursively delete orphaned thumbnails\nand .sim files, and create new\nthumbnails and .sim files"), NULL, FALSE);
1659
1660         hbox = pref_box_new(cd->gd->vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, 0);
1661         pref_spacer(hbox, PREF_PAD_INDENT);
1662         cd->group = pref_box_new(hbox, TRUE, GTK_ORIENTATION_VERTICAL, PREF_PAD_GAP);
1663
1664         hbox = pref_box_new(cd->group, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
1665         pref_label_new(hbox, _("Folder:"));
1666
1667         label = tab_completion_new(&cd->entry, path, NULL, NULL, NULL, NULL);
1668         tab_completion_add_select_button(cd->entry,_("Select folder") , TRUE);
1669         gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
1670         gtk_widget_show(label);
1671
1672         cd->list = NULL;
1673
1674         gtk_widget_show(cd->gd->dialog);
1675 }
1676
1677 static void cache_manager_cache_maintenance_load_cb(GtkWidget *widget, gpointer UNUSED(data))
1678 {
1679         const gchar *path = layout_get_path(NULL);
1680
1681         if (!path || !*path) path = homedir();
1682         cache_manager_cache_maintenance_load_dialog(widget, path);
1683 }
1684
1685 void cache_manager_show(void)
1686 {
1687         GenericDialog *gd;
1688         GtkWidget *group;
1689         GtkWidget *button;
1690         GtkWidget *table;
1691         GtkSizeGroup *sizegroup;
1692         gchar *path;
1693
1694         if (cache_manager)
1695                 {
1696                 gtk_window_present(GTK_WINDOW(cache_manager->dialog->dialog));
1697                 return;
1698                 }
1699
1700         cache_manager = g_new0(CacheManager, 1);
1701
1702         cache_manager->dialog = generic_dialog_new(_("Cache Maintenance"),
1703                                                    "cache_manager",
1704                                                    NULL, FALSE,
1705                                                    NULL, cache_manager);
1706         gd = cache_manager->dialog;
1707
1708         gd->cancel_cb = cache_manager_close_cb;
1709         generic_dialog_add_button(gd, GTK_STOCK_CLOSE, NULL,
1710                                   cache_manager_close_cb, FALSE);
1711         generic_dialog_add_button(gd, GTK_STOCK_HELP, NULL,
1712                                   cache_manager_help_cb, FALSE);
1713
1714         generic_dialog_add_message(gd, NULL, _("Cache and Data Maintenance"), NULL, FALSE);
1715
1716         sizegroup = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
1717
1718         group = pref_group_new(gd->vbox, FALSE, _("Geeqie thumbnail and sim. cache"), GTK_ORIENTATION_VERTICAL);
1719
1720         cache_manager_location_label(group, get_thumbnails_cache_dir());
1721
1722         table = pref_table_new(group, 2, 2, FALSE, FALSE);
1723
1724         button = pref_table_button(table, 0, 0, GTK_STOCK_CLEAR, _("Clean up"), FALSE,
1725                                    G_CALLBACK(cache_manager_main_clean_cb), cache_manager);
1726         gtk_size_group_add_widget(sizegroup, button);
1727         pref_table_label(table, 1, 0, _("Remove orphaned or outdated thumbnails and sim. files."), 0.0);
1728
1729         button = pref_table_button(table, 0, 1, GTK_STOCK_DELETE, _("Clear cache"), FALSE,
1730                                    G_CALLBACK(cache_manager_main_clear_cb), cache_manager);
1731         gtk_size_group_add_widget(sizegroup, button);
1732         pref_table_label(table, 1, 1, _("Delete all cached data."), 0.0);
1733
1734
1735         group = pref_group_new(gd->vbox, FALSE, _("Shared thumbnail cache"), GTK_ORIENTATION_VERTICAL);
1736
1737         path = g_build_filename(get_thumbnails_standard_cache_dir(), NULL);
1738         cache_manager_location_label(group, path);
1739         g_free(path);
1740
1741         table = pref_table_new(group, 2, 2, FALSE, FALSE);
1742
1743         button = pref_table_button(table, 0, 0, GTK_STOCK_CLEAR, _("Clean up"), FALSE,
1744                                    G_CALLBACK(cache_manager_standard_clean_cb), cache_manager);
1745         gtk_size_group_add_widget(sizegroup, button);
1746         pref_table_label(table, 1, 0, _("Remove orphaned or outdated thumbnails."), 0.0);
1747
1748         button = pref_table_button(table, 0, 1, GTK_STOCK_DELETE, _("Clear cache"), FALSE,
1749                                    G_CALLBACK(cache_manager_standard_clear_cb), cache_manager);
1750         gtk_size_group_add_widget(sizegroup, button);
1751         pref_table_label(table, 1, 1, _("Delete all cached thumbnails."), 0.0);
1752
1753         group = pref_group_new(gd->vbox, FALSE, _("Create thumbnails"), GTK_ORIENTATION_VERTICAL);
1754
1755         table = pref_table_new(group, 2, 1, FALSE, FALSE);
1756
1757         button = pref_table_button(table, 0, 1, "system-run", _("Render"), FALSE,
1758                                    G_CALLBACK(cache_manager_render_cb), cache_manager);
1759         gtk_size_group_add_widget(sizegroup, button);
1760         pref_table_label(table, 1, 1, _("Render thumbnails for a specific folder."), 0.0);
1761         gtk_widget_set_sensitive(group, options->thumbnails.enable_caching);
1762
1763         group = pref_group_new(gd->vbox, FALSE, _("File similarity cache"), GTK_ORIENTATION_VERTICAL);
1764
1765         table = pref_table_new(group, 3, 2, FALSE, FALSE);
1766
1767         button = pref_table_button(table, 0, 0, GTK_STOCK_EXECUTE, _("Create"), FALSE,
1768                                    G_CALLBACK(cache_manager_sim_load_cb), cache_manager);
1769         gtk_size_group_add_widget(sizegroup, button);
1770         pref_table_label(table, 1, 0, _("Create sim. files recursively."), 0.0);
1771         gtk_widget_set_sensitive(group, options->thumbnails.enable_caching);
1772
1773         group = pref_group_new(gd->vbox, FALSE, _("Metadata"), GTK_ORIENTATION_VERTICAL);
1774
1775         cache_manager_location_label(group, get_metadata_cache_dir());
1776
1777         table = pref_table_new(group, 2, 1, FALSE, FALSE);
1778
1779         button = pref_table_button(table, 0, 0, GTK_STOCK_CLEAR, _("Clean up"), FALSE,
1780                                    G_CALLBACK(cache_manager_metadata_clean_cb), cache_manager);
1781         gtk_size_group_add_widget(sizegroup, button);
1782         pref_table_label(table, 1, 0, _("Remove orphaned keywords and comments."), 0.0);
1783
1784         group = pref_group_new(gd->vbox, FALSE, _("Background cache maintenance"), GTK_ORIENTATION_VERTICAL);
1785
1786         table = pref_table_new(group, 3, 2, FALSE, FALSE);
1787
1788         button = pref_table_button(table, 0, 0, GTK_STOCK_EXECUTE, _("Select"), FALSE,
1789                                    G_CALLBACK(cache_manager_cache_maintenance_load_cb), cache_manager);
1790         gtk_size_group_add_widget(sizegroup, button);
1791         pref_table_label(table, 1, 0, _("Run cache maintenance as a background job."), 0.0);
1792         gtk_widget_set_sensitive(group, options->thumbnails.enable_caching);
1793
1794         gtk_widget_show(cache_manager->dialog->dialog);
1795 }
1796 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */