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