Blob Blame Raw
From 8675a60e030fc91292d19cfd2cd9a6d74dd1776f Mon Sep 17 00:00:00 2001
From: Bastien Nocera <hadess@hadess.net>
Date: Thu, 6 Aug 2009 22:55:47 +0100
Subject: [PATCH] Add AFC backend

Add a backend based on libiphone to access data on Apple's iPhone,
and iPod Touch.

Code by:
Patrick Walton <pcwalton@cs.ucla.edu>
Martin Szulecki <opensuse@sukimashita.com>
Nikias Bassen <nikias@gmx.li>
Bastien Nocera <hadess@hadess.net>
---
 configure.ac                                       |   26 +-
 daemon/Makefile.am                                 |   23 +
 daemon/afc.mount.in                                |    7 +
 daemon/gvfsbackendafc.c                            | 1228 ++++++++++++++++++++
 daemon/gvfsbackendafc.h                            |   37 +
 monitor/Makefile.am                                |    6 +-
 monitor/afc/Makefile.am                            |   49 +
 monitor/afc/afc.monitor                            |    5 +
 monitor/afc/afcvolume.c                            |  353 ++++++
 monitor/afc/afcvolume.h                            |   44 +
 monitor/afc/afcvolumemonitor.c                     |  224 ++++
 monitor/afc/afcvolumemonitor.h                     |   39 +
 monitor/afc/afcvolumemonitordaemon.c               |   31 +
 .../org.gtk.Private.AfcVolumeMonitor.service.in    |    4 +
 14 files changed, 2074 insertions(+), 2 deletions(-)
 create mode 100644 daemon/afc.mount.in
 create mode 100644 daemon/gvfsbackendafc.c
 create mode 100644 daemon/gvfsbackendafc.h
 create mode 100644 monitor/afc/Makefile.am
 create mode 100644 monitor/afc/afc.monitor
 create mode 100644 monitor/afc/afcvolume.c
 create mode 100644 monitor/afc/afcvolume.h
 create mode 100644 monitor/afc/afcvolumemonitor.c
 create mode 100644 monitor/afc/afcvolumemonitor.h
 create mode 100644 monitor/afc/afcvolumemonitordaemon.c
 create mode 100644 monitor/afc/org.gtk.Private.AfcVolumeMonitor.service.in

diff --git a/configure.ac b/configure.ac
index 9bfb8ad..6b3838c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -324,6 +324,28 @@ AC_SUBST(CDDA_CFLAGS)
 
 AM_CONDITIONAL(USE_CDDA, [test "$msg_cdda" = "yes"])
 
+dnl *************************************************
+dnl *** Check if we should build with AFC backend ***
+dnl *************************************************
+AC_ARG_ENABLE(afc, [  --disable-afc           build without AFC backend])
+msg_afc=no
+AFC_LIBS=
+AFC_CFLAGS=
+
+if test "x$enable_afc" != "xno" -a "x$msg_gudev" = "xyes" ; then
+  PKG_CHECK_EXISTS(libiphone-1.0 >= 0.9.2, msg_afc=yes)
+
+  if test "x$msg_afc" = "xyes"; then
+    PKG_CHECK_MODULES(AFC, libiphone-1.0 gudev-1.0)
+    AC_DEFINE(HAVE_AFC, 1, [Define to 1 if AFC is going to be built])
+  fi
+fi
+
+AC_SUBST(AFC_LIBS)
+AC_SUBST(AFC_CFLAGS)
+
+AM_CONDITIONAL(USE_AFC, [test "$msg_afc" = "yes"])
+
 dnl *****************************************************
 dnl *** Check if we should build with obexftp backend ***
 dnl *****************************************************
@@ -695,6 +717,7 @@ monitor/proxy/Makefile
 monitor/hal/Makefile
 monitor/gdu/Makefile
 monitor/gphoto2/Makefile
+monitor/afc/Makefile
 gconf/Makefile
 programs/Makefile
 test/Makefile
@@ -712,7 +735,8 @@ echo "
 	FUSE support:                 $msg_fuse
         CDDA support:                 $msg_cdda
         Gphoto2 support:              $msg_gphoto2
-	archive support:	      $msg_archive
+	archive support:              $msg_archive
+	AFC support:                  $msg_afc
         GConf support:                $msg_gconf
         DNS-SD support:               $msg_avahi
 	Build HAL volume monitor:     $msg_hal (with fast init path: $have_hal_fast_init)
diff --git a/daemon/Makefile.am b/daemon/Makefile.am
index a03eba1..cf68391 100644
--- a/daemon/Makefile.am
+++ b/daemon/Makefile.am
@@ -101,6 +101,12 @@ mount_DATA += archive.mount
 libexec_PROGRAMS += gvfsd-archive
 endif
 
+mount_in_files += afc.mount.in
+if USE_AFC
+mount_DATA += afc.mount
+libexec_PROGRAMS += gvfsd-afc
+endif
+
 EXTRA_DIST = gvfs-daemon.service.in $(mount_in_files) obexftp-marshal.list
 
 DISTCLEANFILES = gvfs-daemon.service $(mount_DATA)
@@ -442,3 +448,20 @@ gvfsd_dav_LDADD = $(libraries) $(HTTP_LIBS)
 if HAVE_AVAHI
 gvfsd_dav_LDADD += $(top_builddir)/common/libgvfscommon-dnssd.la
 endif
