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