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