+
+gvfsd_afc_SOURCES = \
+	gvfsbackendafc.c gvfsbackendafc.h \
+	daemon-main.c daemon-main.h \
+	daemon-main-generic.c
+
+gvfsd_afc_CPPFLAGS = \
+	-DBACKEND_HEADER=gvfsbackendafc.h \
+	-DDEFAULT_BACKEND_TYPE=afc \
+	-DMAX_JOB_THREADS=1 \
+	$(AFC_CFLAGS) \
+	-DBACKEND_TYPES='"afc", G_VFS_TYPE_BACKEND_AFC,'
+
+gvfsd_afc_LDADD = \
+	$(libraries) \
+	$(AFC_LIBS)
+
diff --git a/daemon/afc.mount.in b/daemon/afc.mount.in
new file mode 100644
index 0000000..727d833
--- /dev/null
+++ b/daemon/afc.mount.in
@@ -0,0 +1,7 @@
+[Mount]
+Type=afc
+Exec=@libexecdir@/gvfsd-afc
+AutoMount=false
+Scheme=afc
+DefaultPort=1
+
diff --git a/daemon/gvfsbackendafc.c b/daemon/gvfsbackendafc.c
new file mode 100644
index 0000000..d30ede8
--- /dev/null
+++ b/daemon/gvfsbackendafc.c
@@ -0,0 +1,1228 @@
+/*
+ * gvfs/daemon/gvfsbackendafc.c
+ *
+ * Copyright (c) 2008 Patrick Walton <pcwalton@cs.ucla.edu>
+ */
+
+#include <config.h>
+
+#include <limits.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <glib/gi18n.h>
+#include <errno.h>
+
+#include <gudev/gudev.h>
+
+#include <libiphone/libiphone.h>
+#include <libiphone/lockdown.h>
+#include <libiphone/afc.h>
+
+#include "gvfsbackendafc.h"
+#include "gvfsjobopenforread.h"
+#include "gvfsjobread.h"
+#include "gvfsjobseekread.h"
+#include "gvfsjobopenforwrite.h"
+#include "gvfsjobwrite.h"
+#include "gvfsjobseekwrite.h"
+#include "gvfsjobsetdisplayname.h"
+#include "gvfsjobqueryinfo.h"
+#include "gvfsjobqueryfsinfo.h"
+#include "gvfsjobqueryattributes.h"
+#include "gvfsjobenumerate.h"
+#include "gvfsdaemonprotocol.h"
+#include "gvfsdaemonutils.h"
+
+#define G_VFS_BACKEND_AFC_MAX_FILE_SIZE G_MAXINT64
+int g_blocksize = 4096; /* assume this is the default block size */
+
+struct _GVfsBackendAfc {
+  GVfsBackend backend;
+
+  GUdevClient *client;
+
+  char uuid[41];
+  char *service;
+  char *model;
+  gboolean connected;
+
+  iphone_device_t dev;
+  afc_client_t afc_cli;
+};
+
+struct afc_error_mapping {
+  afc_error_t from;
+  GIOErrorEnum to;
+};
+
+static struct afc_error_mapping afc_error_to_g_io_error[] = {
+      { AFC_E_UNKNOWN_ERROR            , G_IO_ERROR_FAILED },
+      { AFC_E_OP_HEADER_INVALID        , G_IO_ERROR_FAILED },
+      { AFC_E_NO_RESOURCES             , G_IO_ERROR_TOO_MANY_OPEN_FILES },
+      { AFC_E_READ_ERROR               , G_IO_ERROR_NOT_DIRECTORY },
+      { AFC_E_WRITE_ERROR              , G_IO_ERROR_FAILED },
+      { AFC_E_UNKNOWN_PACKET_TYPE      , G_IO_ERROR_FAILED },
+      { AFC_E_INVALID_ARGUMENT         , G_IO_ERROR_INVALID_ARGUMENT },
+      { AFC_E_OBJECT_NOT_FOUND         , G_IO_ERROR_NOT_FOUND },
+      { AFC_E_OBJECT_IS_DIR            , G_IO_ERROR_IS_DIRECTORY },
+      { AFC_E_DIR_NOT_EMPTY            , G_IO_ERROR_NOT_EMPTY },
+      { AFC_E_PERM_DENIED              , G_IO_ERROR_PERMISSION_DENIED },
+      { AFC_E_SERVICE_NOT_CONNECTED    , G_IO_ERROR_HOST_NOT_FOUND },
+      { AFC_E_OP_TIMEOUT               , G_IO_ERROR_TIMED_OUT },
+      { AFC_E_TOO_MUCH_DATA            , G_IO_ERROR_FAILED },
+      { AFC_E_END_OF_DATA              , G_IO_ERROR_FAILED },
+      { AFC_E_OP_NOT_SUPPORTED         , G_IO_ERROR_NOT_SUPPORTED },
+      { AFC_E_OBJECT_EXISTS            , G_IO_ERROR_EXISTS },
+      { AFC_E_OBJECT_BUSY              , G_IO_ERROR_BUSY },
+      { AFC_E_NO_SPACE_LEFT            , G_IO_ERROR_NO_SPACE },
+      { AFC_E_OP_WOULD_BLOCK           , G_IO_ERROR_WOULD_BLOCK },
+      { AFC_E_IO_ERROR                 , G_IO_ERROR_FAILED },
+      { AFC_E_OP_INTERRUPTED           , G_IO_ERROR_CANCELLED },
+      { AFC_E_OP_IN_PROGRESS           , G_IO_ERROR_PENDING },
+      { AFC_E_INTERNAL_ERROR           , G_IO_ERROR_FAILED },
+      { AFC_E_NOT_ENOUGH_DATA          , G_IO_ERROR_CLOSED },
+      { AFC_E_MUX_ERROR                , G_IO_ERROR_FAILED },
+      { -1 }
+};
+
+/**
+ * Tries to convert the AFC error value into a GIOError.
+ *
+ * @param client AFC client to retrieve status value from.
+ *
+ * @return errno value.
+ */
+static GIOErrorEnum
+g_io_error_from_afc_error (afc_error_t error)
+{
+  GIOErrorEnum res = G_IO_ERROR_FAILED;
+  int i = 0; gboolean found = FALSE;
+
+  while (afc_error_to_g_io_error[i++].from != -1)
+    {
+      if (afc_error_to_g_io_error[i].from == error)
+        {
+          res = afc_error_to_g_io_error[i++].to;
+          found = TRUE;
+          break;
+        }
+    }
+
+  if (!found)
+    g_warning ("Unknown AFC error (%d).\n", error);
+
+  return res;
+}
+
+G_DEFINE_TYPE(GVfsBackendAfc, g_vfs_backend_afc, G_VFS_TYPE_BACKEND)
+
+static void
+g_vfs_backend_afc_close_connection (GVfsBackendAfc *self)
+{
+  if (self->connected)
+    {
+      afc_client_free (self->afc_cli);
+      g_free (self->model);
+      self->model = NULL;
+      iphone_device_free (self->dev);
+    }
+  self->connected = FALSE;
+}
+
+static int
+g_vfs_backend_afc_check (afc_error_t cond, GVfsJob *job)
+{
+  GIOErrorEnum error;
+
+  if (G_LIKELY(cond == AFC_E_SUCCESS))
+        return 0;
+
+  error = g_io_error_from_afc_error (cond);
+  switch (cond)
+    {
+    case AFC_E_INTERNAL_ERROR:
+      g_vfs_job_failed (job, G_IO_ERROR, error,
+                        _("Internal Apple File Control error"));
+      break;
+    case AFC_E_OBJECT_NOT_FOUND:
+      g_vfs_job_failed (job, G_IO_ERROR, error,
+                        _("File does not exist"));
+    case AFC_E_DIR_NOT_EMPTY:
+      g_vfs_job_failed (job, G_IO_ERROR, error,
+                        _("The directory is not empty"));
+      break;
+    case AFC_E_OP_TIMEOUT:
+      g_vfs_job_failed (job, G_IO_ERROR, error,
+                        _("The device did not respond"));
+      break;
+    case AFC_E_NOT_ENOUGH_DATA:
+      g_vfs_job_failed (job, G_IO_ERROR, error,
+                        _("The connection was interrupted"));
+      break;
+    case AFC_E_MUX_ERROR:
+      g_vfs_job_failed (job, G_IO_ERROR, error,
+                        _("Invalid Apple File Control data received"));
+      break;
+    default:
+      g_vfs_job_failed (job, G_IO_ERROR, error,
+                        _("Unhandled Apple File Control error (%d)"), cond);
+      break;
+    }
+
+  return 1;
+}
+
+static int
+g_vfs_backend_lockdownd_check (lockdownd_error_t cond, GVfsJob *job)
+{
+  if (G_LIKELY(cond == LOCKDOWN_E_SUCCESS))
+        return 0;
+
+  switch (cond)
+    {
+    case LOCKDOWN_E_INVALID_ARG:
+      g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
+                        _("Lockdown Error: Invalid Argument"));
+      break;
+    default:
+      g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
+                        _("Unhandled Lockdown error (%d)"), cond);
+      break;
+    }
+
+  return 1;
+}
+
+static int
+g_vfs_backend_iphone_check (iphone_error_t cond, GVfsJob *job)
+{
+  if (G_LIKELY(cond == IPHONE_E_SUCCESS))
+        return 0;
+
+  switch (cond)
+    {
+    case IPHONE_E_INVALID_ARG:
+      g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
+                        _("iPhone Device Error: Invalid Argument"));
+      break;
+    case IPHONE_E_NO_DEVICE:
+      g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
+                        _("iPhone Device Error: No device found. Make sure usbmuxd is set up correctly."));
+    default:
+      g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
+                        _("Unhandled iPhone Device error (%d)"), cond);
+      break;
+    }
+
+  return 1;
+}
+
+static void
+_uevent_cb (GUdevClient  *client,
+            const gchar  *action,
+            GUdevDevice  *device,
+            gpointer      user_data)
+{
+  GVfsBackendAfc *afc_backend = G_VFS_BACKEND_AFC (user_data);
+  const char *uuid;
+
+  g_return_if_fail (afc_backend->uuid != NULL);
+
+  if (g_str_equal (action, "remove") == FALSE)
+    return;
+  uuid = g_udev_device_get_property (device, "ID_SERIAL_SHORT");
+  if (uuid == NULL ||
+      g_str_equal (uuid, afc_backend->uuid) == FALSE)
+    return;
+
+  g_print ("Shutting down AFC backend for device uuid %s\n", afc_backend->uuid);
+
+  g_vfs_backend_afc_close_connection (afc_backend);
+
+  /* TODO: need a cleaner way to force unmount ourselves */
+  exit (1);
+}
+
+/* Callback for mounting. */
+static void
+g_vfs_backend_afc_mount (GVfsBackend *backend,
+                         GVfsJobMount *job,
+                         GMountSpec *spec,
+                         GMountSource *src,
+                         gboolean automounting)
+{
+  const char *str;
+  char *tmp;
+  char *display_name;
+  int port, virtual_port;
+  GMountSpec *real_spec;
+  GVfsBackendAfc *self;
+  int retries;
+  iphone_error_t err;
+  lockdownd_client_t lockdown_cli = NULL;
+  const gchar * const subsystems[] = { "usb", NULL };
+
+  self = G_VFS_BACKEND_AFC(backend);
+  self->connected = FALSE;
+  self->client = g_udev_client_new (subsystems);
+  g_signal_connect (G_OBJECT (self->client), "uevent",
+                    G_CALLBACK (_uevent_cb), self);
+
+  /* setup afc */
+
+  str = g_mount_spec_get(spec, "host");
+  if (G_UNLIKELY(str == NULL))
+    {
+      g_vfs_job_failed (G_VFS_JOB (job),
+                        G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
+                        _("Invalid mount spec"));
+      return;
+    }
+  if (G_UNLIKELY(sscanf(str, "%40s", &self->uuid) < 1))
+    {
+      g_vfs_job_failed (G_VFS_JOB(job), G_IO_ERROR, G_IO_ERROR_FAILED,
+                        _("Invalid AFC location: must be in the form of "
+                          "afc://uuid:port-number"));
+      return;
+    }
+
+  str = g_mount_spec_get (spec, "port");
+  if (str == NULL)
+    virtual_port = 1;
+  else
+    virtual_port = atoi (str);
+
+  /* set a generic display name */
+  if (virtual_port >= 2)
+    {
+      self->service = g_strdup_printf ("com.apple.afc%d", virtual_port);
+      display_name = g_strdup_printf (_("Service %d on Apple Mobile Device"),
+                                      virtual_port);
+    }
+  else
+    {
+      self->service = g_strdup ("com.apple.afc");
+      display_name = g_strdup_printf (_("Apple Mobile Device"));
+    }
+
+  g_vfs_backend_set_display_name (G_VFS_BACKEND(self), display_name);
+  g_free (display_name);
+
+  real_spec = g_mount_spec_new ("afc");
+  tmp = g_strdup_printf ("%40s", &self->uuid);
+  g_mount_spec_set (real_spec, "host", tmp);
+  g_free (tmp);
+
+  /* INFO: Don't ever set the DefaultPort again or everything goes crazy */
+  if (virtual_port != 1)
+    {
+      tmp = g_strdup_printf ("%d", virtual_port);
+      g_mount_spec_set (real_spec, "port", tmp);
+      g_free (tmp);
+    }
+
+  g_vfs_backend_set_mount_spec (G_VFS_BACKEND(self), real_spec);
+  g_mount_spec_unref (real_spec);
+
+  retries = 0;
+  do {
+      err = iphone_get_device_by_uuid(&self->dev, self->uuid);
+      if (err == IPHONE_E_SUCCESS)
+          break;
+      g_usleep (G_USEC_PER_SEC);
+  } while (retries++ < 10);
+
+  if (G_UNLIKELY(g_vfs_backend_iphone_check(err, G_VFS_JOB(job))))
+    goto out_destroy_service;
+  if (G_UNLIKELY(g_vfs_backend_lockdownd_check (lockdownd_client_new (self->dev,
+                                                                      &lockdown_cli),
+                                                G_VFS_JOB(job))))
+    {
+      goto out_destroy_dev;
+    }
+
+  /* try to use pretty device name */
+  if (LOCKDOWN_E_SUCCESS == lockdownd_get_device_name (lockdown_cli, &display_name))
+    {
+      if (display_name)
+        {
+          if (virtual_port >= 2)
+            {
+              /* translators:
+               * This is the device name, with the service being browsed in brackets, eg.:
+               * Alan Smithee's iPhone (Service 2 on Apple Mobile Device */
+              g_vfs_backend_set_display_name (G_VFS_BACKEND(self),
+                                              g_strdup_printf (_("%s (%s)"), display_name, self->service));
+            }
+          else
+            {
+              g_vfs_backend_set_display_name (G_VFS_BACKEND(self), display_name);
+            }
+          g_free (display_name);
+      }
+    }
+
+  if (G_UNLIKELY(g_vfs_backend_lockdownd_check (lockdownd_start_service (lockdown_cli,
+                                                                         self->service, &port),
+                                                G_VFS_JOB(job))))
+    {
+      goto out_destroy_lockdown;
+    }
+  if (G_UNLIKELY(g_vfs_backend_afc_check (afc_client_new (self->dev,
+                                                          port, &self->afc_cli),
+                                          G_VFS_JOB(job))))
+    {
+      goto out_destroy_lockdown;
+    }
+
+  /* set correct fd icon spec name depending on device model */
+  self->model = afc_get_device_info_field (self->afc_cli, "Model");
+  if (G_UNLIKELY(self->model == NULL))
+    goto out_destroy_afc;
+
+  if (strstr(self->model, "iPod") != NULL)
+    {
+      g_vfs_backend_set_icon_name (G_VFS_BACKEND(self), "multimedia-player-apple-ipod-touch");
+    }
+  else
+    {
+      g_vfs_backend_set_icon_name (G_VFS_BACKEND(self), "phone-apple-iphone");
+    }
+
+  /* lockdown connection is not needed anymore */
+  lockdownd_client_free (lockdown_cli);
+
+  self->connected = TRUE;
+  g_vfs_job_succeeded (G_VFS_JOB(job));
+  return;
+
+out_destroy_afc:
+  afc_client_free (self->afc_cli);
+
+out_destroy_lockdown:
+  lockdownd_client_free (lockdown_cli);
+
+out_destroy_dev:
+  iphone_device_free (self->dev);
+
+out_destroy_service:
+  g_free (self->service);
+  g_free(self->model);
+}
+
+static void
+g_vfs_backend_afc_unmount (GVfsBackend *backend,
+                           GVfsJobUnmount *job,
+                           GMountUnmountFlags flags,
+                           GMountSource *mount_source)
+{
+  GVfsBackendAfc *self;
+
+  /* FIXME: check on G_MOUNT_UNMOUNT_FORCE flag */
+  self = G_VFS_BACKEND_AFC (backend);
+  g_vfs_backend_afc_close_connection (self);
+  g_vfs_job_succeeded (G_VFS_JOB(job));
+}
+
+/* Callback to open an existing file for reading. */
+static void
+g_vfs_backend_afc_open_for_read (GVfsBackend *backend,
+                                 GVfsJobOpenForRead *job,
+                                 const char *path)
+{
+  uint64_t fd = 0;
+  GVfsBackendAfc *self;
+
+  self = G_VFS_BACKEND_AFC(backend);
+  g_return_if_fail (self->connected);
+
+  if (G_UNLIKELY(g_vfs_backend_afc_check (afc_file_open (self->afc_cli,
+                                                         path, AFC_FOPEN_RDONLY, &fd),
+                                          G_VFS_JOB(job))))
+    {
+      return;
+    }
+
+  g_vfs_job_open_for_read_set_handle (job, GUINT_TO_POINTER((gulong) fd));
+  g_vfs_job_open_for_read_set_can_seek (job, TRUE);
+  g_vfs_job_succeeded (G_VFS_JOB(job));
+
+  return;
+}
+
+/* Callback to open a nonexistent file for writing. */
+static void
+g_vfs_backend_afc_create (GVfsBackend *backend,
+                          GVfsJobOpenForWrite *job,
+                          const char *path,
+                          GFileCreateFlags flags)
+{
+  uint64_t fd = 0;
+  GVfsBackendAfc *self;
+
+  self = G_VFS_BACKEND_AFC(backend);
+  g_return_if_fail (self->connected);
+
+  if (G_UNLIKELY(g_vfs_backend_afc_check (afc_file_open (self->afc_cli,
+                                                         path, AFC_FOPEN_RW, &fd),
+                                          G_VFS_JOB(job))))
+    {
+      return;
+    }
+
+  g_vfs_job_open_for_write_set_handle (job, GUINT_TO_POINTER((gulong)fd));
+  g_vfs_job_open_for_write_set_can_seek (job, TRUE);
+  g_vfs_job_succeeded (G_VFS_JOB(job));
+
+  return;
+}
+
+/* Callback to open a possibly-existing file for writing. */
+static void
+g_vfs_backend_afc_append_to (GVfsBackend *backend,
+                             GVfsJobOpenForWrite *job,
+                             const char *path,
+                             GFileCreateFlags flags)
+{
+  uint64_t fd = 0;
+  uint64_t off = 0;
+  GVfsBackendAfc *self;
+
+  self = G_VFS_BACKEND_AFC(backend);
+  g_return_if_fail (self->connected);
+
+  if (G_UNLIKELY(g_vfs_backend_afc_check (afc_file_open (self->afc_cli,
+                                                         path, AFC_FOPEN_RW, &fd),
+                                          G_VFS_JOB(job))))
+    {
+      return;
+    }
+
+  if (G_UNLIKELY(g_vfs_backend_afc_check (afc_file_seek (self->afc_cli,
+                                                         fd, 0, SEEK_END),
+                                          G_VFS_JOB(job))))
+    {
+      return;
+    }
+
+  if (G_UNLIKELY(g_vfs_backend_afc_check (afc_file_tell (self->afc_cli,
+                                                         fd, &off),
+                                          G_VFS_JOB(job))))
+    {
+      return;
+    }
+
+  g_vfs_job_open_for_write_set_handle (job, GUINT_TO_POINTER((gulong)fd));
+  g_vfs_job_open_for_write_set_can_seek (job, TRUE);
+  g_vfs_job_open_for_write_set_initial_offset (job, off);
+  g_vfs_job_succeeded (G_VFS_JOB(job));
+
+  return;
+}
+
+static void
+g_vfs_backend_afc_replace (GVfsBackend *backend,
+                           GVfsJobOpenForWrite *job,
+                           const char *filename,
+                           const char *etag,
+                           gboolean make_backup,
+                           GFileCreateFlags flags)
+{
+  uint64_t fd = 0;
+  GVfsBackendAfc *self;
+
+  self = G_VFS_BACKEND_AFC(backend);
+  g_return_if_fail(self->connected);
+
+  if (make_backup)
+    {
+      /* FIXME: implement! */
+      g_vfs_job_failed (G_VFS_JOB (job),
+                        G_IO_ERROR,
+                        G_IO_ERROR_CANT_CREATE_BACKUP,
+                        _("Backups are not yet supported."));
+      return;
+    }
+
+  if (G_UNLIKELY(g_vfs_backend_afc_check (afc_file_open (self->afc_cli,
+                                                         filename, AFC_FOPEN_WR, &fd),
+                                          G_VFS_JOB(job))))
+    {
+      return;
+    }
+
+  g_vfs_job_open_for_write_set_handle (job, GUINT_TO_POINTER((gulong)fd));
+  g_vfs_job_open_for_write_set_can_seek (job, TRUE);
+  g_vfs_job_succeeded (G_VFS_JOB(job));
+
+  return;
+}
+
+/* Callback to close a file that was previously opened for reading. */
+static void
+g_vfs_backend_afc_close_read (GVfsBackend *backend,
+                              GVfsJobCloseRead *job,
+                              GVfsBackendHandle handle)
+{
+  GVfsBackendAfc *self;
+  uint64_t fd = 0;
+
+  fd = GPOINTER_TO_UINT(handle);
+  g_return_if_fail (fd != 0);
+
+  self = G_VFS_BACKEND_AFC(backend);
+
+  if (self->connected)
+    afc_file_close (self->afc_cli, fd);
+
+  g_vfs_job_succeeded (G_VFS_JOB(job));
+}
+
+static void
+g_vfs_backend_afc_close_write (GVfsBackend *backend,
+                               GVfsJobCloseWrite *job,
+                               GVfsBackendHandle handle)
+{
+  GVfsBackendAfc *self;
+  uint64_t fd = 0;
+
+  fd = GPOINTER_TO_UINT(handle);
+  g_return_if_fail (fd != 0);
+
+  self = G_VFS_BACKEND_AFC(backend);
+
+  if (self->connected)
+    afc_file_close(self->afc_cli, fd);
+
+  g_vfs_job_succeeded (G_VFS_JOB(job));
+}
+
+static void
+g_vfs_backend_afc_read (GVfsBackend *backend,
+                        GVfsJobRead *job,
+                        GVfsBackendHandle handle,
+                        char *buffer,
+                        gsize req)
+{
+  guint32 nread = 0;
+  GVfsBackendAfc *self;
+  uint64_t fd = 0;
+
+  fd = GPOINTER_TO_UINT(handle);
+  g_return_if_fail (fd != 0);
+
+  self = G_VFS_BACKEND_AFC(backend);
+  g_return_if_fail (self->connected);
+
+  if (req > 0 &&
+      G_UNLIKELY(g_vfs_backend_afc_check (afc_file_read (self->afc_cli,
+                                                         fd, buffer, req, &nread),
+                                          G_VFS_JOB(job))))
+    {
+      return;
+    }
+
+  g_vfs_job_read_set_size (job, nread);
+  g_vfs_job_succeeded (G_VFS_JOB(job));
+}
+
+static void
+g_vfs_backend_afc_write (GVfsBackend *backend,
+                         GVfsJobWrite *job,
+                         GVfsBackendHandle handle,
+                         char *buffer,
+                         gsize sz)
+{
+  guint32 nwritten = 0;
+  GVfsBackendAfc *self;
+  uint64_t fd = 0;
+
+  fd = GPOINTER_TO_UINT(handle);
+  g_return_if_fail (fd != 0);
+
+  self = G_VFS_BACKEND_AFC(backend);
+  g_return_if_fail (self->connected);
+
+  if (sz > 0 &&
+      G_UNLIKELY(g_vfs_backend_afc_check(afc_file_write (self->afc_cli,
+                                                         fd, buffer, sz, &nwritten),
+                                         G_VFS_JOB(job))))
+    {
+      return;
+    }
+
+  g_vfs_job_write_set_written_size (job, nwritten);
+  g_vfs_job_succeeded (G_VFS_JOB(job));
+}
+
+static int
+g_vfs_backend_afc_seek (GVfsBackendAfc *self,
+                        GVfsJob *job,
+                        GVfsBackendHandle handle,
+                        goffset offset,
+                        GSeekType type)
+{
+  int afc_seek_type;
+  uint64_t fd = 0;
+
+  switch (type)
+    {
+    case G_SEEK_SET:
+      afc_seek_type = SEEK_SET;
+      break;
+    case G_SEEK_CUR:
+      afc_seek_type = SEEK_CUR;
+      break;
+    case G_SEEK_END:
+      afc_seek_type = SEEK_END;
+      break;
+    default:
+      g_vfs_job_failed(job, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
+                       _("Invalid seek type"));
+      return 1;
+    }
+
+  fd = GPOINTER_TO_UINT(handle);
+
+  if (G_UNLIKELY(g_vfs_backend_afc_check (afc_file_seek (self->afc_cli,
+                                                         fd, offset, afc_seek_type),
+                                          job)))
+    {
+      return 1;
+    }
+
+  return 0;
+}
+
+static void
+g_vfs_backend_afc_seek_on_read (GVfsBackend *backend,
+                                GVfsJobSeekRead *job,
+                                GVfsBackendHandle handle,
+                                goffset offset,
+                                GSeekType type)
+{
+  GVfsBackendAfc *self;
+
+  g_return_if_fail (handle != NULL);
+
+  self = G_VFS_BACKEND_AFC(backend);
+  g_return_if_fail (self->connected);
+
+  if (!g_vfs_backend_afc_seek (self, G_VFS_JOB(job), handle, offset, type))
+    {
+      g_vfs_job_seek_read_set_offset (job, offset);
+      g_vfs_job_succeeded (G_VFS_JOB(job));
+    }
+}
+
+static void
+g_vfs_backend_afc_seek_on_write (GVfsBackend *backend,
+                                 GVfsJobSeekWrite *job,
+                                 GVfsBackendHandle handle,
+                                 goffset offset,
+                                 GSeekType type)
+{
+  GVfsBackendAfc *self;
+
+  g_return_if_fail (handle != NULL);
+
+  self = G_VFS_BACKEND_AFC(backend);
+  g_return_if_fail (self->connected);
+
+  if (!g_vfs_backend_afc_seek (self, G_VFS_JOB(job), handle, offset, type))
+    {
+      g_vfs_job_seek_write_set_offset (job, offset);
+      g_vfs_job_succeeded (G_VFS_JOB(job));
+    }
+}
+
+static void
+g_vfs_backend_afc_set_info_from_afcinfo (GVfsBackendAfc *self,
+                                         GFileInfo *info,
+                                         char **afcinfo,
+                                         const char *basename,
+                                         GFileAttributeMatcher *matcher,
+                                         GFileQueryInfoFlags flags)
+{
+  GFileType type = G_FILE_TYPE_REGULAR;
+  GIcon *icon = NULL;
+  gchar *content_type = NULL;
+  char *display_name;
+  char *linktarget = NULL;
+  char **afctargetinfo = NULL;
+  int i;
+
+  /* get file attributes from info list */
+  for (i = 0; afcinfo[i]; i += 2)
+    {
+      if (afcinfo[i] == NULL)
+        continue;
+      if (g_str_equal (afcinfo[i], "st_size"))
+        {
+          g_file_info_set_size (info, atoll(afcinfo[i+1]));
+        } else if (g_str_equal (afcinfo[i], "st_blocks"))
+          {
+            g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_UNIX_BLOCKS, atoi(afcinfo[i+1]));
+          }
+      else if (g_str_equal (afcinfo[i], "st_ifmt"))
+        {
+          if (g_str_equal (afcinfo[i+1], "S_IFREG"))
+            {
+              type = G_FILE_TYPE_REGULAR;
+            }
+          else if (g_str_equal (afcinfo[i+1], "S_IFDIR"))
+            {
+              type = G_FILE_TYPE_DIRECTORY;
+            }
+          else if (g_str_equal (afcinfo[i+1], "S_IFLNK"))
+            {
+              type = G_FILE_TYPE_SYMBOLIC_LINK;
+              content_type = g_strdup ("inode/symlink");
+            }
+          else if (g_str_equal (afcinfo[i+1], "S_IFBLK"))
+            {
+              type = G_FILE_TYPE_SPECIAL;
+              content_type = g_strdup ("inode/blockdevice");
+            }
+          else if (g_str_equal (afcinfo[i+1], "S_IFCHR"))
+            {
+              type = G_FILE_TYPE_SPECIAL;
+              content_type = g_strdup ("inode/chardevice");
+            }
+          else if (g_str_equal (afcinfo[i+1], "S_IFIFO"))
+            {
+              type = G_FILE_TYPE_SPECIAL;
+              content_type = g_strdup ("inode/fifo");
+            }
+          else if (g_str_equal (afcinfo[i+1], "S_IFSOCK"))
+            {
+              type = G_FILE_TYPE_SPECIAL;
+              content_type = g_strdup ("inode/socket");
+            }
+          g_file_info_set_file_type (info, type);
+        }
+      else if (g_str_equal (afcinfo[i], "st_nlink"))
+        {
+          g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_NLINK, atoi(afcinfo[i+1]));
+        }
+      else if (g_str_equal (afcinfo[i], "LinkTarget"))
+        {
+          linktarget = g_strdup (afcinfo[i+1]);
+          g_file_info_set_symlink_target (info, linktarget);
+          g_file_info_set_is_symlink (info, TRUE);
+        }
+    }
+
+  /* and set some additional info */
+  g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_UID, getuid ());
+  g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_GID, getgid ());
+
+  /*
+   * Maybe this icon stuff should be moved out into a generic function? It
+   * seems a little funny to put this in the backends.
+   */
+  if (g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE)
+      || g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_STANDARD_ICON))
+    {
+      if (type == G_FILE_TYPE_DIRECTORY)
+        {
+          content_type = g_strdup ("inode/directory");
+          icon = g_themed_icon_new ("folder");
+        }
+      else
+        {
+          if (content_type == NULL)
+            content_type = g_content_type_guess (basename, NULL, 0, NULL);
+          if (content_type)
+            {
+              icon = g_content_type_get_icon (content_type);
+              if (G_IS_THEMED_ICON(icon))
+                g_themed_icon_append_name (G_THEMED_ICON(icon), "text-x-generic");
+            }
+        }
+
+      if (content_type)
+        g_file_info_set_content_type (info, content_type);
+
+      if (icon == NULL)
+        icon = g_themed_icon_new ("text-x-generic");
+
+      g_file_info_set_icon (info, icon);
+      g_object_unref (icon);
+    }
+
+  g_free (content_type);
+
+  /* for symlinks to work we need to return GFileInfo for the linktarget */
+  if ((flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS) == 0)
+    {
+      if (type == G_FILE_TYPE_SYMBOLIC_LINK)
+        {
+          /* query the linktarget instead and merge the file info of it */
+          if (AFC_E_SUCCESS == afc_get_file_info (self->afc_cli, linktarget, &afctargetinfo))
+            g_vfs_backend_afc_set_info_from_afcinfo (self, info, afctargetinfo, linktarget, matcher, flags);
+          if (afctargetinfo)
+            g_strfreev (afctargetinfo);
+        }
+    }
+
+  g_free (linktarget);
+
+  /* regardless of symlink recursion; still set the basename of the source */
+  g_file_info_set_name(info, basename);
+
+  /* handle root directory */
+  if (g_str_equal (basename, "/"))
+    display_name = g_strdup (g_vfs_backend_get_display_name (G_VFS_BACKEND(self)));
+  else
+    display_name = g_filename_display_name (basename);
+
+  if (g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME))
+    g_file_info_set_display_name (info, display_name);
+
+  if (g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME))
+    g_file_info_set_edit_name (info, display_name);
+
+  g_free (display_name);
+}
+
+/* Callback for iterating over a directory. */
+static void
+g_vfs_backend_afc_enumerate (GVfsBackend *backend,
+                             GVfsJobEnumerate *job,
+                             const char *path,
+                             GFileAttributeMatcher *matcher,
+                             GFileQueryInfoFlags flags)
+{
+  GFileInfo *info;
+  GVfsBackendAfc *self;
+  gboolean trailing_slash;
+  gchar *file_path;
+  char **ptr, **list = NULL;
+  char **afcinfo = NULL;
+
+  self = G_VFS_BACKEND_AFC(backend);
+  g_return_if_fail (self->connected);
+
+  if (G_UNLIKELY(g_vfs_backend_afc_check (afc_read_directory (self->afc_cli, path, &list),
+                                          G_VFS_JOB(job))))
+    {
+      return;
+    }
+
+  trailing_slash = g_str_has_suffix (path, "/");
+
+  for (ptr = list; *ptr; ptr++)
+    {
+      if (g_str_equal(*ptr, ".") || g_str_equal(*ptr, ".."))
+        continue;
+
+      if (!trailing_slash)
+        file_path = g_strdup_printf ("%s/%s", path, *ptr);
+      else
+        file_path = g_strdup_printf ("%s%s", path, *ptr);
+
+      /*
+       * This call might fail if the file in question is removed while we're
+       * iterating over the directory list. In that case, just don't include
+       * it in the list.
+       */
+      if (G_LIKELY(afc_get_file_info(self->afc_cli, file_path, &afcinfo) == AFC_E_SUCCESS))
+        {
+          info = g_file_info_new ();
+          g_vfs_backend_afc_set_info_from_afcinfo (self, info, afcinfo, *ptr, matcher, flags);
+          g_vfs_job_enumerate_add_info (job, info);
+          g_object_unref (G_OBJECT(info));
+          g_strfreev (afcinfo);
+        }
+
+      g_free (file_path);
+    }
+
+  g_strfreev (list);
+
+  g_vfs_job_enumerate_done (job);
+  g_vfs_job_succeeded (G_VFS_JOB(job));
+}
+
+static void
+g_vfs_backend_afc_query_info (GVfsBackend *backend,
+                              GVfsJobQueryInfo *job,
+                              const char *path,
+                              GFileQueryInfoFlags flags,
+                              GFileInfo *info,
+                              GFileAttributeMatcher *matcher)
+{
+  GVfsBackendAfc *self;
+  const char *basename, *ptr;
+  char **afcinfo = NULL;
+
+  self = G_VFS_BACKEND_AFC(backend);
+  g_return_if_fail (self->connected);
+
+  if (G_UNLIKELY(g_vfs_backend_afc_check (afc_get_file_info (self->afc_cli, path, &afcinfo),
+                                          G_VFS_JOB(job))))
+    {
+      if (afcinfo)
+        g_strfreev(afcinfo);
+      return;
+    }
+
+  ptr = strrchr (path, '/');
+  if (ptr && ptr[1] != '\0')
+    basename = ptr + 1;
+  else
+    basename = path;
+
+  g_vfs_backend_afc_set_info_from_afcinfo (self, info, afcinfo, basename, matcher, flags);
+  if (afcinfo)
+    g_strfreev (afcinfo);
+
+  g_vfs_job_succeeded (G_VFS_JOB(job));
+}
+
+/*
+ * The following keys are currently known:
+ *   Model: 'iPhone1,1'
+ *   FSTotalBytes: storage capacity of drive
+ *   FSFreeBytes: free space on drive
+ *   FSBlockSize: block granularity
+ */
+static void
+g_vfs_backend_afc_query_fs_info (GVfsBackend *backend,
+                                 GVfsJobQueryFsInfo *job,
+                                 const char *path,
+                                 GFileInfo *info,
+                                 GFileAttributeMatcher *matcher)
+{
+  GVfsBackendAfc *self;
+  char **kvps, **ptr;
+  uint64_t totalspace = 0, freespace = 0;
+  int blocksize = 0;
+
+  self = G_VFS_BACKEND_AFC(backend);
+
+  g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE, "afc");
+
+  if (self->connected)
+    {
+      if (G_UNLIKELY(g_vfs_backend_afc_check (afc_get_device_info (self->afc_cli, &kvps), G_VFS_JOB(job))))
+        return;
+
+      for (ptr = kvps; *ptr; ptr++)
+        {
+          if (g_str_equal (*ptr, "FSTotalBytes"))
+            {
+              totalspace = g_ascii_strtoull (*(ptr+1), (char **) NULL, 10);
+            }
+          else if (g_str_equal (*ptr, "FSFreeBytes"))
+            {
+              freespace = g_ascii_strtoull (*(ptr+1), (char **) NULL, 10);
+            }
+          else if (g_str_equal (*ptr, "FSBlockSize"))
+            {
+              blocksize = atoi (*(ptr+1));
+            }
+        }
+
+      g_strfreev (kvps);
+
+      g_file_info_set_attribute_uint32 (info,
+                                        G_FILE_ATTRIBUTE_UNIX_BLOCK_SIZE,
+                                        (guint32) blocksize);
+      g_file_info_set_attribute_uint64 (info,
+                                        G_FILE_ATTRIBUTE_FILESYSTEM_SIZE,
+                                        (guint64) totalspace);
+      g_file_info_set_attribute_uint64 (info,
+                                        G_FILE_ATTRIBUTE_FILESYSTEM_FREE,
+                                        (guint64) freespace);
+      g_file_info_set_attribute_boolean (info,
+                                         G_FILE_ATTRIBUTE_FILESYSTEM_READONLY,
+                                         FALSE);
+      g_file_info_set_attribute_uint32 (info,
+                                        G_FILE_ATTRIBUTE_FILESYSTEM_USE_PREVIEW,
+                                        G_FILESYSTEM_PREVIEW_TYPE_IF_LOCAL);
+    }
+
+  g_vfs_job_succeeded (G_VFS_JOB(job));
+}
+
+static void
+g_vfs_backend_afc_set_display_name (GVfsBackend *backend,
+                                    GVfsJobSetDisplayName *job,
+                                    const char *filename,
+                                    const char *display_name)
+{
+  GVfsBackendAfc *self;
+
+  self = G_VFS_BACKEND_AFC(backend);
+  g_return_if_fail (self->connected);
+
+  if (G_UNLIKELY(g_vfs_backend_afc_check (afc_rename_path (self->afc_cli,
+                                                           filename, display_name),
+                                          G_VFS_JOB(job))))
+    {
+      return;
+    }
+
+  g_vfs_job_set_display_name_set_new_path (job, display_name);
+
+  g_vfs_job_succeeded (G_VFS_JOB(job));
+}
+
+static void
+g_vfs_backend_afc_make_directory (GVfsBackend *backend,
+                                  GVfsJobMakeDirectory *job,
+                                  const char *path)
+{
+  GVfsBackendAfc *self;
+
+  self = G_VFS_BACKEND_AFC(backend);
+  g_return_if_fail(self->connected);
+
+  if (G_UNLIKELY(g_vfs_backend_afc_check (afc_make_directory (self->afc_cli,
+                                                              path),
+                                          G_VFS_JOB(job))))
+    {
+      return;
+    }
+
+  g_vfs_job_succeeded (G_VFS_JOB(job));
+}
+
+static void
+g_vfs_backend_afc_make_symlink (GVfsBackend *backend,
+                                GVfsJobMakeSymlink *job,
+                                const char *filename,
+                                const char *symlink_value)
+{
+  GVfsBackendAfc *self;
+
+  self = G_VFS_BACKEND_AFC(backend);
+  g_return_if_fail (self->connected);
+
+  if (G_UNLIKELY(g_vfs_backend_afc_check (afc_make_link (self->afc_cli,
+                                                         AFC_SYMLINK, symlink_value, filename),
+                                          G_VFS_JOB(job))))
+    {
+      return;
+    }
+
+  g_vfs_job_succeeded (G_VFS_JOB(job));
+}
+
+static void
+g_vfs_backend_afc_move (GVfsBackend *backend,
+                        GVfsJobMove *job,
+                        const char *source,
+                        const char *destination,
+                        GFileCopyFlags flags,
+                        GFileProgressCallback progress_callback,
+                        gpointer progress_callback_data)
+{
+  GVfsBackendAfc *self;
+
+  self = G_VFS_BACKEND_AFC(backend);
+  g_return_if_fail(self->connected);
+
+  if (flags & G_FILE_COPY_BACKUP)
+    {
+      /* FIXME: implement! */
+      g_vfs_job_failed (G_VFS_JOB (job),
+                        G_IO_ERROR,
+                        G_IO_ERROR_CANT_CREATE_BACKUP,
+                        _("Backups are not yet supported."));
+      return;
+    }
+
+  if (G_UNLIKELY(g_vfs_backend_afc_check (afc_rename_path (self->afc_cli,
+                                                           source, destination),
+                                          G_VFS_JOB(job))))
+    {
+      return;
+    }
+
+  g_vfs_job_succeeded (G_VFS_JOB(job));
+}
+
+static void
+g_vfs_backend_afc_delete (GVfsBackend *backend,
+                          GVfsJobDelete *job,
+                          const char *filename)
+{
+  GVfsBackendAfc *self;
+
+  self = G_VFS_BACKEND_AFC(backend);
+  g_return_if_fail (self->connected);
+
+  if (G_UNLIKELY(g_vfs_backend_afc_check (afc_remove_path (self->afc_cli,
+                                                           filename),
+                                          G_VFS_JOB(job))))
+    {
+      return;
+    }
+
+  g_vfs_job_succeeded (G_VFS_JOB(job));
+}
+
+
+static void
+g_vfs_backend_afc_finalize (GObject *obj)
+{
+  GVfsBackendAfc *self;
+
+  self = G_VFS_BACKEND_AFC(obj);
+  g_vfs_backend_afc_close_connection (self);
+
+  if (G_OBJECT_CLASS(g_vfs_backend_afc_parent_class)->finalize)
+    (*G_OBJECT_CLASS(g_vfs_backend_afc_parent_class)->finalize) (obj);
+}
+
+static void
+g_vfs_backend_afc_init (GVfsBackendAfc *self)
+{
+  if (g_getenv ("GVFS_DEBUG") != NULL)
+    {
+      /* enable full debugging */
+      iphone_set_debug_level (1);
+      iphone_set_debug_mask (DBGMASK_ALL);
+    }
+}
+
+static void
+g_vfs_backend_afc_class_init (GVfsBackendAfcClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+  GVfsBackendClass *backend_class = G_VFS_BACKEND_CLASS(klass);
+
+  gobject_class->finalize = g_vfs_backend_afc_finalize;
+
+  backend_class->mount            = g_vfs_backend_afc_mount;
+  backend_class->unmount          = g_vfs_backend_afc_unmount;
+  backend_class->open_for_read    = g_vfs_backend_afc_open_for_read;
+  backend_class->close_read       = g_vfs_backend_afc_close_read;
+  backend_class->read             = g_vfs_backend_afc_read;
+  backend_class->seek_on_read     = g_vfs_backend_afc_seek_on_read;
+  backend_class->create           = g_vfs_backend_afc_create;
+  backend_class->append_to        = g_vfs_backend_afc_append_to;
+  backend_class->replace          = g_vfs_backend_afc_replace;
+  backend_class->close_write      = g_vfs_backend_afc_close_write;
+  backend_class->write            = g_vfs_backend_afc_write;
+  backend_class->seek_on_write    = g_vfs_backend_afc_seek_on_write;
+  backend_class->enumerate        = g_vfs_backend_afc_enumerate;
+  backend_class->query_info       = g_vfs_backend_afc_query_info;
+  backend_class->query_fs_info    = g_vfs_backend_afc_query_fs_info;
+  backend_class->make_directory   = g_vfs_backend_afc_make_directory;
+  backend_class->delete           = g_vfs_backend_afc_delete;
+  backend_class->make_symlink     = g_vfs_backend_afc_make_symlink;
+  backend_class->move             = g_vfs_backend_afc_move;
+  backend_class->set_display_name = g_vfs_backend_afc_set_display_name;
+}
+
+/*
+ * vim: sw=2 ts=8 cindent expandtab cinoptions=f0,>4,n2,{2,(0,^-2,t0 ai
+ */
diff --git a/daemon/gvfsbackendafc.h b/daemon/gvfsbackendafc.h
new file mode 100644
index 0000000..6291180
--- /dev/null
+++ b/daemon/gvfsbackendafc.h
@@ -0,0 +1,37 @@
+/*
+ * gvfs/daemon/gvfsbackendafc.h
+ *
+ * Copyright (c) 2008 Patrick Walton <pcwalton@ucla.edu>
+ */
+
+#ifndef GVFSBACKENDAFC_H
+#define GVFSBACKENDAFC_H
+
+#include <gvfsbackend.h>
+#include <gmountspec.h>
+
+G_BEGIN_DECLS
+
+#define G_VFS_TYPE_BACKEND_AFC   (g_vfs_backend_afc_get_type())
+#define G_VFS_BACKEND_AFC(o) (G_TYPE_CHECK_INSTANCE_CAST((o), G_VFS_TYPE_BACKEND_AFC, GVfsBackendAfc))
+#define G_VFS_BACKEND_AFC_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_VFS_TYPE_BACKEND_AFC, GVfsBackendAfcClass))
+#define G_VFS_IS_BACKEND_AFC(o) (G_TYPE_CHECK_INSTANCE_TYPE((o), G_VFS_TYPE_BACKEND_AFC))
+#define G_VFS_IS_BACKEND_AFC_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE((k), G_VFS_TYPE_BACKEND_AFC))
+#define G_VFS_BACKEND_AFC_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), G_VFS_TYPE_BACKEND_AFC, GVfsBackendAfcClass))
+
+typedef struct _GVfsBackendAfc GVfsBackendAfc;
+typedef struct _GVfsBackendAfcClass GVfsBackendAfcClass;
+
+struct _GVfsBackendAfcClass {
+    GVfsBackendClass parent_class;
+};
+
+GType g_vfs_backend_afc_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* GVFSBACKENDAFC_H */
+
+/*
+ * vim: sw=2 ts=8 cindent expandtab cinoptions=f0,>4,n2,{2,(0,^-2,t0 ai
+ */
diff --git a/monitor/Makefile.am b/monitor/Makefile.am
index f68423e..1dc984f 100644
--- a/monitor/Makefile.am
+++ b/monitor/Makefile.am
@@ -1,4 +1,4 @@
-
+DIST_SUBDIRS = proxy hal gdu gphoto2 afc
 SUBDIRS = proxy
 
 if USE_HAL
