Bug fix #229: File Compression and Archiving
[geeqie.git] / src / cache_maint.c
index f710336..92ce4cc 100644 (file)
@@ -26,6 +26,7 @@
 #include "cache-loader.h"
 #include "filedata.h"
 #include "layout.h"
+#include "pixbuf_util.h"
 #include "thumb.h"
 #include "thumb_standard.h"
 #include "ui_fileops.h"
@@ -54,6 +55,65 @@ struct _CMData
 
 #define PURGE_DIALOG_WIDTH 400
 
+/*
+ *-----------------------------------------------------------------------------
+ * Command line cache maintenance program functions *-----------------------------------------------------------------------------
+ */
+static gchar *cache_maintenance_path = NULL;
+static GtkStatusIcon *status_icon;
+
+static void cache_maintenance_sim_stop_cb(gpointer data)
+{
+       exit(EXIT_SUCCESS);
+}
+
+static void cache_maintenance_render_stop_cb(gpointer data)
+{
+       gtk_status_icon_set_tooltip_text(status_icon, _("Geeqie: Creating sim data..."));
+       cache_manager_sim_remote(cache_maintenance_path, TRUE, (GDestroyNotify *)cache_maintenance_sim_stop_cb);
+}
+
+static void cache_maintenance_clean_stop_cb(gpointer data)
+{
+       gtk_status_icon_set_tooltip_text(status_icon, _("Geeqie: Creating thumbs..."));
+       cache_manager_render_remote(cache_maintenance_path, TRUE, options->thumbnails.cache_into_dirs, (GDestroyNotify *)cache_maintenance_render_stop_cb);
+}
+
+static void cache_maintenance_user_cancel_cb()
+{
+       exit(EXIT_FAILURE);
+}
+
+static void cache_maintenance_status_icon_activate_cb(GtkStatusIcon *status, gpointer data)
+{
+       GtkWidget *menu;
+       GtkWidget *item;
+
+       menu = gtk_menu_new();
+
+       item = gtk_menu_item_new_with_label("Exit Geeqie Cache Maintenance");
+
+       g_signal_connect(G_OBJECT(item), "activate", cache_maintenance_user_cancel_cb, item);
+       gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+       gtk_widget_show(item);
+
+       /* take ownership of menu */
+       g_object_ref_sink(G_OBJECT(menu));
+
+       gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time());
+}
+
+void cache_maintenance(const gchar *path)
+{
+       cache_maintenance_path = g_strdup(path);
+
+       status_icon = gtk_status_icon_new_from_pixbuf(pixbuf_inline(PIXBUF_INLINE_ICON));
+       gtk_status_icon_set_tooltip_text(status_icon, _("Geeqie: Cleaning thumbs..."));
+       gtk_status_icon_set_visible(status_icon, TRUE);
+       g_signal_connect(G_OBJECT(status_icon), "activate", G_CALLBACK(cache_maintenance_status_icon_activate_cb), NULL);
+
+       cache_maintain_home_remote(FALSE, FALSE, (GDestroyNotify *)cache_maintenance_clean_stop_cb);
+}
 
 /*
  *-------------------------------------------------------------------
@@ -355,7 +415,15 @@ void cache_maintain_home(gboolean metadata, gboolean clear, GtkWidget *parent)
        cm->idle_id = g_idle_add(cache_maintain_home_cb, cm);
 }
 
-void cache_maintain_home_remote(gboolean metadata, gboolean clear)
+/**
+ * @brief Clears or culls cached data
+ * @param metadata TRUE - work on metadata cache, FALSE - work on thumbnail cache
+ * @param clear TRUE - clear cache, FALSE - delete orphaned cached items
+ * @param func Function called when idle loop function terminates
+ * 
+ * 
+ */
+void cache_maintain_home_remote(gboolean metadata, gboolean clear, GDestroyNotify *func)
 {
        CMData *cm;
        GList *dlist;
@@ -387,7 +455,7 @@ void cache_maintain_home_remote(gboolean metadata, gboolean clear)
        cm->metadata = metadata;
        cm->remote = TRUE;
 
-       cm->idle_id = g_idle_add(cache_maintain_home_cb, cm);
+       cm->idle_id = g_idle_add_full(G_PRIORITY_LOW, cache_maintain_home_cb, cm, (GDestroyNotify)func);
 }
 
 static void cache_file_move(const gchar *src, const gchar *dest)
