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