[nautilus/wip/razvan/automatic-decompression: 12/14] file-operations: implement extract operation



commit 187de8e9d97c9b752ef75870c0159814e470190a
Author: Razvan Chitu <razvan ch95 gmail com>
Date:   Sun Jul 10 11:56:36 2016 +0300

    file-operations: implement extract operation
    
    Add a new operation for extracting archives using gnome-autoar.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=768646

 configure.ac                    |    2 +
 src/nautilus-conflict-manager.c |   85 +++++++++
 src/nautilus-conflict-manager.h |    4 +
 src/nautilus-file-operations.c  |  384 +++++++++++++++++++++++++++++++++++++++
 src/nautilus-file-operations.h  |    9 +
 5 files changed, 484 insertions(+), 0 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 493c5de..3caef9b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -11,6 +11,7 @@ m4_define(exif_minver,                 0.6.20)
 m4_define(exempi_minver,               2.1.0)
 m4_define(notify_minver,               0.7.0)
 m4_define(schemas_minver,              3.8.0)
+m4_define(autoar_minver,               0.1)
 
 dnl 1. If the library code has changed at all since last release, then increment revision.
 dnl 2. If any interfaces have been added, then increment current and set revision to 0.
@@ -270,6 +271,7 @@ dnl base libs
 PKG_CHECK_MODULES(BASE, [
        gtk+-3.0 >= gtk_minver
        glib-2.0 >= glib_minver
+        gnome-autoar >= autoar_minver
 ])
 
 dnl common libs (eel, nautilus)
diff --git a/src/nautilus-conflict-manager.c b/src/nautilus-conflict-manager.c
index 893f54e..6db3579 100644
--- a/src/nautilus-conflict-manager.c
+++ b/src/nautilus-conflict-manager.c
@@ -118,6 +118,42 @@ set_copy_move_dialog_text (FileConflictDialogData *data)
 }
 
 static void
