diff --git a/client/gdaemonfile.c b/client/gdaemonfile.c index 144104b..a8bcd41 100644 --- a/client/gdaemonfile.c +++ b/client/gdaemonfile.c @@ -2807,6 +2807,9 @@ retry: if (proxy == NULL) goto out; + /* File transfers can take arbitrarily long amounts of time. */ + g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (proxy), G_MAXINT); + data.progress_callback = progress_callback; data.progress_callback_data = progress_callback_data; data.context = g_main_context_new (); diff --git a/common/Makefile.am b/common/Makefile.am index 07ac8f4..a0d6072 100644 --- a/common/Makefile.am +++ b/common/Makefile.am @@ -1,6 +1,8 @@ NULL = -lib_LTLIBRARIES=libgvfscommon.la +privlibdir=$(libdir)/gvfs +privlib_LTLIBRARIES=libgvfscommon.la + noinst_LTLIBRARIES = libgvfscommon-monitor.la INCLUDES = -I$(top_srcdir) -I$(top_srcdir)/gvfs \ @@ -31,7 +33,7 @@ libgvfscommon_la_SOURCES = \ $(NULL) # needed by cygwin (see bug #564003) -libgvfscommon_la_LDFLAGS = -no-undefined +libgvfscommon_la_LDFLAGS = -no-undefined -avoid-version libgvfscommon_la_LIBADD = \ $(GLIB_LIBS) \ diff --git a/configure.ac b/configure.ac index f5d8f2d..4bc9d21 100644 --- a/configure.ac +++ b/configure.ac @@ -493,6 +493,38 @@ AC_SUBST(BLURAY_CFLAGS) AC_SUBST(BLURAY_LIBS) AM_CONDITIONAL(HAVE_BLURAY, [test "$msg_bluray" = "yes"]) +dnl ************************* +dnl *** Check for libmtp *** +dnl ************************* +AC_ARG_ENABLE(libmtp, AS_HELP_STRING([--disable-libmtp],[build without libmtp support])) +msg_libmtp=no +LIBMTP_LIBS= +LIBMTP_CFLAGS= + +if test "x$enable_libmtp" != "xno" -a "x$msg_gudev" = "xyes"; then + PKG_CHECK_EXISTS(libmtp, msg_libmtp=yes) + + if test "x$msg_libmtp" = "xyes"; then + PKG_CHECK_MODULES(LIBMTP, libmtp >= 1.1.0) + AC_DEFINE(HAVE_LIBMTP, 1, [Define to 1 if libmtp is available]) + + save_libs="$LIBS" + LIBS="$LIBMTP_LIBS" + AC_CHECK_LIB(mtp, LIBMTP_Get_Thumbnail, have_libmtp_get_thumbnail=yes) + if test "x$have_libmtp_get_thumbnail" = "xyes"; then + AC_DEFINE(HAVE_LIBMTP_GET_THUMBNAIL, 1, [Define to 1 if LIBMTP_Get_Thumbnail is available]) + fi + + AC_CHECK_LIB(mtp, LIBMTP_Read_Event, have_libmtp_read_event=yes) + if test "x$have_libmtp_read_event" = "xyes"; then + AC_DEFINE(HAVE_LIBMTP_READ_EVENT, 1, [Define to 1 if LIBMTP_Read_Event is available]) + fi + LIBS="$save_libs" + fi +fi + +AM_CONDITIONAL(USE_LIBMTP, [test "$msg_libmtp" = "yes"]) + dnl ========================================================================== dnl Samba 3.0 @@ -830,6 +862,7 @@ monitor/gdu/Makefile monitor/udisks2/Makefile monitor/gphoto2/Makefile monitor/afc/Makefile +monitor/mtp/Makefile programs/Makefile man/Makefile test/Makefile @@ -850,6 +883,7 @@ echo " FUSE support: $msg_fuse CDDA support: $msg_cdda Gphoto2 support: $msg_gphoto2 + MTP support: $msg_libmtp archive support: $msg_archive AFC support: $msg_afc AFP support: $msg_afp diff --git a/daemon/Makefile.am b/daemon/Makefile.am index 1cb0cba..45b76f5 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -79,6 +79,12 @@ mount_DATA += gphoto2.mount libexec_PROGRAMS += gvfsd-gphoto2 endif +mount_in_files += mtp.mount.in +if USE_LIBMTP +mount_DATA += mtp.mount +libexec_PROGRAMS += gvfsd-mtp +endif + mount_in_files += obexftp.mount.in if USE_OBEXFTP mount_DATA += obexftp.mount @@ -440,6 +446,19 @@ else gvfsd_gphoto2_LDADD = $(libraries) $(GPHOTO2_LIBS) $(HAL_LIBS) endif +gvfsd_mtp_SOURCES = \ + gvfsbackendmtp.c gvfsbackendmtp.h \ + daemon-main.c daemon-main.h \ + daemon-main-generic.c + +gvfsd_mtp_CPPFLAGS = \ + -DBACKEND_HEADER=gvfsbackendmtp.h \ + -DDEFAULT_BACKEND_TYPE=mtp \ + -DBACKEND_TYPES='"mtp", G_VFS_TYPE_BACKEND_MTP,' \ + $(GUDEV_CFLAGS) $(LIBMTP_CFLAGS) + +gvfsd_mtp_LDADD = $(libraries) $(GUDEV_LIBS) $(LIBMTP_LIBS) + gvfsd_http_SOURCES = \ soup-input-stream.c soup-input-stream.h \ gvfsbackendhttp.c gvfsbackendhttp.h \ diff --git a/daemon/gvfsbackendmtp.c b/daemon/gvfsbackendmtp.c new file mode 100644 index 0000000..ac0f350 --- /dev/null +++ b/daemon/gvfsbackendmtp.c @@ -0,0 +1,1401 @@ +/* GIO - GLib Input, Output and Streaming Library + * MTP Backend + * + * Copyright (C) 2012 Philip Langdale + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "gvfsbackendmtp.h" +#include "gvfsicon.h" +#include "gvfsjobopenforread.h" +#include "gvfsjobread.h" +#include "gvfsjobseekread.h" +#include "gvfsjobopenforwrite.h" +#include "gvfsjobwrite.h" +#include "gvfsjobclosewrite.h" +#include "gvfsjobseekwrite.h" +#include "gvfsjobsetdisplayname.h" +#include "gvfsjobqueryinfo.h" +#include "gvfsjobdelete.h" +#include "gvfsjobqueryfsinfo.h" +#include "gvfsjobqueryattributes.h" +#include "gvfsjobenumerate.h" +#include "gvfsdaemonprotocol.h" +#include "gvfsjobcreatemonitor.h" +#include "gvfsjobmakedirectory.h" +#include "gvfsmonitor.h" + + +/* ------------------------------------------------------------------------------------------------- */ + +/* showing debug traces */ +#define DEBUG_SHOW_TRACES 1 +#define DEBUG_SHOW_ENUMERATE_TRACES 0 + +static void +DEBUG (const gchar *message, ...) +{ +#if DEBUG_SHOW_TRACES + va_list args; + va_start (args, message); + g_vfprintf (stderr, message, args); + va_end (args); + g_fprintf (stderr, "\n"); + fflush (stderr); +#endif +} + +static void +DEBUG_ENUMERATE (const gchar *message, ...) +{ +#if DEBUG_SHOW_ENUMERATE_TRACES + va_list args; + va_start (args, message); + g_vfprintf (stderr, message, args); + va_end (args); + g_fprintf (stderr, "\n"); + fflush (stderr); +#endif +} + + +/************************************************ + * Storage constants copied from ptp.h + * + * ptp.h is treated as a private header by libmtp + ************************************************/ + +/* PTP Storage Types */ + +#define PTP_ST_Undefined 0x0000 +#define PTP_ST_FixedROM 0x0001 +#define PTP_ST_RemovableROM 0x0002 +#define PTP_ST_FixedRAM 0x0003 +#define PTP_ST_RemovableRAM 0x0004 + + +/************************************************ + * Initialization + ************************************************/ + +G_DEFINE_TYPE (GVfsBackendMtp, g_vfs_backend_mtp, G_VFS_TYPE_BACKEND) + +static void +g_vfs_backend_mtp_init (GVfsBackendMtp *backend) +{ + DEBUG ("(I) g_vfs_backend_mtp_init"); + GMountSpec *mount_spec; + + g_mutex_init (&backend->mutex); + g_vfs_backend_set_display_name (G_VFS_BACKEND (backend), "mtp"); + g_vfs_backend_set_icon_name (G_VFS_BACKEND (backend), "multimedia-player"); + + mount_spec = g_mount_spec_new ("mtp"); + g_vfs_backend_set_mount_spec (G_VFS_BACKEND (backend), mount_spec); + g_mount_spec_unref (mount_spec); + + backend->monitors = g_hash_table_new (g_direct_hash, g_direct_equal); + + DEBUG ("(I) g_vfs_backend_mtp_init done."); +} + +static void +g_vfs_backend_mtp_finalize (GObject *object) +{ + GVfsBackendMtp *backend; + + DEBUG ("(I) g_vfs_backend_mtp_finalize"); + + backend = G_VFS_BACKEND_MTP (object); + + g_hash_table_unref (backend->monitors); + g_mutex_clear (&backend->mutex); + + if (G_OBJECT_CLASS (g_vfs_backend_mtp_parent_class)->finalize) + (*G_OBJECT_CLASS (g_vfs_backend_mtp_parent_class)->finalize) (object); + + DEBUG ("(I) g_vfs_backend_mtp_finalize done."); +} + + +/************************************************ + * Monitors + ************************************************/ + +static void +do_create_dir_monitor (GVfsBackend *backend, + GVfsJobCreateMonitor *job, + const char *filename, + GFileMonitorFlags flags) +{ + GVfsBackendMtp *mtp_backend = G_VFS_BACKEND_MTP (backend); + + DEBUG ("(I) create_dir_monitor (%s)", filename); + + GVfsMonitor *vfs_monitor = g_vfs_monitor_new (backend); + + g_object_set_data_full (G_OBJECT (vfs_monitor), "gvfsbackendmtp:path", + g_strdup (filename), g_free); + + g_vfs_job_create_monitor_set_monitor (job, vfs_monitor); + g_hash_table_insert (mtp_backend->monitors, vfs_monitor, NULL); + g_object_weak_ref (G_OBJECT (vfs_monitor), (GWeakNotify)g_hash_table_remove, mtp_backend->monitors); + g_object_unref (vfs_monitor); + g_vfs_job_succeeded (G_VFS_JOB (job)); + + DEBUG ("(I) create_dir_monitor done."); +} + + +static void +do_create_file_monitor (GVfsBackend *backend, + GVfsJobCreateMonitor *job, + const char *filename, + GFileMonitorFlags flags) +{ + GVfsBackendMtp *mtp_backend = G_VFS_BACKEND_MTP (backend); + + DEBUG ("(I) create_file_monitor (%s)", filename); + + GVfsMonitor *vfs_monitor = g_vfs_monitor_new (backend); + + g_object_set_data_full (G_OBJECT (vfs_monitor), "gvfsbackendmtp:path", + g_strdup (filename), g_free); + + g_vfs_job_create_monitor_set_monitor (job, vfs_monitor); + g_hash_table_insert (mtp_backend->monitors, vfs_monitor, NULL); + g_object_weak_ref (G_OBJECT (vfs_monitor), (GWeakNotify)g_hash_table_remove, mtp_backend->monitors); + g_object_unref (vfs_monitor); + g_vfs_job_succeeded (G_VFS_JOB (job)); + + DEBUG ("(I) create_file_monitor done."); +} + +static void +emit_event_internal (GVfsMonitor *monitor, + const char *path, + GFileMonitorEvent event) +{ + DEBUG ("(III) emit_event_internal (%s, %d)", path, event); + + char *dir = g_dirname (path); + const char *monitored_path = g_object_get_data (G_OBJECT (monitor), "gvfsbackendmtp:path"); + if (g_strcmp0 (dir, monitored_path) == 0) { + DEBUG ("(III) emit_event_internal: Event %d on directory %s for %s", event, dir, path); + g_vfs_monitor_emit_event (monitor, event, path, NULL); + } else if (g_strcmp0 (path, monitored_path) == 0) { + DEBUG ("(III) emit_event_internal: Event %d on file %s", event, path); + g_vfs_monitor_emit_event (monitor, event, path, NULL); + } + g_free (dir); + + DEBUG ("(III) emit_event_internal done."); +} + +static void +emit_create_event (gpointer key, + gpointer value, + gpointer user_data) +{ + DEBUG ("(II) emit_create_event."); + emit_event_internal (key, user_data, G_FILE_MONITOR_EVENT_CREATED); +} + +static void +emit_delete_event (gpointer key, + gpointer value, + gpointer user_data) +{ + DEBUG ("(II) emit_delete_event."); + emit_event_internal (key, user_data, G_FILE_MONITOR_EVENT_DELETED); +} + +static void +emit_change_event (gpointer key, + gpointer value, + gpointer user_data) +{ + DEBUG ("(II) emit_change_event."); + emit_event_internal (key, user_data, G_FILE_MONITOR_EVENT_CHANGED); +} + + +/************************************************ + * Errors + ************************************************/ + +static void +fail_job (GVfsJob *job, LIBMTP_mtpdevice_t *device) +{ + LIBMTP_error_t *error = LIBMTP_Get_Errorstack (device); + + g_vfs_job_failed (job, G_IO_ERROR, + g_vfs_job_is_cancelled (job) ? + G_IO_ERROR_CANCELLED : + G_IO_ERROR_FAILED, + _("libmtp error: %s"), + g_strrstr (error->error_text, ":") + 1); + + LIBMTP_Clear_Errorstack (device); +} + + +/************************************************ + * Mounts + ************************************************/ + +static LIBMTP_mtpdevice_t * +get_device (GVfsBackend *backend, const char *id, GVfsJob *job); + + +static void +on_uevent (GUdevClient *client, gchar *action, GUdevDevice *device, gpointer user_data) +{ + const char *dev_path = g_udev_device_get_device_file (device); + DEBUG ("(I) on_uevent (action %s, device %s)", action, dev_path); + + if (dev_path == NULL) { + return; + } + + GVfsBackendMtp *op_backend = G_VFS_BACKEND_MTP (user_data); + + if (g_strcmp0 (op_backend->dev_path, dev_path) == 0 && + g_str_equal (action, "remove")) { + DEBUG ("(I) on_uevent: Quiting after remove event on device %s", dev_path); + /* TODO: need a cleaner way to force unmount ourselves */ + exit (1); + } + + DEBUG ("(I) on_uevent done."); +} + +#if HAVE_LIBMTP_READ_EVENT +static gpointer +check_event (gpointer user_data) +{ + GVfsBackendMtp *backend = user_data; + + LIBMTP_event_t event; + int ret = 0; + while (ret == 0) { + uint32_t param1; + char *path; + ret = LIBMTP_Read_Event (backend->device, &event, ¶m1); + switch (event) { + case LIBMTP_EVENT_STORE_ADDED: + path = g_strdup_printf ("/%u", param1); + g_mutex_lock (&backend->mutex); + g_hash_table_foreach (backend->monitors, emit_create_event, path); + g_mutex_unlock (&backend->mutex); + g_free (path); + break; + default: + break; + } + } + return NULL; +} +#endif + +static gboolean +mtp_heartbeat (GVfsBackendMtp *backend) +{ + if (g_mutex_trylock (&backend->mutex)) { + LIBMTP_Dump_Device_Info(backend->device); + g_mutex_unlock (&backend->mutex); + } + return TRUE; +} + +static void +do_mount (GVfsBackend *backend, + GVfsJobMount *job, + GMountSpec *mount_spec, + GMountSource *mount_source, + gboolean is_automount) +{ + GVfsBackendMtp *op_backend = G_VFS_BACKEND_MTP (backend); + + DEBUG ("(I) do_mount"); + + const char *host = g_mount_spec_get (mount_spec, "host"); + DEBUG ("(I) do_mount: host=%s", host); + if (host == NULL) { + GError *error; + g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_FAILED, _("No device specified")); + g_vfs_job_failed_from_error (G_VFS_JOB (job), error); + g_error_free (error); + return; + } + + const char *subsystems[] = {"usb", NULL}; + op_backend->gudev_client = g_udev_client_new (subsystems); + if (op_backend->gudev_client == NULL) { + GError *error; + g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Cannot create gudev client")); + g_vfs_job_failed_from_error (G_VFS_JOB (job), error); + g_error_free (error); + return; + } + g_signal_connect (op_backend->gudev_client, "uevent", G_CALLBACK (on_uevent), op_backend); + + /* turn usb:001,041 string into an udev device name */ + if (!g_str_has_prefix (host, "[usb:")) { + g_vfs_job_failed (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("Unexpected host uri format.")); + return; + } + + char *comma; + char *dev_path = g_strconcat ("/dev/bus/usb/", host + 5, NULL); + if ((comma = strchr (dev_path, ',')) == NULL) { + g_free (dev_path); + g_vfs_job_failed (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("Malformed host uri.")); + return; + } + *comma = '/'; + dev_path[strlen (dev_path) -1] = '\0'; + DEBUG ("(I) do_mount: Parsed '%s' into device name %s", host, dev_path); + + /* find corresponding GUdevDevice */ + if (!g_udev_client_query_by_device_file (op_backend->gudev_client, dev_path)) { + g_free (dev_path); + g_vfs_job_failed (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + _("Couldn't find matching udev device.")); + return; + } + + op_backend->dev_path = dev_path; + + LIBMTP_Init (); + + get_device (backend, host, G_VFS_JOB (job)); + if (!G_VFS_JOB (job)->failed) { + GMountSpec *mtp_mount_spec = g_mount_spec_new ("mtp"); + g_mount_spec_set (mtp_mount_spec, "host", host); + g_vfs_backend_set_mount_spec (backend, mtp_mount_spec); + g_mount_spec_unref (mtp_mount_spec); + + g_vfs_job_succeeded (G_VFS_JOB (job)); + + op_backend->hb_id = + g_timeout_add_seconds (900, (GSourceFunc)mtp_heartbeat, op_backend); + +#if HAVE_LIBMTP_READ_EVENT + g_thread_new ("events", check_event, backend); +#endif + } + DEBUG ("(I) do_mount done."); +} + + +static void +do_unmount (GVfsBackend *backend, GVfsJobUnmount *job, + GMountUnmountFlags flags, + GMountSource *mount_source) +{ + GVfsBackendMtp *op_backend; + + DEBUG ("(I) do_umount"); + + op_backend = G_VFS_BACKEND_MTP (backend); + + g_source_remove (op_backend->hb_id); + g_object_unref (op_backend->gudev_client); + g_free (op_backend->dev_path); + + g_mutex_lock (&G_VFS_BACKEND_MTP (backend)->mutex); + LIBMTP_Release_Device (op_backend->device); + g_mutex_unlock (&G_VFS_BACKEND_MTP (backend)->mutex); + + g_vfs_job_succeeded (G_VFS_JOB (job)); + + DEBUG ("(I) do_umount done."); +} + + + + + + + +/************************************************ + * Queries + * + */ + +LIBMTP_mtpdevice_t * +get_device (GVfsBackend *backend, const char *id, GVfsJob *job) { + DEBUG ("(II) get_device: %s", id); + + LIBMTP_mtpdevice_t *device = NULL; + + if (G_VFS_BACKEND_MTP (backend)->device != NULL) { + DEBUG ("(II) get_device: Returning cached device %p", device); + return G_VFS_BACKEND_MTP (backend)->device; + } + + LIBMTP_raw_device_t * rawdevices; + int numrawdevices; + LIBMTP_error_number_t err; + + err = LIBMTP_Detect_Raw_Devices (&rawdevices, &numrawdevices); + switch (err) { + case LIBMTP_ERROR_NONE: + break; + case LIBMTP_ERROR_NO_DEVICE_ATTACHED: + g_vfs_job_failed (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + _("No MTP devices found")); + goto exit; + case LIBMTP_ERROR_CONNECTING: + g_vfs_job_failed (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_CONNECTION_REFUSED, + _("Unable to connect to MTP device")); + goto exit; + case LIBMTP_ERROR_MEMORY_ALLOCATION: + g_vfs_job_failed (G_VFS_JOB (job), + G_FILE_ERROR, G_FILE_ERROR_NOMEM, + _("Unable to allocate memory while detecting MTP devices")); + goto exit; + case LIBMTP_ERROR_GENERAL: + default: + g_vfs_job_failed (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_FAILED, + _("Generic libmtp error")); + goto exit; + } + + /* Iterate over connected MTP devices */ + int i; + for (i = 0; i < numrawdevices; i++) { + char *name; + name = g_strdup_printf ("[usb:%03u,%03u]", + rawdevices[i].bus_location, + rawdevices[i].devnum); + + if (strcmp (id, name) == 0) { + device = LIBMTP_Open_Raw_Device_Uncached (&rawdevices[i]); + if (device == NULL) { + g_vfs_job_failed (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_FAILED, + _("Unable to open MTP device '%s'"), name); + g_free (name); + goto exit; + } + + DEBUG ("(II) get_device: Storing device %s", name); + G_VFS_BACKEND_MTP (backend)->device = device; + + LIBMTP_Dump_Errorstack (device); + LIBMTP_Clear_Errorstack (device); + break; + } else { + g_free (name); + } + } + + exit: + DEBUG ("(II) get_device done."); + return device; +} + +static void +get_device_info (GVfsBackendMtp *backend, GFileInfo *info) +{ + LIBMTP_mtpdevice_t *device = backend->device; + const char *name; + + name = g_mount_spec_get (g_vfs_backend_get_mount_spec (G_VFS_BACKEND (backend)), "host"); + + DEBUG_ENUMERATE ("(II) get_device_info: %s", name); + + g_file_info_set_file_type (info, G_FILE_TYPE_DIRECTORY); + g_file_info_set_name (info, name); + + char *friendlyname = LIBMTP_Get_Friendlyname (device); + g_file_info_set_display_name (info, friendlyname == NULL ? + _("Unnamed Device") : friendlyname); + free (friendlyname); + + g_file_info_set_file_type (info, G_FILE_TYPE_DIRECTORY); + g_file_info_set_content_type (info, "inode/directory"); + g_file_info_set_size (info, 0); + + GIcon *icon = g_themed_icon_new ("multimedia-player"); + g_file_info_set_icon (info, icon); + g_object_unref (icon); + + g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ, TRUE); + g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, TRUE); + g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE, FALSE); + g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE, TRUE); + g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH, FALSE); + g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME, TRUE); + + g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE, "mtpfs"); + + int ret = LIBMTP_Get_Storage (device, LIBMTP_STORAGE_SORTBY_NOTSORTED); + if (ret != 0) { + LIBMTP_Dump_Errorstack (device); + LIBMTP_Clear_Errorstack (device); + DEBUG_ENUMERATE ("(II) get_device_info done with no stores."); + return; + } + guint64 freeSpace = 0; + guint64 maxSpace = 0; + LIBMTP_devicestorage_t *storage; + for (storage = device->storage; storage != 0; storage = storage->next) { + freeSpace += storage->FreeSpaceInBytes; + maxSpace += storage->MaxCapacity; + } + + g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE, freeSpace); + g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE, maxSpace); + + DEBUG_ENUMERATE ("(II) get_device_info done."); +} + +static void +get_storage_info (LIBMTP_devicestorage_t *storage, GFileInfo *info) { + + char *id = g_strdup_printf ("%u", storage->id); + g_file_info_set_name (info, id); + g_free (id); + + DEBUG_ENUMERATE ("(II) get_storage_info: %s", storage->id); + + g_file_info_set_display_name (info, storage->StorageDescription); + g_file_info_set_file_type (info, G_FILE_TYPE_DIRECTORY); + g_file_info_set_content_type (info, "inode/directory"); + g_file_info_set_size (info, 0); + + GIcon *icon; + switch (storage->StorageType) { + case PTP_ST_FixedROM: + g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_FILESYSTEM_READONLY, TRUE); + icon = g_themed_icon_new_with_default_fallbacks ("drive-harddisk"); + break; + case PTP_ST_RemovableROM: + g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_FILESYSTEM_READONLY, TRUE); + icon = g_themed_icon_new_with_default_fallbacks ("media-memory-sd"); + break; + case PTP_ST_RemovableRAM: + g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_FILESYSTEM_READONLY, FALSE); + icon = g_themed_icon_new_with_default_fallbacks ("media-memory-sd"); + break; + case PTP_ST_FixedRAM: + default: + g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_FILESYSTEM_READONLY, FALSE); + icon = g_themed_icon_new_with_default_fallbacks ("drive-harddisk"); + break; + } + g_file_info_set_icon (info, icon); + g_object_unref (icon); + + g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ, TRUE); + g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, TRUE); + g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE, FALSE); + g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE, TRUE); + g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH, FALSE); + g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME, FALSE); + + g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE, storage->FreeSpaceInBytes); + g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE, storage->MaxCapacity); + g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE, "mtpfs"); + + DEBUG_ENUMERATE ("(II) get_storage_info done."); +} + +static void +get_file_info (GVfsBackend *backend, + LIBMTP_mtpdevice_t *device, + GFileInfo *info, + LIBMTP_file_t *file) { + GIcon *icon = NULL; + char *content_type = NULL; + + char *id = g_strdup_printf ("%u", file->item_id); + g_file_info_set_name (info, id); + g_free (id); + + DEBUG_ENUMERATE ("(II) get_file_info: %u", file->item_id); + + g_file_info_set_display_name (info, file->filename); + + switch (file->filetype) { + case LIBMTP_FILETYPE_FOLDER: + g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE, TRUE); + g_file_info_set_file_type (info, G_FILE_TYPE_DIRECTORY); + g_file_info_set_content_type (info, "inode/directory"); + icon = g_themed_icon_new ("folder"); + break; + default: + g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE, FALSE); + g_file_info_set_file_type (info, G_FILE_TYPE_REGULAR); + content_type = g_content_type_guess (file->filename, NULL, 0, NULL); + g_file_info_set_content_type (info, content_type); + icon = g_content_type_get_icon (content_type); + break; + } + + +#if HAVE_LIBMTP_GET_THUMBNAIL + if (LIBMTP_FILETYPE_IS_IMAGE (file->filetype) || + LIBMTP_FILETYPE_IS_VIDEO (file->filetype) || + LIBMTP_FILETYPE_IS_AUDIOVIDEO (file->filetype)) { + + char *icon_id; + GIcon *icon; + GMountSpec *mount_spec; + + mount_spec = g_vfs_backend_get_mount_spec (backend); + icon_id = g_strdup_printf ("%u", file->item_id); + icon = g_vfs_icon_new (mount_spec, + icon_id); + g_file_info_set_attribute_object (info, + G_FILE_ATTRIBUTE_PREVIEW_ICON, + G_OBJECT (icon)); + g_object_unref (icon); + g_free (icon_id); + } +#endif + + g_file_info_set_size (info, file->filesize); + + GTimeVal modtime = { file->modificationdate, 0 }; + g_file_info_set_modification_time (info, &modtime); + + g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ, TRUE); + g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, TRUE); + g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE, TRUE); + g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH, FALSE); + g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME, TRUE); + g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_COPY_NAME, file->filename); + + + if (icon != NULL) { + g_file_info_set_icon (info, icon); + g_object_unref (icon); + } + g_free (content_type); + + DEBUG_ENUMERATE ("(II) get_file_info done."); +} + + +static void +do_enumerate (GVfsBackend *backend, + GVfsJobEnumerate *job, + const char *filename, + GFileAttributeMatcher *attribute_matcher, + GFileQueryInfoFlags flags) +{ + GVfsBackendMtp *op_backend = G_VFS_BACKEND_MTP (backend); + GFileInfo *info; + + gchar **elements = g_strsplit_set (filename, "/", -1); + unsigned int ne = 0; + for (ne = 0; elements[ne] != NULL; ne++); + + DEBUG ("(I) do_enumerate (filename = %s, n_elements = %d) ", filename, ne); + + g_mutex_lock (&G_VFS_BACKEND_MTP (backend)->mutex); + + LIBMTP_mtpdevice_t *device; + device = op_backend->device; + + if (ne == 2 && elements[1][0] == '\0') { + LIBMTP_devicestorage_t *storage; + + int ret = LIBMTP_Get_Storage (device, LIBMTP_STORAGE_SORTBY_NOTSORTED); + if (ret != 0) { + LIBMTP_Dump_Errorstack (device); + LIBMTP_Clear_Errorstack (device); + goto success; + } + for (storage = device->storage; storage != 0; storage = storage->next) { + info = g_file_info_new (); + get_storage_info (storage, info); + g_vfs_job_enumerate_add_info (job, info); + g_object_unref (info); + } + } else { + LIBMTP_file_t *files; + + int pid = (ne == 2 ? -1 : strtol (elements[ne-1], NULL, 10)); + + LIBMTP_Clear_Errorstack (device); + files = LIBMTP_Get_Files_And_Folders (device, strtol (elements[1], NULL, 10), pid); + if (files == NULL && LIBMTP_Get_Errorstack (device) != NULL) { + fail_job (G_VFS_JOB (job), device); + goto exit; + } + while (files != NULL) { + LIBMTP_file_t *file = files; + files = files->next; + + info = g_file_info_new (); + get_file_info (backend, device, info, file); + g_vfs_job_enumerate_add_info (job, info); + g_object_unref (info); + + LIBMTP_destroy_file_t (file); + } + } + + success: + g_vfs_job_enumerate_done (job); + g_vfs_job_succeeded (G_VFS_JOB (job)); + + exit: + g_strfreev (elements); + g_mutex_unlock (&G_VFS_BACKEND_MTP (backend)->mutex); + DEBUG ("(I) do_enumerate done."); +} + +static void +do_query_info (GVfsBackend *backend, + GVfsJobQueryInfo *job, + const char *filename, + GFileQueryInfoFlags flags, + GFileInfo *info, + GFileAttributeMatcher *matcher) +{ + DEBUG ("(I) do_query_info (filename = %s) ", filename); + g_mutex_lock (&G_VFS_BACKEND_MTP (backend)->mutex); + + gchar **elements = g_strsplit_set (filename, "/", -1); + unsigned int ne = 0; + for (ne = 0; elements[ne] != NULL; ne++); + + LIBMTP_mtpdevice_t *device; + device = G_VFS_BACKEND_MTP (backend)->device; + + if (ne == 2 && elements[1][0] == '\0') { + get_device_info (G_VFS_BACKEND_MTP (backend), info); + } else if (ne < 3) { + LIBMTP_devicestorage_t *storage; + int ret = LIBMTP_Get_Storage (device, LIBMTP_STORAGE_SORTBY_NOTSORTED); + if (ret != 0) { + LIBMTP_Dump_Errorstack (device); + LIBMTP_Clear_Errorstack (device); + g_vfs_job_failed (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + _("No storage volumes found")); + goto exit; + } + for (storage = device->storage; storage != 0; storage = storage->next) { + if (storage->id == strtol (elements[ne-1], NULL, 10)) { + DEBUG ("(III) found storage %u", storage->id); + get_storage_info (storage, info); + } + } + } else { + LIBMTP_file_t *file = NULL; + char *endptr; + if (strtol (elements[ne-1], &endptr, 10) == 0 || + *endptr != '\0') { + DEBUG ("(II) try get files and folders"); + int parent_id = -1; + if (ne > 3) { + parent_id = strtol (elements[ne-2], NULL, 10); + } + LIBMTP_file_t *i = LIBMTP_Get_Files_And_Folders (device, strtol (elements[1], NULL, 10), + parent_id); + while (i != NULL) { + DEBUG ("(II) backup query (entity = %s, name = %s) ", i->filename, elements[ne-1]); + if (strcmp (i->filename, elements[ne-1]) == 0) { + file = i; + i = i->next; + break; + } else { + LIBMTP_file_t *tmp = i; + i = i->next; + LIBMTP_destroy_file_t (tmp); + } + } + while (i != NULL) { + LIBMTP_file_t *tmp = i; + i = i->next; + LIBMTP_destroy_file_t (tmp); + } + } else { + file = LIBMTP_Get_Filemetadata (device, strtol (elements[ne-1], NULL, 10)); + } + + if (file != NULL) { + get_file_info (backend, device, info, file); + LIBMTP_destroy_file_t (file); + } else { + fail_job (G_VFS_JOB (job), device); + goto exit; + } + } + + g_vfs_job_succeeded (G_VFS_JOB (job)); + + exit: + g_strfreev (elements); + g_mutex_unlock (&G_VFS_BACKEND_MTP (backend)->mutex); + DEBUG ("(I) do_query_info done."); +} + + +static void +do_query_fs_info (GVfsBackend *backend, + GVfsJobQueryFsInfo *job, + const char *filename, + GFileInfo *info, + GFileAttributeMatcher *attribute_matcher) +{ + DEBUG ("(I) do_query_fs_info (filename = %s) ", filename); + g_mutex_lock (&G_VFS_BACKEND_MTP (backend)->mutex); + + gchar **elements = g_strsplit_set (filename, "/", -1); + unsigned int ne = 0; + for (ne = 0; elements[ne] != NULL; ne++); + + LIBMTP_mtpdevice_t *device; + device = G_VFS_BACKEND_MTP (backend)->device; + + if (ne == 2 && elements[1][0] == '\0') { + get_device_info (G_VFS_BACKEND_MTP (backend), info); + } else { + LIBMTP_devicestorage_t *storage; + int ret = LIBMTP_Get_Storage (device, LIBMTP_STORAGE_SORTBY_NOTSORTED); + if (ret != 0) { + LIBMTP_Dump_Errorstack (device); + LIBMTP_Clear_Errorstack (device); + g_vfs_job_failed (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + _("No storage volumes found")); + goto exit; + } + for (storage = device->storage; storage != 0; storage = storage->next) { + if (storage->id == strtol (elements[1], NULL, 10)) { + get_storage_info (storage, info); + } + } + } + + g_vfs_job_succeeded (G_VFS_JOB (job)); + + exit: + g_strfreev (elements); + g_mutex_unlock (&G_VFS_BACKEND_MTP (backend)->mutex); + + DEBUG ("(I) do_query_fs_info done."); +} + + +/************************************************ + * Operations + * + */ + +typedef struct { + GFileProgressCallback progress_callback; + gpointer progress_callback_data; + GVfsJob *job; +} MtpProgressData; + + +static int mtp_progress (uint64_t const sent, uint64_t const total, + MtpProgressData const * const data) +{ + if (data->progress_callback) { + data->progress_callback (sent, total, data->progress_callback_data); + } + return g_vfs_job_is_cancelled (data->job); +} + +static void +do_make_directory (GVfsBackend *backend, + GVfsJobMakeDirectory *job, + const char *filename) +{ + DEBUG ("(I) do_make_directory (filename = %s) ", filename); + g_mutex_lock (&G_VFS_BACKEND_MTP (backend)->mutex); + + gchar **elements = g_strsplit_set (filename, "/", -1); + unsigned int ne = 0; + for (ne = 0; elements[ne] != NULL; ne++); + + if (ne < 3) { + g_vfs_job_failed (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_FAILED, + _("Cannot make directory in this location")); + goto exit; + } + + LIBMTP_mtpdevice_t *device; + device = G_VFS_BACKEND_MTP (backend)->device; + + int parent_id = 0; + if (ne > 3) { + parent_id = strtol (elements[ne-2], NULL, 10); + } + + int ret = LIBMTP_Create_Folder (device, elements[ne-1], parent_id, strtol (elements[1], NULL, 10)); + if (ret == 0) { + fail_job (G_VFS_JOB (job), device); + goto exit; + } + + g_vfs_job_succeeded (G_VFS_JOB (job)); + + g_hash_table_foreach (G_VFS_BACKEND_MTP (backend)->monitors, emit_create_event, (char *)filename); + + exit: + g_strfreev (elements); + g_mutex_unlock (&G_VFS_BACKEND_MTP (backend)->mutex); + + DEBUG ("(I) do_make_directory done."); +} + + +static void +do_pull (GVfsBackend *backend, + GVfsJobPull *job, + const char *source, + const char *local_path, + GFileCopyFlags flags, + gboolean remove_source, + GFileProgressCallback progress_callback, + gpointer progress_callback_data) +{ + DEBUG ("(I) do_pull (filename = %s, local_path = %s) ", source, local_path); + g_mutex_lock (&G_VFS_BACKEND_MTP (backend)->mutex); + + GFileInfo *info = NULL; + gchar **elements = g_strsplit_set (source, "/", -1); + unsigned int ne = 0; + for (ne = 0; elements[ne] != NULL; ne++); + + if (ne < 3) { + g_vfs_job_failed (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_NOT_REGULAR_FILE, + _("Not a regular file")); + goto exit; + } + + LIBMTP_mtpdevice_t *device; + device = G_VFS_BACKEND_MTP (backend)->device; + + LIBMTP_file_t *file = LIBMTP_Get_Filemetadata (device, strtol (elements[ne-1], NULL, 10)); + if (file == NULL) { + g_vfs_job_failed (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + _("File does not exist")); + goto exit; + } + + info = g_file_info_new (); + get_file_info (backend, device, info, file); + LIBMTP_destroy_file_t (file); + file = NULL; + if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) { + GError *error; + GFile *file = g_file_new_for_path (local_path); + g_assert (file != NULL); + if (file) { + error = NULL; + if (g_file_make_directory (file, G_VFS_JOB (job)->cancellable, &error)) { + g_vfs_job_succeeded (G_VFS_JOB (job)); + } else if (error->code == G_IO_ERROR_EXISTS) { + g_vfs_job_succeeded (G_VFS_JOB (job)); + g_error_free (error); + } else { + g_vfs_job_failed_from_error (G_VFS_JOB (job), error); + DEBUG ("(II) pull dir failed: %s", error->message); + g_error_free (error); + } + g_object_unref (file); + } + } else { + MtpProgressData *mtp_progress_data = g_new0 (MtpProgressData, 1); + mtp_progress_data->progress_callback = progress_callback; + mtp_progress_data->progress_callback_data = progress_callback_data; + mtp_progress_data->job = G_VFS_JOB (job); + int ret = LIBMTP_Get_File_To_File (device, + strtol (elements[ne-1], NULL, 10), + local_path, + (LIBMTP_progressfunc_t)mtp_progress, + mtp_progress_data); + g_free (mtp_progress_data); + if (ret != 0) { + fail_job (G_VFS_JOB (job), device); + goto exit; + } + g_vfs_job_succeeded (G_VFS_JOB (job)); + } + + exit: + if (info != NULL) { + g_object_unref (info); + } + g_strfreev (elements); + g_mutex_unlock (&G_VFS_BACKEND_MTP (backend)->mutex); + + DEBUG ("(I) do_pull done."); +} + + +static void +do_push (GVfsBackend *backend, + GVfsJobPush *job, + const char *destination, + const char *local_path, + GFileCopyFlags flags, + gboolean remove_source, + GFileProgressCallback progress_callback, + gpointer progress_callback_data) +{ + DEBUG ("(I) do_push (filename = %s, local_path = %s) ", destination, local_path); + g_mutex_lock (&G_VFS_BACKEND_MTP (backend)->mutex); + + GFile *file = NULL; + GFileInfo *info = NULL; + gchar **elements = g_strsplit_set (destination, "/", -1); + unsigned int ne = 0; + for (ne = 0; elements[ne] != NULL; ne++); + + if (ne < 3) { + g_vfs_job_failed (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_NOT_REGULAR_FILE, + _("Cannot write to this location")); + goto exit; + } + + LIBMTP_mtpdevice_t *device; + device = G_VFS_BACKEND_MTP (backend)->device; + + int parent_id = 0; + + if (ne > 3) { + parent_id = strtol (elements[ne-2], NULL, 10); + } + + file = g_file_new_for_path (local_path); + if (!file) { + g_vfs_job_failed (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + _("File not found")); + goto exit; + } + + if (g_file_query_file_type (file, G_FILE_QUERY_INFO_NONE, + G_VFS_JOB (job)->cancellable) == + G_FILE_TYPE_DIRECTORY) { + /* + * It happens to be the case that we can reuse do_make_directory + * here. + */ + return do_make_directory (backend, G_VFS_JOB_MAKE_DIRECTORY (job), + elements[ne-1]); + } + + GError *error = NULL; + info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_SIZE, + G_FILE_QUERY_INFO_NONE, + G_VFS_JOB (job)->cancellable, + &error); + if (!info) { + g_vfs_job_failed_from_error (G_VFS_JOB (job), error); + g_error_free (error); + goto exit; + } + + LIBMTP_file_t *mtpfile = LIBMTP_new_file_t (); + mtpfile->filename = strdup (elements[ne-1]); + mtpfile->parent_id = parent_id; + mtpfile->storage_id = strtol (elements[1], NULL, 10); + mtpfile->filetype = LIBMTP_FILETYPE_UNKNOWN; + mtpfile->filesize = g_file_info_get_size (info); + + MtpProgressData *mtp_progress_data = g_new0 (MtpProgressData, 1); + mtp_progress_data->progress_callback = progress_callback; + mtp_progress_data->progress_callback_data = progress_callback_data; + mtp_progress_data->job = G_VFS_JOB (job); + int ret = LIBMTP_Send_File_From_File (device, local_path, mtpfile, + (LIBMTP_progressfunc_t)mtp_progress, + mtp_progress_data); + g_free (mtp_progress_data); + LIBMTP_destroy_file_t (mtpfile); + if (ret != 0) { + fail_job (G_VFS_JOB (job), device); + goto exit; + } + + g_vfs_job_succeeded (G_VFS_JOB (job)); + + g_hash_table_foreach (G_VFS_BACKEND_MTP (backend)->monitors, + emit_create_event, + (char *)destination); + + exit: + if (file) { + g_object_unref (file); + } + if (info) { + g_object_unref (info); + } + g_strfreev (elements); + g_mutex_unlock (&G_VFS_BACKEND_MTP (backend)->mutex); + + DEBUG ("(I) do_push done."); +} + + +static void +do_delete (GVfsBackend *backend, + GVfsJobDelete *job, + const char *filename) +{ + DEBUG ("(I) do_delete (filename = %s) ", filename); + g_mutex_lock (&G_VFS_BACKEND_MTP (backend)->mutex); + + gchar **elements = g_strsplit_set (filename, "/", -1); + unsigned int ne = 0; + for (ne = 0; elements[ne] != NULL; ne++); + + if (ne < 3) { + g_vfs_job_failed (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_FAILED, + _("Cannot delete this entity")); + goto exit; + } + + LIBMTP_mtpdevice_t *device; + device = G_VFS_BACKEND_MTP (backend)->device; + + int ret = LIBMTP_Delete_Object (device, strtol (elements[ne-1], NULL, 10)); + if (ret != 0) { + fail_job (G_VFS_JOB (job), device); + goto exit; + } + g_vfs_job_succeeded (G_VFS_JOB (job)); + + g_hash_table_foreach (G_VFS_BACKEND_MTP (backend)->monitors, + emit_delete_event, + (char *)filename); + + exit: + g_strfreev (elements); + g_mutex_unlock (&G_VFS_BACKEND_MTP (backend)->mutex); + + DEBUG ("(I) do_delete done."); +} + + +static void +do_set_display_name (GVfsBackend *backend, + GVfsJobSetDisplayName *job, + const char *filename, + const char *display_name) +{ + DEBUG ("(I) do_set_display_name '%s' --> '%s' ", filename, display_name); + g_mutex_lock (&G_VFS_BACKEND_MTP (backend)->mutex); + + gchar **elements = g_strsplit_set (filename, "/", -1); + unsigned int ne = 0; + for (ne = 0; elements[ne] != NULL; ne++); + + if (ne < 3) { + g_vfs_job_failed (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("Can't rename volume")); + goto exit; + } + + LIBMTP_mtpdevice_t *device; + device = G_VFS_BACKEND_MTP (backend)->device; + + LIBMTP_file_t *file = LIBMTP_Get_Filemetadata (device, strtol (elements[ne-1], NULL, 10)); + int ret = LIBMTP_Set_File_Name (device, file, display_name); + if (ret != 0) { + fail_job (G_VFS_JOB (job), device); + goto exit; + } + LIBMTP_destroy_file_t (file); + file = NULL; + g_vfs_job_set_display_name_set_new_path (job, filename); + g_vfs_job_succeeded (G_VFS_JOB (job)); + + g_hash_table_foreach (G_VFS_BACKEND_MTP (backend)->monitors, + emit_change_event, + (char *)filename); + + exit: + g_strfreev (elements); + g_mutex_unlock (&G_VFS_BACKEND_MTP (backend)->mutex); + + DEBUG ("(I) do_set_display_name done."); +} + + +#if HAVE_LIBMTP_GET_THUMBNAIL +static void +do_open_icon_for_read (GVfsBackend *backend, + GVfsJobOpenIconForRead *job, + const char *icon_id) +{ + DEBUG ("(I) do_open_icon_for_read (%s)", icon_id); + g_mutex_lock (&G_VFS_BACKEND_MTP (backend)->mutex); + + guint id = strtol (icon_id, NULL, 10); + + if (id > 0) { + unsigned char *data; + unsigned int size; + int ret = LIBMTP_Get_Thumbnail (G_VFS_BACKEND_MTP (backend)->device, id, + &data, &size); + if (ret == 0) { + DEBUG ("File %u has thumbnail: %u", id, size); + GByteArray *bytes = g_byte_array_sized_new (size); + g_byte_array_append (bytes, data, size); + free (data); + g_vfs_job_open_for_read_set_can_seek (G_VFS_JOB_OPEN_FOR_READ (job), FALSE); + g_vfs_job_open_for_read_set_handle (G_VFS_JOB_OPEN_FOR_READ (job), bytes); + g_vfs_job_succeeded (G_VFS_JOB (job)); + } else { + LIBMTP_filesampledata_t *sample_data = LIBMTP_new_filesampledata_t (); + ret = LIBMTP_Get_Representative_Sample (G_VFS_BACKEND_MTP (backend)->device, + id, sample_data); + if (ret == 0) { + DEBUG ("File %u has sampledata: %u", id, size); + GByteArray *bytes = g_byte_array_sized_new (sample_data->size); + g_byte_array_append (bytes, (const guint8 *)sample_data->data, sample_data->size); + LIBMTP_destroy_filesampledata_t (sample_data); + g_vfs_job_open_for_read_set_can_seek (G_VFS_JOB_OPEN_FOR_READ (job), FALSE); + g_vfs_job_open_for_read_set_handle (G_VFS_JOB_OPEN_FOR_READ (job), bytes); + g_vfs_job_succeeded (G_VFS_JOB (job)); + } else { + DEBUG ("File %u has no thumbnail:", id); + g_vfs_job_failed (G_VFS_JOB (job), + G_IO_ERROR, + G_IO_ERROR_NOT_FOUND, + _("No thumbnail for entity '%s'"), + icon_id); + } + } + } else { + g_vfs_job_failed (G_VFS_JOB (job), + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Malformed icon identifier '%s'"), + icon_id); + } + g_mutex_unlock (&G_VFS_BACKEND_MTP (backend)->mutex); + + DEBUG ("(I) do_open_icon_for_read done."); +} + +static gboolean +try_read (GVfsBackend *backend, + GVfsJobRead *job, + GVfsBackendHandle handle, + char *buffer, + gsize bytes_requested) +{ + GByteArray *bytes = handle; + + DEBUG ("(I) try_read (%u %lu)", bytes->len, bytes_requested); + + gsize bytes_to_copy = MIN (bytes->len, bytes_requested); + if (bytes_to_copy == 0) { + goto out; + } + memcpy (buffer, bytes->data, bytes_to_copy); + g_byte_array_remove_range (bytes, 0, bytes_to_copy); + + out: + g_vfs_job_read_set_size (job, bytes_to_copy); + g_vfs_job_succeeded (G_VFS_JOB (job)); + + DEBUG ("(I) try_read done."); + return TRUE; +} + +static void +do_close_read (GVfsBackend *backend, + GVfsJobCloseRead *job, + GVfsBackendHandle handle) +{ + DEBUG ("(I) do_close_read"); + g_byte_array_unref (handle); + g_vfs_job_succeeded (G_VFS_JOB (job)); + DEBUG ("(I) do_close_read done."); +} +#endif /* HAVE_LIBMTP_GET_THUMBNAIL */ + + +/************************************************ + * Class init + * + */ + + +static void +g_vfs_backend_mtp_class_init (GVfsBackendMtpClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GVfsBackendClass *backend_class = G_VFS_BACKEND_CLASS (klass); + + gobject_class->finalize = g_vfs_backend_mtp_finalize; + + backend_class->mount = do_mount; + backend_class->unmount = do_unmount; + backend_class->query_info = do_query_info; + backend_class->enumerate = do_enumerate; + backend_class->query_fs_info = do_query_fs_info; + backend_class->pull = do_pull; + backend_class->push = do_push; + backend_class->make_directory = do_make_directory; + backend_class->delete = do_delete; + backend_class->set_display_name = do_set_display_name; + backend_class->create_dir_monitor = do_create_dir_monitor; + backend_class->create_file_monitor = do_create_file_monitor; +#if HAVE_LIBMTP_GET_THUMBNAIL + backend_class->open_icon_for_read = do_open_icon_for_read; + backend_class->try_read = try_read; + backend_class->close_read = do_close_read; +#endif +} diff --git a/daemon/gvfsbackendmtp.h b/daemon/gvfsbackendmtp.h new file mode 100644 index 0000000..93c1cb9 --- /dev/null +++ b/daemon/gvfsbackendmtp.h @@ -0,0 +1,69 @@ +/* GIO - GLib Input, Output and Streaming Library + * MTP Backend + * + * Copyright (C) 2012 Philip Langdale + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __G_VFS_BACKEND_MTP_H__ +#define __G_VFS_BACKEND_MTP_H__ + +#include +#include +#ifdef HAVE_GUDEV +#include +#endif +#include + +G_BEGIN_DECLS + +#define G_VFS_TYPE_BACKEND_MTP (g_vfs_backend_mtp_get_type ()) +#define G_VFS_BACKEND_MTP(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_VFS_TYPE_BACKEND_MTP, GVfsBackendMtp)) +#define G_VFS_BACKEND_MTP_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_VFS_TYPE_BACKEND_MTP, GVfsBackendMtpClass)) +#define G_VFS_IS_BACKEND_MTP(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_VFS_TYPE_BACKEND_MTP)) +#define G_VFS_IS_BACKEND_MTP_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_VFS_TYPE_BACKEND_MTP)) +#define G_VFS_BACKEND_MTP_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_VFS_TYPE_BACKEND_MTP, GVfsBackendMtpClass)) + +typedef struct _GVfsBackendMtp GVfsBackendMtp; +typedef struct _GVfsBackendMtpClass GVfsBackendMtpClass; + +struct _GVfsBackendMtp +{ + GVfsBackend parent_instance; + +#ifdef HAVE_GUDEV + GUdevClient *gudev_client; +#endif + + GMutex mutex; + LIBMTP_mtpdevice_t *device; + char *dev_path; + + GHashTable *monitors; + guint hb_id; +}; + +struct _GVfsBackendMtpClass +{ + GVfsBackendClass parent_class; +}; + +GType g_vfs_backend_mtp_get_type (void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* __G_VFS_BACKEND_MTP_H__ */ diff --git a/daemon/mtp.mount.in b/daemon/mtp.mount.in new file mode 100644 index 0000000..9f728a7 --- /dev/null +++ b/daemon/mtp.mount.in @@ -0,0 +1,4 @@ +[Mount] +Type=mtp +Exec=@libexecdir@/gvfsd-mtp +AutoMount=false diff --git a/monitor/Makefile.am b/monitor/Makefile.am index d903df1..7a4e87f 100644 --- a/monitor/Makefile.am +++ b/monitor/Makefile.am @@ -1,4 +1,4 @@ -DIST_SUBDIRS = proxy hal gdu gphoto2 afc udisks2 +DIST_SUBDIRS = proxy hal gdu gphoto2 afc udisks2 mtp SUBDIRS = proxy if USE_HAL @@ -20,3 +20,7 @@ endif if USE_AFC SUBDIRS += afc endif + +if USE_LIBMTP +SUBDIRS += mtp +endif diff --git a/monitor/gphoto2/ggphoto2volumemonitor.c b/monitor/gphoto2/ggphoto2volumemonitor.c index 64ef383..24811d4 100644 --- a/monitor/gphoto2/ggphoto2volumemonitor.c +++ b/monitor/gphoto2/ggphoto2volumemonitor.c @@ -201,6 +201,13 @@ gudev_add_camera (GGPhoto2VolumeMonitor *monitor, GUdevDevice *device, gboolean return; } #endif /* HAVE_AFC */ +#ifdef HAVE_LIBMTP + if (g_udev_device_get_property_as_boolean (device, "ID_MTP_DEVICE")) + { + /* g_debug ("ignoring device, is MTP"); */ + return; + } +#endif /* HAVE_LIBMTP */ usb_bus_num = g_udev_device_get_property (device, "BUSNUM"); if (usb_bus_num == NULL) { diff --git a/monitor/mtp/Makefile.am b/monitor/mtp/Makefile.am new file mode 100644 index 0000000..2f585c6 --- /dev/null +++ b/monitor/mtp/Makefile.am @@ -0,0 +1,51 @@ + +NULL = + +libexec_PROGRAMS = gvfs-mtp-volume-monitor + +gvfs_mtp_volume_monitor_SOURCES = + +gvfs_mtp_volume_monitor_SOURCES += \ + mtp-volume-monitor-daemon.c \ + gmtpvolume.c gmtpvolume.h \ + gmtpvolumemonitor.c gmtpvolumemonitor.h \ + $(NULL) + +gvfs_mtp_volume_monitor_CFLAGS = \ + -DG_LOG_DOMAIN=\"GVFS-MTP\" \ + -I$(top_srcdir)/common \ + -I$(top_srcdir)/monitor/proxy \ + $(GLIB_CFLAGS) \ + -DGIO_MODULE_DIR=\"$(GIO_MODULE_DIR)\" \ + -DGVFS_LOCALEDIR=\""$(localedir)"\" \ + -DG_UDEV_API_IS_SUBJECT_TO_CHANGE \ + $(NULL) + +gvfs_mtp_volume_monitor_CFLAGS += $(GUDEV_CFLAGS) + +gvfs_mtp_volume_monitor_LDFLAGS = \ + $(NULL) + +gvfs_mtp_volume_monitor_LDADD = \ + $(GLIB_LIBS) \ + $(top_builddir)/common/libgvfscommon.la \ + $(top_builddir)/monitor/proxy/libgvfsproxyvolumemonitordaemon-noin.la \ + $(NULL) + +gvfs_mtp_volume_monitor_LDADD += $(GUDEV_LIBS) + + +remote_volume_monitorsdir = $(datadir)/gvfs/remote-volume-monitors +remote_volume_monitors_DATA = mtp.monitor + +servicedir = $(datadir)/dbus-1/services +service_in_files = org.gtk.Private.MTPVolumeMonitor.service.in +service_DATA = $(service_in_files:.service.in=.service) + +$(service_DATA): $(service_in_files) Makefile + $(AM_V_GEN) $(SED) -e "s|\@libexecdir\@|$(libexecdir)|" $< > $@ + +clean-local: + rm -f *~ *.loT $(service_DATA) + +EXTRA_DIST = $(service_in_files) mtp.monitor diff --git a/monitor/mtp/gmtpvolume.c b/monitor/mtp/gmtpvolume.c new file mode 100644 index 0000000..9f44f84 --- /dev/null +++ b/monitor/mtp/gmtpvolume.c @@ -0,0 +1,433 @@ +/* GIO - GLib Input, Output and Streaming Library + * Volume Monitor for MTP Backend + * + * Copyright (C) 2012 Philip Langdale + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include + +#include +#include +#include + +#include +#include +#include + +#include "gmtpvolume.h" + +G_LOCK_DEFINE_STATIC (mtp_volume); + +struct _GMtpVolume { + GObject parent; + + GVolumeMonitor *volume_monitor; /* owned by volume monitor */ + + char *device_path; + GUdevDevice *device; + + GFile *activation_root; + + char *name; + char *icon; +}; + +static void g_mtp_volume_volume_iface_init (GVolumeIface *iface); + +G_DEFINE_TYPE_EXTENDED (GMtpVolume, g_mtp_volume, G_TYPE_OBJECT, 0, + G_IMPLEMENT_INTERFACE (G_TYPE_VOLUME, + g_mtp_volume_volume_iface_init)) + +static void +g_mtp_volume_finalize (GObject *object) +{ + GMtpVolume *volume; + + volume = G_MTP_VOLUME (object); + + if (volume->device != NULL) + g_object_unref (volume->device); + + if (volume->activation_root != NULL) + g_object_unref (volume->activation_root); + + if (volume->volume_monitor != NULL) + g_object_remove_weak_pointer (G_OBJECT (volume->volume_monitor), (gpointer) &(volume->volume_monitor)); + + g_free (volume->name); + g_free (volume->icon); + + if (G_OBJECT_CLASS (g_mtp_volume_parent_class)->finalize) + (*G_OBJECT_CLASS (g_mtp_volume_parent_class)->finalize) (object); +} + +static void +g_mtp_volume_class_init (GMtpVolumeClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = g_mtp_volume_finalize; +} + +static void +g_mtp_volume_init (GMtpVolume *mtp_volume) +{ +} + +static int hexdigit (char c) +{ + if (c >= 'a') + return c - 'a' + 10; + if (c >= 'A') + return c - 'A' + 10; + g_return_val_if_fail (c >= '0' && c <= '9', 0); + return c - '0'; +} + +/* Do not free result, it's a static buffer */ +static const char* +udev_decode_string (const char* encoded) +{ + static char decoded[4096]; + int len; + const char* s; + + if (encoded == NULL) + return NULL; + + for (len = 0, s = encoded; *s && len < sizeof (decoded) - 1; ++len, ++s) { + /* need to check for NUL terminator in advance */ + if (s[0] == '\\' && s[1] == 'x' && s[2] >= '0' && s[3] >= '0') { + decoded[len] = (hexdigit (s[2]) << 4) | hexdigit (s[3]); + s += 3; + } else { + decoded[len] = *s; + } + } + decoded[len] = '\0'; + return decoded; +} + +static void +set_volume_name (GMtpVolume *v) +{ + const char *gphoto_name; + const char *product = NULL; + const char *vendor; + const char *model; + + /* our preference: ID_MTP > ID_MEDIA_PLAYER_{VENDOR,PRODUCT} > product > + * ID_{VENDOR,MODEL} */ + + gphoto_name = g_udev_device_get_property (v->device, "ID_MTP"); + if (gphoto_name != NULL && strcmp (gphoto_name, "1") != 0) { + v->name = g_strdup (gphoto_name); + return; + } + + vendor = g_udev_device_get_property (v->device, "ID_MEDIA_PLAYER_VENDOR"); + if (vendor == NULL) + vendor = g_udev_device_get_property (v->device, "ID_VENDOR_ENC"); + model = g_udev_device_get_property (v->device, "ID_MEDIA_PLAYER_MODEL"); + if (model == NULL) { + model = g_udev_device_get_property (v->device, "ID_MODEL_ENC"); + product = g_udev_device_get_sysfs_attr (v->device, "product"); + } + + v->name = NULL; + if (product != NULL && strlen (product) > 0) { + v->name = g_strdup (product); + } else if (vendor == NULL) { + if (model != NULL) + v->name = g_strdup (udev_decode_string (model)); + } else { + if (model != NULL) { + /* we can't call udev_decode_string() twice in one g_strdup_printf(), + * it returns a static buffer */ + gchar *temp = g_strdup_printf ("%s %s", vendor, model); + v->name = g_strdup (udev_decode_string (temp)); + g_free (temp); + } else { + if (g_udev_device_has_property (v->device, "ID_MEDIA_PLAYER")) { + /* Translators: %s is the device vendor */ + v->name = g_strdup_printf (_("%s Audio Player"), udev_decode_string (vendor)); + } else { + /* Translators: %s is the device vendor */ + v->name = g_strdup_printf (_("%s Camera"), udev_decode_string (vendor)); + } + } + } + + if (v->name == NULL) + v->name = g_strdup (_("Camera")); +} + +static void +set_volume_icon (GMtpVolume *volume) +{ + if (g_udev_device_has_property (volume->device, "ID_MEDIA_PLAYER_ICON_NAME")) + volume->icon = g_strdup (g_udev_device_get_property (volume->device, "ID_MEDIA_PLAYER_ICON_NAME")); + else if (g_udev_device_has_property (volume->device, "ID_MEDIA_PLAYER")) + volume->icon = g_strdup ("multimedia-player"); + else + volume->icon = g_strdup ("camera-photo"); +} + +GMtpVolume * +g_mtp_volume_new (GVolumeMonitor *volume_monitor, + GUdevDevice *device, + GUdevClient *gudev_client, + GFile *activation_root) +{ + GMtpVolume *volume; + const char *device_path; + + g_return_val_if_fail (volume_monitor != NULL, NULL); + g_return_val_if_fail (device != NULL, NULL); + g_return_val_if_fail (gudev_client != NULL, NULL); + g_return_val_if_fail (activation_root != NULL, NULL); + + if (!g_udev_device_has_property (device, "ID_MTP_DEVICE")) + return NULL; + device_path = g_udev_device_get_device_file (device); + + volume = g_object_new (G_TYPE_MTP_VOLUME, NULL); + volume->volume_monitor = volume_monitor; + g_object_add_weak_pointer (G_OBJECT (volume_monitor), (gpointer) &(volume->volume_monitor)); + volume->device_path = g_strdup (device_path); + volume->device = g_object_ref (device); + volume->activation_root = g_object_ref (activation_root); + + set_volume_name (volume); + set_volume_icon (volume); + /* we do not really need to listen for changes */ + + return volume; +} + +void +g_mtp_volume_removed (GMtpVolume *volume) +{ +} + +static GIcon * +g_mtp_volume_get_icon (GVolume *volume) +{ + GMtpVolume *mtp_volume = G_MTP_VOLUME (volume); + GIcon *icon; + + G_LOCK (mtp_volume); + icon = g_themed_icon_new (mtp_volume->icon); + G_UNLOCK (mtp_volume); + return icon; +} + +static char * +g_mtp_volume_get_name (GVolume *volume) +{ + GMtpVolume *mtp_volume = G_MTP_VOLUME (volume); + char *name; + + G_LOCK (mtp_volume); + name = g_strdup (mtp_volume->name); + G_UNLOCK (mtp_volume); + + return name; +} + +static char * +g_mtp_volume_get_uuid (GVolume *volume) +{ + return NULL; +} + +static gboolean +g_mtp_volume_can_mount (GVolume *volume) +{ + return TRUE; +} + +static gboolean +g_mtp_volume_can_eject (GVolume *volume) +{ + return FALSE; +} + +static gboolean +g_mtp_volume_should_automount (GVolume *volume) +{ + return TRUE; +} + +static GDrive * +g_mtp_volume_get_drive (GVolume *volume) +{ + return NULL; +} + +static GMount * +g_mtp_volume_get_mount (GVolume *volume) +{ + return NULL; +} + +gboolean +g_mtp_volume_has_path (GMtpVolume *volume, + const char *sysfs_path) +{ + GMtpVolume *mtp_volume = G_MTP_VOLUME (volume); + gboolean res; + + G_LOCK (mtp_volume); + res = FALSE; + if (mtp_volume->device != NULL) + res = strcmp (g_udev_device_get_sysfs_path (mtp_volume->device), sysfs_path) == 0; + G_UNLOCK (mtp_volume); + return res; +} + +typedef struct +{ + GMtpVolume *enclosing_volume; + GAsyncReadyCallback callback; + gpointer user_data; +} ActivationMountOp; + +static void +mount_callback (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + ActivationMountOp *data = user_data; + data->callback (G_OBJECT (data->enclosing_volume), res, data->user_data); + g_free (data); +} + +static void +g_mtp_volume_mount (GVolume *volume, + GMountMountFlags flags, + GMountOperation *mount_operation, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GMtpVolume *mtp_volume = G_MTP_VOLUME (volume); + ActivationMountOp *data; + + /*g_warning ("mtp_volume_mount (can_mount=%d foreign=%p device_path=%s)", + g_mtp_volume_can_mount (volume), + mtp_volume->activation_root, + mtp_volume->device_path);*/ + + G_LOCK (mtp_volume); + + data = g_new0 (ActivationMountOp, 1); + data->enclosing_volume = mtp_volume; + data->callback = callback; + data->user_data = user_data; + + g_file_mount_enclosing_volume (mtp_volume->activation_root, + 0, + mount_operation, + cancellable, + mount_callback, + data); + + G_UNLOCK (mtp_volume); +} + +static gboolean +g_mtp_volume_mount_finish (GVolume *volume, + GAsyncResult *result, + GError **error) +{ + GMtpVolume *mtp_volume = G_MTP_VOLUME (volume); + gboolean res; + + G_LOCK (mtp_volume); + res = g_file_mount_enclosing_volume_finish (mtp_volume->activation_root, result, error); + G_UNLOCK (mtp_volume); + + return res; +} + +static char * +g_mtp_volume_get_identifier (GVolume *volume, + const char *kind) +{ + GMtpVolume *mtp_volume = G_MTP_VOLUME (volume); + char *id; + + G_LOCK (mtp_volume); + id = NULL; + if (strcmp (kind, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE) == 0) + id = g_strdup (mtp_volume->device_path); + G_UNLOCK (mtp_volume); + + return id; +} + +static char ** +g_mtp_volume_enumerate_identifiers (GVolume *volume) +{ + GMtpVolume *mtp_volume = G_MTP_VOLUME (volume); + GPtrArray *res; + + G_LOCK (mtp_volume); + + res = g_ptr_array_new (); + + if (mtp_volume->device_path && *mtp_volume->device_path != 0) + g_ptr_array_add (res, + g_strdup (G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE)); + + /* Null-terminate */ + g_ptr_array_add (res, NULL); + + G_UNLOCK (mtp_volume); + + return (char **)g_ptr_array_free (res, FALSE); +} + +static GFile * +g_mtp_volume_get_activation_root (GVolume *volume) +{ + GMtpVolume *mtp_volume = G_MTP_VOLUME (volume); + + return g_object_ref (mtp_volume->activation_root); +} + +static void +g_mtp_volume_volume_iface_init (GVolumeIface *iface) +{ + iface->get_name = g_mtp_volume_get_name; + iface->get_icon = g_mtp_volume_get_icon; + iface->get_uuid = g_mtp_volume_get_uuid; + iface->get_drive = g_mtp_volume_get_drive; + iface->get_mount = g_mtp_volume_get_mount; + iface->can_mount = g_mtp_volume_can_mount; + iface->can_eject = g_mtp_volume_can_eject; + iface->should_automount = g_mtp_volume_should_automount; + iface->mount_fn = g_mtp_volume_mount; + iface->mount_finish = g_mtp_volume_mount_finish; + iface->eject = NULL; + iface->eject_finish = NULL; + iface->get_identifier = g_mtp_volume_get_identifier; + iface->enumerate_identifiers = g_mtp_volume_enumerate_identifiers; + iface->get_activation_root = g_mtp_volume_get_activation_root; +} diff --git a/monitor/mtp/gmtpvolume.h b/monitor/mtp/gmtpvolume.h new file mode 100644 index 0000000..a1d2e6b --- /dev/null +++ b/monitor/mtp/gmtpvolume.h @@ -0,0 +1,59 @@ +/* GIO - GLib Input, Output and Streaming Library + * Volume Monitor for MTP Backend + * + * Copyright (C) 2012 Philip Langdale + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __G_MTP_VOLUME_H__ +#define __G_MTP_VOLUME_H__ + +#include +#include + +#include +#include "gmtpvolumemonitor.h" + +G_BEGIN_DECLS + +#define G_TYPE_MTP_VOLUME (g_mtp_volume_get_type ()) +#define G_MTP_VOLUME(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_MTP_VOLUME, GMtpVolume)) +#define G_MTP_VOLUME_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_MTP_VOLUME, GMtpVolumeClass)) +#define G_IS_MTP_VOLUME(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_MTP_VOLUME)) +#define G_IS_MTP_VOLUME_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_MTP_VOLUME)) + +typedef struct _GMtpVolumeClass GMtpVolumeClass; + +struct _GMtpVolumeClass { + GObjectClass parent_class; +}; + +GType g_mtp_volume_get_type (void) G_GNUC_CONST; + +GMtpVolume *g_mtp_volume_new (GVolumeMonitor *volume_monitor, + GUdevDevice *device, + GUdevClient *gudev_client, + GFile *activation_root); + +gboolean g_mtp_volume_has_path (GMtpVolume *volume, + const char *path); + +void g_mtp_volume_removed (GMtpVolume *volume); + +G_END_DECLS + +#endif /* __G_MTP_VOLUME_H__ */ diff --git a/monitor/mtp/gmtpvolumemonitor.c b/monitor/mtp/gmtpvolumemonitor.c new file mode 100644 index 0000000..f6d1abb --- /dev/null +++ b/monitor/mtp/gmtpvolumemonitor.c @@ -0,0 +1,329 @@ +/* GIO - GLib Input, Output and Streaming Library + * Volume Monitor for MTP Backend + * + * Copyright (C) 2012 Philip Langdale + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include + +#include +#include +#include + +#include +#include +#include + +#include "gmtpvolumemonitor.h" +#include "gmtpvolume.h" + +#include + +G_LOCK_DEFINE_STATIC(vm_lock); + +static GMtpVolumeMonitor *the_volume_monitor = NULL; + +struct _GMtpVolumeMonitor { + GNativeVolumeMonitor parent; + + GUnixMountMonitor *mount_monitor; + + GUdevClient *gudev_client; + + GList *last_devices; + + GList *device_volumes; +}; + +static void on_uevent (GUdevClient *client, + gchar *action, + GUdevDevice *device, + gpointer user_data); + +G_DEFINE_TYPE (GMtpVolumeMonitor, g_mtp_volume_monitor, G_TYPE_VOLUME_MONITOR) + +static void +list_free (GList *objects) +{ + g_list_foreach (objects, (GFunc)g_object_unref, NULL); + g_list_free (objects); +} + +static void +g_mtp_volume_monitor_dispose (GObject *object) +{ + G_LOCK (vm_lock); + the_volume_monitor = NULL; + G_UNLOCK (vm_lock); + + if (G_OBJECT_CLASS (g_mtp_volume_monitor_parent_class)->dispose) + (*G_OBJECT_CLASS (g_mtp_volume_monitor_parent_class)->dispose) (object); +} + +static void +g_mtp_volume_monitor_finalize (GObject *object) +{ + GMtpVolumeMonitor *monitor; + + monitor = G_MTP_VOLUME_MONITOR (object); + + g_signal_handlers_disconnect_by_func (monitor->gudev_client, on_uevent, monitor); + + g_object_unref (monitor->gudev_client); + + list_free (monitor->last_devices); + list_free (monitor->device_volumes); + + if (G_OBJECT_CLASS (g_mtp_volume_monitor_parent_class)->finalize) + (*G_OBJECT_CLASS (g_mtp_volume_monitor_parent_class)->finalize) (object); +} + +static GList * +get_mounts (GVolumeMonitor *volume_monitor) +{ + return NULL; +} + +static GList * +get_volumes (GVolumeMonitor *volume_monitor) +{ + GMtpVolumeMonitor *monitor; + GList *l; + + monitor = G_MTP_VOLUME_MONITOR (volume_monitor); + + G_LOCK (vm_lock); + + l = g_list_copy (monitor->device_volumes); + g_list_foreach (l, (GFunc)g_object_ref, NULL); + + G_UNLOCK (vm_lock); + + return l; +} + +static GList * +get_connected_drives (GVolumeMonitor *volume_monitor) +{ + return NULL; +} + +static GVolume * +get_volume_for_uuid (GVolumeMonitor *volume_monitor, const char *uuid) +{ + return NULL; +} + +static GMount * +get_mount_for_uuid (GVolumeMonitor *volume_monitor, const char *uuid) +{ + return NULL; +} + +static void +gudev_add_device (GMtpVolumeMonitor *monitor, GUdevDevice *device, gboolean do_emit) +{ + GMtpVolume *volume; + const char *usb_bus_num, *usb_device_num, *uri; + GFile *activation_mount_root; + + usb_bus_num = g_udev_device_get_property (device, "BUSNUM"); + if (usb_bus_num == NULL) { + g_warning ("device %s has no BUSNUM property, ignoring", g_udev_device_get_device_file (device)); + return; + } + + usb_device_num = g_udev_device_get_property (device, "DEVNUM"); + if (usb_device_num == NULL) { + g_warning ("device %s has no DEVNUM property, ignoring", g_udev_device_get_device_file (device)); + return; + } + + g_debug ("gudev_add_device: device %s (bus: %i, device: %i)", + g_udev_device_get_device_file (device), + usb_bus_num, usb_device_num); + + uri = g_strdup_printf ("mtp://[usb:%s,%s]", usb_bus_num, usb_device_num); + activation_mount_root = g_file_new_for_uri (uri); + g_free (uri); + + volume = g_mtp_volume_new (G_VOLUME_MONITOR (monitor), + device, + monitor->gudev_client, + activation_mount_root); + if (volume != NULL) { + monitor->device_volumes = g_list_prepend (monitor->device_volumes, volume); + if (do_emit) + g_signal_emit_by_name (monitor, "volume_added", volume); + } + + if (activation_mount_root != NULL) + g_object_unref (activation_mount_root); +} + +static void +gudev_remove_device (GMtpVolumeMonitor *monitor, GUdevDevice *device) +{ + GList *l, *ll; + const gchar* sysfs_path; + + sysfs_path = g_udev_device_get_sysfs_path (device); + + g_debug ("gudev_remove_device: %s", g_udev_device_get_device_file (device)); + + for (l = monitor->device_volumes; l != NULL; l = ll) { + GMtpVolume *volume = G_MTP_VOLUME (l->data); + + ll = l->next; + + if (g_mtp_volume_has_path (volume, sysfs_path)) { + g_debug ("gudev_remove_device: found volume %s, deleting", sysfs_path); + g_signal_emit_by_name (monitor, "volume_removed", volume); + g_signal_emit_by_name (volume, "removed"); + g_mtp_volume_removed (volume); + monitor->device_volumes = g_list_remove (monitor->device_volumes, volume); + g_object_unref (volume); + } + } +} + +static void +on_uevent (GUdevClient *client, gchar *action, GUdevDevice *device, gpointer user_data) +{ + GMtpVolumeMonitor *monitor = G_MTP_VOLUME_MONITOR (user_data); + + g_debug ("on_uevent: action=%s, device=%s", action, g_udev_device_get_device_file(device)); + + /* filter out uninteresting events */ + if (!g_udev_device_has_property (device, "ID_MTP_DEVICE")) + { + g_debug ("on_uevent: discarding, not ID_MTP"); + return; + } + + if (strcmp (action, "add") == 0) + gudev_add_device (monitor, device, TRUE); + else if (strcmp (action, "remove") == 0) + gudev_remove_device (monitor, device); +} + +static void +gudev_coldplug_devices (GMtpVolumeMonitor *monitor) +{ + GList *usb_devices, *l; + + usb_devices = g_udev_client_query_by_subsystem (monitor->gudev_client, "usb"); + for (l = usb_devices; l != NULL; l = l->next) { + GUdevDevice *d = l->data; + if (g_udev_device_has_property (d, "ID_MTP_DEVICE")) + gudev_add_device (monitor, d, FALSE); + } +} + +static GObject * +g_mtp_volume_monitor_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GObject *object; + GMtpVolumeMonitor *monitor; + GMtpVolumeMonitorClass *klass; + GObjectClass *parent_class; + + G_LOCK (vm_lock); + if (the_volume_monitor != NULL) { + object = g_object_ref (the_volume_monitor); + G_UNLOCK (vm_lock); + return object; + } + G_UNLOCK (vm_lock); + + /*g_warning ("creating vm singleton");*/ + + object = NULL; + + /* Invoke parent constructor. */ + klass = G_MTP_VOLUME_MONITOR_CLASS (g_type_class_peek (G_TYPE_MTP_VOLUME_MONITOR)); + parent_class = G_OBJECT_CLASS (g_type_class_peek_parent (klass)); + object = parent_class->constructor (type, + n_construct_properties, + construct_properties); + + monitor = G_MTP_VOLUME_MONITOR (object); + + const char *subsystems[] = { "usb", NULL }; + monitor->gudev_client = g_udev_client_new (subsystems); + + g_signal_connect (monitor->gudev_client, + "uevent", G_CALLBACK (on_uevent), + monitor); + + gudev_coldplug_devices (monitor); + + G_LOCK (vm_lock); + the_volume_monitor = monitor; + G_UNLOCK (vm_lock); + + return object; +} + +static void +g_mtp_volume_monitor_init (GMtpVolumeMonitor *monitor) +{ +} + +static gboolean +is_supported (void) +{ + /* Today's Linux desktops pretty much need udev to have anything working, so + * assume it's there */ + return TRUE; +} + +static void +g_mtp_volume_monitor_class_init (GMtpVolumeMonitorClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GVolumeMonitorClass *monitor_class = G_VOLUME_MONITOR_CLASS (klass); + + gobject_class->constructor = g_mtp_volume_monitor_constructor; + gobject_class->finalize = g_mtp_volume_monitor_finalize; + gobject_class->dispose = g_mtp_volume_monitor_dispose; + + monitor_class->get_mounts = get_mounts; + monitor_class->get_volumes = get_volumes; + monitor_class->get_connected_drives = get_connected_drives; + monitor_class->get_volume_for_uuid = get_volume_for_uuid; + monitor_class->get_mount_for_uuid = get_mount_for_uuid; + monitor_class->is_supported = is_supported; +} + +/** + * g_mtp_volume_monitor_new: + * + * Returns: a new #GVolumeMonitor. + **/ +GVolumeMonitor * +g_mtp_volume_monitor_new (void) +{ + GMtpVolumeMonitor *monitor; + + monitor = g_object_new (G_TYPE_MTP_VOLUME_MONITOR, NULL); + + return G_VOLUME_MONITOR (monitor); +} diff --git a/monitor/mtp/gmtpvolumemonitor.h b/monitor/mtp/gmtpvolumemonitor.h new file mode 100644 index 0000000..0a36a9b --- /dev/null +++ b/monitor/mtp/gmtpvolumemonitor.h @@ -0,0 +1,53 @@ +/* GIO - GLib Input, Output and Streaming Library + * Volume Monitor for MTP Backend + * + * Copyright (C) 2012 Philip Langdale + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __G_MTP_VOLUME_MONITOR_H__ +#define __G_MTP_VOLUME_MONITOR_H__ + +#include +#include + +G_BEGIN_DECLS + +#define G_TYPE_MTP_VOLUME_MONITOR (g_mtp_volume_monitor_get_type ()) +#define G_MTP_VOLUME_MONITOR(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_MTP_VOLUME_MONITOR, GMtpVolumeMonitor)) +#define G_MTP_VOLUME_MONITOR_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_MTP_VOLUME_MONITOR, GMtpVolumeMonitorClass)) +#define G_IS_MTP_VOLUME_MONITOR(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_MTP_VOLUME_MONITOR)) +#define G_IS_MTP_VOLUME_MONITOR_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_MTP_VOLUME_MONITOR)) + +typedef struct _GMtpVolumeMonitor GMtpVolumeMonitor; +typedef struct _GMtpVolumeMonitorClass GMtpVolumeMonitorClass; + +/* Forward definitions */ +typedef struct _GMtpVolume GMtpVolume; + +struct _GMtpVolumeMonitorClass { + GVolumeMonitorClass parent_class; +}; + +GType g_mtp_volume_monitor_get_type (void) G_GNUC_CONST; + +GVolumeMonitor *g_mtp_volume_monitor_new (void); +void g_mtp_volume_monitor_force_update (GMtpVolumeMonitor *monitor); + +G_END_DECLS + +#endif /* __G_MTP_VOLUME_MONITOR_H__ */ diff --git a/monitor/mtp/mtp-volume-monitor-daemon.c b/monitor/mtp/mtp-volume-monitor-daemon.c new file mode 100644 index 0000000..2019940 --- /dev/null +++ b/monitor/mtp/mtp-volume-monitor-daemon.c @@ -0,0 +1,36 @@ +/* GIO - GLib Input, Output and Streaming Library + * Volume Monitor for MTP Backend + * + * Copyright (C) 2012 Philip Langdale + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include + +#include + +#include "gmtpvolumemonitor.h" + +int +main (int argc, char *argv[]) +{ + g_vfs_proxy_volume_monitor_daemon_init (); + return g_vfs_proxy_volume_monitor_daemon_main (argc, + argv, + "org.gtk.Private.MTPVolumeMonitor", + G_TYPE_MTP_VOLUME_MONITOR); +} diff --git a/monitor/mtp/mtp.monitor b/monitor/mtp/mtp.monitor new file mode 100644 index 0000000..bfb0c7f --- /dev/null +++ b/monitor/mtp/mtp.monitor @@ -0,0 +1,4 @@ +[RemoteVolumeMonitor] +Name=GProxyVolumeMonitorMTP +DBusName=org.gtk.Private.MTPVolumeMonitor +IsNative=false diff --git a/monitor/mtp/org.gtk.Private.MTPVolumeMonitor.service.in b/monitor/mtp/org.gtk.Private.MTPVolumeMonitor.service.in new file mode 100644 index 0000000..4cd7d19 --- /dev/null +++ b/monitor/mtp/org.gtk.Private.MTPVolumeMonitor.service.in @@ -0,0 +1,3 @@ +[D-BUS Service] +Name=org.gtk.Private.MTPVolumeMonitor +Exec=@libexecdir@/gvfs-mtp-volume-monitor diff --git a/programs/gvfs-ls.c b/programs/gvfs-ls.c index b15cf91..2c45273 100644 --- a/programs/gvfs-ls.c +++ b/programs/gvfs-ls.c @@ -78,7 +78,7 @@ type_to_string (GFileType type) static void show_info (GFileInfo *info) { - const char *name, *type; + const char *name, *display_name, *type; goffset size; char **attributes; int i; @@ -91,12 +91,16 @@ show_info (GFileInfo *info) if (name == NULL) name = ""; + display_name = g_file_info_get_display_name (info); + if (display_name == NULL) + display_name = name; + size = g_file_info_get_size (info); type = type_to_string (g_file_info_get_file_type (info)); if (show_long) - g_print ("%s\t%"G_GUINT64_FORMAT"\t(%s)", name, (guint64)size, type); + g_print ("%s\t%s\t\t\t%"G_GUINT64_FORMAT"\t(%s)", name, display_name, (guint64)size, type); else - g_print ("%s", name); + g_print ("%s", display_name); first_attr = TRUE; attributes = g_file_info_list_attributes (info, NULL); @@ -106,6 +110,7 @@ show_info (GFileInfo *info) if (!show_long || strcmp (attributes[i], G_FILE_ATTRIBUTE_STANDARD_NAME) == 0 || + strcmp (attributes[i], G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME) == 0 || strcmp (attributes[i], G_FILE_ATTRIBUTE_STANDARD_SIZE) == 0 || strcmp (attributes[i], G_FILE_ATTRIBUTE_STANDARD_TYPE) == 0 || strcmp (attributes[i], G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN) == 0) @@ -426,6 +431,7 @@ main (int argc, char *argv[]) } attributes = g_strconcat (G_FILE_ATTRIBUTE_STANDARD_NAME "," + G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME "," G_FILE_ATTRIBUTE_STANDARD_TYPE "," G_FILE_ATTRIBUTE_STANDARD_SIZE "," G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN, diff --git a/gvfs/po/POTFILES.in b/gvfs/po/POTFILES.in index 07e478e..5e2f567 100644 --- a/gvfs/po/POTFILES.in +++ b/gvfs/po/POTFILES.in @@ -32,6 +32,7 @@ daemon/gvfsbackendftp.c daemon/gvfsbackendgphoto2.c daemon/gvfsbackendhttp.c daemon/gvfsbackendlocaltest.c +daemon/gvfsbackendmtp.c daemon/gvfsbackendnetwork.c daemon/gvfsbackendobexftp.c daemon/gvfsbackendrecent.c