summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhilip Langdale <philipl@overt.org>2013-01-11 20:34:25 -0800
committerPhilip Langdale <philipl@overt.org>2013-01-11 20:34:25 -0800
commit55ef4a2a84731f6e87f9d5983ef8473269df4f65 (patch)
tree7dad96be030335b9fcd77f8019809f3aab9782e6
parent4cd213d4db7c77de6788cb8d790babcc0dd6daa0 (diff)
parent66015d2669f4f393ab9fb0fbf734c6f137a56cb4 (diff)
downloadgvfs-55ef4a2a84731f6e87f9d5983ef8473269df4f65.tar.gz
Merge branch 'mtp-backend'
This merge brings in the libmtp based backend for MTP devices. Previously, MTP devices were handled, with limited success by the GPhoto2 backend as MTP is nominally backwards compatible with PTP. The most serious limitation was that the GPhoto2 backend operated in a way that doesn't work reliably with Android 4.x based devices. This problem is rectified by the new MTP backend.
-rw-r--r--configure.ac36
-rw-r--r--daemon/.gitignore1
-rw-r--r--daemon/Makefile.am19
-rw-r--r--daemon/gvfsbackendmtp.c1536
-rw-r--r--daemon/gvfsbackendmtp.h70
-rw-r--r--daemon/mtp.mount.in4
-rw-r--r--monitor/Makefile.am6
-rw-r--r--monitor/gphoto2/ggphoto2volumemonitor.c7
-rw-r--r--monitor/mtp/.gitignore1
-rw-r--r--monitor/mtp/Makefile.am51
-rw-r--r--monitor/mtp/gmtpvolume.c433
-rw-r--r--monitor/mtp/gmtpvolume.h59
-rw-r--r--monitor/mtp/gmtpvolumemonitor.c330
-rw-r--r--monitor/mtp/gmtpvolumemonitor.h53
-rw-r--r--monitor/mtp/mtp-volume-monitor-daemon.c36
-rw-r--r--monitor/mtp/mtp.monitor4
-rw-r--r--monitor/mtp/org.gtk.Private.MTPVolumeMonitor.service.in3
17 files changed, 2648 insertions, 1 deletions
diff --git a/configure.ac b/configure.ac
index 9897a5f0..d2df1f02 100644
--- a/configure.ac
+++ b/configure.ac
@@ -493,6 +493,40 @@ 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
+
+AC_SUBST(LIBMTP_CFLAGS)
+AC_SUBST(LIBMTP_LIBS)
+AM_CONDITIONAL(USE_LIBMTP, [test "$msg_libmtp" = "yes"])
+
dnl ==========================================================================
dnl Samba 3.0
@@ -830,6 +864,7 @@ monitor/gdu/Makefile
monitor/udisks2/Makefile
monitor/gphoto2/Makefile
monitor/afc/Makefile
+monitor/mtp/Makefile
programs/Makefile
man/Makefile
test/Makefile
@@ -850,6 +885,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/.gitignore b/daemon/.gitignore
index 3c50dbcf..0b3b322c 100644
--- a/daemon/.gitignore
+++ b/daemon/.gitignore
@@ -11,6 +11,7 @@ gvfsd-gphoto2
gvfsd-http
gvfsd-localtest
gvfsd-network
+gvfsd-mtp
gvfsd-obexftp
gvfsd-sftp
gvfsd-smb
diff --git a/daemon/Makefile.am b/daemon/Makefile.am
index 3a1f684f..24622a17 100644
--- a/daemon/Makefile.am
+++ b/daemon/Makefile.am
@@ -82,6 +82,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
@@ -448,6 +454,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 = \
gvfshttpinputstream.c gvfshttpinputstream.h \
gvfsbackendhttp.c gvfsbackendhttp.h \
diff --git a/daemon/gvfsbackendmtp.c b/daemon/gvfsbackendmtp.c
new file mode 100644
index 00000000..121115a4
--- /dev/null
+++ b/daemon/gvfsbackendmtp.c
@@ -0,0 +1,1536 @@
+/* GIO - GLib Input, Output and Streaming Library
+ * MTP Backend
+ *
+ * Copyright (C) 2012 Philip Langdale <philipl@overt.org>
+ *
+ * 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 <config.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <glib/gstdio.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+
+#include <libmtp.h>
+
+#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 (NULL, NULL);
+
+ 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);
+
+ (*G_OBJECT_CLASS (g_vfs_backend_mtp_parent_class)->finalize) (object);
+
+ DEBUG ("(I) g_vfs_backend_mtp_finalize done.");
+}
+
+
+/************************************************
+ * Monitors
+ ************************************************/
+
+/**
+ * do_create_dir_monitor:
+ *
+ * Called with backend mutex lock held.
+ */
+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_add (mtp_backend->monitors, vfs_monitor);
+ 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.");
+}
+
+
+/**
+ * do_create_file_monitor:
+ *
+ * Called with backend mutex lock held.
+ */
+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_add (mtp_backend->monitors, vfs_monitor);
+ 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)
+{
+ GWeakRef *event_ref = user_data;
+
+ LIBMTP_event_t event;
+ int ret = 0;
+ while (ret == 0) {
+ uint32_t param1;
+ char *path;
+ GVfsBackendMtp *backend;
+
+ backend = g_weak_ref_get (event_ref);
+ if (backend && !g_atomic_int_get (&backend->unmount_started)) {
+ LIBMTP_mtpdevice_t *device = backend->device;
+ g_object_unref (backend);
+ /*
+ * Unavoidable race. We can't hold a reference when
+ * calling Read_Event as it blocks while waiting and
+ * we can't interrupt it in any sane way, so it would
+ * end up preventing finalization of the backend.
+ */
+ ret = LIBMTP_Read_Event (device, &event, &param1);
+ } else {
+ return NULL;
+ }
+
+ switch (event) {
+ case LIBMTP_EVENT_STORE_ADDED:
+ backend = g_weak_ref_get (event_ref);
+ if (backend && !g_atomic_int_get (&backend->unmount_started)) {
+ 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);
+ g_object_unref (backend);
+ break;
+ } else {
+ return NULL;
+ }
+ 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 char *
+get_dev_path_from_host (GVfsJob *job,
+ GUdevClient *gudev_client,
+ const char *host)
+{
+ /* turn usb:001,041 string into an udev device name */
+ if (!g_str_has_prefix (host, "[usb:")) {
+ g_vfs_job_failed_literal (G_VFS_JOB (job), G_IO_ERROR,
+ G_IO_ERROR_NOT_SUPPORTED,
+ _("Unexpected host uri format."));
+ return NULL;
+ }
+
+ 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_literal (G_VFS_JOB (job), G_IO_ERROR,
+ G_IO_ERROR_NOT_SUPPORTED,
+ _("Malformed host uri."));
+ return NULL;
+ }
+ *comma = '/';
+ dev_path[strlen (dev_path) -1] = '\0';
+ DEBUG ("(II) get_dev_path_from_host: Parsed '%s' into device name %s", host, dev_path);
+
+ /* find corresponding GUdevDevice */
+ GUdevDevice *device = g_udev_client_query_by_device_file (gudev_client, dev_path);
+ if (!device) {
+ g_free (dev_path);
+ g_vfs_job_failed_literal (G_VFS_JOB (job),
+ G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+ _("Couldn't find matching udev device."));
+ return NULL;
+ }
+ g_object_unref (device);
+
+ return dev_path;
+}
+
+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) {
+ g_vfs_job_failed_literal (G_VFS_JOB (job), G_IO_ERROR,
+ G_IO_ERROR_FAILED, _("No device specified"));
+ return;
+ }
+
+ const char *subsystems[] = {"usb", NULL};
+ op_backend->gudev_client = g_udev_client_new (subsystems);
+ if (op_backend->gudev_client == NULL) {
+ g_vfs_job_failed_literal (G_VFS_JOB (job), G_IO_ERROR,
+ G_IO_ERROR_FAILED, _("Cannot create gudev client"));
+ return;
+ }
+
+ char *dev_path = get_dev_path_from_host (G_VFS_JOB (job), op_backend->gudev_client, host);
+ if (dev_path == NULL) {
+ g_object_unref (op_backend->gudev_client);
+ /* get_dev_path_from_host() sets job state. */
+ return;
+ }
+ op_backend->dev_path = dev_path;
+
+ g_signal_connect (op_backend->gudev_client, "uevent", G_CALLBACK (on_uevent), op_backend);
+
+ 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
+ GWeakRef *event_ref = g_new0 (GWeakRef, 1);
+ g_weak_ref_init (event_ref, backend);
+ GThread *event_thread = g_thread_new ("events", check_event, event_ref);
+ /*
+ * We don't need our ref to the thread, as the libmtp semantics mean
+ * that in the normal case, the thread will block forever when we are
+ * cleanining up before termination, so we can never join the thread.
+ */
+ g_thread_unref (event_thread);
+#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_mutex_lock (&op_backend->mutex);
+
+ g_atomic_int_set (&op_backend->unmount_started, TRUE);
+
+ g_source_remove (op_backend->hb_id);
+ g_object_unref (op_backend->gudev_client);
+ g_free (op_backend->dev_path);
+ LIBMTP_Release_Device (op_backend->device);
+
+ g_mutex_unlock (&op_backend->mutex);
+
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+
+ DEBUG ("(I) do_umount done.");
+}
+
+
+/************************************************
+ * Queries
+ *
+ */
+
+
+/**
+ * get_device:
+ *
+ * Called with backend mutex lock held.
+ */
+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_literal (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_literal (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_literal (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_literal (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);
+ g_free (name);
+ break;
+ } else {
+ g_free (name);
+ }
+ }
+
+ exit:
+ DEBUG ("(II) get_device done.");
+ return device;
+}
+
+
+/**
+ * get_device_info:
+ *
+ * Called with backend mutex lock held.
+ */
+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_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.");
+}
+
+
+/**
+ * get_storage_info:
+ *
+ * Called with backend mutex lock held.
+ */
+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.");
+}
+
+
+/**
+ * get_file_info:
+ *
+ * Called with backend mutex lock held.
+ */
+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)) {
+
+ GIcon *preview;
+ char *icon_id;
+ GMountSpec *mount_spec;
+
+ mount_spec = g_vfs_backend_get_mount_spec (backend);
+ icon_id = g_strdup_printf ("%u", file->item_id);
+ preview = g_vfs_icon_new (mount_spec,
+ icon_id);
+ g_file_info_set_attribute_object (info,
+ G_FILE_ATTRIBUTE_PREVIEW_ICON,
+ G_OBJECT (preview));
+ g_object_unref (preview);
+ 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 = g_strv_length (elements);
+
+ 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.");
+}
+
+
+/**
+ * get_file_for_filename:
+ *
+ * Get the entity ID for an element given its filename and
+ * the IDs of its parents.
+ *
+ * Called with backend mutex lock held.
+ */
+static LIBMTP_file_t *
+get_file_for_filename (LIBMTP_mtpdevice_t *device,
+ gchar **elements,
+ unsigned int i)
+{
+ LIBMTP_file_t *file = NULL;
+
+ DEBUG ("(III) get_file_for_filename (element %d '%s') ", i, elements[i]);
+ long parent_id = -1;
+ if (i > 2) {
+ parent_id = strtol (elements[i - 1], NULL, 10);
+ }
+ LIBMTP_file_t *f = LIBMTP_Get_Files_And_Folders (device, strtol (elements[1], NULL, 10),
+ parent_id);
+ while (f != NULL) {
+ DEBUG_ENUMERATE ("(III) query (entity = %s, name = %s) ", f->filename, elements[i]);
+ if (strcmp (f->filename, elements[i]) == 0) {
+ file = f;
+ f = f->next;
+ break;
+ } else {
+ LIBMTP_file_t *tmp = f;
+ f = f->next;
+ LIBMTP_destroy_file_t (tmp);
+ }
+ }
+ while (f != NULL) {
+ LIBMTP_file_t *tmp = f;
+ f = f->next;
+ LIBMTP_destroy_file_t (tmp);
+ }
+ DEBUG ("(III) get_file_for_filename done");
+ return file;
+}
+
+
+/**
+ * normalize_elements:
+ *
+ * Take a set of path elements and turn any file/directory names into
+ * MTP entity IDs.
+ *
+ * Called with backend mutex lock held.
+ */
+static void
+normalize_elements (LIBMTP_mtpdevice_t *device,
+ gchar **elements,
+ unsigned int ne)
+{
+ DEBUG ("(II) normalize_elements (ne = %d)", ne);
+ if (ne < 3) {
+ /* In these cases, elements are always normal. */
+ return;
+ }
+
+ unsigned int i;
+ for (i = 2; i < ne; i++) {
+ LIBMTP_file_t *file = NULL;
+ char *endptr;
+ long file_id = strtol (elements[i], &endptr, 10);
+
+ if (file_id == 0 || *endptr != '\0') {
+ file = get_file_for_filename(device, elements, i);
+ if (file == NULL) {
+ /* Missing entity. Cannot normalize. */
+ DEBUG ("(II) Cannot normalize missing entity '%s'", elements[i]);
+ continue;
+ } else {
+ char *item_id = g_strdup_printf ("%d", file->item_id);
+ DEBUG ("(II) %s = %s", elements[i], item_id);
+ g_free (elements[i]);
+ elements[i] = item_id;
+ LIBMTP_destroy_file_t (file);
+ }
+ } else {
+ /* Already normal. */
+ DEBUG ("(II) normal entity '%s'", elements[i]);
+ continue;
+ }
+ }
+ DEBUG ("(II) normalize_elements 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 = g_strv_length (elements);
+
+ 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_literal (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 ("(I) found storage %u", storage->id);
+ get_storage_info (storage, info);
+ }
+ }
+ } else {
+ LIBMTP_file_t *file = NULL;
+ char *endptr;
+ long file_id = strtol (elements[ne - 1], &endptr, 10);
+
+ if (file_id == 0 || *endptr != '\0') {
+ file = get_file_for_filename (device, elements, ne - 1);
+ if (file == NULL) {
+ /* The backup query might have found nothing. */
+ DEBUG ("(I) get_file_for_filename could not find '%s'",
+ elements[ne - 1]);
+ g_vfs_job_failed_literal (G_VFS_JOB (job),
+ G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+ _("File not found"));
+ goto exit;
+ }
+ } else {
+ file = LIBMTP_Get_Filemetadata (device, file_id);
+ }
+
+ 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 = g_strv_length (elements);
+
+ 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_literal (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 = g_strv_length (elements);
+
+ if (ne < 3) {
+ g_vfs_job_failed_literal (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;
+
+ /*
+ * Might be called as part of a batch copy of a nested directory hierarchy.
+ * New directories would then be referred to by name and not id.
+ */
+ normalize_elements(device, elements, ne - 1);
+
+ 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 = g_strv_length (elements);
+
+ if (ne < 3) {
+ g_vfs_job_failed_literal (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_literal (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);
+ if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) {
+ g_vfs_job_failed_literal (G_VFS_JOB (job),
+ G_IO_ERROR, G_IO_ERROR_WOULD_RECURSE,
+ _("Can't recursively copy directory"));
+ goto exit;
+ } else {
+ MtpProgressData mtp_progress_data;
+ 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);
+ if (ret != 0) {
+ fail_job (G_VFS_JOB (job), device);
+ goto exit;
+ }
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+ }
+
+ exit:
+ g_clear_object (&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 = g_strv_length (elements);
+
+ if (ne < 3) {
+ g_vfs_job_failed_literal (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;
+
+ /*
+ * Might be called as part of a batch copy of a nested directory hierarchy.
+ * New files would then be referred to by name and not id.
+ */
+ normalize_elements(device, elements, ne - 1);
+
+ int parent_id = 0;
+
+ if (ne > 3) {
+ parent_id = strtol (elements[ne-2], NULL, 10);
+ }
+
+ file = g_file_new_for_path (local_path);
+ g_assert(file);
+
+ if (g_file_query_file_type (file, G_FILE_QUERY_INFO_NONE,
+ G_VFS_JOB (job)->cancellable) ==
+ G_FILE_TYPE_DIRECTORY) {
+ g_vfs_job_failed_literal (G_VFS_JOB (job),
+ G_IO_ERROR, G_IO_ERROR_WOULD_RECURSE,
+ _("Can't recursively copy directory"));
+ goto exit;
+ }
+
+ 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;
+ 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);
+ 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:
+ g_clear_object (&file);
+ g_clear_object (&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 = g_strv_length (elements);
+
+ if (ne < 3) {
+ g_vfs_job_failed_literal (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 = g_strv_length (elements);
+
+ if (ne < 3) {
+ g_vfs_job_failed_literal (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 00000000..9dbaa0c4
--- /dev/null
+++ b/daemon/gvfsbackendmtp.h
@@ -0,0 +1,70 @@
+/* GIO - GLib Input, Output and Streaming Library
+ * MTP Backend
+ *
+ * Copyright (C) 2012 Philip Langdale <philipl@overt.org>
+ *
+ * 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 <gvfsbackend.h>
+#include <gmountspec.h>
+#ifdef HAVE_GUDEV
+#include <gudev/gudev.h>
+#endif
+#include <libmtp.h>
+
+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;
+ gint unmount_started;
+};
+
+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 00000000..9f728a73
--- /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 d903df1f..7a4e87fc 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 64ef3838..24811d45 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/.gitignore b/monitor/mtp/.gitignore
new file mode 100644
index 00000000..0bf331b1
--- /dev/null
+++ b/monitor/mtp/.gitignore
@@ -0,0 +1 @@
+gvfs-mtp-volume-monitor
diff --git a/monitor/mtp/Makefile.am b/monitor/mtp/Makefile.am
new file mode 100644
index 00000000..2f585c65
--- /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 00000000..07d8674a
--- /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 <philipl@overt.org>
+ * - Based on ggphoto2volume.c
+ * - Copyright (C) 2006-2007 Red Hat, Inc.
+ *
+ * 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 <config.h>
+
+#include <string.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+#include <gio/gio.h>
+
+#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);
+
+ g_clear_object (&volume->device);
+ g_clear_object (&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);
+
+ (*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)
+{
+ int len;
+ const char* s;
+ char *decoded;
+
+ if (encoded == NULL)
+ return NULL;
+
+ decoded = g_malloc(4096);
+
+ 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_strconcat (vendor, " ", model, NULL);
+ 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 00000000..a1d2e6b9
--- /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 <philipl@overt.org>
+ *
+ * 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 <glib-object.h>
+#include <gio/gio.h>
+
+#include <gudev/gudev.h>
+#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 00000000..bf483fbc
--- /dev/null
+++ b/monitor/mtp/gmtpvolumemonitor.c
@@ -0,0 +1,330 @@
+/* GIO - GLib Input, Output and Streaming Library
+ * Volume Monitor for MTP Backend
+ *
+ * Copyright (C) 2012 Philip Langdale <philipl@overt.org>
+ * - Based on ggphoto2volume.c
+ * - Copyright (C) 2006-2007 Red Hat, Inc.
+ *
+ * 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 <config.h>
+
+#include <limits.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+#include <gio/gio.h>
+
+#include "gmtpvolumemonitor.h"
+#include "gmtpvolume.h"
+
+#include <gio/gunixmounts.h>
+
+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_free_full (objects, g_object_unref);
+}
+
+static void
+g_mtp_volume_monitor_dispose (GObject *object)
+{
+ G_LOCK (vm_lock);
+ the_volume_monitor = NULL;
+ G_UNLOCK (vm_lock);
+
+ (*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);
+
+ (*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;
+ char *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: %s, device: %s)",
+ 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);
+ }
+ g_list_free_full(usb_devices, g_object_unref);
+}
+
+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 00000000..0a36a9b4
--- /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 <philipl@overt.org>
+ *
+ * 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 <glib-object.h>
+#include <gio/gio.h>
+
+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 00000000..20199409
--- /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 <philipl@overt.org>
+ *
+ * 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 <config.h>
+
+#include <gvfsproxyvolumemonitordaemon.h>
+
+#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 00000000..bfb0c7fe
--- /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 00000000..4cd7d190
--- /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