From 7344cd3d7a8718010a6f818a0da9df966ec3c0aa Mon Sep 17 00:00:00 2001
From: "mshrimp@sogang.ac.kr" <mshrimp@sogang.ac.kr>
Date: Sun, 18 Jul 2021 08:03:00 +0900
Subject: [PATCH] Use *.partial~ as an intermediate file for copy

MR !130

To resume copy after an interrupted attempt, a user would retry the same process again. To save time, one would skip all duplicate files. But unfortunately, it is almost guaranteed to have a single fragmented file that has a same name as an original file. This causes an incomplete copy, and normally the only way to solve this is to give up this method and remove all the files that are copied. This MR provides a way to do this properly by copying individual files into an intermediate file that is named *.partial~. Only after the file is properly transfered, its name is changed to its original name. By this method, fragmented file is guaranteed to have a separate name, so a user can resume copy without a problem.

Changes:

* thunar_g_file_copy()
* ThunarUsePartialMode
* transform_enum_value_to_index()
---
 configure.ac.in                        |  12 +-
 docs/reference/thunarx/thunarx.actions |   0
 thunar/thunar-enum-types.c             |  23 +++
 thunar/thunar-enum-types.h             |  19 +++
 thunar/thunar-gio-extensions.c         | 109 ++++++++++++++
 thunar/thunar-gio-extensions.h         |  10 +-
 thunar/thunar-preferences-dialog.c     | 187 +++++++++++++------------
 thunar/thunar-preferences.c            |  14 ++
 thunar/thunar-transfer-job.c           |  46 +++++-
 9 files changed, 318 insertions(+), 102 deletions(-)
 delete mode 100644 docs/reference/thunarx/thunarx.actions

diff --git a/configure.ac.in b/configure.ac.in
index c2ee37976..b9ea8e371 100644
--- a/configure.ac.in
+++ b/configure.ac.in
@@ -146,10 +146,10 @@ dnl ***********************************
 dnl *** Check for required packages ***
 dnl ***********************************
 XDT_CHECK_PACKAGE([EXO], [exo-2], [4.17.0])
-XDT_CHECK_PACKAGE([GLIB], [glib-2.0], [2.50.0])
-XDT_CHECK_PACKAGE([GIO], [gio-2.0], [2.50.0])
-XDT_CHECK_PACKAGE([GTHREAD], [gthread-2.0], [2.50.0])
-XDT_CHECK_PACKAGE([GMODULE], [gmodule-2.0], [2.50.0])
+XDT_CHECK_PACKAGE([GLIB], [glib-2.0], [2.56.0])
+XDT_CHECK_PACKAGE([GIO], [gio-2.0], [2.56.0])
+XDT_CHECK_PACKAGE([GTHREAD], [gthread-2.0], [2.56.0])
+XDT_CHECK_PACKAGE([GMODULE], [gmodule-2.0], [2.56.0])
 XDT_CHECK_PACKAGE([GTK], [gtk+-3.0], [3.22.0])
 XDT_CHECK_PACKAGE([GDK_PIXBUF], [gdk-pixbuf-2.0], [2.14.0])
 XDT_CHECK_PACKAGE([LIBXFCE4UTIL], [libxfce4util-1.0], [4.17.0])
@@ -158,8 +158,8 @@ XDT_CHECK_PACKAGE([LIBXFCE4KBD_PRIVATE], [libxfce4kbd-private-3], [4.12.0])
 XDT_CHECK_PACKAGE([XFCONF], [libxfconf-0], [4.12.0])
 XDT_CHECK_PACKAGE([PANGO], [pango], [1.38.0])
 
-AC_DEFINE(GLIB_VERSION_MIN_REQUIRED, GLIB_VERSION_2_50, [Ignore post 2.50 deprecations])
-AC_DEFINE(GLIB_VERSION_MAX_ALLOWED, GLIB_VERSION_2_50, [Prevent post 2.50 APIs])
+AC_DEFINE(GLIB_VERSION_MIN_REQUIRED, GLIB_VERSION_2_56, [Ignore post 2.56 deprecations])
+AC_DEFINE(GLIB_VERSION_MAX_ALLOWED, GLIB_VERSION_2_56, [Prevent post 2.56 APIs])
 
 dnl ******************************
 dnl *** GObject Instrospection ***
