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