diff options
author | Philip Langdale <philipl@overt.org> | 2018-03-15 21:59:53 -0700 |
---|---|---|
committer | Philip Langdale <philipl@overt.org> | 2018-03-26 19:34:03 -0700 |
commit | c4cf671d1c845488d512b2cabac50fb49bf767d8 (patch) | |
tree | 04b2ec07c9e55998697b2f3066a4d560955a294b | |
parent | 06c28a632d738b0141b619ea1c500d2563a20298 (diff) | |
download | gvfs-c4cf671d1c845488d512b2cabac50fb49bf767d8.tar.gz |
mtp: Implement support for move and copy operations
Android P has introduced support for these operations, and they
are useful - making device->device moves and copies much faster
and less vulnerable to messy failures.
After making the relevant changes in libmtp, I've now added
implementations here. The basic mechanics are simple, but we
need to include the precise handling of file vs directory
combinations that were first put together for the push operation.
The one main difference is that moves and copies of folders work
correctly, and these do not need to be decomposed.
Note that these operations provide no progress information (the
MTP spec does not provide for it), but they can take a long time,
especially for copies. This results in the client (eg: Nautilus)
spinning without progress reporting. Unfortunately, there's nothing
we can do; at least the operation is faster.
https://bugzilla.gnome.org/show_bug.cgi?id=794388
-rw-r--r-- | configure.ac | 5 | ||||
-rw-r--r-- | daemon/gvfsbackendmtp.c | 325 | ||||
-rw-r--r-- | daemon/gvfsbackendmtp.h | 2 | ||||
-rw-r--r-- | meson.build | 2 |
4 files changed, 333 insertions, 1 deletions
diff --git a/configure.ac b/configure.ac index a671d613..2f9afc99 100644 --- a/configure.ac +++ b/configure.ac @@ -524,6 +524,11 @@ if test "x$enable_libmtp" != "xno" -a "x$msg_gudev" = "xyes"; then AC_DEFINE(HAVE_LIBMTP_1_1_12, 1, [Define to 1 if libmtp 1.1.12 is available]), [] ) + + PKG_CHECK_MODULES(LIBMTP_1_1_15, libmtp >= 1.1.15, + AC_DEFINE(HAVE_LIBMTP_1_1_15, 1, [Define to 1 if libmtp 1.1.15 is available]), + [] + ) fi fi diff --git a/daemon/gvfsbackendmtp.c b/daemon/gvfsbackendmtp.c index a44485fc..6b62e9ed 100644 --- a/daemon/gvfsbackendmtp.c +++ b/daemon/gvfsbackendmtp.c @@ -1132,6 +1132,12 @@ get_device (GVfsBackend *backend, const char *id, GVfsJob *job) { G_VFS_BACKEND_MTP (backend)->get_partial_object_capability = LIBMTP_Check_Capability (device, LIBMTP_DEVICECAP_GetPartialObject); #endif +#if HAVE_LIBMTP_1_1_15 + G_VFS_BACKEND_MTP (backend)->move_object_capability + = LIBMTP_Check_Capability (device, LIBMTP_DEVICECAP_MoveObject); + G_VFS_BACKEND_MTP (backend)->copy_object_capability + = LIBMTP_Check_Capability (device, LIBMTP_DEVICECAP_CopyObject); +#endif exit: g_debug ("(II) get_device done.\n"); @@ -2909,6 +2915,321 @@ do_close_write (GVfsBackend *backend, } #endif /* HAVE_LIBMTP_1_1_6 */ + +#if HAVE_LIBMTP_1_1_15 +static void +do_move (GVfsBackend *backend, + GVfsJobMove *job, + const char *source, + const char *destination, + GFileCopyFlags flags, + GFileProgressCallback progress_callback, + gpointer progress_callback_data) +{ + if (!G_VFS_BACKEND_MTP (backend)->move_object_capability) { + g_vfs_job_failed_literal (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("Operation unsupported")); + return; + } + + g_debug ("(I) do_move (source = %s, dest = %s)\n", source, destination); + g_mutex_lock (&G_VFS_BACKEND_MTP (backend)->mutex); + + char *dir_name = g_path_get_dirname (destination); + char *filename = g_path_get_basename (destination); + + gchar **elements = g_strsplit_set (destination, "/", -1); + unsigned int ne = g_strv_length (elements); + + if (ne < 3) { + g_vfs_job_failed_literal (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED, + _("Cannot write to this location")); + goto exit; + } + + LIBMTP_mtpdevice_t *device; + device = G_VFS_BACKEND_MTP (backend)->device; + + CacheEntry *src_entry = get_cache_entry (G_VFS_BACKEND_MTP (backend), source); + if (src_entry == NULL) { + g_vfs_job_failed_literal (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + _("File doesn’t exist")); + goto exit; + } else if (src_entry->id == -1) { + g_vfs_job_failed_literal (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED, + _("Not a regular file")); + goto exit; + } + + gboolean source_is_dir = FALSE; + uint64_t filesize = 0; + LIBMTP_file_t *file = LIBMTP_Get_Filemetadata (device, src_entry->id); + if (file != NULL) { + source_is_dir = (file->filetype == LIBMTP_FILETYPE_FOLDER); + /* + * filesize is 0 for directories. However, given that we will only move + * a directory if it's staying on the same storage, then these moves + * will always be fast, and will finish too quickly for the progress + * value to matter. Moves between storages will be decomposed, with + * each file moved separately. + */ + filesize = file->filesize; + LIBMTP_destroy_file_t (file); + } + + CacheEntry *entry = get_cache_entry (G_VFS_BACKEND_MTP (backend), destination); + gboolean dest_exists = (entry != NULL && entry->id != -1); + gboolean dest_is_dir = FALSE; + + if (dest_exists) { + LIBMTP_file_t *file = LIBMTP_Get_Filemetadata (device, entry->id); + if (file != NULL) { + dest_is_dir = (file->filetype == LIBMTP_FILETYPE_FOLDER); + LIBMTP_destroy_file_t (file); + } + } + + CacheEntry *parent = get_cache_entry (G_VFS_BACKEND_MTP (backend), dir_name); + if (!parent) { + g_vfs_job_failed_literal (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + _("Directory doesn’t exist")); + 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")); + goto exit; + } + } + /* + * 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); + int ret = LIBMTP_Move_Object (device, + src_entry->id, + parent->storage, + parent_id); + if (ret != 0) { + fail_job (G_VFS_JOB (job), device); + goto exit; + } + + if (progress_callback) { + progress_callback (filesize, filesize, progress_callback_data); + } + + g_vfs_job_succeeded (G_VFS_JOB (job)); + + g_hash_table_foreach (G_VFS_BACKEND_MTP (backend)->monitors, + emit_delete_event, + (char *)source); + g_hash_table_foreach (G_VFS_BACKEND_MTP (backend)->monitors, + emit_create_event, + (char *)destination); + + exit: + g_strfreev (elements); + g_free (dir_name); + g_free (filename); + g_mutex_unlock (&G_VFS_BACKEND_MTP (backend)->mutex); + + g_debug ("(I) do_move done.\n"); +} + + +static void +do_copy (GVfsBackend *backend, + GVfsJobCopy *job, + const char *source, + const char *destination, + GFileCopyFlags flags, + GFileProgressCallback progress_callback, + gpointer progress_callback_data) +{ + if (!G_VFS_BACKEND_MTP (backend)->copy_object_capability) { + g_vfs_job_failed_literal (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("Operation unsupported")); + return; + } + + g_debug ("(I) do_copy (source = %s, dest = %s)\n", source, destination); + g_mutex_lock (&G_VFS_BACKEND_MTP (backend)->mutex); + + char *dir_name = g_path_get_dirname (destination); + char *filename = g_path_get_basename (destination); + + gchar **elements = g_strsplit_set (destination, "/", -1); + unsigned int ne = g_strv_length (elements); + + if (ne < 3) { + g_vfs_job_failed_literal (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED, + _("Cannot write to this location")); + goto exit; + } + + LIBMTP_mtpdevice_t *device; + device = G_VFS_BACKEND_MTP (backend)->device; + + CacheEntry *src_entry = get_cache_entry (G_VFS_BACKEND_MTP (backend), source); + if (src_entry == NULL) { + g_vfs_job_failed_literal (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + _("File doesn’t exist")); + goto exit; + } else if (src_entry->id == -1) { + g_vfs_job_failed_literal (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED, + _("Not a regular file")); + goto exit; + } + + gboolean source_is_dir = FALSE; + uint64_t filesize = 0; + LIBMTP_file_t *file = LIBMTP_Get_Filemetadata (device, src_entry->id); + if (file != NULL) { + source_is_dir = (file->filetype == LIBMTP_FILETYPE_FOLDER); + filesize = file->filesize; + LIBMTP_destroy_file_t (file); + } + + CacheEntry *entry = get_cache_entry (G_VFS_BACKEND_MTP (backend), destination); + gboolean dest_exists = (entry != NULL && entry->id != -1); + gboolean dest_is_dir = FALSE; + + if (dest_exists) { + LIBMTP_file_t *file = LIBMTP_Get_Filemetadata (device, entry->id); + if (file != NULL) { + dest_is_dir = (file->filetype == LIBMTP_FILETYPE_FOLDER); + LIBMTP_destroy_file_t (file); + } + } + + CacheEntry *parent = get_cache_entry (G_VFS_BACKEND_MTP (backend), dir_name); + if (!parent) { + g_vfs_job_failed_literal (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + _("Directory doesn’t exist")); + 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")); + goto exit; + } + } + /* + * 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; + int ret = LIBMTP_Copy_Object (device, + src_entry->id, + parent->storage, + parent_id); + if (ret != 0) { + fail_job (G_VFS_JOB (job), device); + goto exit; + } + + if (progress_callback) { + progress_callback (filesize, filesize, progress_callback_data); + } + + g_vfs_job_succeeded (G_VFS_JOB (job)); + + g_hash_table_foreach (G_VFS_BACKEND_MTP (backend)->monitors, + emit_create_event, + (char *)destination); + + exit: + g_strfreev (elements); + g_free (dir_name); + g_free (filename); + g_mutex_unlock (&G_VFS_BACKEND_MTP (backend)->mutex); + + g_debug ("(I) do_copy done.\n"); +} +#endif /* HAVE_LIBMTP_1_1_15 */ + + /************************************************ * Class init * @@ -2953,4 +3274,8 @@ g_vfs_backend_mtp_class_init (GVfsBackendMtpClass *klass) backend_class->truncate = do_truncate; backend_class->close_write = do_close_write; #endif +#if HAVE_LIBMTP_1_1_15 + backend_class->move = do_move; + backend_class->copy = do_copy; +#endif } diff --git a/daemon/gvfsbackendmtp.h b/daemon/gvfsbackendmtp.h index 2f1b27a9..34e049f4 100644 --- a/daemon/gvfsbackendmtp.h +++ b/daemon/gvfsbackendmtp.h @@ -60,6 +60,8 @@ struct _GVfsBackendMtp gboolean android_extension; gboolean get_partial_object_capability; + gboolean move_object_capability; + gboolean copy_object_capability; #ifdef HAVE_LIBMTP_1_1_5 GThreadPool *event_pool; diff --git a/meson.build b/meson.build index 52184dbf..c4cf97e4 100644 --- a/meson.build +++ b/meson.build @@ -443,7 +443,7 @@ if enable_mtp assert(enable_gudev, 'libmtp requested but gudev is required') libmtp_dep = dependency('libmtp', version: '>= 1.1.0') - foreach version: ['1.1.5', '1.1.6', '1.1.9', '1.1.12'] + foreach version: ['1.1.5', '1.1.6', '1.1.9', '1.1.12', '1.1.15'] config_h.set10('HAVE_LIBMTP_' + version.underscorify(), libmtp_dep.version().version_compare('>= ' + version)) endforeach endif |