diff --git a/docs/reference/thunarx/thunarx.actions b/docs/reference/thunarx/thunarx.actions
deleted file mode 100644
index e69de29bb..000000000
diff --git a/thunar/thunar-enum-types.c b/thunar/thunar-enum-types.c
index f79f19377..6b6e81537 100644
--- a/thunar/thunar-enum-types.c
+++ b/thunar/thunar-enum-types.c
@@ -524,3 +524,26 @@ thunar_file_mode_get_type (void)
     }
   return type;
 }
+
+
+
+GType
+thunar_use_partial_get_type (void)
+{
+  static GType type = G_TYPE_INVALID;
+
+  if (G_UNLIKELY (type == G_TYPE_INVALID))
+    {
+      static const GEnumValue values[] =
+      {
+        { THUNAR_USE_PARTIAL_MODE_DISABLED,    "THUNAR_USE_PARTIAL_MODE_NEVER",    N_("Never"),},
+        { THUNAR_USE_PARTIAL_MODE_REMOTE_ONLY, "THUNAR_USE_PARTIAL_MODE_REMOTE",   N_("Only for remote location"),},
+        { THUNAR_USE_PARTIAL_MODE_ALWAYS,      "THUNAR_USE_PARTIAL_MODE_ALWAYS",   N_("Always"),},
+        { 0,                                NULL,                               NULL,},
+      };
+
+      type = g_enum_register_static (I_("ThunarUsePartialMode"), values);
+    }
+
+  return type;
+}
diff --git a/thunar/thunar-enum-types.h b/thunar/thunar-enum-types.h
index b0908a4ce..394053207 100644
--- a/thunar/thunar-enum-types.h
+++ b/thunar/thunar-enum-types.h
@@ -328,6 +328,25 @@ GType thunar_file_mode_get_type (void) G_GNUC_CONST;
 
 
 
+#define THUNAR_TYPE_USE_PARTIAL_MODE (thunar_use_partial_get_type ())
+
+/**
+ * ThunarUsePartialMode:
+ * @THUNAR_USE_PARTIAL_MODE_DISABLED    : Disable *.partial~
+ * @THUNAR_USE_PARTIAL_MODE_REMOTE_ONLY : Only when src/dst is remote
+ * @THUNAR_USE_PARTIAL_MODE_ALWAYS      : Always copy to *.partial~
+ **/
+typedef enum
+{
+  THUNAR_USE_PARTIAL_MODE_DISABLED,
+  THUNAR_USE_PARTIAL_MODE_REMOTE_ONLY,
+  THUNAR_USE_PARTIAL_MODE_ALWAYS,
+} ThunarUsePartialMode;
+
+GType thunar_use_partial_get_type (void) G_GNUC_CONST;
+
+
+
 /**
  * ThunarNewTabBehavior:
  * @THUNAR_NEW_TAB_BEHAVIOR_FOLLOW_PREFERENCE   : switching to the new tab or not is controlled by a preference.
diff --git a/thunar/thunar-gio-extensions.c b/thunar/thunar-gio-extensions.c
index 46837dc2b..a5cd89b1c 100644
--- a/thunar/thunar-gio-extensions.c
+++ b/thunar/thunar-gio-extensions.c
@@ -645,6 +645,115 @@ thunar_g_file_list_get_type (void)
 
 
 
+/**
+ * thunar_g_file_copy:
+ * @source                 : input #GFile
+ * @destination            : destination #GFile
+ * @flags                  : set of #GFileCopyFlags
+ * @use_partial            : option to use *.partial~
+ * @cancellable            : (nullable): optional GCancellable object
+ * @progress_callback      : (nullable) (scope call): function to callback with progress information
+ * @progress_callback_data : (clousure): user data to pass to @progress_callback
+ * @error                  : (nullable): #GError to set on error
+ *
+ * Calls g_file_copy() if @use_partial is not enabled.
+ * If enabled, copies files to *.partial~ first and then
+ * renames *.partial~ into its original name.
+ *
+ * Return value: %TRUE on success, %FALSE otherwise.
+ **/
+gboolean
+thunar_g_file_copy (GFile                *source,
+                    GFile                *destination,
+                    GFileCopyFlags        flags,
+                    gboolean              use_partial,
+                    GCancellable         *cancellable,
+                    GFileProgressCallback progress_callback,
+                    gpointer              progress_callback_data,
+                    GError              **error)
+{
+  gboolean            success;
+  GFileQueryInfoFlags query_flags;
+  GFileInfo          *info = NULL;
+  GFile              *parent;
+  GFile              *partial;
+  gchar              *partial_name;
+  gchar              *base_name;
+
+  _thunar_return_val_if_fail (g_file_has_parent (destination, NULL), FALSE);
+
+  if (use_partial)
+    {
+      query_flags = (flags & G_FILE_COPY_NOFOLLOW_SYMLINKS) ? G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS : G_FILE_QUERY_INFO_NONE;
+      info = g_file_query_info (source,
+                                G_FILE_ATTRIBUTE_STANDARD_TYPE,
+                                query_flags,
+                                cancellable,
+                                NULL);
+    }
+
+  /* directory does not need .partial */
+  if (info == NULL)
+    {
+      use_partial = FALSE;
+    }
+  else
+    {
+      use_partial = g_file_info_get_file_type (info) == G_FILE_TYPE_REGULAR;
+      g_clear_object (&info);
+    }
+
+  if (!use_partial)
+    {
+      return g_file_copy (source, destination, flags, cancellable, progress_callback, progress_callback_data, error);
+    }
+
+  /* check destination */
+  if (g_file_query_exists (destination, NULL))
+    {
+      /* Try to mimic g_file_copy() error */
+      if (error != NULL)
+        *error = g_error_new (G_IO_ERROR, G_IO_ERROR_EXISTS,
+                              "Error opening file \"%s\": File exists", g_file_peek_path (destination));
+      return FALSE;
+    }
+
+  /* generate partial file name */
+  base_name    = g_file_get_basename (destination);
+  if (base_name == NULL)
+    {
+      base_name = g_strdup ("UNNAMED");
+    }
+
+  /* limit filename length */
+  partial_name = g_strdup_printf ("%.100s.partial~", base_name);
+  parent       = g_file_get_parent (destination);
+
+  /* parent can't be NULL since destination must be a file */
+  partial      = g_file_get_child (parent, partial_name);
+  g_clear_object (&parent);
+  g_free (partial_name);
+
+  /* check if partial file exists */
+  if (g_file_query_exists (partial, NULL))
+    g_file_delete (partial, NULL, error);
+
+  /* copy file to .partial */
+  success = g_file_copy (source, partial, flags, cancellable, progress_callback, progress_callback_data, error);
+
+  /* rename .partial if done without problem */
+  if (success)
+    {
+      success = (g_file_set_display_name (partial, base_name, NULL, error) != NULL);
+    }
+
+  g_clear_object (&partial);
+  g_free (base_name);
+  return success;
+}
+
+
+
 /**
  * thunar_g_file_list_new_from_string:
  * @string : a string representation of an URI list.
diff --git a/thunar/thunar-gio-extensions.h b/thunar/thunar-gio-extensions.h
index 5a84fe4c2..aba839c70 100644
--- a/thunar/thunar-gio-extensions.h
+++ b/thunar/thunar-gio-extensions.h
@@ -67,6 +67,15 @@ gboolean     thunar_g_file_get_free_space           (GFile                *file,
 gchar       *thunar_g_file_get_free_space_string    (GFile                *file,
                                                      gboolean              file_size_binary);
 
+gboolean     thunar_g_file_copy                     (GFile                *source,
+                                                     GFile                *destination,
+                                                     GFileCopyFlags        flags,
+                                                     gboolean              use_partial,
+                                                     GCancellable         *cancellable,
+                                                     GFileProgressCallback progress_callback,
+                                                     gpointer              progress_callback_data,
+                                                     GError              **error);
+
 /**
  * THUNAR_TYPE_G_FILE_LIST:
  *
@@ -97,7 +106,6 @@ gboolean     thunar_g_app_info_should_show             (GAppInfo          *info)
 
 gboolean     thunar_g_vfs_metadata_is_supported        (void);
 
-
 G_END_DECLS
 
 #endif /* !__THUNAR_GIO_EXTENSIONS_H__ */
