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