[nautilus/wip/razvan/automatic-decompression: 19/21] file-operations: implement extract operation



commit bc2f35aba3265338893d9a565011e714178796be
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 |   91 ++++++++
 src/nautilus-conflict-manager.h |    4 +
 src/nautilus-file-operations.c  |  454 +++++++++++++++++++++++++++++++++++++++
 src/nautilus-file-operations.h  |    9 +
 src/nautilus-file-utilities.c   |   42 ++++
 src/nautilus-file-utilities.h   |    2 +
 7 files changed, 604 insertions(+), 0 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 38ead2e..44051cd 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..c2e62f1 100644
--- a/src/nautilus-conflict-manager.c
+++ b/src/nautilus-conflict-manager.c
@@ -118,6 +118,48 @@ 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 (g_file_equal (data->src_name, data->dest_name)) {
+                primary_text = g_strdup_printf (_("Replace source archive “%s”?"), dest_name);
+                message = g_strdup_printf
+                        (_("The archive has the same name as the extract destination."));
+                message_extra =
+                        _("Replacing it will overwrite its contents once the operation is complete.");
+        } else 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 contents.");
+        }
+
+        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 +341,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 +493,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 f06371c..6d348f8 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,11 +173,32 @@ 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 replace_source;
+        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
 
+#define UNIQUE_FILE_HASH_LENGTH 5
+
 #define IS_IO_ERROR(__error, KIND) (((__error)->domain == G_IO_ERROR && (__error)->code == G_IO_ERROR_ ## 
KIND))
 
 #define CANCEL _("_Cancel")
@@ -7088,6 +7112,436 @@ 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) {
+                nautilus_file_changes_queue_file_removed (file);
+                return FALSE;
+        }
+
+        if (!job_aborted ((CommonJob *)extract_job)) {
+                if (g_file_equal (file, extract_job->source)) {
+                        run_error ((CommonJob *)extract_job,
+                                   f (_("Error while removing “%B”."),
+                                      extract_job->source),
+                                   f (_("The original archive “%B” could not be removed."),
+                                      extract_job->decided_destination),
+                                   error->message,
+                                   FALSE,
+                                   CANCEL,
+                                   NULL);
+                } else {
+                        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;
+
+                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"));
+
+                                if (!g_file_equal (extract_job->source, extract_job->decided_destination)) {
+                                        gboolean extract_destination_removed;
+
+                                        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);
+                                        }
+                                } else {
+                                        g_clear_object (&extract_job->decided_destination);
+
+                                        extract_job->decided_destination =
+                                                nautilus_ensure_unique_file_with_hash (extract_job->source,
+                                                                                       
UNIQUE_FILE_HASH_LENGTH);
+                                        extract_job->replace_source = TRUE;
+                                }
+
+                                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;
+
+        if (extract_job->replace_source) {
+                gboolean source_removed;
+
+                nautilus_progress_info_set_details (extract_job->common.progress,
+                                                    _("Replacing original archive"));
+
+                source_removed = delete_file_recursively (extract_job->source,
+                                                          extract_job->common.cancellable,
+                                                          extract_destination_removed_callback,
+                                                          extract_job);
+
+                if (source_removed) {
+                        g_autoptr (GError) error = NULL;
+
+                        if (g_file_move (extract_job->decided_destination,
+                                         extract_job->source,
+                                         G_FILE_COPY_NOFOLLOW_SYMLINKS |
+                                         G_FILE_COPY_NO_FALLBACK_FOR_MOVE,
+                                         NULL, NULL, &error, NULL)) {
+                                nautilus_file_changes_queue_file_moved (extract_job->decided_destination,
+                                                                        extract_job->source);
+
+                                g_clear_object (&extract_job->decided_destination);
+
+                                extract_job->decided_destination = g_object_ref (extract_job->source);
+                        }
+
+                        if (error != NULL) {
+                                run_error ((CommonJob *)extract_job,
+                                           f (_("There was an error while renaming “%B”."),
+                                              extract_job->decided_destination),
+                                           g_strdup (error->message),
+                                           NULL,
+                                           FALSE,
+                                           CANCEL,
+                                           NULL);
+                        }
+                }
+        }
+
+        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 */
diff --git a/src/nautilus-file-utilities.c b/src/nautilus-file-utilities.c
index 14929aa..150cc25 100644
--- a/src/nautilus-file-utilities.c
+++ b/src/nautilus-file-utilities.c
@@ -632,6 +632,48 @@ nautilus_ensure_unique_file_name (const char *directory_uri,
 }
 
 GFile *
+nautilus_ensure_unique_file_with_hash (GFile *original_file,
+                                       int    hash_length)
+{
+        static const char letters[] =
+                "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+        static const int NLETTERS = sizeof (letters) - 1;
+
+        g_autoptr (GFile) parent_directory;
+        g_autofree char *basename;
+        g_autoptr (GRand) random_generator;
+        GFile *new_file = NULL;
+
+        random_generator = g_rand_new();
+        parent_directory = g_file_get_parent (original_file);
+        basename = g_file_get_basename (original_file);
+
+        do {
+                g_autofree char *hash;
+                g_autofree char *new_basename;
+                int i;
+
+                hash = g_new0 (char, hash_length + 1);
+                for (i = 0; i < hash_length; ++i) {
+                        gint32 random_letter;
+
+                        random_letter = g_rand_int_range (random_generator,
+                                                          0, NLETTERS);
+                        hash[i] = letters[random_letter];
+                }
+
+                hash[hash_length] = '\0';
+
+                g_clear_object (&new_file);
+
+                new_basename = g_strdup_printf ("%s (%s)", basename, hash);
+                new_file = g_file_get_child (parent_directory, new_basename);
+        } while (g_file_query_exists (new_file, NULL));
+
+        return new_file;
+}
+
+GFile *
 nautilus_find_existing_uri_in_hierarchy (GFile *location)
 {
        GFileInfo *info;
diff --git a/src/nautilus-file-utilities.h b/src/nautilus-file-utilities.h
index 6d46df5..a55a4fa 100644
--- a/src/nautilus-file-utilities.h
+++ b/src/nautilus-file-utilities.h
@@ -71,6 +71,8 @@ gboolean nautilus_uri_parse                          (const char  *uri,
 char *   nautilus_ensure_unique_file_name            (const char *directory_uri,
                                                      const char *base_name,
                                                      const char *extension);
+GFile * nautilus_ensure_unique_file_with_hash (GFile *original_file,
+                                               int    hash_length);
 
 GFile *  nautilus_find_existing_uri_in_hierarchy     (GFile *location);
 


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