[nautilus] file-operations: implement compression operation



commit 918f2b6163bb61cb53612496c2c381b85900dbda
Author: Razvan Chitu <razvan ch95 gmail com>
Date:   Sun Aug 21 12:22:29 2016 +0300

    file-operations: implement compression operation
    
    Add an operation for compressing files using gnome-autoar. The operation is
    similar in functionality to the one offered by file roller but comes with
    integrated progress feedback and support for undoing and redoing.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=770199

 src/nautilus-file-operations.c      |  319 ++++++++++++++++++++++++++++++++++-
 src/nautilus-file-operations.h      |    9 +
 src/nautilus-file-undo-operations.c |  142 ++++++++++++++++
 src/nautilus-file-undo-operations.h |   29 +++
 4 files changed, 495 insertions(+), 4 deletions(-)
---
diff --git a/src/nautilus-file-operations.c b/src/nautilus-file-operations.c
index 58c825e..e8a2c4d 100644
--- a/src/nautilus-file-operations.c
+++ b/src/nautilus-file-operations.c
@@ -51,7 +51,6 @@
 #include <gtk/gtk.h>
 #include <gio/gio.h>
 #include <glib.h>
-#include <gnome-autoar/gnome-autoar.h>
 
 #include "nautilus-operations-ui-manager.h"
 #include "nautilus-file-changes-queue.h"
@@ -153,7 +152,8 @@ typedef enum {
        OP_KIND_COPY,
        OP_KIND_MOVE,
        OP_KIND_DELETE,
-       OP_KIND_TRASH
+       OP_KIND_TRASH,
+        OP_KIND_COMPRESS
 } OpKind;
 
 typedef struct {
@@ -186,6 +186,23 @@ typedef struct {
         gpointer done_callback_data;
 } ExtractJob;
 
+typedef struct {
+        CommonJob common;
+        GList *source_files;
+        GFile *output_file;
+
+        AutoarFormat format;
+        AutoarFilter filter;
+
+        guint64 total_size;
+        guint total_files;
+
+        gboolean success;
+
+        NautilusCreateCallback done_callback;
+        gpointer done_callback_data;
+} CompressJob;
+
 #define SECONDS_NEEDED_FOR_RELIABLE_TRANSFER_RATE 8
 #define NSEC_PER_MICROSEC 1000
 #define PROGRESS_NOTIFY_INTERVAL 100 * NSEC_PER_MICROSEC
@@ -2710,7 +2727,12 @@ report_preparing_count_progress (CommonJob *job,
                                source_info->num_files),
                       source_info->num_files);
                break;
-       } 
+        case OP_KIND_COMPRESS:
+                s = f (ngettext("Preparing to compress %'d file",
+                                "Preparing to compress %'d files",
+                                source_info->num_files),
+                       source_info->num_files);
+        }
 
        nautilus_progress_info_take_details (job->progress, s);
        nautilus_progress_info_pulse_progress (job->progress);
@@ -2743,7 +2765,9 @@ get_scan_primary (OpKind kind)
                return f (_("Error while deleting."));
        case OP_KIND_TRASH:
                return f (_("Error while moving files to trash."));
-       }
+        case OP_KIND_COMPRESS:
+                return f (_("Error while compressing files."));
+        }
 }
 
 static void
@@ -7403,6 +7427,293 @@ nautilus_file_operations_extract_files (GList                   *files,
         g_task_run_in_thread (task, extract_task_thread_func);
 }
 