diff --git a/thunar/thunar-preferences-dialog.c b/thunar/thunar-preferences-dialog.c
index 8d3a35e1f..89873e934 100644
--- a/thunar/thunar-preferences-dialog.c
+++ b/thunar/thunar-preferences-dialog.c
@@ -158,15 +158,16 @@ transform_view_index_to_string (GBinding     *binding,
 
 
 static gboolean
-transform_thumbnail_mode_to_index (GBinding     *binding,
-                                   const GValue *src_value,
-                                   GValue       *dst_value,
-                                   gpointer      user_data)
+transform_enum_value_to_index (GBinding     *binding,
+                         const GValue *src_value,
+                         GValue       *dst_value,
+                         gpointer      user_data)
 {
   GEnumClass *klass;
+  GType     (*type_func)() = user_data;
   guint       n;
 
-  klass = g_type_class_ref (THUNAR_TYPE_THUMBNAIL_MODE);
+  klass = g_type_class_ref (type_func ());
   for (n = 0; n < klass->n_values; ++n)
     if (klass->values[n].value == g_value_get_enum (src_value))
       g_value_set_int (dst_value, n);
@@ -178,51 +179,15 @@ transform_thumbnail_mode_to_index (GBinding     *binding,
 
 
 static gboolean
-transform_thumbnail_index_to_mode (GBinding     *binding,
-                                   const GValue *src_value,
-                                   GValue       *dst_value,
-                                   gpointer      user_data)
+transform_index_to_enum_value (GBinding     *binding,
+                         const GValue *src_value,
+                         GValue       *dst_value,
+                         gpointer      user_data)
 {
   GEnumClass *klass;
+  GType     (*type_func)() = user_data;
 
-  klass = g_type_class_ref (THUNAR_TYPE_THUMBNAIL_MODE);
-  g_value_set_enum (dst_value, klass->values[g_value_get_int (src_value)].value);
-  g_type_class_unref (klass);
-
-  return TRUE;
-}
-
-
-
-static gboolean
-transform_parallel_copy_mode_to_index (GBinding     *binding,
-                                       const GValue *src_value,
-                                       GValue       *dst_value,
-                                       gpointer      user_data)
-{
-  GEnumClass *klass;
-  guint       n;
-
-  klass = g_type_class_ref (THUNAR_TYPE_PARALLEL_COPY_MODE);
-  for (n = 0; n < klass->n_values; ++n)
-    if (klass->values[n].value == g_value_get_enum (src_value))
-      g_value_set_int (dst_value, n);
-  g_type_class_unref (klass);
-
-  return TRUE;
-}
-
-
-
-static gboolean
-transform_parallel_copy_index_to_mode (GBinding     *binding,
-                                       const GValue *src_value,
-                                       GValue       *dst_value,
-                                       gpointer      user_data)
-{
-  GEnumClass *klass;
-
-  klass = g_type_class_ref (THUNAR_TYPE_PARALLEL_COPY_MODE);
+  klass = g_type_class_ref (type_func ());
   g_value_set_enum (dst_value, klass->values[g_value_get_int (src_value)].value);
   g_type_class_unref (klass);
 
@@ -281,6 +246,7 @@ thunar_preferences_dialog_init (ThunarPreferencesDialog *dialog)
   GtkWidget      *ibox;
   GtkWidget      *vbox;
   GtkWidget      *infobar;
+  GEnumClass     *type;
   gchar          *path;
   gchar          *date;
 
@@ -377,9 +343,9 @@ thunar_preferences_dialog_init (ThunarPreferencesDialog *dialog)
                                G_OBJECT (combo),
                                "active",
                                G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE,
-                               transform_thumbnail_mode_to_index,
-                               transform_thumbnail_index_to_mode,
-                               NULL, NULL);
+                               transform_enum_value_to_index,
+                               transform_index_to_enum_value,
+                               (gpointer) thunar_thumbnail_mode_get_type, NULL);
   gtk_widget_set_hexpand (combo, TRUE);
   gtk_grid_attach (GTK_GRID (grid), combo, 1, 1, 1, 1);
   thunar_gtk_label_set_a11y_relation (GTK_LABEL (label), combo);
@@ -849,6 +815,50 @@ thunar_preferences_dialog_init (ThunarPreferencesDialog *dialog)
   gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, TRUE, 0);
   gtk_widget_show (frame);
 
+  if (thunar_g_vfs_is_uri_scheme_supported ("trash"))
+    {
+      frame = g_object_new (GTK_TYPE_FRAME, "border-width", 0, "shadow-type", GTK_SHADOW_NONE, NULL);
+      gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, TRUE, 0);
+      gtk_widget_show (frame);
+
+      label = gtk_label_new (_("Context Menu"));
+      gtk_label_set_attributes (GTK_LABEL (label), thunar_pango_attr_list_bold ());
+      gtk_frame_set_label_widget (GTK_FRAME (frame), label);
+      gtk_widget_show (label);
+
+      grid = gtk_grid_new ();
+      gtk_grid_set_column_spacing (GTK_GRID (grid), 12);
+      gtk_grid_set_row_spacing (GTK_GRID (grid), 2);
+      gtk_container_set_border_width (GTK_CONTAINER (grid), 12);
+      gtk_container_add (GTK_CONTAINER (frame), grid);
+      gtk_widget_show (grid);
+
+      button = gtk_check_button_new_with_mnemonic (_("Show action to permanently delete files and folders"));
+      g_object_bind_property (G_OBJECT (dialog->preferences),
+                              "misc-show-delete-action",
+                              G_OBJECT (button),
+                              "active",
+                              G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+      gtk_widget_set_tooltip_text (button, _("Select this option to show the 'Delete' action in the context menu"));
+      gtk_widget_set_hexpand (button, TRUE);
+      gtk_grid_attach (GTK_GRID (grid), button, 0, 0, 1, 1);
+      gtk_widget_show (button);
+    }
+
+  /*
+     Advanced
+   */
+
+  label = gtk_label_new (_("Advanced"));
+  vbox = g_object_new (GTK_TYPE_BOX, "orientation", GTK_ORIENTATION_VERTICAL, "border-width", 12, "spacing", 18, NULL);
+  gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox, label);
+  gtk_widget_show (label);
+  gtk_widget_show (vbox);
+
+  frame = g_object_new (GTK_TYPE_FRAME, "border-width", 0, "shadow-type", GTK_SHADOW_NONE, NULL);
+  gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, TRUE, 0);
+  gtk_widget_show (frame);
+
   label = gtk_label_new (_("File transfer"));
   gtk_label_set_attributes (GTK_LABEL (label), thunar_pango_attr_list_bold ());
   gtk_frame_set_label_widget (GTK_FRAME (frame), label);
@@ -856,8 +866,9 @@ thunar_preferences_dialog_init (ThunarPreferencesDialog *dialog)
 
   grid = gtk_grid_new ();
   gtk_grid_set_column_spacing (GTK_GRID (grid), 12);
-  gtk_grid_set_row_spacing (GTK_GRID (grid), 2);
-  gtk_container_set_border_width (GTK_CONTAINER (grid), 12);
+  gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
+  gtk_widget_set_margin_top (GTK_WIDGET (grid), 6);
+  gtk_widget_set_margin_start (GTK_WIDGET (grid), 12);
   gtk_container_add (GTK_CONTAINER (frame), grid);
   gtk_widget_show (grid);
 
@@ -883,53 +894,45 @@ thunar_preferences_dialog_init (ThunarPreferencesDialog *dialog)
                                G_OBJECT (combo),
                                "active",
                                G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE,
-                               transform_parallel_copy_mode_to_index,
-                               transform_parallel_copy_index_to_mode,
-                               NULL, NULL);
+                               transform_enum_value_to_index,
+                               transform_index_to_enum_value,
+                               (gpointer) thunar_parallel_copy_mode_get_type, NULL);
   gtk_widget_set_hexpand (combo, TRUE);
   gtk_grid_attach (GTK_GRID (grid), combo, 1, 0, 1, 1);
   thunar_gtk_label_set_a11y_relation (GTK_LABEL (label), combo);
   gtk_label_set_mnemonic_widget (GTK_LABEL (label), combo);
   gtk_widget_show (combo);
 
-  if (thunar_g_vfs_is_uri_scheme_supported ("trash"))
-    {
-      frame = g_object_new (GTK_TYPE_FRAME, "border-width", 0, "shadow-type", GTK_SHADOW_NONE, NULL);
-      gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, TRUE, 0);
-      gtk_widget_show (frame);
-
-      label = gtk_label_new (_("Context Menu"));
-      gtk_label_set_attributes (GTK_LABEL (label), thunar_pango_attr_list_bold ());
-      gtk_frame_set_label_widget (GTK_FRAME (frame), label);
-      gtk_widget_show (label);
-
-      grid = gtk_grid_new ();
-      gtk_grid_set_column_spacing (GTK_GRID (grid), 12);
-      gtk_grid_set_row_spacing (GTK_GRID (grid), 2);
-      gtk_container_set_border_width (GTK_CONTAINER (grid), 12);
-      gtk_container_add (GTK_CONTAINER (frame), grid);
-      gtk_widget_show (grid);
-
-      button = gtk_check_button_new_with_mnemonic (_("Show action to permanently delete files and folders"));
-      g_object_bind_property (G_OBJECT (dialog->preferences),
-                              "misc-show-delete-action",
-                              G_OBJECT (button),
-                              "active",
-                              G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
-      gtk_widget_set_tooltip_text (button, _("Select this option to show the 'Delete' action in the context menu"));
-      gtk_widget_set_hexpand (button, TRUE);
-      gtk_grid_attach (GTK_GRID (grid), button, 0, 0, 1, 1);
-      gtk_widget_show (button);
-    }
-
-  /*
-     Advanced
-   */
-  label = gtk_label_new (_("Advanced"));
-  vbox = g_object_new (GTK_TYPE_BOX, "orientation", GTK_ORIENTATION_VERTICAL, "border-width", 12, "spacing", 18, NULL);
-  gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox, label);
+  label = gtk_label_new_with_mnemonic (_("Use intermediate file on copy"));
+  gtk_label_set_xalign (GTK_LABEL (label), 0.0f);
+  gtk_grid_attach (GTK_GRID (grid), label, 0, 1, 1, 1);
   gtk_widget_show (label);