+set_extract_dialog_text (FileConflictDialogData *data)
+{
+
+        g_autofree gchar *primary_text = NULL;
+        g_autofree gchar *secondary_text = NULL;
+        const gchar *message_extra;
+        g_autofree gchar *message = NULL;
+        g_autofree gchar *dest_name;
+        g_autofree gchar *dest_dir_name;
+
+        dest_name = nautilus_file_get_display_name (data->destination);
+        dest_dir_name = nautilus_file_get_display_name (data->dest_dir);
+
+        if (nautilus_file_is_directory (data->destination)) {
+                primary_text = g_strdup_printf (_("Replace folder “%s”?"), dest_name);
+                message = g_strdup_printf
+                        (_("A folder with the same name as the extract destination already exists in “%s”."),
+                         dest_dir_name);
+                message_extra =
+                        _("Replacing it will remove all files in the folder.");
+        } else {
+                primary_text = g_strdup_printf (_("Replace file “%s”?"), dest_name);
+                message = g_strdup_printf
+                        (_("A file with the same name as the extract destination already exists in “%s”."),
+                         dest_dir_name);
+                message_extra = _("Replacing it will overwrite its content.");
+        }
+
+        secondary_text = g_strdup_printf ("%s\n%s", message, message_extra);
+
+        nautilus_file_conflict_dialog_set_text (data->dialog,
+                                                primary_text,
+                                                secondary_text);
+}
+
+static void
 set_images (FileConflictDialogData *data)
 {
         GdkPixbuf *source_pixbuf;
@@ -299,6 +335,36 @@ copy_move_file_list_ready_cb (GList    *files,
 }
 
 static void
+extract_file_list_ready_cb (GList    *files,
+                            gpointer  user_data)
+{
+        FileConflictDialogData *data = user_data;
+
+        data->handle = NULL;
+
+        gtk_window_set_title (GTK_WINDOW (data->dialog), _("Extract conflict"));
+
+        set_extract_dialog_text (data);
+
+        set_images (data);
+
+        set_file_labels (data);
+
+        set_conflict_name (data);
+
+        nautilus_file_conflict_dialog_disable_skip (data->dialog);
+        nautilus_file_conflict_dialog_disable_apply_to_all (data->dialog);
+
+        nautilus_file_monitor_add (data->source, data, NAUTILUS_FILE_ATTRIBUTES_FOR_ICON);
+        nautilus_file_monitor_add (data->destination, data, NAUTILUS_FILE_ATTRIBUTES_FOR_ICON);
+
+        data->src_handler_id = g_signal_connect (data->source, "changed",
+                          G_CALLBACK (file_icons_changed), data);
+        data->dest_handler_id = g_signal_connect (data->destination, "changed",
+                          G_CALLBACK (file_icons_changed), data);
+}
+
+static void
 build_dialog_appearance (FileConflictDialogData *data)
 {
         GList *files = NULL;
@@ -421,3 +487,22 @@ copy_move_file_conflict_ask_user_action (GtkWindow *parent_window,
 
         return file_conflict_ask_user_action (data);
 }
+
+FileConflictResponse *
+extract_file_conflict_ask_user_action (GtkWindow *parent_window,
+                                       GFile     *src_name,
+                                       GFile     *dest_name,
+                                       GFile     *dest_dir_name)
+{
+        FileConflictDialogData *data;
+
+        data = g_slice_new0 (FileConflictDialogData);
+        data->parent = parent_window;
+        data->src_name = src_name;
+        data->dest_name = dest_name;
+        data->dest_dir_name = dest_dir_name;
+
+        data->files_list_ready_cb = extract_file_list_ready_cb;
+
+        return file_conflict_ask_user_action (data);
+}
diff --git a/src/nautilus-conflict-manager.h b/src/nautilus-conflict-manager.h
index 0477428..edfca1c 100644
--- a/src/nautilus-conflict-manager.h
+++ b/src/nautilus-conflict-manager.h
@@ -14,4 +14,8 @@ FileConflictResponse * copy_move_file_conflict_ask_user_action (GtkWindow *paren
                                                                 GFile     *dest,
                                                                 GFile     *dest_dir);
 
+FileConflictResponse * extract_file_conflict_ask_user_action (GtkWindow *parent_window,
+                                                              GFile     *src_name,
+                                                              GFile     *dest_name,
+                                                              GFile     *dest_dir_name);
 #endif
diff --git a/src/nautilus-file-operations.c b/src/nautilus-file-operations.c
index 4353c23..55c8efd 100644
--- a/src/nautilus-file-operations.c
+++ b/src/nautilus-file-operations.c
@@ -51,6 +51,8 @@
 #include <gtk/gtk.h>
 #include <gio/gio.h>
 #include <glib.h>
+#include <gnome-autoar/autoar.h>
+
 #include "nautilus-conflict-manager.h"
 #include "nautilus-file-changes-queue.h"
 #include "nautilus-file-private.h"
@@ -61,6 +63,7 @@
 #include "nautilus-file-conflict-dialog.h"
 #include "nautilus-file-undo-operations.h"
 #include "nautilus-file-undo-manager.h"
+#include "nautilus-application.h"
 
 /* TODO: TESTING!!! */
 
@@ -170,8 +173,26 @@ typedef struct {
        int last_reported_files_left;
 } TransferInfo;
 
+typedef struct {
+        CommonJob common;
+        GFile *source;
+        GFile *output;
+
+        NautilusExtractCallback done_callback;
+        gpointer done_callback_data;
+
+        AutoarExtract *ar_extract;
+
+        GFile *decided_destination;
+        gboolean success;
+
+        guint64 total_size;
+        guint total_files;
+} ExtractJob;
+
 #define SECONDS_NEEDED_FOR_RELIABLE_TRANSFER_RATE 8
 #define NSEC_PER_MICROSEC 1000
+#define PROGRESS_NOTIFY_INTERVAL 100 * NSEC_PER_MICROSEC
 
 #define MAXIMUM_DISPLAYED_FILE_NAME_LENGTH 50
 
@@ -7068,6 +7089,369 @@ nautilus_file_mark_desktop_file_trusted (GFile *file,
        g_object_unref (task);
 }
 
+static FileConflictResponse *
+handle_extract_conflict (CommonJob *job,
+                         GFile *src,
+                         GFile *dest,
+                         GFile *dest_dir)
+{
+        FileConflictResponse *response;
+
+        g_timer_stop (job->time);
+        nautilus_progress_info_pause (job->progress);
+
+        response = extract_file_conflict_ask_user_action (job->parent_window,
+                                                          src,
+                                                          dest,
+                                                          dest_dir);
+
+        nautilus_progress_info_resume (job->progress);
+        g_timer_continue (job->time);
+
+        return response;
+}
+
+static void
+extract_task_done (GObject      *source_object,
+                   GAsyncResult *res,
+                   gpointer      user_data)
+{
+        ExtractJob *extract_job = user_data;
+
+        if (extract_job->done_callback) {
+                extract_job->done_callback (extract_job->source,
+                                            extract_job->decided_destination,
+                                            extract_job->success,
+                                            extract_job->done_callback_data);
+        }
+
+        g_signal_handlers_disconnect_by_data (extract_job->ar_extract,
+                                              extract_job);
+
+        g_clear_object (&extract_job->source);
+        g_clear_object (&extract_job->output);
+        g_clear_object (&extract_job->ar_extract);
+        g_clear_object (&extract_job->decided_destination);
+
+        finalize_common ((CommonJob *)extract_job);
+}
+
+static void
+extract_scanned_handler (AutoarExtract *ar_extract,
+                         guint          files_count,
+                         gpointer       user_data)
+{
+        ExtractJob *extract_job = user_data;
+
+        extract_job->total_files = autoar_extract_get_files (ar_extract);
+        extract_job->total_size = autoar_extract_get_size (ar_extract);
+}
+
+static gboolean
+extract_destination_removed_callback (GFile    *file,
+                                      GError   *error,
+                                      gpointer  error_func,
+                                      gpointer  callback_data)
+{
+        ExtractJob *extract_job = callback_data;
+
+        if (error != NULL && !job_aborted ((CommonJob *)extract_job)) {
+                run_error ((CommonJob *)extract_job,
+                           f (_("Error while extracting “%B”."),
+                              extract_job->source),
+                           f (_("The existing destination “%B” could not be replaced."),
+                              extract_job->decided_destination),
+                           error->message,
+                           FALSE,
+                           CANCEL,
+                           NULL);
+        }
+
+        return FALSE;
+}
+
+static GFile*
+extract_decide_dest_handler (AutoarExtract *ar_extract,
+                             GFile         *destination,
+                             GList         *files,
+                             gpointer       user_data)
+{
+        ExtractJob *extract_job = user_data;
+        g_autoptr (GFile) destination_directory;
+
+        nautilus_progress_info_set_details (extract_job->common.progress,
+                                            _("Verifying destination"));
+
+        destination_directory = g_file_get_parent (destination);
+
+        extract_job->decided_destination = g_object_ref (destination);
+
+        while (g_file_query_exists (extract_job->decided_destination, NULL)) {
+                FileConflictResponse *response;
+                gboolean extract_destination_removed;
+
+                response = handle_extract_conflict ((CommonJob *)extract_job,
+                                                    extract_job->source,
+                                                    extract_job->decided_destination,
+                                                    destination_directory);
+
+                switch (response->id) {
+                        case GTK_RESPONSE_CANCEL:
+                        case GTK_RESPONSE_DELETE_EVENT:
+                                abort_job ((CommonJob *)extract_job);
+
+                                conflict_response_data_free (response);
+
+                                goto out;
+                        case CONFLICT_RESPONSE_REPLACE:
+                                nautilus_progress_info_set_details (extract_job->common.progress,
+                                                                    _("Replacing existing destination"));
+
+                                extract_destination_removed =
+                                        delete_file_recursively (extract_job->decided_destination,
+                                                                 extract_job->common.cancellable,
+                                                                 extract_destination_removed_callback,
+                                                                 extract_job);
+
+
+                                if (!extract_destination_removed) {
+                                        abort_job ((CommonJob *)extract_job);
+                                }
+
+                                conflict_response_data_free (response);
+
+                                goto out;
+                        case CONFLICT_RESPONSE_RENAME:
+                                g_clear_object (&extract_job->decided_destination);
+
+                                extract_job->decided_destination =
+                                        get_target_file_for_display_name (destination_directory,
+                                                                          response->new_name);
+
+                                conflict_response_data_free (response);
+
+                                break;
+                        default:
+                                g_assert_not_reached ();
+                }
+
+        }
+out:
+        if (job_aborted ((CommonJob *)extract_job) ||
+            g_file_equal (extract_job->decided_destination, destination)) {
+                return NULL;
+        }
+
+        return g_object_ref (extract_job->decided_destination);
+}
+
+static void
+extract_progress_handler (AutoarExtract *ar_extract,
+                          guint64        completed_size,
+                          guint          completed_files,
+                          gpointer       user_data)
+{
+        ExtractJob *extract_job = user_data;
+        CommonJob *common = user_data;
+        char *details;
+        int files_left;
+        double elapsed;
+        double transfer_rate;
+        int remaining_time;
+
+        files_left = extract_job->total_files - completed_files;
+
+        nautilus_progress_info_take_status (common->progress,
+                                            f (_("Extracting “%B”"), extract_job->source));
+
+        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 = (extract_job->total_size - completed_size) / transfer_rate;
+                } else if (completed_files > 0) {
+                        transfer_rate = completed_files / elapsed;
+                        remaining_time = (extract_job->total_files - completed_files) / transfer_rate;
+                }
+        }
+
+        if (elapsed < SECONDS_NEEDED_FOR_RELIABLE_TRANSFER_RATE ||
+            transfer_rate == 0) {
+                if (extract_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, extract_job->total_size);
+                } else {
+                        details = f (_("%'d / %'d"),
+                                     files_left > 0 ? completed_files + 1 : completed_files,
+                                     extract_job->total_files);
+                }
+        } else {
+                if (extract_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, extract_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,
+                                             extract_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, extract_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,
+                                             extract_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,
+                                             extract_job->total_size);
+}
+
+static void
+extract_error_handler (AutoarExtract *ar_extract,
+                       GError        *error,
+                       gpointer       user_data)
+{
+        ExtractJob *extract_job = user_data;
+
+        nautilus_progress_info_take_status (extract_job->common.progress,
+                                            f (_("Error extracting “%B””"),
+                                               extract_job->source));
+
+        run_error ((CommonJob *)extract_job,
+                   f (_("There was an error while extracting “%B”."),
+                      extract_job->source),
+                   g_strdup (error->message),
+                   NULL,
+                   FALSE,
+                   CANCEL,
+                   NULL);
+}
+
+static void
+extract_completed_handler (AutoarExtract *ar_extract,
+                           gpointer       user_data)
+{
+        ExtractJob *extract_job = user_data;
+
+        extract_job->success = TRUE;
+
+        nautilus_progress_info_take_status (extract_job->common.progress,
+                                            f (_("Extracted “%B” to “%B”"),
+                                               extract_job->source,
+                                               extract_job->decided_destination));
+
+        nautilus_progress_info_set_destination (extract_job->common.progress,
+                                                extract_job->decided_destination);
+}
+
+static void
+extract_task_thread_func (GTask        *task,
+                          gpointer      source_object,
+                          gpointer      task_data,
+                          GCancellable *cancellable)
+{
+        ExtractJob *extract_job = task_data;
+        g_autoptr (GSettings) archive_settings;
+        AutoarPref *ar_preferences;
+
+        archive_settings = g_settings_new (AUTOAR_PREF_DEFAULT_GSCHEMA_ID);
+        ar_preferences = autoar_pref_new_with_gsettings (archive_settings);
+
+        extract_job->ar_extract = autoar_extract_new_file (extract_job->source,
+                                                           extract_job->output,
+                                                           ar_preferences);
+
+        autoar_extract_set_notify_interval (extract_job->ar_extract,
+                                            PROGRESS_NOTIFY_INTERVAL);
+
+        g_signal_connect (extract_job->ar_extract, "scanned",
+                          G_CALLBACK (extract_scanned_handler), extract_job);
+        g_signal_connect (extract_job->ar_extract, "error",
+                          G_CALLBACK (extract_error_handler), extract_job);
+        g_signal_connect (extract_job->ar_extract, "decide-dest",
+                          G_CALLBACK (extract_decide_dest_handler), extract_job);
+        g_signal_connect (extract_job->ar_extract, "progress",
+                          G_CALLBACK (extract_progress_handler), extract_job);
+        g_signal_connect (extract_job->ar_extract, "completed",
+                          G_CALLBACK (extract_completed_handler), extract_job);
+
+        g_timer_start (extract_job->common.time);
+
+        nautilus_progress_info_start (extract_job->common.progress);
+
+        nautilus_progress_info_set_details (extract_job->common.progress,
+                                            _("Scanning archive contents"));
+
+        autoar_extract_start (extract_job->ar_extract, extract_job->common.cancellable);
+
+        g_object_unref (ar_preferences);
+}
+
+void
+nautilus_file_operations_extract (GFile                   *source,
+                                  GFile                   *output,
+                                  GtkWindow               *parent_window,
+                                  NautilusExtractCallback  done_callback,
+                                  gpointer                done_callback_data)
+{
+        GTask *task;
+        ExtractJob *job;
+
+        job = op_job_new (ExtractJob, parent_window);
+        job->source = g_object_ref (source);
+        job->output = g_object_ref (output);
+        job->done_callback = done_callback;
+        job->done_callback = done_callback_data;
+        job->success = FALSE;
+
+        inhibit_power_manager ((CommonJob *)job, _("Extracting Files"));
+
+        task = g_task_new (NULL, job->common.cancellable, extract_task_done, job);
+        g_task_set_task_data (task, job, NULL);
+        g_task_run_in_thread (task, extract_task_thread_func);
+        g_object_unref (task);
+}
+
 #if !defined (NAUTILUS_OMIT_SELF_CHECK)
 
 void
diff --git a/src/nautilus-file-operations.h b/src/nautilus-file-operations.h
index 08b11ca..ec9ba53 100644
--- a/src/nautilus-file-operations.h
+++ b/src/nautilus-file-operations.h
@@ -44,6 +44,10 @@ typedef void (* NautilusMountCallback)     (GVolume    *volume,
                                            gboolean    success,
                                            GObject    *callback_data_object);
 typedef void (* NautilusUnmountCallback)   (gpointer    callback_data);
+typedef void (* NautilusExtractCallback)   (GFile    *source,
+                                            GFile    *destination,
+                                            gboolean  success,
+                                            gpointer  callback_data);
 
 /* FIXME: int copy_action should be an enum */
 
@@ -147,6 +151,11 @@ void nautilus_file_mark_desktop_file_trusted (GFile           *file,
                                              gboolean          interactive,
                                              NautilusOpCallback done_callback,
                                              gpointer          done_callback_data);
+void nautilus_file_operations_extract (GFile                   *source,
+                                       GFile                   *output,
+                                       GtkWindow               *parent_window,
+                                       NautilusExtractCallback  done_callback,
+                                       gpointer                 done_callback_data);
 
 
 #endif /* NAUTILUS_FILE_OPERATIONS_H */


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