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