-  gtk_widget_show (vbox);
+  gtk_widget_set_tooltip_text (label, _("Use intermediate file '*.partial~' to copy files. "
+                                        "This will prevent fragmented files."
+                                        "The new file will only be shown after the copy was successfully finished."));
+
+  combo = gtk_combo_box_text_new ();
+  type = g_type_class_ref (THUNAR_TYPE_USE_PARTIAL_MODE);
+  gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo),
+                                  g_enum_get_value (type, THUNAR_USE_PARTIAL_MODE_DISABLED)->value_nick);
+  gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo),
+                                  g_enum_get_value (type, THUNAR_USE_PARTIAL_MODE_REMOTE_ONLY)->value_nick);
+  gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo),
+                                  g_enum_get_value (type, THUNAR_USE_PARTIAL_MODE_ALWAYS)->value_nick);
+  g_type_class_unref (type);
+  g_object_bind_property_full (G_OBJECT (dialog->preferences),
+                               "misc-transfer-use-partial",
+                               G_OBJECT (combo),
+                               "active",
+                               G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE,
+                               transform_enum_value_to_index,
+                               transform_index_to_enum_value,
+                               (gpointer) thunar_use_partial_get_type, NULL);
+  gtk_widget_set_hexpand (combo, TRUE);
+  gtk_grid_attach (GTK_GRID (grid), combo, 1, 1, 1, 1);
+  thunar_gtk_label_set_a11y_relation (GTK_LABEL (label), combo);
+  gtk_label_set_mnemonic_widget (GTK_LABEL (label), combo);
+  gtk_widget_show (combo);
 
   frame = g_object_new (GTK_TYPE_FRAME, "border-width", 0, "shadow-type", GTK_SHADOW_NONE, NULL);
   gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, TRUE, 0);
diff --git a/thunar/thunar-preferences.c b/thunar/thunar-preferences.c
index 6e3b24372..071d402a2 100644
--- a/thunar/thunar-preferences.c
+++ b/thunar/thunar-preferences.c
@@ -106,6 +106,7 @@ enum
   PROP_MISC_CONFIRM_CLOSE_MULTIPLE_TABS,
   PROP_MISC_PARALLEL_COPY_MODE,
   PROP_MISC_WINDOW_ICON,
+  PROP_MISC_TRANSFER_USE_PARTIAL,
   PROP_SHORTCUTS_ICON_EMBLEMS,
   PROP_SHORTCUTS_ICON_SIZE,
   PROP_TREE_ICON_EMBLEMS,
@@ -885,6 +886,19 @@ thunar_preferences_class_init (ThunarPreferencesClass *klass)
                             TRUE,
                             EXO_PARAM_READWRITE);
 
+  /**
+   * ThunarPreferences:misc-transfer-use-partial:
+   *
+   * Whether to use intermediate file(*.partial~) to copy.
+   **/
+  preferences_props[PROP_MISC_TRANSFER_USE_PARTIAL] =
+    g_param_spec_enum ("misc-transfer-use-partial",
+                       "MiscTransferUsePartial",
+                       NULL,
+                       THUNAR_TYPE_USE_PARTIAL_MODE,
+                       THUNAR_USE_PARTIAL_MODE_DISABLED,
+                       EXO_PARAM_READWRITE);
+
   /**
    * ThunarPreferences:misc-confirm-close-multiple-tabs:
    *
diff --git a/thunar/thunar-transfer-job.c b/thunar/thunar-transfer-job.c
index 5baa48bfd..9bdbc99f3 100644
--- a/thunar/thunar-transfer-job.c
+++ b/thunar/thunar-transfer-job.c
@@ -48,6 +48,7 @@ enum
   PROP_0,
   PROP_FILE_SIZE_BINARY,
   PROP_PARALLEL_COPY_MODE,
+  PROP_TRANSFER_USE_PARTIAL,
 };
 
 
@@ -102,6 +103,7 @@ struct _ThunarTransferJob
   ThunarPreferences      *preferences;
   gboolean                file_size_binary;
   ThunarParallelCopyMode  parallel_copy_mode;
+  ThunarUsePartialMode    transfer_use_partial;
 };
 
 struct _ThunarTransferNode
@@ -160,6 +162,20 @@ thunar_transfer_job_class_init (ThunarTransferJobClass *klass)
                                                       THUNAR_TYPE_PARALLEL_COPY_MODE,
                                                       THUNAR_PARALLEL_COPY_MODE_ONLY_LOCAL,
                                                       EXO_PARAM_READWRITE));
+
+  /**
+   * ThunarPropertiesdialog:transfer_use_partial:
+   *
+   * Whether to use intermediate file to copy
+   **/
+  g_object_class_install_property (gobject_class,
+                                   PROP_TRANSFER_USE_PARTIAL,
+                                   g_param_spec_enum ("transfer-use-partial",
+                                                      "TransferUsePartial",
+                                                      NULL,
+                                                      THUNAR_TYPE_USE_PARTIAL_MODE,
+                                                      THUNAR_USE_PARTIAL_MODE_DISABLED,
+                                                      EXO_PARAM_READWRITE));
 }
 
 