+static void
+compress_task_done (GObject      *source_object,
+                    GAsyncResult *res,
+                    gpointer      user_data)
+{
+        CompressJob *compress_job = user_data;
+
+        if (compress_job->done_callback) {
+                compress_job->done_callback (compress_job->output_file,
+                                             compress_job->success,
+                                             compress_job->done_callback_data);
+        }
+
+        g_object_unref (compress_job->output_file);
+        g_list_free_full (compress_job->source_files, g_object_unref);
+
+        finalize_common ((CommonJob *)compress_job);
+
+        nautilus_file_changes_consume_changes (TRUE);
+}
+
+static void
+compress_job_on_progress (AutoarCompressor *compressor,
+                          guint64           completed_size,
+                          guint             completed_files,
+                          gpointer          user_data)
+{
+        CompressJob *compress_job = user_data;
+        CommonJob *common = user_data;
+        char *status;
+        char *details;
+        int files_left;
+        double elapsed;
+        double transfer_rate;
+        int remaining_time;
+
+        files_left = compress_job->total_files - completed_files;
+
+        if (compress_job->total_files == 1) {
+                status = f (_("Compressing “%B” into “%B”"),
+                            G_FILE (compress_job->source_files->data),
+                            compress_job->output_file);
+        } else {
+                status = f (ngettext ("Compressing %'d file into “%B”",
+                                      "Compressing %'d files into “%B”",
+                                      compress_job->total_files),
+                            compress_job->total_files,
+                            compress_job->output_file);
+
+        }
+
+        nautilus_progress_info_take_status (common->progress, status);
+
+        elapsed = g_timer_elapsed (common->time, NULL);
+
+        transfer_rate = 0;
+        remaining_time = -1;
+
+        if (elapsed > 0) {
+                if (completed_size > 0) {
+                        transfer_rate = completed_size / elapsed;
+                        remaining_time = (compress_job->total_size - completed_size) / transfer_rate;
+                } else if (completed_files > 0) {
+                        transfer_rate = completed_files / elapsed;
+                        remaining_time = (compress_job->total_files - completed_files) / transfer_rate;
+                }
+        }
+
+        if (elapsed < SECONDS_NEEDED_FOR_RELIABLE_TRANSFER_RATE ||
+            transfer_rate == 0) {
+                if (compress_job->total_files == 1) {
+                        /* To translators: %S will expand to a size like "2 bytes" or "3 MB", so something 
like "4 kb / 4 MB" */
+                        details = f (_("%S / %S"), completed_size, compress_job->total_size);
+                } else {
+                        details = f (_("%'d / %'d"),
+                                     files_left > 0 ? completed_files + 1 : completed_files,
+                                     compress_job->total_files);
+                }
+        } else {
+                if (compress_job->total_files == 1) {
+                        if (files_left > 0) {
+                                /* To translators: %S will expand to a size like "2 bytes" or "3 MB", %T to 
a time duration like
+                                 * "2 minutes". So the whole thing will be something like "2 kb / 4 MB -- 2 
hours left (4kb/sec)"
+                                 *
+                                 * The singular/plural form will be used depending on the remaining time 
(i.e. the %T argument).
+                                 */
+                                details = f (ngettext ("%S / %S \xE2\x80\x94 %T left (%S/sec)",
+                                                       "%S / %S \xE2\x80\x94 %T left (%S/sec)",
+                                                       seconds_count_format_time_units (remaining_time)),
+                                             completed_size, compress_job->total_size,
+                                             remaining_time,
+                                             (goffset)transfer_rate);
+                        } else {
+                                /* To translators: %S will expand to a size like "2 bytes" or "3 MB". */
+                                details = f (_("%S / %S"),
+                                             completed_size,
+                                             compress_job->total_size);
+                        }
+                } else {
+                        if (files_left > 0) {
+                                /* To translators: %T will expand to a time duration like "2 minutes".
+                                 * So the whole thing will be something like "1 / 5 -- 2 hours left 
(4kb/sec)"
+                                 *
+                                 * The singular/plural form will be used depending on the remaining time 
(i.e. the %T argument).
+                                 */
+                                details = f (ngettext ("%'d / %'d \xE2\x80\x94 %T left (%S/sec)",
+                                                       "%'d / %'d \xE2\x80\x94 %T left (%S/sec)",
+                                                       seconds_count_format_time_units (remaining_time)),
+                                             completed_files + 1, compress_job->total_files,
+                                             remaining_time,
+                                             (goffset)transfer_rate);
+                        } else {
+                                /* To translators: %'d is the number of files completed for the operation,
+                                 * so it will be something like 2/14. */
+                                details = f (_("%'d / %'d"),
+                                             completed_files,
+                                             compress_job->total_files);
+                        }
+                }
+        }
+
+        nautilus_progress_info_take_details (common->progress, details);
+
+        if (elapsed > SECONDS_NEEDED_FOR_APROXIMATE_TRANSFER_RATE) {
+                nautilus_progress_info_set_remaining_time (common->progress,
+                                                           remaining_time);
+                nautilus_progress_info_set_elapsed_time (common->progress,
+                                                         elapsed);
+        }
+
+        nautilus_progress_info_set_progress (common->progress,
+                                             completed_size,
+                                             compress_job->total_size);
+}
+
+static void
+compress_job_on_error (AutoarCompressor *compressor,
+                       GError           *error,
+                       gpointer          user_data)
+{
+        CompressJob *compress_job = user_data;
+        char *status;
+
+        if (compress_job->total_files == 1) {
+                status = f (_("Error compressing “%B” into “%B”"),
+                            G_FILE (compress_job->source_files->data),
+                            compress_job->output_file);
+        } else {
+                status = f (ngettext ("Error compressing %'d file into “%B”",
+                                      "Error compressing %'d files into “%B”",
+                                      compress_job->total_files),
+                            compress_job->total_files,
+                            compress_job->output_file);
+        }
+
+        nautilus_progress_info_take_status (compress_job->common.progress,
+                                            status);
+
+        run_error ((CommonJob *)compress_job,
+                   _("There was an error while compressing files."),
+                   g_strdup (error->message),
+                   NULL,
+                   FALSE,
+                   CANCEL,
+                   NULL);
+
+        abort_job ((CommonJob *)compress_job);
+}
+
+static void
+compress_job_on_completed (AutoarCompressor *compressor,
+                           gpointer          user_data)
+{
+        CompressJob *compress_job = user_data;
+        g_autoptr (GFile) destination_directory;
+        char *status;
+
+        if (compress_job->total_files == 1) {
+                status = f (_("Compressed “%B” into “%B”"),
+                            G_FILE (compress_job->source_files->data),
+                            compress_job->output_file);
+        } else {
+                status = f (ngettext ("Compressed %'d file into “%B”",
+                                      "Compressed %'d files into “%B”",
+                                      compress_job->total_files),
+                            compress_job->total_files,
+                            compress_job->output_file);
+        }
+
+        nautilus_progress_info_take_status (compress_job->common.progress,
+                                            status);
+
+        nautilus_file_changes_queue_file_added (compress_job->output_file);
+
+        destination_directory = g_file_get_parent (compress_job->output_file);
+        nautilus_progress_info_set_destination (compress_job->common.progress,
+                                                destination_directory);
+}
+
+static void
+compress_task_thread_func (GTask        *task,
+                           gpointer      source_object,
+                           gpointer      task_data,
+                           GCancellable *cancellable)
+{
+        CompressJob *compress_job = task_data;
+        SourceInfo source_info;
+        g_autoptr (AutoarCompressor) compressor;
+
+        g_timer_start (compress_job->common.time);
+
+        nautilus_progress_info_start (compress_job->common.progress);
+
+        scan_sources (compress_job->source_files,
+                      &source_info,
+                      (CommonJob *)compress_job,
+                      OP_KIND_COMPRESS);
+
+        compress_job->total_files = source_info.num_files;
+        compress_job->total_size = source_info.num_bytes;
+
+        compressor = autoar_compressor_new (compress_job->source_files,
+                                            compress_job->output_file,
+                                            compress_job->format,
+                                            compress_job->filter,
+                                            FALSE);
+
+        autoar_compressor_set_output_is_dest (compressor, TRUE);
+
+        autoar_compressor_set_notify_interval (compressor,
+                                               PROGRESS_NOTIFY_INTERVAL);
+
+        g_signal_connect (compressor, "progress",
+                          G_CALLBACK (compress_job_on_progress), compress_job);
+        g_signal_connect (compressor, "error",
+                          G_CALLBACK (compress_job_on_error), compress_job);
+        g_signal_connect (compressor, "completed",
+                          G_CALLBACK (compress_job_on_completed), compress_job);
+        autoar_compressor_start (compressor,
+                                 compress_job->common.cancellable);
+
+        compress_job->success = g_file_query_exists (compress_job->output_file,
+                                                     NULL);
+
+        /* There is nothing to undo if the output was not created */
+        if (compress_job->common.undo_info != NULL && !compress_job->success) {
+                g_clear_object (&compress_job->common.undo_info);
+        }
+}
+
+void
+nautilus_file_operations_compress (GList                  *files,
+                                   GFile                  *output,
+                                   AutoarFormat            format,
+                                   AutoarFilter            filter,
+                                   GtkWindow              *parent_window,
+                                   NautilusCreateCallback  done_callback,
+                                   gpointer                done_callback_data)
+{
+        g_autoptr (GTask) task;
+        CompressJob *compress_job;
+
+        compress_job = op_job_new (CompressJob, parent_window);
+        compress_job->source_files = g_list_copy_deep (files,
+                                                       (GCopyFunc)g_object_ref,
+                                                       NULL);
+        compress_job->output_file = g_object_ref (output);
+        compress_job->format = format;
+        compress_job->filter = filter;
+        compress_job->done_callback = done_callback;
+        compress_job->done_callback_data = done_callback_data;
+
+        inhibit_power_manager ((CommonJob *)compress_job, _("Compressing Files"));
+
+        if (!nautilus_file_undo_manager_is_operating ()) {
+                compress_job->common.undo_info = nautilus_file_undo_info_compress_new (files,
+                                                                                       output,
+                                                                                       format,
+                                                                                       filter);
+        }
+
+        task = g_task_new (NULL, compress_job->common.cancellable,
+                           compress_task_done, compress_job);
+        g_task_set_task_data (task, compress_job, NULL);
+        g_task_run_in_thread (task, compress_task_thread_func);
+}
+
 #if !defined (NAUTILUS_OMIT_SELF_CHECK)
 
 void
