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