@@ -556,6 +624,7 @@ struct _CacheOpsData
        GenericDialog *gd;
        ThumbLoaderStd *tl;
        CacheLoader *cl;
+       GDestroyNotify *destroy_func; /* Used by the command line prog. functions */
 
        GList *list;
        GList *list_dir;
@@ -713,9 +782,17 @@ static gboolean cache_manager_render_file(CacheOpsData *cd)
                return TRUE;
                }
 
-       gtk_entry_set_text(GTK_ENTRY(cd->progress), _("done"));
+       if (!cd->remote)
+               {
+               gtk_entry_set_text(GTK_ENTRY(cd->progress), _("done"));
+               }
        cache_manager_render_finish(cd);
 
+       if (cd->destroy_func)
+               {
+               g_idle_add((GSourceFunc)cd->destroy_func, NULL);
+               }
+
        return FALSE;
 }
 
@@ -861,7 +938,16 @@ static void cache_manager_render_dialog(GtkWidget *widget, const gchar *path)
        gtk_widget_show(cd->gd->dialog);
 }
 
-void cache_manager_render_remote(const gchar *path, gboolean recurse, gboolean local)
+/**
+ * @brief Create thumbnails
+ * @param path Path to image folder
+ * @param recurse 
+ * @param local Create thumbnails in same folder as images
+ * @param func Function called when idle loop function terminates
+ * 
+ * 
+ */
+void cache_manager_render_remote(const gchar *path, gboolean recurse, gboolean local, GDestroyNotify *func)
 {
        CacheOpsData *cd;
 
@@ -869,6 +955,7 @@ void cache_manager_render_remote(const gchar *path, gboolean recurse, gboolean l
        cd->recurse = recurse;
        cd->local = local;
        cd->remote = TRUE;
+       cd->destroy_func = func;
 
        cache_manager_render_start_render_remote(cd, path);
 }
@@ -1197,7 +1284,12 @@ static GtkWidget *cache_manager_location_label(GtkWidget *group, const gchar *su
        buf = g_strdup_printf(_("Location: %s"), subdir);
        label = pref_label_new(group, buf);
        g_free(buf);
+#if GTK_CHECK_VERSION(3,16,0)
+       gtk_label_set_xalign(GTK_LABEL(label), 0.0);
+       gtk_label_set_yalign(GTK_LABEL(label), 0.5);
+#else
        gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
+#endif
 
        return label;
 }
@@ -1280,6 +1372,50 @@ static void cache_manager_sim_file_done_cb(CacheLoader *cl, gint error, gpointer
        while (cache_manager_sim_file(cd));
 }
 
+static void cache_manager_sim_start_sim_remote(CacheOpsData *cd, const gchar *user_path)
+{
+       gchar *path;
+
+       path = remove_trailing_slash(user_path);
+       parse_out_relatives(path);
+
+       if (!isdir(path))
+               {
+               log_printf("The specified folder can not be found: %s\n", path);
+               }
+       else
+               {
+               FileData *dir_fd;
+
+               dir_fd = file_data_new_dir(path);
+               cache_manager_sim_folder(cd, dir_fd);
+               file_data_unref(dir_fd);
+               while (cache_manager_sim_file(cd));
+               }
+
+       g_free(path);
+}
+
+/**
+ * @brief Generate .sim files
+ * @param path Path to image folder
+ * @param recurse 
+ * @param func Function called when idle loop function terminates
+ * 
+ * 
+ */
+void cache_manager_sim_remote(const gchar *path, gboolean recurse, GDestroyNotify *func)
+{
+       CacheOpsData *cd;
+
+       cd = g_new0(CacheOpsData, 1);
+       cd->recurse = recurse;
+       cd->remote = TRUE;
+       cd->destroy_func = func;
+
+       cache_manager_sim_start_sim_remote(cd, path);
+}
+
 static gboolean cache_manager_sim_file(CacheOpsData *cd)
 {
        CacheDataType load_mask;
@@ -1293,11 +1429,17 @@ static gboolean cache_manager_sim_file(CacheOpsData *cd)
                load_mask = CACHE_LOADER_DIMENSIONS | CACHE_LOADER_DATE | CACHE_LOADER_MD5SUM | CACHE_LOADER_SIMILARITY;
                cd->cl = (CacheLoader *)cache_loader_new(fd, load_mask, (cache_manager_sim_file_done_cb), cd);
 
-               gtk_entry_set_text(GTK_ENTRY(cd->progress), fd->path);
+               if (!cd->remote)
+                       {
+                       gtk_entry_set_text(GTK_ENTRY(cd->progress), fd->path);
+                       }
 
                file_data_unref(fd);
                cd->count_done = cd->count_done + 1;
-               gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(cd->progress_bar), (gdouble)cd->count_done / cd->count_total);
+               if (!cd->remote)
+                       {
+                       gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(cd->progress_bar), (gdouble)cd->count_done / cd->count_total);
+                       }
 
                return FALSE;
                }