diff --git a/src/nautilus-file-operations.h b/src/nautilus-file-operations.h
index fb209d1..31b782d 100644
--- a/src/nautilus-file-operations.h
+++ b/src/nautilus-file-operations.h
@@ -26,6 +26,8 @@
 
 #include <gtk/gtk.h>
 #include <gio/gio.h>
+#include <gnome-autoar/gnome-autoar.h>
+
 
 #define SECONDS_NEEDED_FOR_APROXIMATE_TRANSFER_RATE 1
 
@@ -154,6 +156,13 @@ void nautilus_file_operations_extract_files (GList                   *files,
                                              GtkWindow               *parent_window,
                                              NautilusExtractCallback  done_callback,
                                              gpointer                 done_callback_data);
+void nautilus_file_operations_compress (GList                  *files,
+                                        GFile                  *output,
+                                        AutoarFormat            format,
+                                        AutoarFilter            filter,
+                                        GtkWindow              *parent_window,
+                                        NautilusCreateCallback  done_callback,
+                                        gpointer                done_callback_data);
 
 
 #endif /* NAUTILUS_FILE_OPERATIONS_H */
diff --git a/src/nautilus-file-undo-operations.c b/src/nautilus-file-undo-operations.c
index 6025848..9e95310 100644
--- a/src/nautilus-file-undo-operations.c
+++ b/src/nautilus-file-undo-operations.c
@@ -1849,3 +1849,145 @@ nautilus_file_undo_info_extract_new (GList *sources,
 
         return NAUTILUS_FILE_UNDO_INFO (self);
 }
+
+
+/* compress */
+G_DEFINE_TYPE (NautilusFileUndoInfoCompress, nautilus_file_undo_info_compress, NAUTILUS_TYPE_FILE_UNDO_INFO)
+
+struct _NautilusFileUndoInfoCompressDetails {
+        GList *sources;
+        GFile *output;
+        AutoarFormat format;
+        AutoarFilter filter;
+};
+
+static void
+compress_callback (GFile    *new_file,
+                   gboolean  success,
+                   gpointer  callback_data)
+{
+        NautilusFileUndoInfoCompress *self = NAUTILUS_FILE_UNDO_INFO_COMPRESS (callback_data);
+
+        if (success) {
+                g_object_unref (self->priv->output);
+
+                self->priv->output = g_object_ref (new_file);
+        }
+
+        file_undo_info_transfer_callback (NULL, success, self);
+}
+
+static void
+compress_strings_func (NautilusFileUndoInfo  *info,
+                       gchar                **undo_label,
+                       gchar                **undo_description,
+                       gchar                **redo_label,
+                       gchar                **redo_description)
+{
+        NautilusFileUndoInfoCompress *self = NAUTILUS_FILE_UNDO_INFO_COMPRESS (info);
+        g_autofree gchar *output_name;
+        gint sources_count;
+
+        output_name = g_file_get_parse_name (self->priv->output);
+        *undo_description = g_strdup_printf (_("Delete '%s'"), output_name);
+
+        sources_count = g_list_length (self->priv->sources);
+        if (sources_count == 1) {
+                GFile *source;
+                g_autofree gchar *source_name;
+
+                source = self->priv->sources->data;
+                source_name = g_file_get_parse_name (source);
+
+                *redo_description = g_strdup_printf (_("Compress '%s'"), source_name);
+        } else {
+                *redo_description = g_strdup_printf (_("Compress '%d' files"), sources_count);
+        }
+
+        *undo_label = g_strdup (_("_Undo Compress"));
+        *redo_label = g_strdup (_("_Redo Compress"));
+}
+
+static void
+compress_redo_func (NautilusFileUndoInfo *info,
+                    GtkWindow            *parent_window)
+{
+        NautilusFileUndoInfoCompress *self = NAUTILUS_FILE_UNDO_INFO_COMPRESS (info);
+
+        nautilus_file_operations_compress (self->priv->sources,
+                                           self->priv->output,
+                                           self->priv->format,
+                                           self->priv->filter,
+                                           parent_window,
+                                           compress_callback,
+                                           self);
+}
+
+static void
+compress_undo_func (NautilusFileUndoInfo *info,
+                    GtkWindow            *parent_window)
+{
+        NautilusFileUndoInfoCompress *self = NAUTILUS_FILE_UNDO_INFO_COMPRESS (info);
+        GList *files = NULL;
+
+        files = g_list_prepend (files, self->priv->output);
+
+        nautilus_file_operations_delete (files, parent_window,
+                                         file_undo_info_delete_callback, self);
+
+        g_list_free (files);
+}
+
+static void
+nautilus_file_undo_info_compress_init (NautilusFileUndoInfoCompress *self)
+{
+        self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, nautilus_file_undo_info_compress_get_type (),
+                                                  NautilusFileUndoInfoCompressDetails);
+}
+
+static void
+nautilus_file_undo_info_compress_finalize (GObject *obj)
+{
+        NautilusFileUndoInfoCompress *self = NAUTILUS_FILE_UNDO_INFO_COMPRESS (obj);
+
+        g_list_free_full (self->priv->sources, g_object_unref);
+        g_clear_object (&self->priv->output);
+
+        G_OBJECT_CLASS (nautilus_file_undo_info_compress_parent_class)->finalize (obj);
+}
+
+static void
+nautilus_file_undo_info_compress_class_init (NautilusFileUndoInfoCompressClass *klass)
+{
+        GObjectClass *oclass = G_OBJECT_CLASS (klass);
+        NautilusFileUndoInfoClass *iclass = NAUTILUS_FILE_UNDO_INFO_CLASS (klass);
+
+        oclass->finalize = nautilus_file_undo_info_compress_finalize;
+
+        iclass->undo_func = compress_undo_func;
+        iclass->redo_func = compress_redo_func;
+        iclass->strings_func = compress_strings_func;
+
+        g_type_class_add_private (klass, sizeof (NautilusFileUndoInfoCompressDetails));
+}
+
+NautilusFileUndoInfo *
+nautilus_file_undo_info_compress_new (GList        *sources,
+                                      GFile        *output,
+                                      AutoarFormat  format,
+                                      AutoarFilter  filter)
+{
+        NautilusFileUndoInfoCompress *self;
+
+        self = g_object_new (NAUTILUS_TYPE_FILE_UNDO_INFO_COMPRESS,
+                             "item-count", 1,
+                             "op-type", NAUTILUS_FILE_UNDO_OP_COMPRESS,
+                             NULL);
+
+        self->priv->sources = g_list_copy_deep (sources, (GCopyFunc)g_object_ref, NULL);
+        self->priv->output = g_object_ref (output);
+        self->priv->format = format;
+        self->priv->filter = filter;
+
+        return NAUTILUS_FILE_UNDO_INFO (self);
+}
diff --git a/src/nautilus-file-undo-operations.h b/src/nautilus-file-undo-operations.h
index ef01363..9402469 100644
--- a/src/nautilus-file-undo-operations.h
+++ b/src/nautilus-file-undo-operations.h
@@ -27,6 +27,7 @@
 
 #include <gio/gio.h>
 #include <gtk/gtk.h>
+#include <gnome-autoar/gnome-autoar.h>
 
 typedef enum {
        NAUTILUS_FILE_UNDO_OP_COPY,
@@ -37,6 +38,7 @@ typedef enum {
        NAUTILUS_FILE_UNDO_OP_CREATE_FILE_FROM_TEMPLATE,
        NAUTILUS_FILE_UNDO_OP_CREATE_FOLDER,
        NAUTILUS_FILE_UNDO_OP_EXTRACT,
+       NAUTILUS_FILE_UNDO_OP_COMPRESS,
        NAUTILUS_FILE_UNDO_OP_MOVE_TO_TRASH,
        NAUTILUS_FILE_UNDO_OP_RESTORE_FROM_TRASH,
        NAUTILUS_FILE_UNDO_OP_CREATE_LINK,
@@ -324,5 +326,32 @@ NautilusFileUndoInfo * nautilus_file_undo_info_extract_new (GList *sources,
 void nautilus_file_undo_info_extract_set_outputs (NautilusFileUndoInfoExtract *self,
                                                   GList                       *outputs);
 
+/* compress */
+#define NAUTILUS_TYPE_FILE_UNDO_INFO_COMPRESS         (nautilus_file_undo_info_compress_get_type ())
+#define NAUTILUS_FILE_UNDO_INFO_COMPRESS(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), 
NAUTILUS_TYPE_FILE_UNDO_INFO_COMPRESS, NautilusFileUndoInfoCompress))
+#define NAUTILUS_FILE_UNDO_INFO_COMPRESS_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), 
NAUTILUS_TYPE_FILE_UNDO_INFO_COMPRESS, NautilusFileUndoInfoCompressClass))
+#define NAUTILUS_IS_FILE_UNDO_INFO_COMPRESS(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), 
NAUTILUS_TYPE_FILE_UNDO_INFO_COMPRESS))
+#define NAUTILUS_IS_FILE_UNDO_INFO_COMPRESS_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), 
NAUTILUS_TYPE_FILE_UNDO_INFO_COMPRESS))
+#define NAUTILUS_FILE_UNDO_INFO_COMPRESS_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), 
NAUTILUS_TYPE_FILE_UNDO_INFO_COMPRESS, NautilusFileUndoInfoCompressClass))
+
+typedef struct _NautilusFileUndoInfoCompress        NautilusFileUndoInfoCompress;
+typedef struct _NautilusFileUndoInfoCompressClass   NautilusFileUndoInfoCompressClass;
+typedef struct _NautilusFileUndoInfoCompressDetails NautilusFileUndoInfoCompressDetails;
+
+struct _NautilusFileUndoInfoCompress {
+        NautilusFileUndoInfo parent;
+        NautilusFileUndoInfoCompressDetails *priv;
+};
+
+struct _NautilusFileUndoInfoCompressClass {
+        NautilusFileUndoInfoClass parent_class;
+};
+
+GType nautilus_file_undo_info_compress_get_type (void) G_GNUC_CONST;
+NautilusFileUndoInfo * nautilus_file_undo_info_compress_new (GList        *sources,
+                                                             GFile        *output,
+                                                             AutoarFormat  format,
+                                                             AutoarFilter  filter);
+
 
 #endif /* __NAUTILUS_FILE_UNDO_OPERATIONS_H__ */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]