[gnome-autoar/wip/razvan/extract-refactor2: 3/8] AutoarExtract: add signal for name conflicts



commit eaa81ae961bac437a290c3e39b9dde5dc0a868f4
Author: Razvan Chitu <razvan ch95 gmail com>
Date:   Sun May 29 13:13:01 2016 +0300

    AutoarExtract: add signal for name conflicts
    
    Extraction was previously done with overwrite as the default option for existing
    files. A better solution would be to detect conflicts and notify the user
    application, offering the possibility to change the destination of the extracted
    file.
    
    In order to fix this, add a signal for notifying conflicts. This signal gets
    emitted before actually extracting a file.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=768645

 gnome-autoar/autoar-extract.c |  149 +++++++++++++++++++++++++++++++++++++---
 gnome-autoar/autoar-extract.h |    6 ++
 tests/test-extract.c          |   17 +++++
 3 files changed, 160 insertions(+), 12 deletions(-)
---
diff --git a/gnome-autoar/autoar-extract.c b/gnome-autoar/autoar-extract.c
index f841da3..ab3cf31 100644
--- a/gnome-autoar/autoar-extract.c
+++ b/gnome-autoar/autoar-extract.c
@@ -175,6 +175,7 @@ enum
   SCANNED,
   DECIDE_DESTINATION,
   PROGRESS,
+  CONFLICT,
   CANCELLED,
   COMPLETED,
   AR_ERROR,
@@ -842,6 +843,31 @@ autoar_extract_signal_progress (AutoarExtract *arextract)
   }
 }
 
+static AutoarConflictAction
+autoar_extract_signal_conflict (AutoarExtract *arextract,
+                                GFile *file,
+                                GFile **new_file)
+{
+  AutoarConflictAction action = AUTOAR_CONFLICT_OVERWRITE;
+
+  autoar_common_g_signal_emit (arextract, arextract->priv->in_thread,
+                               autoar_extract_signals[CONFLICT], 0,
+                               file,
+                               new_file,
+                               &action);
+
+  if (*new_file) {
+    g_autofree char *previous_path, *new_path;
+
+    previous_path = g_file_get_path (file);
+    new_path = g_file_get_path (*new_file);
+
+    g_debug ("autoar_extract_signal_conflict: %s => %s", previous_path, new_path);
+  }
+
+  return action;
+}
+
 static inline void
 autoar_extract_signal_cancelled (AutoarExtract *arextract)
 {
@@ -992,6 +1018,43 @@ autoar_extract_do_pattern_check (const char *path,
   return TRUE;
 }
 
+static gboolean
+autoar_extract_check_file_conflict (GFile *file,
+                                    mode_t extracted_filetype)
+{
+  GFileType file_type;
+  gboolean conflict = FALSE;
+
+  file_type = g_file_query_file_type (file,
+                                      G_FILE_QUERY_INFO_NONE,
+                                      NULL);
+  /* If there is no file with the given name, there will be no conflict */
+  if (file_type == G_FILE_TYPE_UNKNOWN) {
+    return conflict;
+  }
+
+  switch (extracted_filetype) {
+    case AE_IFDIR:
+      break;
+    case AE_IFREG:
+    case AE_IFLNK:
+#if defined HAVE_MKFIFO || defined HAVE_MKNOD
+    case AE_IFIFO:
+#endif
+#ifdef HAVE_MKNOD
+    case AE_IFSOCK:
+    case AE_IFBLK:
+    case AE_IFCHR:
+#endif
+      conflict = TRUE;
+      break;
+    default:
+      break;
+  }
+
+  return conflict;
+}
+
 static void
 autoar_extract_do_write_entry (AutoarExtract *arextract,
                                struct archive *a,
@@ -1133,6 +1196,7 @@ autoar_extract_do_write_entry (AutoarExtract *arextract,
 
   g_debug ("autoar_extract_do_write_entry: writing");
   r = 0;
+
   switch (filetype = archive_entry_filetype (entry)) {
     default:
     case AE_IFREG:
@@ -1143,16 +1207,18 @@ autoar_extract_do_write_entry (AutoarExtract *arextract,
         gint64 offset;
 
         g_debug ("autoar_extract_do_write_entry: case REG");
+
         ostream = (GOutputStream*)g_file_replace (dest,
                                                   NULL,
                                                   FALSE,
                                                   G_FILE_CREATE_NONE,
                                                   priv->cancellable,
                                                   &(priv->error));
-        if (arextract->priv->error != NULL) {
+        if (priv->error != NULL) {
           g_object_unref (info);
           return;
         }
+
         if (ostream != NULL) {
           /* Archive entry size may be zero if we use raw format. */
           if (archive_entry_size(entry) > 0 || priv->use_raw_format) {
@@ -1194,17 +1260,28 @@ autoar_extract_do_write_entry (AutoarExtract *arextract,
         GFileAndInfo fileandinfo;
 
         g_debug ("autoar_extract_do_write_entry: case DIR");
+
         g_file_make_directory_with_parents (dest, priv->cancellable, &(priv->error));
+
         if (priv->error != NULL) {
-          /* "File exists" is not a fatal error */
-          if (priv->error->code == G_IO_ERROR_EXISTS) {
-            g_error_free (priv->error);
-            priv->error = NULL;
+          /* "File exists" is not a fatal error, as long as the existing file
+           * is a directory
+           */
+          GFileType file_type;
+
+          file_type = g_file_query_file_type (dest,
+                                              G_FILE_QUERY_INFO_NONE,
+                                              NULL);
+
+          if (g_error_matches (priv->error, G_IO_ERROR, G_IO_ERROR_EXISTS) &&
+              file_type == G_FILE_TYPE_DIRECTORY) {
+            g_clear_error (&priv->error);
           } else {
             g_object_unref (info);
             return;
           }
         }
+
         fileandinfo.file = g_object_ref (dest);
         fileandinfo.info = g_object_ref (info);
         g_array_append_val (priv->extracted_dir_list, fileandinfo);
@@ -1475,6 +1552,28 @@ autoar_extract_class_init (AutoarExtractClass *klass)
                   G_TYPE_UINT);
 
 /**
+ * AutoarExtract::conflict:
+ * @arextract: the #AutoarExtract
+ * @file: the file that caused a conflict
+ * @new_file: an address to store the new destination for a conflict file
+ *
+ * Returns: the action to be performed by #AutoarExtract
+ *
+ * This signal is used to report and offer the possibility to solve name
+ * conflicts when extracting files.
+ **/
+  autoar_extract_signals[CONFLICT] =
+    g_signal_new ("conflict",
+                  type,
+                  G_SIGNAL_RUN_LAST,
+                  0, NULL, NULL,
+                  g_cclosure_marshal_generic,
+                  G_TYPE_UINT,
+                  2,
+                  G_TYPE_FILE,
+                  G_TYPE_POINTER);
+
+/**
  * AutoarExtract::cancelled:
  * @arextract: the #AutoarExtract
  *
@@ -2024,8 +2123,8 @@ autoar_extract_step_extract (AutoarExtract *arextract) {
   while ((r = archive_read_next_header (a, &entry)) == ARCHIVE_OK) {
     const char *pathname;
     const char *hardlink;
-    GFile *extracted_filename;
-    GFile *hardlink_filename;
+    g_autoptr (GFile) extracted_filename = NULL;
+    g_autoptr (GFile) hardlink_filename = NULL;
 
     if (g_cancellable_is_cancelled (priv->cancellable)) {
       archive_read_free (a);
@@ -2034,7 +2133,6 @@ autoar_extract_step_extract (AutoarExtract *arextract) {
 
     pathname = archive_entry_pathname (entry);
     hardlink = archive_entry_hardlink (entry);
-    hardlink_filename = NULL;
     if (GPOINTER_TO_UINT (g_hash_table_lookup (priv->bad_filename, pathname)))
       continue;
 
@@ -2046,13 +2144,40 @@ autoar_extract_step_extract (AutoarExtract *arextract) {
         autoar_extract_do_sanitize_pathname (arextract, pathname);
     }
 
+    /* Attempt to solve any name conflict before doing any operations */
+    if (autoar_extract_check_file_conflict (extracted_filename,
+                                            archive_entry_filetype (entry))) {
+      GFile *new_extracted_filename = NULL;
+      AutoarConflictAction action;
+
+      action = autoar_extract_signal_conflict (arextract,
+                                               extracted_filename,
+                                               &new_extracted_filename);
+
+      switch (action) {
+        case AUTOAR_CONFLICT_OVERWRITE:
+          break;
+        case AUTOAR_CONFLICT_CHANGE_DESTINATION:
+          g_assert_nonnull (new_extracted_filename);
+          g_clear_object (&extracted_filename);
+          extracted_filename = new_extracted_filename;
+          break;
+        case AUTOAR_CONFLICT_SKIP:
+          archive_read_data_skip (a);
+          break;
+        default:
+          g_assert_not_reached ();
+          break;
+      }
+
+      if (action == AUTOAR_CONFLICT_SKIP) {
+        continue;
+      }
+    }
+
     autoar_extract_do_write_entry (arextract, a, entry,
                                    extracted_filename, hardlink_filename);
 
-    g_object_unref (extracted_filename);
-    if (hardlink_filename != NULL)
-      g_object_unref (hardlink_filename);
-
     if (priv->error != NULL) {
       archive_read_free (a);
       return;
diff --git a/gnome-autoar/autoar-extract.h b/gnome-autoar/autoar-extract.h
index be97e83..0f7c121 100644
--- a/gnome-autoar/autoar-extract.h
+++ b/gnome-autoar/autoar-extract.h
@@ -110,6 +110,12 @@ void            autoar_extract_set_output_is_dest  (AutoarExtract *arextract,
 void            autoar_extract_set_notify_interval (AutoarExtract *arextract,
                                                     gint64 notify_interval);
 
+typedef enum {
+    AUTOAR_CONFLICT_OVERWRITE = 0,
+    AUTOAR_CONFLICT_CHANGE_DESTINATION,
+    AUTOAR_CONFLICT_SKIP
+} AutoarConflictAction;
+
 G_END_DECLS
 
 #endif /* AUTOAR_EXTRACT_H */
diff --git a/tests/test-extract.c b/tests/test-extract.c
index e40a360..6fd003d 100644
--- a/tests/test-extract.c
+++ b/tests/test-extract.c
@@ -53,6 +53,22 @@ my_handler_progress (AutoarExtract *arextract,
            ((double)(completed_files)) * 100 / autoar_extract_get_files (arextract));
 }
 
+static GFile*
+my_handler_conflict (AutoarExtract *arextract,
+                     GFile *file,
+                     gpointer data)
+{
+  char *path;
+
+  path = g_file_get_path (file);
+
+  g_print ("Conflict on: %s\n", path);
+
+  g_free (path);
+
+  return NULL;
+}
+
 static void
 my_handler_error (AutoarExtract *arextract,
                   GError *error,
@@ -121,6 +137,7 @@ main (int argc,
   g_signal_connect (arextract, "scanned", G_CALLBACK (my_handler_scanned), NULL);
   g_signal_connect (arextract, "decide-destination", G_CALLBACK (my_handler_decide_destination), NULL);
   g_signal_connect (arextract, "progress", G_CALLBACK (my_handler_progress), NULL);
+  g_signal_connect (arextract, "conflict", G_CALLBACK (my_handler_conflict), NULL);
   g_signal_connect (arextract, "error", G_CALLBACK (my_handler_error), NULL);
   g_signal_connect (arextract, "completed", G_CALLBACK (my_handler_completed), NULL);
 


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