@@ -1314,9 +1456,18 @@ static gboolean cache_manager_sim_file(CacheOpsData *cd)
                return TRUE;
                }
 
-       gtk_entry_set_text(GTK_ENTRY(cd->progress), _("done"));
+               if (!cd->remote)
+                       {
+                       gtk_entry_set_text(GTK_ENTRY(cd->progress), _("done"));
+                       }
+
        cache_manager_sim_finish((CacheOpsData *)cd);
 
+       if (cd->destroy_func)
+               {
+               g_idle_add((GSourceFunc)cd->destroy_func, NULL);
+               }
+
        return FALSE;
 }
 
@@ -1431,17 +1582,110 @@ static void cache_manager_sim_load_dialog(GtkWidget *widget, const gchar *path)
        gtk_widget_show(cd->gd->dialog);
 }
 
-static void cache_manager_sim_clear_ok_cb(GenericDialog *gd, gpointer data)
+static void cache_manager_sim_load_cb(GtkWidget *widget, gpointer data)
+{
+       const gchar *path = layout_get_path(NULL);
+
+       if (!path || !*path) path = homedir();
+       cache_manager_sim_load_dialog(widget, path);
+}
+
+static void cache_manager_cache_maintenance_close_cb(GenericDialog *fd, gpointer data)
 {
-       cache_maintain_home(FALSE, TRUE, NULL);
+       CacheOpsData *cd = data;
+
+       if (!gtk_widget_get_sensitive(cd->button_close)) return;
+
+       cache_manager_sim_reset(cd);
+       generic_dialog_close(cd->gd);
+       g_free(cd);
 }
 
