gqview.h -> main.h
[geeqie.git] / src / cache_maint.c
1 /*
2  * Geeqie
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 "main.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                                     GQ_WMCLASS, "main_maintenance",
316                                     parent, FALSE,
317                                     NULL, cm);
318         cm->gd->cancel_cb = cache_maintain_home_close_cb;
319         cm->button_close = generic_dialog_add_button(cm->gd, GTK_STOCK_CLOSE, NULL,
320                                                      cache_maintain_home_close_cb, FALSE);
321         gtk_widget_set_sensitive(cm->button_close, FALSE);
322         cm->button_stop = generic_dialog_add_button(cm->gd, GTK_STOCK_STOP, NULL,
323                                                     cache_maintain_home_stop_cb, FALSE);
324
325         generic_dialog_add_message(cm->gd, NULL, msg, NULL);
326         gtk_window_set_default_size(GTK_WINDOW(cm->gd->dialog), PURGE_DIALOG_WIDTH, -1);
327
328         hbox = gtk_hbox_new(FALSE, 0);
329         gtk_box_pack_start(GTK_BOX(cm->gd->vbox), hbox, FALSE, FALSE, 5);
330         gtk_widget_show(hbox);
331
332         cm->entry = gtk_entry_new();
333         GTK_WIDGET_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 ~/.geeqie/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(FileData *fd)
504 {
505         gchar *base;
506         mode_t mode = 0755;
507         const gchar *src = fd->change->source;
508         const gchar *dest = fd->change->dest;
509
510         if (!src || !dest) return;
511
512         base = cache_get_location(CACHE_TYPE_THUMB, dest, FALSE, &mode);
513         if (cache_ensure_dir_exists(base, mode))
514                 {
515                 gchar *buf;
516                 gchar *d;
517
518                 buf = cache_find_location(CACHE_TYPE_THUMB, src);
519                 d = cache_get_location(CACHE_TYPE_THUMB, dest, TRUE, NULL);
520                 cache_file_move(buf, d);
521                 g_free(d);
522                 g_free(buf);
523
524                 buf = cache_find_location(CACHE_TYPE_SIM, src);
525                 d = cache_get_location(CACHE_TYPE_SIM, dest, TRUE, NULL);
526                 cache_file_move(buf, d);
527                 g_free(d);
528                 g_free(buf);
529                 }
530         else
531                 {
532                 printf("Failed to create cache dir for move %s\n", base);
533                 }
534         g_free(base);
535
536         base = cache_get_location(CACHE_TYPE_METADATA, dest, FALSE, &mode);
537         if (cache_ensure_dir_exists(base, mode))
538                 {
539                 gchar *buf;
540                 gchar *d;
541                                                                                                                     
542                 buf = cache_find_location(CACHE_TYPE_METADATA, src);
543                 d = cache_get_location(CACHE_TYPE_METADATA, dest, TRUE, NULL);
544                 cache_file_move(buf, d);
545                 g_free(d);
546                 g_free(buf);
547                 }
548         g_free(base);
549
550         if (enable_thumb_caching && thumbnail_spec_standard) thumb_std_maint_moved(src, dest);
551 }
552
553 static void cache_file_remove(const gchar *path)
554 {
555         if (path && isfile(path) && !unlink_file(path))
556                 {
557                 if (debug) printf("Failed to remove cache file %s\n", path);
558                 }
559 }
560
561 void cache_maint_removed(FileData *fd)
562 {
563         gchar *buf;
564
565         buf = cache_find_location(CACHE_TYPE_THUMB, fd->path);
566         cache_file_remove(buf);
567         g_free(buf);
568
569         buf = cache_find_location(CACHE_TYPE_SIM, fd->path);
570         cache_file_remove(buf);
571         g_free(buf);
572
573         buf = cache_find_location(CACHE_TYPE_METADATA, fd->path);
574         cache_file_remove(buf);
575         g_free(buf);
576
577         if (enable_thumb_caching && thumbnail_spec_standard) thumb_std_maint_removed(fd->path);
578 }
579
580 void cache_maint_copied(FileData *fd)
581 {
582         gchar *dest_base;
583         gchar *src_cache;
584         mode_t mode = 0755;
585
586         src_cache = cache_find_location(CACHE_TYPE_METADATA, fd->change->source);
587         if (!src_cache) return;
588
589         dest_base = cache_get_location(CACHE_TYPE_METADATA, fd->change->dest, FALSE, &mode);
590         if (cache_ensure_dir_exists(dest_base, mode))
591                 {
592                 gchar *path;
593                                                                                                                     
594                 path = cache_get_location(CACHE_TYPE_METADATA, fd->change->dest, TRUE, NULL);
595                 if (!copy_file(src_cache, path))
596                         {
597                         if (debug) printf("failed to copy metadata %s to %s\n", src_cache, path);
598                         }
599                 g_free(path);
600                 }
601
602         g_free(dest_base);
603         g_free(src_cache);
604 }
605
606 /*
607  *-------------------------------------------------------------------
608  * new cache maintenance utilities
609  *-------------------------------------------------------------------
610  */
611
612 typedef struct _CacheManager CacheManager;
613 struct _CacheManager
614 {
615         GenericDialog *dialog;
616         GtkWidget *folder_entry;
617         GtkWidget *progress;
618
619         GList *list_todo;
620
621         gint count_total;
622         gint count_done;
623 };
624
625 typedef struct _CleanData CleanData;
626 struct _CleanData
627 {
628         GenericDialog *gd;
629         ThumbLoaderStd *tl;
630
631         GList *list;
632         GList *list_dir;
633
634         gint days;
635         gint clear;
636
637         GtkWidget *button_close;
638         GtkWidget *button_stop;
639         GtkWidget *button_start;
640         GtkWidget *progress;
641         GtkWidget *spinner;
642
643         GtkWidget *group;
644         GtkWidget *entry;
645
646         gint count_total;
647         gint count_done;
648
649         gint local;
650         gint recurse;
651
652         gint idle_id;
653 };
654
655 static void cache_manager_render_reset(CleanData *cd)
656 {
657         path_list_free(cd->list);
658         cd->list = NULL;
659
660         path_list_free(cd->list_dir);
661         cd->list_dir = NULL;
662
663         thumb_loader_free((ThumbLoader *)cd->tl);
664         cd->tl = NULL;
665 }
666
667 static void cache_manager_render_close_cb(GenericDialog *fd, gpointer data)
668 {
669         CleanData *cd = data;
670
671         if (!GTK_WIDGET_SENSITIVE(cd->button_close)) return;
672
673         cache_manager_render_reset(cd);
674         generic_dialog_close(cd->gd);
675         g_free(cd);
676 }
677
678 static void cache_manager_render_finish(CleanData *cd)
679 {
680         cache_manager_render_reset(cd);
681
682         gtk_entry_set_text(GTK_ENTRY(cd->progress), _("done"));
683         spinner_set_interval(cd->spinner, -1);
684
685         gtk_widget_set_sensitive(cd->group, TRUE);
686         gtk_widget_set_sensitive(cd->button_start, TRUE);
687         gtk_widget_set_sensitive(cd->button_stop, FALSE);
688         gtk_widget_set_sensitive(cd->button_close, TRUE);
689 }
690
691 static void cache_manager_render_stop_cb(GenericDialog *fd, gpointer data)
692 {
693         CleanData *cd = data;
694
695         cache_manager_render_finish(cd);
696 }
697
698 static void cache_manager_render_folder(CleanData *cd, const gchar *path)
699 {
700         GList *list_d = NULL;
701         GList *list_f = NULL;
702
703         if (cd->recurse)
704                 {
705                 path_list(path, &list_f, &list_d);
706                 }
707         else
708                 {
709                 path_list(path, &list_f, NULL);
710                 }
711
712         list_f = path_list_filter(list_f, FALSE);
713         list_d = path_list_filter(list_d, TRUE);
714
715         cd->list = g_list_concat(list_f, cd->list);
716         cd->list_dir = g_list_concat(list_d, cd->list_dir);
717 }
718
719 static gint cache_manager_render_file(CleanData *cd);
720
721 static void cache_manager_render_thumb_done_cb(ThumbLoader *tl, gpointer data)
722 {
723         CleanData *cd = data;
724
725         thumb_loader_free((ThumbLoader *)cd->tl);
726         cd->tl = NULL;
727
728         while (cache_manager_render_file(cd));
729 }
730
731 static gint cache_manager_render_file(CleanData *cd)
732 {
733         if (cd->list)
734                 {
735                 gchar *path;
736                 gint success;
737
738                 path = cd->list->data;
739                 cd->list = g_list_remove(cd->list, path);
740
741                 cd->tl = (ThumbLoaderStd *)thumb_loader_new(thumb_max_width, thumb_max_height);
742                 thumb_loader_set_callbacks((ThumbLoader *)cd->tl,
743                                            cache_manager_render_thumb_done_cb,
744                                            cache_manager_render_thumb_done_cb,
745                                            NULL, cd);
746                 thumb_loader_set_cache((ThumbLoader *)cd->tl, TRUE, cd->local, TRUE);
747                 success = thumb_loader_start((ThumbLoader *)cd->tl, path);
748                 if (success)
749                         {
750                         gtk_entry_set_text(GTK_ENTRY(cd->progress), path);
751                         }
752                 else
753                         {
754                         thumb_loader_free((ThumbLoader *)cd->tl);
755                         cd->tl = NULL;
756                         }
757
758                 g_free(path);
759
760                 return (!success);
761                 }
762         else if (cd->list_dir)
763                 {
764                 gchar *path;
765
766                 path = cd->list_dir->data;
767                 cd->list_dir = g_list_remove(cd->list_dir, path);
768
769                 cache_manager_render_folder(cd, path);
770
771                 g_free(path);
772
773                 return TRUE;
774                 }
775
776         cache_manager_render_finish(cd);
777
778         return FALSE;
779 }
780
781 static void cache_manager_render_start_cb(GenericDialog *fd, gpointer data)
782 {
783         CleanData *cd = data;
784         gchar *path;
785
786         if (cd->list || !GTK_WIDGET_SENSITIVE(cd->button_start)) return;
787
788         path = remove_trailing_slash((gtk_entry_get_text(GTK_ENTRY(cd->entry))));
789         parse_out_relatives(path);
790
791         if (!isdir(path))
792                 {
793                 warning_dialog(_("Invalid folder"),
794                                 _("The specified folder can not be found."),
795                                GTK_STOCK_DIALOG_WARNING, cd->gd->dialog);
796                 }
797         else
798                 {
799                 gtk_widget_set_sensitive(cd->group, FALSE);
800                 gtk_widget_set_sensitive(cd->button_start, FALSE);
801                 gtk_widget_set_sensitive(cd->button_stop, TRUE);
802                 gtk_widget_set_sensitive(cd->button_close, FALSE);
803
804                 spinner_set_interval(cd->spinner, SPINNER_SPEED);
805
806                 cache_manager_render_folder(cd, path);
807                 while (cache_manager_render_file(cd));
808                 }
809
810         g_free(path);
811 }
812
813 static void cache_manager_render_dialog(GtkWidget *widget, const gchar *path)
814 {
815         CleanData *cd;
816         GtkWidget *hbox;
817         GtkWidget *label;
818         GtkWidget *button;
819
820         cd = g_new0(CleanData, 1);
821
822         cd->gd = generic_dialog_new(_("Create thumbnails"),
823                                     GQ_WMCLASS, "create_thumbnails",
824                                     widget, FALSE,
825                                     NULL, cd);
826         gtk_window_set_default_size(GTK_WINDOW(cd->gd->dialog), PURGE_DIALOG_WIDTH, -1);
827         cd->gd->cancel_cb = cache_manager_render_close_cb;
828         cd->button_close = generic_dialog_add_button(cd->gd, GTK_STOCK_CLOSE, NULL,
829                                                      cache_manager_render_close_cb, FALSE);
830         cd->button_start = generic_dialog_add_button(cd->gd, GTK_STOCK_OK, _("S_tart"),
831                                                      cache_manager_render_start_cb, FALSE);
832         cd->button_stop = generic_dialog_add_button(cd->gd, GTK_STOCK_STOP, NULL,
833                                                     cache_manager_render_stop_cb, FALSE);
834         gtk_widget_set_sensitive(cd->button_stop, FALSE);
835
836         generic_dialog_add_message(cd->gd, NULL, _("Create thumbnails"), NULL);
837
838         hbox = pref_box_new(cd->gd->vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, 0);
839         pref_spacer(hbox, PREF_PAD_INDENT);
840         cd->group = pref_box_new(hbox, TRUE, GTK_ORIENTATION_VERTICAL, PREF_PAD_GAP);
841
842         hbox = pref_box_new(cd->group, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
843         pref_label_new(hbox, _("Folder:"));
844
845         label = tab_completion_new(&cd->entry, path, NULL, NULL);
846         tab_completion_add_select_button(cd->entry,_("Select folder") , TRUE);
847         gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
848         gtk_widget_show(label);
849
850         pref_checkbox_new_int(cd->group, _("Include subfolders"), FALSE, &cd->recurse);
851         button = pref_checkbox_new_int(cd->group, _("Store thumbnails local to source images"), FALSE, &cd->local);
852         gtk_widget_set_sensitive(button, thumbnail_spec_standard);
853
854         pref_line(cd->gd->vbox, PREF_PAD_SPACE);
855         hbox = pref_box_new(cd->gd->vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
856
857         cd->progress = gtk_entry_new();
858         GTK_WIDGET_UNSET_FLAGS(cd->progress, GTK_CAN_FOCUS);
859         gtk_editable_set_editable(GTK_EDITABLE(cd->progress), FALSE);
860         gtk_entry_set_text(GTK_ENTRY(cd->progress), _("click start to begin"));
861         gtk_box_pack_start(GTK_BOX(hbox), cd->progress, TRUE, TRUE, 0);
862         gtk_widget_show(cd->progress);
863
864         cd->spinner = spinner_new(NULL, -1);
865         gtk_box_pack_start(GTK_BOX(hbox), cd->spinner, FALSE, FALSE, 0);
866         gtk_widget_show(cd->spinner);
867
868         cd->list = NULL;
869
870         gtk_widget_show(cd->gd->dialog);
871 }
872
873
874
875
876 static void cache_manager_standard_clean_close_cb(GenericDialog *gd, gpointer data)
877 {
878         CleanData *cd = data;
879
880         if (!GTK_WIDGET_SENSITIVE(cd->button_close)) return;
881
882         generic_dialog_close(cd->gd);
883
884         thumb_loader_std_thumb_file_validate_cancel(cd->tl);
885         path_list_free(cd->list);
886         g_free(cd);
887 }
888
889 static void cache_manager_standard_clean_done(CleanData *cd)
890 {
891         gtk_widget_set_sensitive(cd->button_stop, FALSE);
892         gtk_widget_set_sensitive(cd->button_close, TRUE);
893
894         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(cd->progress), 1.0);
895         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(cd->progress), _("done"));
896
897         if (cd->idle_id != -1)
898                 {
899                 g_source_remove(cd->idle_id);
900                 cd->idle_id = -1;
901                 }
902
903         thumb_loader_std_thumb_file_validate_cancel(cd->tl);
904         cd->tl = NULL;
905
906         path_list_free(cd->list);
907         cd->list = NULL;
908 }
909
910 static void cache_manager_standard_clean_stop_cb(GenericDialog *gd, gpointer data)
911 {
912         CleanData *cd = data;
913
914         cache_manager_standard_clean_done(cd);
915 }
916
917 static gint cache_manager_standard_clean_clear_cb(gpointer data)
918 {
919         CleanData *cd = data;
920
921         if (cd->list)
922                 {
923                 gchar *next_path;
924
925                 next_path = cd->list->data;
926                 cd->list = g_list_remove(cd->list, next_path);
927
928                 if (debug) printf("thumb removed: %s\n", next_path);
929
930                 unlink_file(next_path);
931                 g_free(next_path);
932
933                 cd->count_done++;
934                 if (cd->count_total != 0)
935                         {
936                         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(cd->progress),
937                                                       (gdouble)cd->count_done / cd->count_total);
938                         }
939
940                 return TRUE;
941                 }
942
943         cd->idle_id = -1;
944         cache_manager_standard_clean_done(cd);
945         return FALSE;
946 }
947
948 static void cache_manager_standard_clean_valid_cb(const gchar *path, gint valid, gpointer data)
949 {
950         CleanData *cd = data;
951
952         if (path)
953                 {
954                 if (!valid)
955                         {
956                         if (debug) printf("thumb cleaned: %s\n", path);
957                         unlink_file(path);
958                         }
959
960                 cd->count_done++;
961                 if (cd->count_total != 0)
962                         {
963                         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(cd->progress),
964                                                       (gdouble)cd->count_done / cd->count_total);
965                         }
966                 }
967
968         cd->tl = NULL;
969         if (cd->list)
970                 {
971                 gchar *next_path;
972
973                 next_path = cd->list->data;
974                 cd->list = g_list_remove(cd->list, next_path);
975         
976                 cd->tl = thumb_loader_std_thumb_file_validate(next_path, cd->days,
977                                                               cache_manager_standard_clean_valid_cb, cd);
978                 g_free(next_path);
979                 }
980         else
981                 {
982                 cache_manager_standard_clean_done(cd);
983                 }
984 }
985
986 static void cache_manager_standard_clean_start_cb(GenericDialog *gd, gpointer data)
987 {
988         CleanData *cd = data;
989         GList *list;
990         gchar *path;
991
992         if (cd->list || !GTK_WIDGET_SENSITIVE(cd->button_start)) return;
993
994         gtk_widget_set_sensitive(cd->button_start, FALSE);
995         gtk_widget_set_sensitive(cd->button_stop, TRUE);
996         gtk_widget_set_sensitive(cd->button_close, FALSE);
997
998         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(cd->progress), _("running..."));
999
1000         path = g_strconcat(homedir(), "/", THUMB_FOLDER_GLOBAL, "/", THUMB_FOLDER_NORMAL, NULL);
1001         list = NULL;
1002         path_list(path, &list, NULL);
1003         cd->list = list;
1004         g_free(path);
1005
1006         path = g_strconcat(homedir(), "/", THUMB_FOLDER_GLOBAL, "/", THUMB_FOLDER_LARGE, NULL);
1007         list = NULL;
1008         path_list(path, &list, NULL);
1009         cd->list = g_list_concat(cd->list, list);
1010         g_free(path);
1011
1012         path = g_strconcat(homedir(), "/", THUMB_FOLDER_GLOBAL, "/", THUMB_FOLDER_FAIL, NULL);
1013         list = NULL;
1014         path_list(path, &list, NULL);
1015         cd->list = g_list_concat(cd->list, list);
1016         g_free(path);
1017
1018         cd->count_total = g_list_length(cd->list);
1019         cd->count_done = 0;
1020
1021         /* start iterating */
1022         if (cd->clear)
1023                 {
1024                 cd->idle_id = g_idle_add(cache_manager_standard_clean_clear_cb, cd);
1025                 }
1026         else
1027                 {
1028                 cache_manager_standard_clean_valid_cb(NULL, TRUE, cd);
1029                 }
1030 }
1031
1032 static void cache_manager_standard_process(GtkWidget *widget, gint clear)
1033 {
1034         CleanData *cd;
1035         const gchar *stock_id;
1036         const gchar *msg;
1037
1038         cd = g_new0(CleanData, 1);
1039         cd->clear = clear;
1040
1041         if (clear)
1042                 {
1043                 stock_id = GTK_STOCK_DELETE;
1044                 msg = _("Clearing thumbnails...");
1045                 }
1046         else
1047                 {
1048                 stock_id = GTK_STOCK_CLEAR;
1049                 msg = _("Removing old thumbnails...");
1050                 }
1051
1052         cd->gd = generic_dialog_new(_("Maintenance"),
1053                                     GQ_WMCLASS, "standard_maintenance",
1054                                     widget, FALSE,
1055                                     NULL, cd);
1056         cd->gd->cancel_cb = cache_manager_standard_clean_close_cb;
1057         cd->button_close = generic_dialog_add_button(cd->gd, GTK_STOCK_CLOSE, NULL,
1058                                                      cache_manager_standard_clean_close_cb, FALSE);
1059         cd->button_start = generic_dialog_add_button(cd->gd, GTK_STOCK_OK, _("S_tart"),
1060                                                      cache_manager_standard_clean_start_cb, FALSE);
1061         cd->button_stop = generic_dialog_add_button(cd->gd, GTK_STOCK_STOP, NULL,
1062                                                     cache_manager_standard_clean_stop_cb, FALSE);
1063         gtk_widget_set_sensitive(cd->button_stop, FALSE);
1064
1065         generic_dialog_add_message(cd->gd, stock_id, msg, NULL);
1066
1067         cd->progress = gtk_progress_bar_new();
1068         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(cd->progress), _("click start to begin"));
1069         gtk_box_pack_start(GTK_BOX(cd->gd->vbox), cd->progress, FALSE, FALSE, 0);
1070         gtk_widget_show(cd->progress);
1071
1072         cd->days = 30;
1073         cd->tl = NULL;
1074         cd->idle_id = -1;
1075
1076         gtk_widget_show(cd->gd->dialog);
1077 }
1078
1079 static void cache_manager_standard_clean_cb(GtkWidget *widget, gpointer data)
1080 {
1081         cache_manager_standard_process(widget, FALSE);
1082 }
1083
1084 static void cache_manager_standard_clear_cb(GtkWidget *widget, gpointer data)
1085 {
1086         cache_manager_standard_process(widget, TRUE);
1087 }
1088
1089
1090 static void cache_manager_main_clean_cb(GtkWidget *widget, gpointer data)
1091 {
1092         cache_maintain_home(FALSE, FALSE, widget);
1093 }
1094
1095
1096 static void dummy_cancel_cb(GenericDialog *gd, gpointer data)
1097 {
1098         /* no op, only so cancel button appears */
1099 }
1100
1101 static void cache_manager_main_clear_ok_cb(GenericDialog *gd, gpointer data)
1102 {
1103         cache_maintain_home(FALSE, TRUE, NULL);
1104 }
1105
1106 void cache_manager_main_clear_confirm(GtkWidget *parent)
1107 {
1108         GenericDialog *gd;
1109
1110         gd = generic_dialog_new(_("Clear cache"),
1111                                 GQ_WMCLASS, "clear_cache", parent, TRUE,
1112                                 dummy_cancel_cb, NULL);
1113         generic_dialog_add_message(gd, GTK_STOCK_DIALOG_QUESTION, _("Clear cache"),
1114                                    _("This will remove all thumbnails that have\nbeen saved to disk, continue?"));
1115         generic_dialog_add_button(gd, GTK_STOCK_OK, NULL, cache_manager_main_clear_ok_cb, TRUE);
1116
1117         gtk_widget_show(gd->dialog);
1118 }
1119
1120 static void cache_manager_main_clear_cb(GtkWidget *widget, gpointer data)
1121 {
1122         cache_manager_main_clear_confirm(widget);
1123 }
1124
1125 static void cache_manager_render_cb(GtkWidget *widget, gpointer data)
1126 {
1127         cache_manager_render_dialog(widget, homedir());
1128 }
1129
1130 static void cache_manager_metadata_clean_cb(GtkWidget *widget, gpointer data)
1131 {
1132         cache_maintain_home(TRUE, FALSE, widget);
1133 }
1134
1135
1136 static CacheManager *cache_manager = NULL;
1137
1138 static void cache_manager_close_cb(GenericDialog *gd, gpointer data)
1139 {
1140         generic_dialog_close(gd);
1141
1142         g_free(cache_manager);
1143         cache_manager = NULL;
1144 }
1145
1146 void cache_manager_show(void)
1147 {
1148         GenericDialog *gd;
1149         GtkWidget *group;
1150         GtkWidget *button;
1151         GtkWidget *label;
1152         GtkWidget *table;
1153         GtkSizeGroup *sizegroup;
1154         gchar *buf;
1155
1156         if (cache_manager)
1157                 {
1158                 gtk_window_present(GTK_WINDOW(cache_manager->dialog->dialog));
1159                 return;
1160                 }
1161
1162         cache_manager = g_new0(CacheManager, 1);
1163
1164         cache_manager->dialog = generic_dialog_new(_("Cache Maintenance - Geeqie"),
1165                                                    GQ_WMCLASS, "cache_manager",
1166                                                    NULL, FALSE,
1167                                                    NULL, cache_manager);
1168         gd = cache_manager->dialog;
1169
1170         gd->cancel_cb = cache_manager_close_cb;
1171         generic_dialog_add_button(gd, GTK_STOCK_CLOSE, NULL,
1172                                   cache_manager_close_cb, FALSE);
1173
1174         generic_dialog_add_message(gd, NULL, _("Cache and Data Maintenance"), NULL);
1175
1176         sizegroup = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
1177
1178         group = pref_group_new(gd->vbox, FALSE, _("Geeqie thumbnail cache"), GTK_ORIENTATION_VERTICAL);
1179
1180         buf = g_strconcat(_("Location:"), " ", homedir(), "/", GQVIEW_CACHE_RC_THUMB, NULL);
1181         label = pref_label_new(group, buf);
1182         gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
1183         g_free(buf);
1184
1185         table = pref_table_new(group, 2, 2, FALSE, FALSE);
1186
1187         button = pref_table_button(table, 0, 0, GTK_STOCK_CLEAR, _("Clean up"), FALSE,
1188                                    G_CALLBACK(cache_manager_main_clean_cb), cache_manager);
1189         gtk_size_group_add_widget(sizegroup, button);
1190         pref_table_label(table, 1, 0, _("Remove orphaned or outdated thumbnails."), 0.0);
1191
1192         button = pref_table_button(table, 0, 1, GTK_STOCK_DELETE, _("Clear cache"), FALSE,
1193                                    G_CALLBACK(cache_manager_main_clear_cb), cache_manager);
1194         gtk_size_group_add_widget(sizegroup, button);
1195         pref_table_label(table, 1, 1, _("Delete all cached thumbnails."), 0.0);
1196
1197
1198         group = pref_group_new(gd->vbox, FALSE, _("Shared thumbnail cache"), GTK_ORIENTATION_VERTICAL);
1199
1200         buf = g_strconcat(_("Location:"), " ", homedir(), "/", THUMB_FOLDER_GLOBAL, NULL);
1201         label = pref_label_new(group, buf);
1202         gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
1203         g_free(buf);
1204
1205         table = pref_table_new(group, 2, 2, FALSE, FALSE);
1206
1207         button = pref_table_button(table, 0, 0, GTK_STOCK_CLEAR, _("Clean up"), FALSE,
1208                                    G_CALLBACK(cache_manager_standard_clean_cb), cache_manager);
1209         gtk_size_group_add_widget(sizegroup, button);
1210         pref_table_label(table, 1, 0, _("Remove orphaned or outdated thumbnails."), 0.0);
1211
1212         button = pref_table_button(table, 0, 1, GTK_STOCK_DELETE, _("Clear cache"), FALSE,
1213                                    G_CALLBACK(cache_manager_standard_clear_cb), cache_manager);
1214         gtk_size_group_add_widget(sizegroup, button);
1215         pref_table_label(table, 1, 1, _("Delete all cached thumbnails."), 0.0);
1216
1217         group = pref_group_new(gd->vbox, FALSE, _("Create thumbnails"), GTK_ORIENTATION_VERTICAL);
1218
1219         table = pref_table_new(group, 2, 1, FALSE, FALSE);
1220
1221         button = pref_table_button(table, 0, 1, GTK_STOCK_EXECUTE, _("Render"), FALSE,
1222                                    G_CALLBACK(cache_manager_render_cb), cache_manager);
1223         gtk_size_group_add_widget(sizegroup, button);
1224         pref_table_label(table, 1, 1, _("Render thumbnails for a specific folder."), 0.0);
1225
1226         group = pref_group_new(gd->vbox, FALSE, _("Metadata"), GTK_ORIENTATION_VERTICAL);
1227         
1228         buf = g_strconcat(_("Location:"), " ", homedir(), "/", GQVIEW_CACHE_RC_METADATA, NULL);
1229         label = pref_label_new(group, buf);
1230         gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
1231         g_free(buf);
1232
1233         table = pref_table_new(group, 2, 1, FALSE, FALSE);
1234
1235         button = pref_table_button(table, 0, 0, GTK_STOCK_CLEAR, _("Clean up"), FALSE,
1236                                    G_CALLBACK(cache_manager_metadata_clean_cb), cache_manager);
1237         gtk_size_group_add_widget(sizegroup, button);
1238         pref_table_label(table, 1, 0, _("Remove orphaned keywords and comments."), 0.0);
1239
1240         gtk_widget_show(cache_manager->dialog->dialog);
1241 }
1242