diff options
author | Philip Langdale <philipl@overt.org> | 2018-03-17 12:35:54 -0700 |
---|---|---|
committer | Philip Langdale <philipl@overt.org> | 2018-03-26 19:34:03 -0700 |
commit | 528ac6f989beb2e89beb9732f7bfe9eefcd8e036 (patch) | |
tree | 8b0b59e99590f63c7b34e1adc5dec507ff68cf6c /daemon | |
parent | c4cf671d1c845488d512b2cabac50fb49bf767d8 (diff) | |
download | gvfs-528ac6f989beb2e89beb9732f7bfe9eefcd8e036.tar.gz |
mtp: Refactor source/dest file/dir validation logic
There is a fairly elaborate set of conditions that need to be
respected when copying or moving files. Right now, we've got this
logic duplicated in four different places (push/pull/move/copy),
so it's worth the effort to consolidate that logic into a single
place.
Along the way, I'm deliberately ignoring the ability of CopyObject
to copy folders. By forcing the client to do file-by-file copies,
we can at least notify when each file completes, to provide slightly
better progress reports. There is a performance impact - I suspect
that a large directory of small files will end up being orders of
magnitude slower, but for a small number of files, it's negligible.
I have not done this for MoveObject, because that is fast unless
the source and target are on different Storages - and devices
with multiple storages are getting less and less common.
https://bugzilla.gnome.org/show_bug.cgi?id=794388
Diffstat (limited to 'daemon')
-rw-r--r-- | daemon/gvfsbackendmtp.c | 294 |
1 files changed, 136 insertions, 158 deletions
diff --git a/daemon/gvfsbackendmtp.c b/daemon/gvfsbackendmtp.c index 6b62e9ed..3537f4e8 100644 --- a/daemon/gvfsbackendmtp.c +++ b/daemon/gvfsbackendmtp.c @@ -1635,6 +1635,59 @@ mtp_progress (uint64_t const sent, uint64_t const total, } +/** + * Validate whether a given combination of source and destination + * are valid for copying/moving. If not valid, set the appropriate + * error on the job. + */ +static gboolean +validate_source_and_dest (gboolean dest_exists, + gboolean dest_is_dir, + gboolean source_is_dir, + gboolean source_can_be_dir, + GFileCopyFlags flags, + GVfsJob *job) +{ + /* Test all the GIO defined failure conditions */ + if (dest_exists) { + if (flags & G_FILE_COPY_OVERWRITE) { + if (!source_is_dir && dest_is_dir) { + g_vfs_job_failed_literal (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY, + _("Target is a directory")); + return FALSE; + } else if (source_is_dir && dest_is_dir) { + g_vfs_job_failed_literal (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_WOULD_MERGE, + _("Can’t merge directories")); + return FALSE; + } else if (source_is_dir && !dest_is_dir) { + g_vfs_job_failed_literal (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_WOULD_RECURSE, + _("Can’t recursively copy directory")); + return FALSE; + } + + /* Source can overwrite dest as both are files */ + return TRUE; + } else { + g_vfs_job_failed_literal (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_EXISTS, + _("Target file already exists")); + return FALSE; + } + } else if (source_is_dir && !source_can_be_dir) { + g_vfs_job_failed_literal (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_WOULD_RECURSE, + _("Can’t recursively copy directory")); + return FALSE; + } + + /* Source is valid and dest doesn't exist */ + return TRUE; +} + + static void do_make_directory (GVfsBackend *backend, GVfsJobMakeDirectory *job, @@ -1759,50 +1812,29 @@ do_pull (GVfsBackend *backend, g_error_free (error); } - /* Test all the GIO defined failure conditions */ - if (dest_exists) { - gboolean dest_is_dir = + gboolean dest_is_dir = dest_exists && g_file_info_get_file_type (local_info) == G_FILE_TYPE_DIRECTORY; - if (flags & G_FILE_COPY_OVERWRITE) { - if (!source_is_dir && dest_is_dir) { - g_vfs_job_failed_literal (G_VFS_JOB (job), - G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY, - _("Target is a directory")); - goto exit; - } else if (source_is_dir && dest_is_dir) { - g_vfs_job_failed_literal (G_VFS_JOB (job), - G_IO_ERROR, G_IO_ERROR_WOULD_MERGE, - _("Can’t merge directories")); - goto exit; - } else if (source_is_dir && !dest_is_dir) { - g_vfs_job_failed_literal (G_VFS_JOB (job), - G_IO_ERROR, G_IO_ERROR_WOULD_RECURSE, - _("Can’t recursively copy directory")); - goto exit; - } - /* Source and Dest are files */ - g_debug ("(I) Removing destination.\n"); - GError *error = NULL; - gboolean ret = g_file_delete (local_file, - G_VFS_JOB (job)->cancellable, - &error); - if (!ret) { - g_vfs_job_failed_from_error (G_VFS_JOB (job), error); - g_error_free (error); - goto exit; - } - } else { - g_vfs_job_failed_literal (G_VFS_JOB (job), - G_IO_ERROR, G_IO_ERROR_EXISTS, - _("Target file already exists")); + gboolean valid_pull = validate_source_and_dest (dest_exists, + dest_is_dir, + source_is_dir, + FALSE, // source_can_be_dir + flags, + G_VFS_JOB (job)); + if (!valid_pull) { + goto exit; + } else if (dest_exists) { + /* Source and Dest are files */ + g_debug ("(I) Removing destination.\n"); + GError *error = NULL; + gboolean ret = g_file_delete (local_file, + G_VFS_JOB (job)->cancellable, + &error); + if (!ret) { + g_vfs_job_failed_from_error (G_VFS_JOB (job), error); + g_error_free (error); goto exit; } - } else if (source_is_dir) { - g_vfs_job_failed_literal (G_VFS_JOB (job), - G_IO_ERROR, G_IO_ERROR_WOULD_RECURSE, - _("Can’t recursively copy directory")); - goto exit; } MtpProgressData mtp_progress_data; @@ -2075,48 +2107,27 @@ do_push (GVfsBackend *backend, gboolean source_is_dir = g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY; - /* Test all the GIO defined failure conditions */ - if (dest_exists) { - if (flags & G_FILE_COPY_OVERWRITE) { - if (!source_is_dir && dest_is_dir) { - g_vfs_job_failed_literal (G_VFS_JOB (job), - G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY, - _("Target is a directory")); - goto exit; - } else if (source_is_dir && dest_is_dir) { - g_vfs_job_failed_literal (G_VFS_JOB (job), - G_IO_ERROR, G_IO_ERROR_WOULD_MERGE, - _("Can’t merge directories")); - goto exit; - } else if (source_is_dir && !dest_is_dir) { - g_vfs_job_failed_literal (G_VFS_JOB (job), - G_IO_ERROR, G_IO_ERROR_WOULD_RECURSE, - _("Can’t recursively copy directory")); - goto exit; - } - /* Source and Dest are files */ - g_debug ("(I) Removing destination.\n"); - int ret = LIBMTP_Delete_Object (device, entry->id); - if (ret != 0) { - fail_job (G_VFS_JOB (job), device); - goto exit; - } - g_hash_table_foreach (G_VFS_BACKEND_MTP (backend)->monitors, - emit_delete_event, - (char *)destination); - remove_cache_entry (G_VFS_BACKEND_MTP (backend), - destination); - } else { - g_vfs_job_failed_literal (G_VFS_JOB (job), - G_IO_ERROR, G_IO_ERROR_EXISTS, - _("Target file already exists")); + gboolean valid_push = validate_source_and_dest (dest_exists, + dest_is_dir, + source_is_dir, + FALSE, // source_can_be_dir + flags, + G_VFS_JOB (job)); + if (!valid_push) { + goto exit; + } else if (dest_exists) { + /* Source and Dest are files */ + g_debug ("(I) Removing destination.\n"); + int ret = LIBMTP_Delete_Object (device, entry->id); + if (ret != 0) { + fail_job (G_VFS_JOB (job), device); goto exit; } - } else if (source_is_dir) { - g_vfs_job_failed_literal (G_VFS_JOB (job), - G_IO_ERROR, G_IO_ERROR_WOULD_RECURSE, - _("Can’t recursively copy directory")); - goto exit; + g_hash_table_foreach (G_VFS_BACKEND_MTP (backend)->monitors, + emit_delete_event, + (char *)destination); + remove_cache_entry (G_VFS_BACKEND_MTP (backend), + destination); } LIBMTP_file_t *mtpfile = LIBMTP_new_file_t (); @@ -3001,48 +3012,30 @@ do_move (GVfsBackend *backend, goto exit; } - /* Test all the GIO defined failure conditions */ - if (dest_exists) { - if (flags & G_FILE_COPY_OVERWRITE) { - if (!source_is_dir && dest_is_dir) { - g_vfs_job_failed_literal (G_VFS_JOB (job), - G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY, - _("Target is a directory")); - goto exit; - } else if (source_is_dir && dest_is_dir) { - g_vfs_job_failed_literal (G_VFS_JOB (job), - G_IO_ERROR, G_IO_ERROR_WOULD_MERGE, - _("Can’t merge directories")); - goto exit; - } else if (source_is_dir && !dest_is_dir) { - g_vfs_job_failed_literal (G_VFS_JOB (job), - G_IO_ERROR, G_IO_ERROR_WOULD_RECURSE, - _("Can’t recursively copy directory")); - goto exit; - } - /* Source and Dest are files */ - g_debug ("(I) Removing destination.\n"); - int ret = LIBMTP_Delete_Object (device, entry->id); - if (ret != 0) { - fail_job (G_VFS_JOB (job), device); - goto exit; - } - g_hash_table_foreach (G_VFS_BACKEND_MTP (backend)->monitors, - emit_delete_event, - (char *)destination); - remove_cache_entry (G_VFS_BACKEND_MTP (backend), - destination); - } else { - g_vfs_job_failed_literal (G_VFS_JOB (job), - G_IO_ERROR, G_IO_ERROR_EXISTS, - _("Target file already exists")); + // Only do directory moves on the same storage, where they are fast. + gboolean source_can_be_dir = (parent->storage == src_entry->storage); + gboolean valid_move = validate_source_and_dest (dest_exists, + dest_is_dir, + source_is_dir, + source_can_be_dir, + flags, + G_VFS_JOB (job)); + if (!valid_move) { + goto exit; + } else if (dest_exists) { + /* Source and Dest are files */ + g_debug ("(I) Removing destination.\n"); + int ret = LIBMTP_Delete_Object (device, entry->id); + if (ret != 0) { + fail_job (G_VFS_JOB (job), device); goto exit; } + g_hash_table_foreach (G_VFS_BACKEND_MTP (backend)->monitors, + emit_delete_event, + (char *)destination); + remove_cache_entry (G_VFS_BACKEND_MTP (backend), + destination); } - /* - * There is no special case here for the source being a directory. The device - * is quite happy to move a directory around along with its contents. - */ /* Unlike most calls, we must pass 0 for the root directory.*/ uint32_t parent_id = (parent->id == -1 ? 0 : parent->id); @@ -3155,48 +3148,33 @@ do_copy (GVfsBackend *backend, goto exit; } - /* Test all the GIO defined failure conditions */ - if (dest_exists) { - if (flags & G_FILE_COPY_OVERWRITE) { - if (!source_is_dir && dest_is_dir) { - g_vfs_job_failed_literal (G_VFS_JOB (job), - G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY, - _("Target is a directory")); - goto exit; - } else if (source_is_dir && dest_is_dir) { - g_vfs_job_failed_literal (G_VFS_JOB (job), - G_IO_ERROR, G_IO_ERROR_WOULD_MERGE, - _("Can’t merge directories")); - goto exit; - } else if (source_is_dir && !dest_is_dir) { - g_vfs_job_failed_literal (G_VFS_JOB (job), - G_IO_ERROR, G_IO_ERROR_WOULD_RECURSE, - _("Can’t recursively copy directory")); - goto exit; - } - /* Source and Dest are files */ - g_debug ("(I) Removing destination.\n"); - int ret = LIBMTP_Delete_Object (device, entry->id); - if (ret != 0) { - fail_job (G_VFS_JOB (job), device); - goto exit; - } - g_hash_table_foreach (G_VFS_BACKEND_MTP (backend)->monitors, - emit_delete_event, - (char *)destination); - remove_cache_entry (G_VFS_BACKEND_MTP (backend), - destination); - } else { - g_vfs_job_failed_literal (G_VFS_JOB (job), - G_IO_ERROR, G_IO_ERROR_EXISTS, - _("Target file already exists")); + /* + * We ignore the ability to copy whole folders because we get poor progress + * updates in this situation. At least with file-by-file copies, we can + * notify as each file completes. + */ + gboolean valid_copy = validate_source_and_dest (dest_exists, + dest_is_dir, + source_is_dir, + FALSE, // source_can_be_dir + flags, + G_VFS_JOB (job)); + if (!valid_copy) { + goto exit; + } else if (dest_exists) { + /* Source and Dest are files */ + g_debug ("(I) Removing destination.\n"); + int ret = LIBMTP_Delete_Object (device, entry->id); + if (ret != 0) { + fail_job (G_VFS_JOB (job), device); goto exit; } + g_hash_table_foreach (G_VFS_BACKEND_MTP (backend)->monitors, + emit_delete_event, + (char *)destination); + remove_cache_entry (G_VFS_BACKEND_MTP (backend), + destination); } - /* - * There is no special case here for the source being a directory. The device - * is quite happy to copy a directory around along with its contents. - */ /* Unlike most calls, we must pass 0 for the root directory.*/ uint32_t parent_id = (parent->id == -1) ? 0 : parent->id; |