-static void cache_manager_sim_load_cb(GtkWidget *widget, gpointer data)
+static void cache_manager_cache_maintenance_start_cb(GenericDialog *fd, gpointer data)
+{
+       CacheOpsData *cd = data;
+       gchar *path;
+       gchar *cmd_line;
+
+       if (!cd->remote)
+               {
+               if (cd->list || !gtk_widget_get_sensitive(cd->button_start)) return;
+               }
+
+       path = remove_trailing_slash((gtk_entry_get_text(GTK_ENTRY(cd->entry))));
+       parse_out_relatives(path);
+
+       if (!isdir(path))
+               {
+               if (!cd->remote)
+                       {
+                       warning_dialog(_("Invalid folder"),
+                       _("The specified folder can not be found."),
+                       GTK_STOCK_DIALOG_WARNING, cd->gd->dialog);
+                       }
+               else
+                       {
+                       log_printf("The specified folder can not be found: \"%s\"\n", path);
+                       }
+               }
+       else
+               {
+               cmd_line = g_strdup_printf("%s --cache-maintenance \"%s\"", gq_executable_path, path);
+
+               g_spawn_command_line_async(cmd_line, NULL);
+
+               g_free(cmd_line);
+               generic_dialog_close(cd->gd);
+               cache_manager_sim_reset(cd);
+               g_free(cd);
+               }
+
+       g_free(path);
+}
+
+static void cache_manager_cache_maintenance_load_dialog(GtkWidget *widget, const gchar *path)
+{
+       CacheOpsData *cd;
+       GtkWidget *hbox;
+       GtkWidget *label;
+
+       cd = g_new0(CacheOpsData, 1);
+       cd->remote = FALSE;
+       cd->recurse = TRUE;
+
+       cd->gd = generic_dialog_new(_("Background cache maintenance"), "background_cache_maintenance", widget, FALSE, NULL, cd);
+       gtk_window_set_default_size(GTK_WINDOW(cd->gd->dialog), PURGE_DIALOG_WIDTH, -1);
+       cd->gd->cancel_cb = cache_manager_cache_maintenance_close_cb;
+       cd->button_close = generic_dialog_add_button(cd->gd, GTK_STOCK_CLOSE, NULL,
+                                                    cache_manager_cache_maintenance_close_cb, FALSE);
+       cd->button_start = generic_dialog_add_button(cd->gd, GTK_STOCK_OK, _("S_tart"),
+                                                    cache_manager_cache_maintenance_start_cb, FALSE);
+
+       generic_dialog_add_message(cd->gd, NULL, _("Recursively delete orphaned thumbnails\nand .sim files, and create new\nthumbnails and .sim files"), NULL, FALSE);
+
+       hbox = pref_box_new(cd->gd->vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, 0);
+       pref_spacer(hbox, PREF_PAD_INDENT);
+       cd->group = pref_box_new(hbox, TRUE, GTK_ORIENTATION_VERTICAL, PREF_PAD_GAP);
+
+       hbox = pref_box_new(cd->group, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
+       pref_label_new(hbox, _("Folder:"));
+
+       label = tab_completion_new(&cd->entry, path, NULL, NULL, NULL, NULL);
+       tab_completion_add_select_button(cd->entry,_("Select folder") , TRUE);
+       gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
+       gtk_widget_show(label);
+
+       cd->list = NULL;
+
+       gtk_widget_show(cd->gd->dialog);
+}
+
+static void cache_manager_cache_maintenance_load_cb(GtkWidget *widget, gpointer data)
 {
        const gchar *path = layout_get_path(NULL);
 
        if (!path || !*path) path = homedir();
-       cache_manager_sim_load_dialog(widget, path);
+       cache_manager_cache_maintenance_load_dialog(widget, path);
 }
 
 void cache_manager_show(void)
@@ -1543,6 +1787,16 @@ void cache_manager_show(void)
        gtk_size_group_add_widget(sizegroup, button);
        pref_table_label(table, 1, 0, _("Remove orphaned keywords and comments."), 0.0);
 
+       group = pref_group_new(gd->vbox, FALSE, _("Background cache maintenance"), GTK_ORIENTATION_VERTICAL);
+
+       table = pref_table_new(group, 3, 2, FALSE, FALSE);
+
+       button = pref_table_button(table, 0, 0, GTK_STOCK_EXECUTE, _("Select"), FALSE,
+                                  G_CALLBACK(cache_manager_cache_maintenance_load_cb), cache_manager);
+       gtk_size_group_add_widget(sizegroup, button);
+       pref_table_label(table, 1, 0, _("Run cache maintenance as a background job."), 0.0);
+       gtk_widget_set_sensitive(group, options->thumbnails.enable_caching);
+
        gtk_widget_show(cache_manager->dialog->dialog);
 }
 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */