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