diff options
author | Cyrille Pontvieux <cyrille@enialis.net> | 2020-05-02 14:24:34 +0200 |
---|---|---|
committer | Alexander Schwinn <alexxcons@xfce.org> | 2020-05-02 14:24:34 +0200 |
commit | 500026ac163dfb5f93ce187b3a5b29cca4582a34 (patch) | |
tree | 1310cecbeeff5eaeb3bfe8fc414c875c7d84b428 /thunar | |
parent | 544dad41c6b6eee640fed9fece3f22a856342372 (diff) | |
download | thunar-500026ac163dfb5f93ce187b3a5b29cca4582a34.tar.gz |
Option to rename a file when existing copy conflicts (Bug #16686)
Diffstat (limited to 'thunar')
-rw-r--r-- | thunar/thunar-dialogs.c | 18 | ||||
-rw-r--r-- | thunar/thunar-enum-types.c | 2 | ||||
-rw-r--r-- | thunar/thunar-enum-types.h | 6 | ||||
-rw-r--r-- | thunar/thunar-io-jobs-util.c | 88 | ||||
-rw-r--r-- | thunar/thunar-io-jobs-util.h | 6 | ||||
-rw-r--r-- | thunar/thunar-job.c | 8 | ||||
-rw-r--r-- | thunar/thunar-transfer-job.c | 91 |
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 */ |