diff options
author | Mayank Sharma <mayank8019@gmail.com> | 2019-08-18 03:09:23 +0530 |
---|---|---|
committer | Ondrej Holy <oholy@redhat.com> | 2020-01-31 06:54:01 +0000 |
commit | 19a767805de30f648320f6cbd820bbb794821973 (patch) | |
tree | 69a9a37aa7534b7ba3216e05d5522e574b84f0eb /daemon | |
parent | 14fa8dc5ca1b72018bd46e42f377100bd899f866 (diff) | |
download | gvfs-19a767805de30f648320f6cbd820bbb794821973.tar.gz |
google: Added vfunc for move operation
Earlier, google-drive backend relied on copy/delete fallback to perform
the move operation. This operation would atleast require 2 HTTP requests
to the Drive API and in the cases of trying to move directories, it
won't even succeed (non-empty directory deletion is disabled).
We now add vfunc for proper move operation which can move files/directories
in a single HTTP request. Since, update operation on file/directory
supports writing to the "parents" array, this operation uses
`gdata_service_update_entry ()` to update the parents of file/directory
to be moved.
Fixes: https://gitlab.gnome.org/GNOME/gvfs/issues/8
Diffstat (limited to 'daemon')
-rw-r--r-- | daemon/gvfsbackendgoogle.c | 234 |
1 files changed, 234 insertions, 0 deletions
diff --git a/daemon/gvfsbackendgoogle.c b/daemon/gvfsbackendgoogle.c index bddd8b6d..d4842c59 100644 --- a/daemon/gvfsbackendgoogle.c +++ b/daemon/gvfsbackendgoogle.c @@ -1619,6 +1619,239 @@ g_vfs_backend_google_copy (GVfsBackend *_self, /* ---------------------------------------------------------------------------------------------------- */ +static void +g_vfs_backend_google_move (GVfsBackend *_self, + GVfsJobMove *job, + const gchar *source, + const gchar *destination, + GFileCopyFlags flags, + GFileProgressCallback progress_callback, + gpointer progress_callback_data) +{ + GVfsBackendGoogle *self = G_VFS_BACKEND_GOOGLE (_self); + GCancellable *cancellable = G_VFS_JOB (job)->cancellable; + GDataAuthorizationDomain *auth_domain; + GDataEntry *new_entry = NULL; + GDataEntry *destination_parent; + GDataEntry *source_parent; + GDataEntry *existing_entry; + GDataEntry *source_entry = NULL; + GDataLink *source_parent_link = NULL; + GDataLink *destination_parent_link = NULL; + GError *error = NULL; + const gchar *source_id; + const gchar *volatile_entry_id; + const gchar *source_parent_id; + const gchar *destination_parent_id; + const gchar *title; + gchar *source_parent_link_uri = NULL; + gchar *destination_parent_link_uri = NULL; + gchar *destination_basename = NULL; + gchar *entry_path = NULL; + gchar *parent_path = NULL; + goffset size; + + g_rec_mutex_lock (&self->mutex); + g_debug ("+ move: %s -> %s, %d\n", source, destination, flags); + log_dir_entries (self); + + if (flags & G_FILE_COPY_BACKUP) + { + /* Return G_IO_ERROR_NOT_SUPPORTED instead of + * G_IO_ERROR_CANT_CREATE_BACKUP to proceed with the GIO + * fallback copy. + */ + g_vfs_job_failed_literal (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Operation not supported")); + goto out; + } + + source_entry = resolve (self, source, cancellable, NULL, &error); + if (error != NULL) + { + g_vfs_job_failed_from_error (G_VFS_JOB (job), error); + g_error_free (error); + goto out; + } + + source_parent = resolve_dir (self, source, cancellable, NULL, NULL, &error); + if (error != NULL) + { + g_vfs_job_failed_from_error (G_VFS_JOB (job), error); + g_error_free (error); + goto out; + } + + destination_parent = resolve_dir (self, destination, cancellable, &destination_basename, &parent_path, &error); + if (error != NULL) + { + g_vfs_job_failed_from_error (G_VFS_JOB (job), error); + g_error_free (error); + goto out; + } + + source_id = gdata_entry_get_id (source_entry); + source_parent_id = gdata_entry_get_id (source_parent); + destination_parent_id = gdata_entry_get_id (destination_parent); + title = gdata_entry_get_title (source_entry); + + /* We disallow over-writes to the same file in the same parent directory + * since Drive doesn't have any notion of "over-writing" a file. For the + * cases like renaming a file using `gio move id1 new_title`, we check using + * the below condition, and later update the title of the file conditionally. */ + if (g_strcmp0 (source_parent_id, destination_parent_id) == 0 && + g_strcmp0 (destination_basename, title) == 0) + { + g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_EXISTS, _("Target file already exists")); + goto out; + } + + auth_domain = gdata_documents_service_get_primary_authorization_domain (); + + existing_entry = resolve_child (self, destination_parent, source_id, cancellable, NULL); + if (existing_entry != NULL && g_strcmp0 (destination_parent_id, source_parent_id) != 0) + { + DirEntriesKey *key = dir_entries_key_new (source_id, destination_parent_id); + + /* We need to check if the real ID of source_entry being moved clashes with some, + * other entry's volatile ID or not. This happens when we're trying to move an entry + * but there already exists some tuple (source_id, destination_parent_id) in the + * dir_entries hash table. Here, the already present key corresponds to the + * volatile_entry_id of some previously copied file. This can happen for the + * following operations: + * + * 1. `gio copy ./id1 ./id2/` ("id2" is the real ID of a folder) + * 2. `gio move ./id1 ./id2/` + * + * The first copy operation will create a volatile entry in the + * cache i.e. (id1, id2) -> NewEntry. The subsequent move operation will see that there + * already exists a tuple (id1, id2), and hence this case will happen. If + * volatile_entry_id != NULL, we simply ignore the case and allow the operation + * to happen. */ + if ((volatile_entry_id = get_volatile_entry_id (self, source_entry, key)) == NULL) + { + /* This case happens when a file has multiple parents and we're trying to + * perform a move operation from one parent to another parent (where the + * file already exists). Such operation simply doesn't makes sense + * since moving the same file here and there has no consequence. + * Hence, we return _EXISTS. */ + g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_EXISTS, _("Target file already exists")); + goto out; + } + } + + existing_entry = resolve_child (self, destination_parent, destination_basename, cancellable, NULL); + if (existing_entry != NULL) + { + const gchar *existing_entry_id = gdata_entry_get_id (existing_entry); + + /* If the destination-basename matches that of some other entry, we + * further check if the files have the same ID or not. If the source + * file as well as the destination file have same ID, we fail the + * operation. + * + * Doing the below check allows us to support the following sequence + * of operations - + * 1. `gio copy id1 id2/foo` + * 2. `gio move id1 id2/foo` + * + * Moreover, it disallows operations where foo is the real ID of some + * other file existing in folder with ID `id2`. Although, in-practive + * we can allow this operation, but later when query_info will be called, + * it will resolve to some other entry instead of the expected entry. */ + if (g_strcmp0 (existing_entry_id, source_id) == 0 || + g_strcmp0 (existing_entry_id, destination_basename) == 0) + { + /* The reason why we're returning G_IO_ERROR_FAILED here + * instead of G_IO_ERROR_NOT_SUPPORTED is because _NOT_SUPPORTED + * implies that this operation isn't implemented and it tells GIO + * to use some other fallback for it. + * So, for move operation, it'll use a copy+delete fallback here. + * Since, we don't support backups, so the fallback will end up + * removing the source file completely without making any copy, and + * we lose the file. Hence, we return G_IO_ERROR_FAILED. + */ + g_vfs_job_failed_literal (G_VFS_JOB (job), + G_IO_ERROR, + G_IO_ERROR_FAILED, + _("Operation not supported")); + goto out; + } + } + + /* The internal ref count has to be increased before removing the + * source_entry since remove_entry_full calls g_object_unref() internally */ + g_object_ref (source_entry); + remove_entry (self, source_entry); + + source_parent_link_uri = g_strconcat (URI_PREFIX, source_parent_id, NULL); + source_parent_link = gdata_link_new (source_parent_link_uri, GDATA_LINK_PARENT); + + destination_parent_link_uri = g_strconcat (URI_PREFIX, destination_parent_id, NULL); + destination_parent_link = gdata_link_new (destination_parent_link_uri, GDATA_LINK_PARENT); + + if (!gdata_entry_remove_link (source_entry, source_parent_link)) + { + g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_FAILED, _("Error moving file/folder")); + g_clear_object (&source_parent_link); + g_object_unref (source_entry); + goto out; + } + + gdata_entry_add_link (source_entry, destination_parent_link); + + /* Handle the cases like `gio move id1 folder1/Foobar` and `gio move id1 Foobar` + * where Foobar is the new title of the file to be moved. + * + * The catch here is that we can't support the case `gio move id1 folder1/id1` + * because that conflicts with what nautilus does when doing a cut + paste + * operation. This is a tradeoff between handling the common case vs handling a + * rare case. If you explicity wish to move the file and update the title of + * the file to its ID, do a move + rename operation. */ + if (g_strcmp0 (destination_basename, source_id) != 0) + gdata_entry_set_title (source_entry, destination_basename); + + error = NULL; + new_entry = gdata_service_update_entry (GDATA_SERVICE (self->service), + auth_domain, + source_entry, + cancellable, + &error); + if (error != NULL) + { + sanitize_error (&error); + g_vfs_job_failed_from_error (G_VFS_JOB (job), error); + g_error_free (error); + g_object_unref (source_entry); + goto out; + } + + entry_path = g_build_path ("/", parent_path, gdata_entry_get_id (new_entry), NULL); + g_debug (" new entry path: %s\n", entry_path); + + insert_entry (self, new_entry); + g_hash_table_foreach (self->monitors, emit_create_event, entry_path); + + size = gdata_documents_entry_get_file_size (GDATA_DOCUMENTS_ENTRY (new_entry)); + g_vfs_job_progress_callback (size, size, job); + g_vfs_job_succeeded (G_VFS_JOB (job)); + + g_object_unref (source_entry); + + out: + g_clear_object (&destination_parent_link); + g_clear_object (&new_entry); + g_free (source_parent_link_uri); + g_free (destination_parent_link_uri); + g_free (destination_basename); + g_free (entry_path); + g_free (parent_path); + log_dir_entries (self); + g_debug ("- move\n"); + g_rec_mutex_unlock (&self->mutex); +} + +/* ---------------------------------------------------------------------------------------------------- */ + static gboolean g_vfs_backend_google_create_dir_monitor (GVfsBackend *_self, GVfsJobCreateMonitor *job, @@ -3250,6 +3483,7 @@ g_vfs_backend_google_class_init (GVfsBackendGoogleClass * klass) backend_class->try_close_read = g_vfs_backend_google_close_read; backend_class->close_write = g_vfs_backend_google_close_write; backend_class->copy = g_vfs_backend_google_copy; + backend_class->move = g_vfs_backend_google_move; backend_class->create = g_vfs_backend_google_create; backend_class->try_create_dir_monitor = g_vfs_backend_google_create_dir_monitor; backend_class->delete = g_vfs_backend_google_delete; |