Commit 7fb7892f authored by Sebastian Luy's avatar Sebastian Luy
Browse files

batch-rename: Use tmp filename in the case of filename "loops"

Errors can occur when batch renaming files if a
new filename matches an existing filename.

Reordering the files does not solve this problem if the
filenames contain loops (ex. A -> B & B -> A).

Instead of reordering the files, give any problematic
files a temporary name and handle them after the rest of
the files have been renamed.

GNOME/nautilus#1443
parent 6d780723
Pipeline #199189 passed with stage
in 13 minutes and 40 seconds
......@@ -476,8 +476,6 @@ static void
begin_batch_rename (NautilusBatchRenameDialog *dialog,
GList *new_names)
{
batch_rename_sort_lists_for_rename (&dialog->selection, &new_names, NULL, NULL, NULL, FALSE);
/* do the actual rename here */
nautilus_file_batch_rename (dialog->selection, new_names, NULL, NULL);
......
......@@ -141,120 +141,6 @@ batch_rename_replace (gchar *string,
return new_string;
}
void
batch_rename_sort_lists_for_rename (GList **selection,
GList **new_names,
GList **old_names,
GList **new_files,
GList **old_files,
gboolean is_undo_redo)
{
GList *new_names_list;
GList *new_names_list2;
GList *files;
GList *files2;
GList *old_names_list = NULL;
GList *new_files_list = NULL;
GList *old_files_list = NULL;
GList *old_names_list2 = NULL;
GList *new_files_list2 = NULL;
GList *old_files_list2 = NULL;
GString *new_file_name;
GString *new_name;
GString *old_name;
GFile *new_file;
GFile *old_file;
NautilusFile *file;
gboolean order_changed = TRUE;
/* in the following case:
* file1 -> file2
* file2 -> file3
* file2 must be renamed first, so because of that, the list has to be reordered
*/
while (order_changed)
{
order_changed = FALSE;
if (is_undo_redo)
{
old_names_list = *old_names;
new_files_list = *new_files;
old_files_list = *old_files;
}
for (new_names_list = *new_names, files = *selection;
new_names_list != NULL && files != NULL;
new_names_list = new_names_list->next, files = files->next)
{
g_autofree gchar *old_file_name = NULL;
old_file_name = nautilus_file_get_name (NAUTILUS_FILE (files->data));
new_file_name = new_names_list->data;
if (is_undo_redo)
{
old_names_list2 = old_names_list;
new_files_list2 = new_files_list;
old_files_list2 = old_files_list;
}
for (files2 = files, new_names_list2 = new_names_list;
files2 != NULL && new_names_list2 != NULL;
files2 = files2->next, new_names_list2 = new_names_list2->next)
{
g_autofree gchar *file_name = NULL;
file_name = nautilus_file_get_name (NAUTILUS_FILE (files2->data));
new_name = new_names_list2->data;
if (files2 != files && g_strcmp0 (file_name, new_file_name->str) == 0)
{
file = NAUTILUS_FILE (files2->data);
*selection = g_list_remove_link (*selection, files2);
*new_names = g_list_remove_link (*new_names, new_names_list2);
*selection = g_list_prepend (*selection, file);
*new_names = g_list_prepend (*new_names, new_name);
if (is_undo_redo)
{
old_name = old_names_list2->data;
new_file = new_files_list2->data;
old_file = old_files_list2->data;
*old_names = g_list_remove_link (*old_names, old_names_list2);
*new_files = g_list_remove_link (*new_files, new_files_list2);
*old_files = g_list_remove_link (*old_files, old_files_list2);
*old_names = g_list_prepend (*old_names, old_name);
*new_files = g_list_prepend (*new_files, new_file);
*old_files = g_list_prepend (*old_files, old_file);
}
order_changed = TRUE;
break;
}
if (is_undo_redo)
{
old_names_list2 = old_names_list2->next;
new_files_list2 = new_files_list2->next;
old_files_list2 = old_files_list2->next;
}
}
if (is_undo_redo)
{
old_names_list = old_names_list->next;
new_files_list = new_files_list->next;
old_files_list = old_files_list->next;
}
}
}
}
/* This function changes the background color of the replaced part of the name */
GString *
batch_rename_replace_label_text (gchar *label,
......
......@@ -61,10 +61,3 @@ GString* batch_rename_replace_label_text (gchar *label,
const gchar *substr);
gchar* batch_rename_get_tag_text_representation (TagConstants tag_constants);
void batch_rename_sort_lists_for_rename (GList **selection,
GList **new_names,
GList **old_names,
GList **new_files,
GList **old_files,
gboolean is_undo_redo);
\ No newline at end of file
......@@ -1199,13 +1199,6 @@ batch_rename_redo_func (NautilusFileUndoInfo *info,
files = g_list_reverse (files);
batch_rename_sort_lists_for_rename (&files,
&self->new_display_names,
&self->old_display_names,
&self->new_files,
&self->old_files,
TRUE);
nautilus_file_batch_rename (files, self->new_display_names, file_undo_info_operation_callback, self);
}
......@@ -1232,13 +1225,6 @@ batch_rename_undo_func (NautilusFileUndoInfo *info,
files = g_list_reverse (files);
batch_rename_sort_lists_for_rename (&files,
&self->old_display_names,
&self->new_display_names,
&self->old_files,
&self->new_files,
TRUE);
nautilus_file_batch_rename (files, self->old_display_names, file_undo_info_operation_callback, self);
}
......
......@@ -1512,3 +1512,70 @@ nautilus_file_system_is_remote (const char *file_system)
return file_system != NULL && g_strv_contains (remote_types, file_system);
}
/*
* Based on gfileutils.c:get_tmp_file from GLib.
*/
GFile *
rename_file_to_tmp (GFile *file,
gchar *tmpl,
GCancellable *cancellable,
GError **error)
{
char *XXXXXX;
GFile *new_file;
int count;
static const char letters[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
static const int NLETTERS = sizeof (letters) - 1;
glong value;
gint64 now_us;
static int counter = 0;
g_return_val_if_fail (tmpl != NULL, NULL);
/* find the last occurrence of "XXXXXX" */
XXXXXX = g_strrstr (tmpl, "XXXXXX");
if (!XXXXXX || strncmp (XXXXXX, "XXXXXX", 6))
{
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Invalid template"));
return NULL;
}
/* Get some more or less random data. */
now_us = g_get_real_time ();
value = ((now_us % G_USEC_PER_SEC) ^ (now_us / G_USEC_PER_SEC)) + counter++;
for (count = 0; count < 100; value += 7777, ++count)
{
glong v = value;
/* Fill in the random bits. */
XXXXXX[0] = letters[v % NLETTERS];
v /= NLETTERS;
XXXXXX[1] = letters[v % NLETTERS];
v /= NLETTERS;
XXXXXX[2] = letters[v % NLETTERS];
v /= NLETTERS;
XXXXXX[3] = letters[v % NLETTERS];
v /= NLETTERS;
XXXXXX[4] = letters[v % NLETTERS];
v /= NLETTERS;
XXXXXX[5] = letters[v % NLETTERS];
new_file = g_file_set_display_name (file, tmpl, cancellable, error);
if (new_file != NULL)
{
return new_file;
}
if (!g_error_matches (*error, G_IO_ERROR, G_IO_ERROR_EXISTS))
{
break;
}
g_clear_error (error);
}
return NULL;
}
......@@ -143,3 +143,9 @@ NautilusQueryRecursive location_settings_search_get_recursive (void);
NautilusQueryRecursive location_settings_search_get_recursive_for_location (GFile *location);
gboolean nautilus_file_system_is_remote (const char *file_system);
GFile* rename_file_to_tmp (GFile *file,
gchar *tmpl,
GCancellable *cancellable,
GError **error);
......@@ -180,6 +180,9 @@ static const char *nautilus_file_peek_display_name_collation_key (NautilusFile *
static void file_mount_unmounted (GMount *mount,
gpointer data);
static void metadata_hash_free (GHashTable *hash);
static gboolean update_name_internal (NautilusFile *file,
const char *name,
gboolean in_directory);
G_DEFINE_TYPE_WITH_CODE (NautilusFile, nautilus_file, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (NAUTILUS_TYPE_FILE_INFO,
......@@ -2156,17 +2159,21 @@ batch_rename_get_info_callback (GObject *source_object,
}
}
static void
real_batch_rename (GList *files,
GList *new_names,
NautilusFileOperationCallback callback,
gpointer callback_data)
{
GList *l1, *l2, *old_files, *new_files;
GList *l1, *l2, *l3, *old_files, *new_files;
GList *tmp_locations, *tmp_new_names, *tmp_files;
NautilusFileOperation *op;
GFile *location;
GString *new_name;
NautilusFile *file;
NautilusFile *file2;
NautilusFile *existing;
GError *error;
GFile *new_file;
BatchRenameData *data;
......@@ -2174,6 +2181,9 @@ real_batch_rename (GList *files,
error = NULL;
old_files = NULL;
new_files = NULL;
tmp_locations = NULL;
tmp_new_names = NULL;
tmp_files = NULL;
/* Set up a batch renaming operation. */
op = nautilus_file_operation_new (files->data, callback, callback_data);
......@@ -2191,7 +2201,7 @@ real_batch_rename (GList *files,
for (l1 = files, l2 = new_names; l1 != NULL && l2 != NULL; l1 = l1->next, l2 = l2->next)
{
g_autofree gchar *new_file_name = NULL;
gchar *new_file_name = NULL;
file = NAUTILUS_FILE (l1->data);
new_name = l2->data;
......@@ -2211,23 +2221,67 @@ real_batch_rename (GList *files,
g_assert (G_IS_FILE (location));
/* Do the renaming. */
new_file = g_file_set_display_name (location,
new_file_name,
op->cancellable,
&error);
/* Try to find an existing file with the same name. */
existing = NULL;
for (l3 = l1->next; l3 != NULL; l3 = l3->next)
{
g_autofree gchar *name2;
file2 = NAUTILUS_FILE (l3->data);
name2 = nautilus_file_get_name (file2);
if (g_strcmp0 (new_file_name, name2) == 0)
{
existing = file2;
break;
}
}
data = g_new0 (BatchRenameData, 1);
data->op = op;
data->file = file;
/* If the file would cause a conflict, rename it to a temp filename. */
if (existing)
{
GFile *tmp_file;
g_autofree gchar *template;
g_file_query_info_async (new_file,
NAUTILUS_FILE_DEFAULT_ATTRIBUTES,
0,
G_PRIORITY_DEFAULT,
op->cancellable,
batch_rename_get_info_callback,
data);
template = g_strdup ("nautilusXXXXXX");
tmp_file = rename_file_to_tmp (location,
template,
op->cancellable,
&error);
if (error == NULL)
{
nautilus_file_mark_gone (file);
nautilus_file_changed (file);
tmp_locations = g_list_append (tmp_locations, tmp_file);
tmp_new_names = g_list_append (tmp_new_names, new_file_name);
tmp_files = g_list_append (tmp_files, file);
new_file = nautilus_file_get_location (existing);
}
}
else
{
/* Do the renaming. */
new_file = g_file_set_display_name (location,
new_file_name,
op->cancellable,
&error);
if (error == NULL)
{
update_name_internal (file, new_file_name, TRUE);
}
data = g_new0 (BatchRenameData, 1);
data->op = op;
data->file = file;
g_file_query_info_async (new_file,
NAUTILUS_FILE_DEFAULT_ATTRIBUTES,
0,
G_PRIORITY_DEFAULT,
op->cancellable,
batch_rename_get_info_callback,
data);
}
if (error != NULL)
{
......@@ -2244,6 +2298,46 @@ real_batch_rename (GList *files,
}
}
/* Rename all the temp files to their intended names. */
for (l1 = tmp_locations, l2 = tmp_new_names, l3 = tmp_files;
l1 != NULL;
l1 = l1->next, l2 = l2->next, l3 = l3->next)
{
GFile *tmp_location;
gchar *new_file_name;
NautilusFile *tmp_file;
tmp_location = l1->data;
new_file_name = l2->data;
tmp_file = l3->data;
new_file = g_file_set_display_name (tmp_location,
new_file_name,
op->cancellable,
&error);
if (error != NULL)
{
g_warning ("Batch rename for file \"%s\" failed", nautilus_file_get_name (tmp_file));
g_error_free (error);
error = NULL;
}
data = g_new0 (BatchRenameData, 1);
data->op = op;
data->file = tmp_file;
g_file_query_info_async (new_file,
NAUTILUS_FILE_DEFAULT_ATTRIBUTES,
0,
G_PRIORITY_DEFAULT,
op->cancellable,
batch_rename_get_info_callback,
data);
g_object_unref (tmp_location);
g_free (new_file_name);
}
/* Tell the undo manager a batch rename is taking place if at least
* a file has been renamed*/
if (!nautilus_file_undo_manager_is_operating () && op->skipped_files != g_list_length (files))
......@@ -2263,6 +2357,10 @@ real_batch_rename (GList *files,
{
nautilus_file_operation_complete (op, NULL, error);
}
g_list_free (tmp_locations);
g_list_free (tmp_new_names);
g_list_free (tmp_files);
}
void
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment