summaryrefslogtreecommitdiff
path: root/daemon/gvfsbackendmtp.c
diff options
context:
space:
mode:
authorPhilip Langdale <philipl@overt.org>2018-03-15 21:59:53 -0700
committerPhilip Langdale <philipl@overt.org>2018-03-26 19:34:03 -0700
commitc4cf671d1c845488d512b2cabac50fb49bf767d8 (patch)
tree04b2ec07c9e55998697b2f3066a4d560955a294b /daemon/gvfsbackendmtp.c
parent06c28a632d738b0141b619ea1c500d2563a20298 (diff)
downloadgvfs-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
Diffstat (limited to 'daemon/gvfsbackendmtp.c')
-rw-r--r--daemon/gvfsbackendmtp.c325
1 files changed, 325 insertions, 0 deletions
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
}