@@ -174,6 +190,9 @@ thunar_transfer_job_init (ThunarTransferJob *job)
   g_object_bind_property (job->preferences, "misc-parallel-copy-mode",
                           job,              "parallel-copy-mode",
                           G_BINDING_SYNC_CREATE);
+  g_object_bind_property (job->preferences, "misc-transfer-use-partial",
+                          job,              "transfer-use-partial",
+                          G_BINDING_SYNC_CREATE);
 
   job->type = 0;
   job->source_node_list = NULL;
@@ -230,6 +249,9 @@ thunar_transfer_job_get_property (GObject     *object,
     case PROP_PARALLEL_COPY_MODE:
       g_value_set_enum (value, job->parallel_copy_mode);
       break;
+    case PROP_TRANSFER_USE_PARTIAL:
+      g_value_set_enum (value, job->transfer_use_partial);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -254,6 +276,9 @@ thunar_transfer_job_set_property (GObject      *object,
     case PROP_PARALLEL_COPY_MODE:
       job->parallel_copy_mode = g_value_get_enum (value);
       break;
+    case PROP_TRANSFER_USE_PARTIAL:
+      job->transfer_use_partial = g_value_get_enum (value);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -415,6 +440,7 @@ ttj_copy_file (ThunarTransferJob *job,
   GFileType  source_type;
   GFileType  target_type;
   gboolean   target_exists;
+  gboolean   use_partial;
   GError    *err = NULL;
 
   _thunar_return_val_if_fail (THUNAR_IS_TRANSFER_JOB (job), FALSE);
@@ -454,10 +480,21 @@ ttj_copy_file (ThunarTransferJob *job,
         }
     }
 
+  switch (job->transfer_use_partial)
+    {
+    case THUNAR_USE_PARTIAL_MODE_REMOTE_ONLY:
+      use_partial = !g_file_is_native (source_file) || !g_file_is_native (target_file);
+      break;
+    case THUNAR_USE_PARTIAL_MODE_ALWAYS:
+      use_partial = TRUE;
+      break;
+    default:
+      use_partial = FALSE;
+    }
   /* try to copy the file */
-  g_file_copy (source_file, target_file, copy_flags,
-               exo_job_get_cancellable (EXO_JOB (job)),
-               thunar_transfer_job_progress, job, &err);
+  thunar_g_file_copy (source_file, target_file, copy_flags, use_partial,
+                      exo_job_get_cancellable (EXO_JOB (job)),
+                      thunar_transfer_job_progress, job, &err);
 
   /**
    * MR !127 notes:
@@ -482,6 +519,7 @@ ttj_copy_file (ThunarTransferJob *job,
   /* check if there were errors */
   if (G_UNLIKELY (err != NULL && err->domain == G_IO_ERROR))
     {
+      g_info ("%s", err->message);
       if (err->code == G_IO_ERROR_WOULD_MERGE
           || (err->code == G_IO_ERROR_EXISTS
               && source_type == G_FILE_TYPE_DIRECTORY
@@ -1119,6 +1157,8 @@ thunar_transfer_job_move_file (ExoJob                *job,
   exo_job_info_message (job, _("Trying to move \"%s\""),
                         g_file_info_get_display_name (info));
 
+  g_info ("%s", g_file_info_get_display_name (info));
+
   move_successful = g_file_move (node->source_file,
                                  tp->data,
                                  move_flags,
-- 
GitLab