@@ -12,3 +12,7 @@ endif
 if USE_GPHOTO2
 SUBDIRS += gphoto2
 endif
+
+if USE_AFC
+SUBDIRS += afc
+endif
diff --git a/monitor/afc/Makefile.am b/monitor/afc/Makefile.am
new file mode 100644
index 0000000..9b3b17c
--- /dev/null
+++ b/monitor/afc/Makefile.am
@@ -0,0 +1,49 @@
+NULL =
+
+gvfs_src_dir = $(top_srcdir)/@with_gvfs_source@
+
+libexec_PROGRAMS = gvfs-afc-volume-monitor
+
+gvfs_afc_volume_monitor_SOURCES = \
+	afcvolume.c		afcvolume.h \
+	afcvolumemonitor.c 	afcvolumemonitor.h \
+	afcvolumemonitordaemon.c \
+	$(NULL)
+
+gvfs_afc_volume_monitor_CFLAGS = \
+	-DG_LOG_DOMAIN=\"GVFS-AFC\" \
+	-I$(top_srcdir)/common \
+	-I$(top_srcdir)/monitor/proxy \
+	$(GLIB_CFLAGS) \
+	$(AFC_CFLAGS) \
+	$(WARN_CFLAGS) \
+	-DGIO_MODULE_DIR=\"$(GIO_MODULE_DIR)\" \
+	-DGVFS_LOCALEDIR=\"$(localedir)\" \
+	-DG_DISABLE_DEPRECATED \
+	$(NULL)
+
+gvfs_afc_volume_monitor_LDADD = \
+	$(GLIB_LIBS) \
+	$(DBUS_LIBS) \
+	$(AFC_LIBS) \
+	$(top_srcdir)/common/libgvfscommon.la \
+	$(top_srcdir)/monitor/proxy/libgvfsproxyvolumemonitordaemon-noin.la \
+	$(NULL)
+
+remote_volume_monitorsdir = $(datadir)/gvfs/remote-volume-monitors
+remote_volume_monitors_DATA = afc.monitor
+
+servicedir = $(datadir)/dbus-1/services
+service_in_files = org.gtk.Private.AfcVolumeMonitor.service.in
+service_DATA = $(service_in_files:.service.in=.service)
+
+$(service_DATA): $(service_in_files) Makefile
+	@sed -e "s|\@libexecdir\@|$(libexecdir)|" $< > $@
+
+clean-local:
+	rm -f *~ *.loT $(BUILT_SOURCES) $(service_DATA)
+
+DISTCLEANFILES = $(service_DATA)
+
+EXTRA_DIST = $(service_in_files) afc.monitor
+
diff --git a/monitor/afc/afc.monitor b/monitor/afc/afc.monitor
new file mode 100644
index 0000000..1663573
--- /dev/null
+++ b/monitor/afc/afc.monitor
@@ -0,0 +1,5 @@
+[RemoteVolumeMonitor]
+Name=GProxyVolumeMonitorAfc
+DBusName=org.gtk.Private.AfcVolumeMonitor
+IsNative=false
+
diff --git a/monitor/afc/afcvolume.c b/monitor/afc/afcvolume.c
new file mode 100644
index 0000000..688f75b
--- /dev/null
+++ b/monitor/afc/afcvolume.c
@@ -0,0 +1,353 @@
+/*
+ * gvfs/monitor/afc/afc-volume.c
+ *
+ * Copyright (c) 2008 Patrick Walton <pcwalton@cs.ucla.edu>
+ */
+
+#include <config.h>
+#include <string.h>
+#include <glib.h>
+#include <gio/gio.h>
+
+#include <libiphone/libiphone.h>
+#include <libiphone/lockdown.h>
+#include <libiphone/afc.h>
+
+#include "afcvolume.h"
+
+#define DEFAULT_SERVICE "com.apple.afc"
+
+struct _GVfsAfcVolume {
+  GObject parent;
+
+  GVolumeMonitor *monitor;
+
+  char *uuid;
+
+  char *name;
+  char *icon;
+  char *icon_fallback;
+};
+
+static void g_vfs_afc_volume_iface_init (GVolumeIface *iface);
+
+G_DEFINE_TYPE_EXTENDED(GVfsAfcVolume, g_vfs_afc_volume, G_TYPE_OBJECT, 0,
+                       G_IMPLEMENT_INTERFACE(G_TYPE_VOLUME, g_vfs_afc_volume_iface_init))
+
+static void
+g_vfs_afc_volume_finalize (GObject *self_)
+{
+  GVfsAfcVolume *self;
+
+  self = G_VFS_AFC_VOLUME(self);
+
+  g_free (self->uuid);
+
+  g_free (self->name);
+  g_free (self->icon);
+  g_free (self->icon_fallback);
+
+  if (G_OBJECT_CLASS(g_vfs_afc_volume_parent_class)->finalize)
+    (*G_OBJECT_CLASS(g_vfs_afc_volume_parent_class)->finalize) (G_OBJECT(self));
+}
+
+static void
+g_vfs_afc_volume_init (GVfsAfcVolume *self)
+{
+  GVfsAfcVolume *afc_volume = G_VFS_AFC_VOLUME (self);
+
+  afc_volume->name = g_strdup ("iPhone");
+  afc_volume->icon = g_strdup ("phone-apple-iphone");
+}
+
+static void
+g_vfs_afc_volume_class_init (GVfsAfcVolumeClass *klass)
+{
+  GObjectClass *gobject_class;
+  gobject_class = G_OBJECT_CLASS(klass);
+  gobject_class->finalize = g_vfs_afc_volume_finalize;
+}
+
+static int
+_g_vfs_afc_volume_update_metadata (GVfsAfcVolume *self)
+{
+  iphone_device_t dev;
+  afc_client_t afc_cli;
+  lockdownd_client_t lockdown_cli = NULL;
+  iphone_error_t err;
+  guint retries;
+  char *model, *display_name;
+  int port;
+
+  retries = 0;
+  do {
+      err = iphone_get_device_by_uuid (&dev, self->uuid);
+      if (err == IPHONE_E_SUCCESS)
+        break;
+      g_usleep (G_USEC_PER_SEC);
+  } while (retries++ < 10);
+
+  if (err != IPHONE_E_SUCCESS)
+    return 0;
+
+  if (lockdownd_client_new (dev, &lockdown_cli) != LOCKDOWN_E_SUCCESS)
+    {
+      iphone_device_free (dev);
+      return 0;
+    }
+
+  /* try to use pretty device name */
+  if (lockdownd_get_device_name (lockdown_cli, &display_name) == LOCKDOWN_E_SUCCESS)
+    {
+      g_free (self->name);
+      self->name = display_name;
+    }
+
+  if (lockdownd_start_service (lockdown_cli, DEFAULT_SERVICE, &port) != LOCKDOWN_E_SUCCESS)
+    {
+      lockdownd_client_free (lockdown_cli);
+      iphone_device_free (dev);
+      return 0;
+    }
+
+  if (afc_client_new (dev, port, &afc_cli) == AFC_E_SUCCESS)
+    {
+      /* set correct fd icon spec name depending on device model */
+      model = afc_get_device_info_field (afc_cli, "Model");
+      if (model != NULL)
+        {
+          if(g_str_has_prefix(model, "iPod") != FALSE)
+            {
+              g_free (self->icon);
+              self->icon = g_strdup ("multimedia-player-apple-ipod-touch");
+            }
+          g_free (model);
+        }
+      afc_client_free(afc_cli);
+    }
+
+  lockdownd_client_free (lockdown_cli);
+  iphone_device_free (dev);
+
+  return 1;
+}
+
+GVfsAfcVolume *
+g_vfs_afc_volume_new (GVolumeMonitor *monitor,
+                      const char     *uuid)
+{
+  GVfsAfcVolume *self;
+
+  self = G_VFS_AFC_VOLUME(g_object_new (G_VFS_TYPE_AFC_VOLUME, NULL));
+  self->monitor = monitor;
+  self->uuid = g_strdup (uuid);
+
+  /* Get mount information here */
+  if (!_g_vfs_afc_volume_update_metadata (self))
+      return NULL;
+
+  return self;
+}
+
+static char *
+g_vfs_afc_volume_get_name (GVolume *volume)
+{
+  GVfsAfcVolume *afc_volume = G_VFS_AFC_VOLUME (volume);
+  char *name;
+
+  name = g_strdup (afc_volume->name);
+
+  return name;
+}
+
+static GIcon *
+g_vfs_afc_volume_get_icon (GVolume *volume)
+{
+  GVfsAfcVolume *afc_volume = G_VFS_AFC_VOLUME (volume);
+  GIcon *icon;
+
+  icon = g_themed_icon_new_with_default_fallbacks (afc_volume->icon);
+
+  return icon;
+}
+
+static char *
+g_vfs_afc_volume_get_uuid (GVolume *volume)
+{
+  GVfsAfcVolume *afc_volume = G_VFS_AFC_VOLUME (volume);
+
+  return g_strdup (afc_volume->uuid);
+}
+
+static gboolean
+g_vfs_afc_volume_can_mount (GVolume *volume)
+{
+  return TRUE;
+}
+
+static gboolean
+g_vfs_afc_volume_should_automount (GVolume *volume)
+{
+  return TRUE;
+}
+
+static GDrive *
+g_vfs_afc_volume_get_drive (GVolume *volume)
+{
+  return NULL;
+}
+
+static GMount *
+g_vfs_afc_volume_get_mount (GVolume *volume)
+{
+  return NULL;
+}
+
+typedef struct
+{
+  GVfsAfcVolume *enclosing_volume;
+  GAsyncReadyCallback  callback;
+  GFile *root;
+  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_object_unref (data->root);
+  g_free (data);
+}
+
+static void
+g_vfs_afc_volume_mount (GVolume             *volume,
+                        GMountMountFlags     flags,
+                        GMountOperation     *mount_operation,
+                        GCancellable        *cancellable,
+                        GAsyncReadyCallback  callback,
+                        gpointer             user_data)
+{
+  GVfsAfcVolume *afc_volume = G_VFS_AFC_VOLUME (volume);
+  ActivationMountOp *data;
+  GFile *root;
+  char *uri;
+
+  g_print ("g_vfs_afc_volume_mount (can_mount=%d uuid=%s)\n",
+           g_vfs_afc_volume_can_mount (volume),
+           afc_volume->uuid);
+
+  uri = g_strdup_printf ("afc://%s", afc_volume->uuid);
+  root = g_file_new_for_uri (uri);
+  g_free (uri);
+
+  data = g_new0 (ActivationMountOp, 1);
+  data->enclosing_volume = afc_volume;
+  data->callback = callback;
+  data->user_data = user_data;
+  data->root = root;
+
+  g_object_set_data_full (G_OBJECT(volume), "root", g_object_ref (root), g_object_unref);
+
+  g_file_mount_enclosing_volume (root,
+                                 0,
+                                 mount_operation,
+                                 cancellable,
+                                 mount_callback,
+                                 data);
+}
+
+static gboolean
+g_vfs_afc_volume_mount_finish (GVolume       *volume,
+                               GAsyncResult  *result,
+                               GError       **error)
+{
+  GFile *root;
+  gboolean res;
+
+  root = g_object_get_data (G_OBJECT (volume), "root");
+  res = g_file_mount_enclosing_volume_finish (root, result, error);
+
+  return res;
+}
+
+static char *
+g_vfs_afc_volume_get_identifier (GVolume              *volume,
+                                 const char          *kind)
+{
+  GVfsAfcVolume *afc_volume = G_VFS_AFC_VOLUME (volume);
+  char *id;
+
+  id = NULL;
+  if (g_str_equal (kind, G_VOLUME_IDENTIFIER_KIND_UUID) != FALSE)
+    id = g_strdup (afc_volume->uuid);
+
+  return id;
+}
+
+static char **
+g_vfs_afc_volume_enumerate_identifiers (GVolume *volume)
+{
+  GVfsAfcVolume *afc_volume = G_VFS_AFC_VOLUME (volume);
+  GPtrArray *res;
+
+  res = g_ptr_array_new ();
+
+  g_ptr_array_add (res,
+                   g_strdup (G_VOLUME_IDENTIFIER_KIND_HAL_UDI));
+
+  if (afc_volume->uuid && *afc_volume->uuid != 0)
+    {
+        g_ptr_array_add (res,
+                         g_strdup (G_VOLUME_IDENTIFIER_KIND_UUID));
+    }
+
+  /* Null-terminate */
+  g_ptr_array_add (res, NULL);
+
+  return (char **)g_ptr_array_free (res, FALSE);
+}
+
+static GFile *
+g_vfs_afc_volume_get_activation_root (GVolume *volume)
+{
+  GFile *root;
+
+  root = g_object_get_data (G_OBJECT (volume), "root");
+  if (root == NULL)
+    return NULL;
+
+  return g_object_ref (root);
+}
+
+static void
+g_vfs_afc_volume_iface_init (GVolumeIface *iface)
+{
+  iface->get_name = g_vfs_afc_volume_get_name;
+  iface->get_icon = g_vfs_afc_volume_get_icon;
+  iface->get_uuid = g_vfs_afc_volume_get_uuid;
+  iface->get_drive = g_vfs_afc_volume_get_drive;
+  iface->get_mount = g_vfs_afc_volume_get_mount;
+  iface->can_mount = g_vfs_afc_volume_can_mount;
+  iface->should_automount = g_vfs_afc_volume_should_automount;
+  iface->mount_fn = g_vfs_afc_volume_mount;
+  iface->mount_finish = g_vfs_afc_volume_mount_finish;
+  iface->eject = NULL;
+  iface->eject_finish = NULL;
+  iface->get_identifier = g_vfs_afc_volume_get_identifier;
+  iface->enumerate_identifiers = g_vfs_afc_volume_enumerate_identifiers;
+  iface->get_activation_root = g_vfs_afc_volume_get_activation_root;
+}
+
+gboolean g_vfs_afc_volume_has_uuid(GVfsAfcVolume *volume, const char *uuid)
+{
+  GVfsAfcVolume *afc_volume = G_VFS_AFC_VOLUME (volume);
+  g_return_val_if_fail (uuid != NULL, FALSE);
+  return (g_strcmp0 (afc_volume->uuid, uuid) == 0);
+}
+
+/*
+ * vim: sw=2 ts=8 cindent expandtab cinoptions=f0,>4,n2,{2,(0,^-2,t0 ai
+ */
diff --git a/monitor/afc/afcvolume.h b/monitor/afc/afcvolume.h
new file mode 100644
index 0000000..de24cd5
--- /dev/null
+++ b/monitor/afc/afcvolume.h
@@ -0,0 +1,44 @@
+/*
+ * gvfs/monitor/afc/afc-volume.h
+ *
+ * Copyright (c) 2008 Patrick Walton <pcwalton@cs.ucla.edu>
+ */
+
+#ifndef GVFS_MONITOR_AFC_AFC_VOLUME_H
+#define GVFS_MONITOR_AFC_AFC_VOLUME_H
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+#include "afcvolumemonitor.h"
+
+G_BEGIN_DECLS
+
+#define G_VFS_TYPE_AFC_VOLUME   (g_vfs_afc_volume_get_type())
+#define G_VFS_AFC_VOLUME(o) (G_TYPE_CHECK_INSTANCE_CAST((o), G_VFS_TYPE_AFC_VOLUME, GVfsAfcVolume))
+#define G_VFS_AFC_VOLUME_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_VFS_TYPE_AFC_VOLUME, GVfsAfcVolumeClass))
+#define G_VFS_IS_AFC_VOLUME(o) (G_TYPE_CHECK_INSTANCE_TYPE((o), G_VFS_TYPE_AFC_VOLUME))
+#define G_VFS_IS_AFC_VOLUME_CLASS(k) ((G_TYPE_CHECK_CLASS_TYPE((k), G_VFS_TYPE_AFC_VOLUME))
+#define G_VFS_AFC_VOLUME_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), G_VFS_TYPE_AFC_VOLUME, GVfsAfcVolumeClass))
+
+typedef struct _GVfsAfcVolume GVfsAfcVolume;
+typedef struct _GVfsAfcVolumeClass GVfsAfcVolumeClass;
+
+struct _GVfsAfcVolumeClass {
+  GObjectClass parent_class;
+};
+
+GType g_vfs_afc_volume_get_type (void) G_GNUC_CONST;
+
+GVfsAfcVolume *g_vfs_afc_volume_new (GVolumeMonitor *monitor,
+                                     const char     *uuid);
+
+gboolean g_vfs_afc_volume_has_uuid (GVfsAfcVolume *volume, const char *uuid);
+
+G_END_DECLS
+
+#endif /* GVFS_MONITOR_AFC_AFC_VOLUME_H */
+
+/*
+ * vim: sw=2 ts=8 cindent expandtab cinoptions=f0,>4,n2,{2,(0,^-2,t0 ai
+ */
diff --git a/monitor/afc/afcvolumemonitor.c b/monitor/afc/afcvolumemonitor.c
new file mode 100644
index 0000000..93926eb
--- /dev/null
+++ b/monitor/afc/afcvolumemonitor.c
@@ -0,0 +1,224 @@
+/*
+ * gvfs/monitor/afc/afc-volume-monitor.c
+ *
+ * Copyright (c) 2008 Patrick Walton <pcwalton@ucla.edu>
+ */
+
+#include <config.h>
+#include <glib.h>
+#include <gmodule.h>
+#include <gvfsproxyvolumemonitordaemon.h>
+#include <stdio.h>
+#include <gio/gio.h>
+#define G_UDEV_API_IS_SUBJECT_TO_CHANGE
+#include <gudev/gudev.h>
+#include "afcvolume.h"
+#include "afcvolumemonitor.h"
+
+struct _GVfsAfcVolumeMonitor {
+  GNativeVolumeMonitor parent;
+  GUdevClient *client;
+  GList *volumes;
+};
+
+G_DEFINE_TYPE(GVfsAfcVolumeMonitor, g_vfs_afc_volume_monitor, G_TYPE_VOLUME_MONITOR)
+
+static void
+g_vfs_afc_monitor_create_volume (GVfsAfcVolumeMonitor *self,
+                                 const char *uuid)
+{
+  GVfsAfcVolume *volume = NULL;
+
+  g_print ("creating volume for device uuid '%s'\n", uuid);
+
+  volume = g_vfs_afc_volume_new (G_VOLUME_MONITOR (self), uuid);
+  if (volume != NULL)
+    {
+      self->volumes = g_list_prepend (self->volumes, volume);
+      g_signal_emit_by_name (self, "volume-added", volume);
+    }
+}
+
+static GVfsAfcVolume *
+find_volume_by_uuid (GVfsAfcVolumeMonitor *self,
+                     const char * uuid)
+{
+  GList *l;
+
+  for (l = self->volumes; l != NULL; l = l->next)
+    {
+      GVfsAfcVolume *volume = l->data;
+      if (volume && g_vfs_afc_volume_has_uuid (volume, uuid))
+        return volume;
+    }
+
+  return NULL;
+}
+
+static void
+g_vfs_afc_monitor_remove_volume (GVfsAfcVolumeMonitor *self,
+                                 const char *uuid)
+{
+  GVfsAfcVolume *volume = NULL;
+
+  volume = find_volume_by_uuid (self, uuid);
+  if (volume != NULL)
+    {
+      g_print ("removing volume for device uuid '%s'\n", uuid);
+      self->volumes = g_list_remove (self->volumes, volume);
+      g_signal_emit_by_name (self, "volume-removed", volume);
+    }
+}
+
+static void
+g_vfs_afc_monitor_uevent (GUdevClient  *client,
+                          const gchar  *action,
+                          GUdevDevice  *device,
+                          gpointer      user_data)
+{
+  GVfsAfcVolumeMonitor *self;
+  const char *vendor, *uuid;
+
+  self = G_VFS_AFC_VOLUME_MONITOR(user_data);
+
+  /* Vendor is Apple? */
+  vendor = g_udev_device_get_property (device, "ID_VENDOR");
+  if (vendor == NULL ||
+      g_str_equal (vendor, "Apple_Inc.") == FALSE) {
+    return;
+  }
+
+  /* Get us a UUID */
+  uuid = g_udev_device_get_property (device, "ID_SERIAL_SHORT");
+  if (uuid == NULL)
+    {
+      g_message ("No serial for device");
+      return;
+    }
+
+  if (g_str_equal (action, "add") != FALSE)
+    g_vfs_afc_monitor_create_volume (self, uuid);
+  else if (g_str_equal (action, "remove") != FALSE)
+    g_vfs_afc_monitor_remove_volume (self, uuid);
+}
+
+static GObject *
+g_vfs_afc_volume_monitor_constructor (GType type, guint ncps,
+                                      GObjectConstructParam *cps)
+{
+  GVfsAfcVolumeMonitor *self;
+  GList *devices, *l;
+  const gchar * const subsystems[] = { "usb", NULL };
+
+  /* Boilerplate code to chain from parent. */
+  self = G_VFS_AFC_VOLUME_MONITOR((*G_OBJECT_CLASS(g_vfs_afc_volume_monitor_parent_class)->constructor)(type, ncps, cps));
+
+  self->client = g_udev_client_new (subsystems);
+  g_signal_connect (G_OBJECT (self->client), "uevent",
+                    G_CALLBACK (g_vfs_afc_monitor_uevent), self);
+
+  self->volumes = NULL;
+
+  devices = g_udev_client_query_by_subsystem (self->client, subsystems[0]);
+  for (l = devices; l != NULL; l = l->next)
+    {
+      GUdevDevice *device = l->data;
+      g_vfs_afc_monitor_uevent (self->client, "add", device, self);
+      g_object_unref (device);
+    }
+  g_list_free (devices);
+
+  g_print ("Volume monitor alive\n");
+
+  return G_OBJECT(self);
+}
+
+static void
+list_free (GList *objects)
+{
+  g_list_foreach (objects, (GFunc)g_object_unref, NULL);
+  g_list_free (objects);
+}
+
+static void
+g_vfs_afc_volume_monitor_finalize (GObject *_self)
+{
+  GVfsAfcVolumeMonitor *self;
+
+  self = G_VFS_AFC_VOLUME_MONITOR(_self);
+
+  if (self->volumes)
+    list_free (self->volumes);
+
+  if (self->client)
+    {
+      g_object_unref (self->client);
+      self->client = NULL;
+    }
+
+  if (G_OBJECT_CLASS(g_vfs_afc_volume_monitor_parent_class)->finalize)
+    (*G_OBJECT_CLASS(g_vfs_afc_volume_monitor_parent_class)->finalize)( G_OBJECT(self));
+}
+
+static GList *
+g_vfs_afc_volume_monitor_get_mounts (GVolumeMonitor *_self)
+{
+  return NULL;
+}
+
+static GList *
+g_vfs_afc_volume_monitor_get_volumes (GVolumeMonitor *_self)
+{
+  GVfsAfcVolumeMonitor *self;
+  GList *l;
+
+  self = G_VFS_AFC_VOLUME_MONITOR (_self);
+
+  l = g_list_copy (self->volumes);
+  g_list_foreach (l, (GFunc)g_object_ref, NULL);
+
+  return l;
+}
+
+static GList *
+g_vfs_afc_volume_monitor_get_connected_drives (GVolumeMonitor *_self)
+{
+  return NULL;
+}
+
+static gboolean
+g_vfs_afc_volume_monitor_is_supported (void)
+{
+  return TRUE;
+}
+
+static void
+g_vfs_afc_volume_monitor_class_init (GVfsAfcVolumeMonitorClass * klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+  GVolumeMonitorClass *monitor_class = G_VOLUME_MONITOR_CLASS(klass);
+
+  gobject_class->constructor = g_vfs_afc_volume_monitor_constructor;
+  gobject_class->finalize = g_vfs_afc_volume_monitor_finalize;
+
+  monitor_class->get_mounts = g_vfs_afc_volume_monitor_get_mounts;
+  monitor_class->get_volumes = g_vfs_afc_volume_monitor_get_volumes;
+  monitor_class->get_connected_drives = g_vfs_afc_volume_monitor_get_connected_drives;
+  monitor_class->is_supported = g_vfs_afc_volume_monitor_is_supported;
+}
+
+static void
+g_vfs_afc_volume_monitor_init(GVfsAfcVolumeMonitor *self)
+{
+}
+
+GVolumeMonitor *
+g_vfs_afc_volume_monitor_new (void)
+{
+  return G_VOLUME_MONITOR(g_object_new (G_VFS_TYPE_AFC_VOLUME_MONITOR,
+                                        NULL));
+}
+
+/*
+ * vim: sw=2 ts=8 cindent expandtab cinoptions=f0,>4,n2,{2,(0,^-2,t0 ai
+ */
diff --git a/monitor/afc/afcvolumemonitor.h b/monitor/afc/afcvolumemonitor.h
new file mode 100644
index 0000000..0bd5f32
--- /dev/null
+++ b/monitor/afc/afcvolumemonitor.h
@@ -0,0 +1,39 @@
+/*
+ * gvfs/monitor/afc/afc-volume-monitor.h
+ *
+ * Copyright (c) 2008 Patrick Walton <pcwalton@ucla.edu>
+ */
+
+#ifndef AFC_VOLUME_MONITOR_H
+#define AFC_VOLUME_MONITOR_H
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define G_VFS_TYPE_AFC_VOLUME_MONITOR   (g_vfs_afc_volume_monitor_get_type())
+#define G_VFS_AFC_VOLUME_MONITOR(o) (G_TYPE_CHECK_INSTANCE_CAST((o), G_VFS_TYPE_AFC_VOLUME_MONITOR, GVfsAfcVolumeMonitor))
+#define G_VFS_AFC_VOLUME_MONITOR_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_VFS_TYPE_AFC_VOLUME_MONITOR, GVfsAfcVolumeMonitorClass))
+#define G_VFS_IS_AFC_VOLUME_MONITOR(o) (G_TYPE_CHECK_INSTANCE_TYPE((o), G_VFS_TYPE_AFC_VOLUME_MONITOR))
+#define G_VFS_IS_AFC_VOLUME_MONITOR_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE((k), G_VFS_TYPE_AFC_VOLUME_MONITOR))
+#define G_VFS_AFC_VOLUME_MONITOR_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), G_VFS_TYPE_AFC_VOLUME_MONITOR, GVfsAfcVolumeMonitorClass))
+
+typedef struct _GVfsAfcVolumeMonitor GVfsAfcVolumeMonitor;
+typedef struct _GVfsAfcVolumeMonitorClass GVfsAfcVolumeMonitorClass;
+
+struct _GVfsAfcVolumeMonitorClass {
+  GVolumeMonitorClass parent_class;
+};
+
+GType g_vfs_afc_volume_monitor_get_type (void) G_GNUC_CONST;
+
+GVolumeMonitor *g_vfs_afc_volume_monitor_new (void);
+
+G_END_DECLS
+
+#endif /* AFC_VOLUME_MONITOR_H */
+
+/*
+ * vim: sw=2 ts=8 cindent expandtab cinoptions=f0,>4,n2,{2,(0,^-2,t0 ai
+ */
diff --git a/monitor/afc/afcvolumemonitordaemon.c b/monitor/afc/afcvolumemonitordaemon.c
new file mode 100644
index 0000000..9c24a34
--- /dev/null
+++ b/monitor/afc/afcvolumemonitordaemon.c
@@ -0,0 +1,31 @@
+/*
+ * gvfs/monitor/afc/afc-volume-monitor-daemon.c
+ *
+ * Copyright (c) 2008-2009 Patrick Walton <pcwalton@ucla.edu>
+ * Copyright (c) 2009 Martin Szulecki <opensuse@sukimashita.com>
+ */
+
+#include <config.h>
+
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+#include <gmodule.h>
+#include <gio/gio.h>
+
+#include <gvfsproxyvolumemonitordaemon.h>
+
+#include "afcvolumemonitor.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.AfcVolumeMonitor",
+                                                 G_VFS_TYPE_AFC_VOLUME_MONITOR);
+}
+
+/*
+ * vim: sw=2 ts=8 cindent expandtab cinoptions=f0,>4,n2,{2,(0,^-2,t0 ai
+ */
diff --git a/monitor/afc/org.gtk.Private.AfcVolumeMonitor.service.in b/monitor/afc/org.gtk.Private.AfcVolumeMonitor.service.in
new file mode 100644
index 0000000..4e6bd33
--- /dev/null
+++ b/monitor/afc/org.gtk.Private.AfcVolumeMonitor.service.in
@@ -0,0 +1,4 @@
+[D-BUS Service]
+Name=org.gtk.Private.AfcVolumeMonitor
+Exec=@libexecdir@/gvfs-afc-volume-monitor
+
-- 
1.6.2.5