summaryrefslogtreecommitdiff
path: root/thunar
diff options
context:
space:
mode:
authorCyrille Pontvieux <cyrille@enialis.net>2020-05-02 14:24:34 +0200
committerAlexander Schwinn <alexxcons@xfce.org>2020-05-02 14:24:34 +0200
commit500026ac163dfb5f93ce187b3a5b29cca4582a34 (patch)
tree1310cecbeeff5eaeb3bfe8fc414c875c7d84b428 /thunar
parent544dad41c6b6eee640fed9fece3f22a856342372 (diff)
downloadthunar-500026ac163dfb5f93ce187b3a5b29cca4582a34.tar.gz
Option to rename a file when existing copy conflicts (Bug #16686)
Diffstat (limited to 'thunar')
-rw-r--r--thunar/thunar-dialogs.c18
-rw-r--r--thunar/thunar-enum-types.c2
-rw-r--r--thunar/thunar-enum-types.h6
-rw-r--r--thunar/thunar-io-jobs-util.c88
-rw-r--r--thunar/thunar-io-jobs-util.h6
-rw-r--r--thunar/thunar-job.c8
-rw-r--r--thunar/thunar-transfer-job.c91
7 files changed, 208 insertions, 11 deletions
diff --git a/thunar/thunar-dialogs.c b/thunar/thunar-dialogs.c
index b87ac1e0..2cb1ce50 100644
--- a/thunar/thunar-dialogs.c
+++ b/thunar/thunar-dialogs.c
@@ -474,6 +474,14 @@ thunar_dialogs_show_job_ask (GtkWindow *parent,
mnemonic = _("S_kip All");
break;
+ case THUNAR_JOB_RESPONSE_RENAME:
+ mnemonic = _("Re_name");
+ break;
+
+ case THUNAR_JOB_RESPONSE_RENAME_ALL:
+ mnemonic = _("Rena_me All");
+ break;
+
case THUNAR_JOB_RESPONSE_NO:
mnemonic = _("_No");
break;
@@ -579,6 +587,8 @@ thunar_dialogs_show_job_ask_replace (GtkWindow *parent,
GtkWidget *skip_button;
GtkWidget *replaceall_button;
GtkWidget *replace_button;
+ GtkWidget *renameall_button;
+ GtkWidget *rename_button;
GdkPixbuf *icon;
gchar *date_custom_style;
gchar *date_string;
@@ -626,24 +636,32 @@ thunar_dialogs_show_job_ask_replace (GtkWindow *parent,
skip_button = gtk_button_new_with_mnemonic (_("_Skip"));
replaceall_button = gtk_button_new_with_mnemonic (_("Replace _All"));
replace_button = gtk_button_new_with_mnemonic (_("_Replace"));
+ renameall_button = gtk_button_new_with_mnemonic (_("Rena_me All"));
+ rename_button = gtk_button_new_with_mnemonic (_("Re_name"));
g_signal_connect (cancel_button, "clicked", G_CALLBACK (thunar_dialogs_show_job_ask_replace_callback), dialog);
g_signal_connect (skipall_button, "clicked", G_CALLBACK (thunar_dialogs_show_job_ask_replace_callback), dialog);
g_signal_connect (skip_button, "clicked", G_CALLBACK (thunar_dialogs_show_job_ask_replace_callback), dialog);
g_signal_connect (replaceall_button, "clicked", G_CALLBACK (thunar_dialogs_show_job_ask_replace_callback), dialog);
g_signal_connect (replace_button, "clicked", G_CALLBACK (thunar_dialogs_show_job_ask_replace_callback), dialog);
+ g_signal_connect (renameall_button, "clicked", G_CALLBACK (thunar_dialogs_show_job_ask_replace_callback), dialog);
+ g_signal_connect (rename_button, "clicked", G_CALLBACK (thunar_dialogs_show_job_ask_replace_callback), dialog);
g_object_set_data (G_OBJECT (cancel_button), "response-id", GINT_TO_POINTER (GTK_RESPONSE_CANCEL));
g_object_set_data (G_OBJECT (skipall_button), "response-id", GINT_TO_POINTER (THUNAR_JOB_RESPONSE_SKIP_ALL));
g_object_set_data (G_OBJECT (skip_button), "response-id", GINT_TO_POINTER (THUNAR_JOB_RESPONSE_SKIP));
g_object_set_data (G_OBJECT (replaceall_button), "response-id", GINT_TO_POINTER (THUNAR_JOB_RESPONSE_REPLACE_ALL));
g_object_set_data (G_OBJECT (replace_button), "response-id", GINT_TO_POINTER (THUNAR_JOB_RESPONSE_REPLACE));
+ g_object_set_data (G_OBJECT (renameall_button), "response-id", GINT_TO_POINTER (THUNAR_JOB_RESPONSE_RENAME_ALL));
+ g_object_set_data (G_OBJECT (rename_button), "response-id", GINT_TO_POINTER (THUNAR_JOB_RESPONSE_RENAME));
gtk_container_add (GTK_CONTAINER (button_box), cancel_button);
gtk_container_add (GTK_CONTAINER (button_box), skipall_button);
gtk_container_add (GTK_CONTAINER (button_box), skip_button);
gtk_container_add (GTK_CONTAINER (button_box), replaceall_button);
gtk_container_add (GTK_CONTAINER (button_box), replace_button);
+ gtk_container_add (GTK_CONTAINER (button_box), renameall_button);
+ gtk_container_add (GTK_CONTAINER (button_box), rename_button);
gtk_container_add (GTK_CONTAINER (content_area), button_box);
gtk_widget_set_halign (button_box, GTK_ALIGN_CENTER);
gtk_box_set_spacing (GTK_BOX (button_box), 5);
diff --git a/thunar/thunar-enum-types.c b/thunar/thunar-enum-types.c
index aaa877e4..d3d94278 100644
--- a/thunar/thunar-enum-types.c
+++ b/thunar/thunar-enum-types.c
@@ -372,6 +372,8 @@ thunar_job_response_get_type (void)
{ THUNAR_JOB_RESPONSE_REPLACE_ALL, "THUNAR_JOB_RESPONSE_REPLACE_ALL", "replace-all" },
{ THUNAR_JOB_RESPONSE_SKIP, "THUNAR_JOB_RESPONSE_SKIP", "skip" },
{ THUNAR_JOB_RESPONSE_SKIP_ALL, "THUNAR_JOB_RESPONSE_SKIP_ALL", "skip-all" },
+ { THUNAR_JOB_RESPONSE_RENAME, "THUNAR_JOB_RESPONSE_RENAME", "rename" },
+ { THUNAR_JOB_RESPONSE_RENAME_ALL, "THUNAR_JOB_RESPONSE_RENAME_ALL", "rename-all " },
{ 0, NULL, NULL }
};
diff --git a/thunar/thunar-enum-types.h b/thunar/thunar-enum-types.h
index bbd4a231..df25c955 100644
--- a/thunar/thunar-enum-types.h
+++ b/thunar/thunar-enum-types.h
@@ -240,6 +240,8 @@ ThunarThumbnailSize thunar_zoom_level_to_thumbnail_size (ThunarZoomLevel zoom_
* @THUNAR_JOB_RESPONSE_REPLACE_ALL :
* @THUNAR_JOB_RESPONSE_SKIP :
* @THUNAR_JOB_RESPONSE_SKIP_ALL :
+ * @THUNAR_JOB_RESPONSE_RENAME :
+ * @THUNAR_JOB_RESPONSE_RENAME_ALL :
*
* Possible responses for the ThunarJob::ask signal.
**/
@@ -256,8 +258,10 @@ typedef enum /*< flags >*/
THUNAR_JOB_RESPONSE_REPLACE_ALL = 1 << 8,
THUNAR_JOB_RESPONSE_SKIP = 1 << 9,
THUNAR_JOB_RESPONSE_SKIP_ALL = 1 << 10,
+ THUNAR_JOB_RESPONSE_RENAME = 1 << 11,
+ THUNAR_JOB_RESPONSE_RENAME_ALL = 1 << 12,
} ThunarJobResponse;
-#define THUNAR_JOB_RESPONSE_MAX_INT 10
+#define THUNAR_JOB_RESPONSE_MAX_INT 12
GType thunar_job_response_get_type (void) G_GNUC_CONST;
diff --git a/thunar/thunar-io-jobs-util.c b/thunar/thunar-io-jobs-util.c
index 0032985e..e875f60d 100644
--- a/thunar/thunar-io-jobs-util.c
+++ b/thunar/thunar-io-jobs-util.c
@@ -141,3 +141,91 @@ thunar_io_jobs_util_next_duplicate_file (ThunarJob *job,
+/**
+ * thunar_io_jobs_util_next_renamed_file:
+ * @job : a #ThunarJob.
+ * @src_file : the source #GFile.
+ * @tgt_file : the target #GFile.
+ * @n : the @n<!---->th copy/move to create the #GFile for.
+ * @error : return location for errors or %NULL.
+ *
+ * Determines the #GFile for the next copy/move to @tgt_file.
+ *
+ * File named X will be renamed to "X (copy 1)".
+ *
+ * If there are errors or the job was cancelled, the return value
+ * will be %NULL and @error will be set.
+ *
+ * Return value: the #GFile referencing the @n<!---->th copy/move
+ * of @tgt_file or %NULL on error/cancellation.
+ **/
+GFile *
+thunar_io_jobs_util_next_renamed_file (ThunarJob *job,
+ GFile *src_file,
+ GFile *tgt_file,
+ guint n,
+ GError **error)
+{
+ GFileInfo *info;
+ GError *err = NULL;
+ GFile *renamed_file = NULL;
+ GFile *parent_file = NULL;
+ const gchar *old_display_name;
+ gchar *display_name;
+ gchar *file_basename;
+ gchar *dot = NULL;
+
+ _thunar_return_val_if_fail (THUNAR_IS_JOB (job), NULL);
+ _thunar_return_val_if_fail (G_IS_FILE (src_file), NULL);
+ _thunar_return_val_if_fail (G_IS_FILE (tgt_file), NULL);
+ _thunar_return_val_if_fail (0 < n, NULL);
+ _thunar_return_val_if_fail (error == NULL || *error == NULL, NULL);
+ _thunar_return_val_if_fail (!thunar_g_file_is_root (src_file), NULL);
+ _thunar_return_val_if_fail (!thunar_g_file_is_root (tgt_file), NULL);
+
+ /* abort on cancellation */
+ if (exo_job_set_error_if_cancelled (EXO_JOB (job), error))
+ return NULL;
+
+ /* query the source file info / display name */
+ info = g_file_query_info (src_file, G_FILE_ATTRIBUTE_STANDARD_TYPE ","
+ G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ exo_job_get_cancellable (EXO_JOB (job)), &err);
+
+ /* abort on error */
+ if (info == NULL)
+ {
+ g_propagate_error (error, err);
+ return NULL;
+ }
+
+ old_display_name = g_file_info_get_display_name (info);
+ /* get file extension if file is not a directory */
+ if (g_file_info_get_file_type (info) != G_FILE_TYPE_DIRECTORY)
+ dot = thunar_util_str_get_extension (old_display_name);
+
+ if (dot != NULL)
+ {
+ file_basename = g_strndup (old_display_name, dot - old_display_name);
+ /* I18N: put " (copy #)" between basename and extension */
+ display_name = g_strdup_printf (_("%s (copy %u)%s"), file_basename, n, dot);
+ g_free(file_basename);
+ }
+ else
+ {
+ /* I18N: put " (copy #)" after filename (for files without extension) */
+ display_name = g_strdup_printf (_("%s (copy %u)"), old_display_name, n);
+ }
+
+ /* create the GFile for the copy/move */
+ parent_file = g_file_get_parent (tgt_file);
+ renamed_file = g_file_get_child (parent_file, display_name);
+ g_object_unref (parent_file);
+
+ /* free resources */
+ g_object_unref (info);
+ g_free (display_name);
+
+ return renamed_file;
+}
diff --git a/thunar/thunar-io-jobs-util.h b/thunar/thunar-io-jobs-util.h
index 2ff28960..b79687e9 100644
--- a/thunar/thunar-io-jobs-util.h
+++ b/thunar/thunar-io-jobs-util.h
@@ -31,6 +31,12 @@ GFile *thunar_io_jobs_util_next_duplicate_file (ThunarJob *job,
guint n,
GError **error) G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
+GFile *thunar_io_jobs_util_next_renamed_file (ThunarJob *job,
+ GFile *src_file,
+ GFile *tgt_file,
+ guint n,
+ GError **error) G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
+
G_END_DECLS
#endif /* !__THUNAR_IO_JOBS_UITL_H__ */
diff --git a/thunar/thunar-job.c b/thunar/thunar-job.c
index 7f9f8de1..33f6c43e 100644
--- a/thunar/thunar-job.c
+++ b/thunar/thunar-job.c
@@ -259,6 +259,8 @@ thunar_job_real_ask_replace (ThunarJob *job,
g_signal_emit (job, job_signals[ASK], 0, message,
THUNAR_JOB_RESPONSE_REPLACE
| THUNAR_JOB_RESPONSE_REPLACE_ALL
+ | THUNAR_JOB_RESPONSE_RENAME
+ | THUNAR_JOB_RESPONSE_RENAME_ALL
| THUNAR_JOB_RESPONSE_SKIP
| THUNAR_JOB_RESPONSE_SKIP_ALL
| THUNAR_JOB_RESPONSE_CANCEL,
@@ -484,6 +486,10 @@ thunar_job_ask_replace (ThunarJob *job,
if (G_UNLIKELY (job->priv->earlier_ask_overwrite_response == THUNAR_JOB_RESPONSE_REPLACE_ALL))
return THUNAR_JOB_RESPONSE_REPLACE;
+ /* check if the user said "Rename All" earlier */
+ if (G_UNLIKELY (job->priv->earlier_ask_overwrite_response == THUNAR_JOB_RESPONSE_RENAME_ALL))
+ return THUNAR_JOB_RESPONSE_RENAME;
+
/* check if the user said "Overwrite None" earlier */
if (G_UNLIKELY (job->priv->earlier_ask_overwrite_response == THUNAR_JOB_RESPONSE_SKIP_ALL))
return THUNAR_JOB_RESPONSE_SKIP;
@@ -513,6 +519,8 @@ thunar_job_ask_replace (ThunarJob *job,
/* translate the response */
if (response == THUNAR_JOB_RESPONSE_REPLACE_ALL)
response = THUNAR_JOB_RESPONSE_REPLACE;
+ else if (response == THUNAR_JOB_RESPONSE_RENAME_ALL)
+ response = THUNAR_JOB_RESPONSE_RENAME;
else if (response == THUNAR_JOB_RESPONSE_SKIP_ALL)
response = THUNAR_JOB_RESPONSE_SKIP;
else if (response == THUNAR_JOB_RESPONSE_CANCEL)
diff --git a/thunar/thunar-transfer-job.c b/thunar/thunar-transfer-job.c
index bb299ab8..40b4d720 100644
--- a/thunar/thunar-transfer-job.c
+++ b/thunar/thunar-transfer-job.c
@@ -104,6 +104,7 @@ struct _ThunarTransferNode
ThunarTransferNode *children;
GFile *source_file;
gboolean replace_confirmed;
+ gboolean rename_confirmed;
};
@@ -336,6 +337,7 @@ thunar_transfer_job_collect_node (ThunarTransferJob *job,
child_node = g_slice_new0 (ThunarTransferNode);
child_node->source_file = g_object_ref (lp->data);
child_node->replace_confirmed = node->replace_confirmed;
+ child_node->rename_confirmed = FALSE;
/* hook the child node into the child list */
child_node->next = node->children;
@@ -493,14 +495,15 @@ ttj_copy_file (ThunarTransferJob *job,
* @source_file : the source #GFile to copy.
* @target_file : the destination #GFile to copy to.
* @replace_confirmed : whether the user has already confirmed that this file should replace an existing one
+ * @rename_confirmed : whether the user has already confirmed that this file should be renamed to a new unique file name
* @error : return location for errors or %NULL.
*
* Tries to copy @source_file to @target_file. The real destination is the
* return value and may differ from @target_file (e.g. if you try to copy
* the file "/foo/bar" into the same directory you'll end up with something
* like "/foo/copy of bar" instead of "/foo/bar"). If an existing file would
- * be replaced, the user is asked to confirm this unless @replace_confirmed
- * is TRUE.
+ * be replaced, the user is asked to confirm replace or rename it unless
+ * @replace_confirmed or @rename_confirmed is TRUE.
*
* The return value is guaranteed to be %NULL on errors and @error will
* always be set in those cases. If the file is skipped, the return value
@@ -517,12 +520,15 @@ thunar_transfer_job_copy_file (ThunarTransferJob *job,
GFile *source_file,
GFile *target_file,
gboolean replace_confirmed,
+ gboolean rename_confirmed,
GError **error)
{
ThunarJobResponse response;
+ GFile *dest_file = target_file;
GFileCopyFlags copy_flags = G_FILE_COPY_NOFOLLOW_SYMLINKS;
GError *err = NULL;
gint n;
+ gint n_rename = 0;
_thunar_return_val_if_fail (THUNAR_IS_TRANSFER_JOB (job), NULL);
_thunar_return_val_if_fail (G_IS_FILE (source_file), NULL);
@@ -537,13 +543,13 @@ thunar_transfer_job_copy_file (ThunarTransferJob *job,
while (err == NULL)
{
thunar_transfer_job_check_pause (job);
- if (G_LIKELY (!g_file_equal (source_file, target_file)))
+ if (G_LIKELY (!g_file_equal (source_file, dest_file)))
{
- /* try to copy the file from source_file to the target_file */
- if (ttj_copy_file (job, source_file, target_file, copy_flags, TRUE, &err))
+ /* try to copy the file from source_file to the dest_file */
+ if (ttj_copy_file (job, source_file, dest_file, copy_flags, TRUE, &err))
{
/* return the real target file */
- return g_object_ref (target_file);
+ return g_object_ref (dest_file);
}
}
else
@@ -581,12 +587,14 @@ thunar_transfer_job_copy_file (ThunarTransferJob *job,
/* reset the error */
g_clear_error (&err);
- /* if necessary, ask the user whether to replace the target file */
+ /* if necessary, ask the user whether to replace or rename the target file */
if (replace_confirmed)
response = THUNAR_JOB_RESPONSE_REPLACE;
+ else if (rename_confirmed)
+ response = THUNAR_JOB_RESPONSE_RENAME;
else
response = thunar_job_ask_replace (THUNAR_JOB (job), source_file,
- target_file, &err);
+ dest_file, &err);
if (err != NULL)
break;
@@ -597,6 +605,24 @@ thunar_transfer_job_copy_file (ThunarTransferJob *job,
copy_flags |= G_FILE_COPY_OVERWRITE;
continue;
}
+ else if (response == THUNAR_JOB_RESPONSE_RENAME)
+ {
+ GFile *renamed_file;
+ renamed_file = thunar_io_jobs_util_next_renamed_file (THUNAR_JOB (job),
+ source_file,
+ dest_file,
+ ++n_rename, &err);
+ if (renamed_file != NULL)
+ {
+ if (err != NULL)
+ g_object_unref (renamed_file);
+ else
+ {
+ dest_file = renamed_file;
+ rename_confirmed = TRUE;
+ }
+ }
+ }
/* tell the caller we skipped the file if the user
* doesn't want to retry/overwrite */
@@ -681,6 +707,7 @@ retry_copy:
real_target_file = thunar_transfer_job_copy_file (job, node->source_file,
target_file,
node->replace_confirmed,
+ node->rename_confirmed,
&err);
if (G_LIKELY (real_target_file != NULL))
{
@@ -963,6 +990,44 @@ thunar_transfer_job_prepare_untrash_file (ExoJob *job,
static gboolean
+thunar_transfer_job_move_file_with_rename (ExoJob *job,
+ ThunarTransferNode *node,
+ GList *tp,
+ GFileCopyFlags flags,
+ GError **error)
+{
+ gboolean move_rename_successful = FALSE;
+ gint n_rename = 1;
+ GFile *renamed_file;
+
+ node->rename_confirmed = TRUE;
+ while (TRUE)
+ {
+ g_clear_error (error);
+ renamed_file = thunar_io_jobs_util_next_renamed_file (THUNAR_JOB (job),
+ node->source_file,
+ tp->data,
+ n_rename++, error);
+ if (renamed_file == NULL)
+ return FALSE;
+
+ /* Try to move it again to the new renamed file.
+ * Directly try to move, because it is racy to first check for file existence
+ * and then execute something based on the outcome of that. */
+ move_rename_successful = g_file_move (node->source_file,
+ renamed_file,
+ flags,
+ exo_job_get_cancellable (job),
+ NULL, NULL, error);
+ if (!move_rename_successful && !exo_job_is_cancelled (job) && ((*error)->code == G_IO_ERROR_EXISTS))
+ continue;
+
+ return move_rename_successful;
+ }
+}
+
+
+static gboolean
thunar_transfer_job_move_file (ExoJob *job,
GFileInfo *info,
GList *sp,
@@ -986,7 +1051,7 @@ thunar_transfer_job_move_file (ExoJob *job,
move_flags,
exo_job_get_cancellable (job),
NULL, NULL, error);
- /* if the file already exists, ask the user if they want to overwrite or skip it */
+ /* if the file already exists, ask the user if they want to overwrite, rename or skip it */
if (!move_successful && (*error)->code == G_IO_ERROR_EXISTS)
{
g_clear_error (error);
@@ -1002,6 +1067,11 @@ thunar_transfer_job_move_file (ExoJob *job,
exo_job_get_cancellable (job),
NULL, NULL, error);
}
+ /* if the user chose to rename then try to do so */
+ else if (response == THUNAR_JOB_RESPONSE_RENAME)
+ {
+ move_successful = thunar_transfer_job_move_file_with_rename (job, node, tp, move_flags, error);
+ }
/* if the user chose to cancel then abort all remaining file moves */
else if (response == THUNAR_JOB_RESPONSE_CANCEL)
{
@@ -1012,7 +1082,7 @@ thunar_transfer_job_move_file (ExoJob *job,
transfer_job->target_file_list= NULL;
return FALSE;
}
- /* if the user chose not to replace the file, so that response == THUNAR_JOB_RESPONSE_SKIP,
+ /* if the user chose not to replace nor rename the file, so that response == THUNAR_JOB_RESPONSE_SKIP,
* then *error will be NULL but move_successful will be FALSE, so that the source and target
* files will be released and the matching list items will be dropped below
*/
@@ -1249,6 +1319,7 @@ thunar_transfer_job_new (GList *source_node_list,
node = g_slice_new0 (ThunarTransferNode);
node->source_file = g_object_ref (sp->data);
node->replace_confirmed = FALSE;
+ node->rename_confirmed = FALSE;
job->source_node_list = g_list_append (job->source_node_list, node);
/* append target file */