From 94a739ae0571995db430edb49e2e9adb318c89df Mon Sep 17 00:00:00 2001 From: Bastien Nocera Date: Aug 11 2009 09:11:04 +0000 Subject: - Add AFC backend --- diff --git a/0001-Add-AFC-backend.patch b/0001-Add-AFC-backend.patch new file mode 100644 index 0000000..b425449 --- /dev/null +++ b/0001-Add-AFC-backend.patch @@ -0,0 +1,2210 @@ +From 1ba08909cdc395c14116d7cdf0f8f3442ba5e4c0 Mon Sep 17 00:00:00 2001 +From: Bastien Nocera +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 +Martin Szulecki +Nikias Bassen +Bastien Nocera +--- + configure.ac | 26 +- + daemon/Makefile.am | 23 + + daemon/afc.mount.in | 7 + + daemon/gvfsbackendafc.c | 1226 ++++++++++++++++++++ + daemon/gvfsbackendafc.h | 37 + + monitor/Makefile.am | 6 +- + monitor/afc/Makefile.am | 49 + + monitor/afc/afc.monitor | 5 + + monitor/afc/afcvolume.c | 336 ++++++ + monitor/afc/afcvolume.h | 44 + + monitor/afc/afcvolumemonitor.c | 215 ++++ + monitor/afc/afcvolumemonitor.h | 39 + + monitor/afc/afcvolumemonitordaemon.c | 31 + + .../org.gtk.Private.AfcVolumeMonitor.service.in | 4 + + 14 files changed, 2046 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 7f1ec16..4675ae9 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 733fa41..4c9e4af 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) +@@ -433,3 +439,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..97d0a22 +--- /dev/null ++++ b/daemon/gvfsbackendafc.c +@@ -0,0 +1,1226 @@ ++/* ++ * gvfs/daemon/gvfsbackendafc.c ++ * ++ * Copyright (c) 2008 Patrick Walton ++ */ ++ ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define G_UDEV_API_IS_SUBJECT_TO_CHANGE ++#include ++ ++#include ++#include ++#include ++ ++#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_endpoint", 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) ++{ ++ GVfsBackendAfc *self; ++ ++ 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 ++ */ ++ ++#ifndef GVFSBACKENDAFC_H ++#define GVFSBACKENDAFC_H ++ ++#include ++#include ++ ++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..fe290a1 +--- /dev/null ++++ b/monitor/afc/afcvolume.c +@@ -0,0 +1,336 @@ ++/* ++ * gvfs/monitor/afc/afc-volume.c ++ * ++ * Copyright (c) 2008 Patrick Walton ++ */ ++ ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++ ++#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(strstr(model, "iPod") != NULL) ++ { ++ 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; ++} ++ ++ ++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 (strcmp (kind, G_VOLUME_IDENTIFIER_KIND_UUID) == 0) ++ 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 = g_object_get_data (G_OBJECT (volume), "root"); ++ ++ 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->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 ++ */ ++ ++#ifndef GVFS_MONITOR_AFC_AFC_VOLUME_H ++#define GVFS_MONITOR_AFC_AFC_VOLUME_H ++ ++#include ++#include ++ ++#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..c98b603 +--- /dev/null ++++ b/monitor/afc/afcvolumemonitor.c +@@ -0,0 +1,215 @@ ++/* ++ * gvfs/monitor/afc/afc-volume-monitor.c ++ * ++ * Copyright (c) 2008 Patrick Walton ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#define G_UDEV_API_IS_SUBJECT_TO_CHANGE ++#include ++#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, *devname, *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; ++ ++ /* Device is for end point 85? */ ++ devname = g_udev_device_get_device_file (device); ++ if (devname == NULL || ++ g_str_has_suffix (devname, "_ep85") == FALSE) ++ return; ++ ++ /* Get us a UUID */ ++ uuid = g_udev_device_get_property (device, "ID_SERIAL_SHORT"); ++ if (uuid == NULL) ++ { ++ g_warning ("Could not get UUID for device '%s'", devname); ++ return; ++ } ++ ++ if (g_str_equal (action, "add") != FALSE) ++ g_vfs_afc_monitor_create_volume (self, uuid); ++ else ++ 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_endpoint", 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_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 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_volumes = g_vfs_afc_volume_monitor_get_volumes; ++ 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 ++ */ ++ ++#ifndef AFC_VOLUME_MONITOR_H ++#define AFC_VOLUME_MONITOR_H ++ ++#include ++#include ++ ++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 ++ * Copyright (c) 2009 Martin Szulecki ++ */ ++ ++#include ++ ++#include ++#include ++#include ++#include ++ ++#include ++ ++#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 + diff --git a/gvfs.spec b/gvfs.spec index 438ce04..82e24aa 100644 --- a/gvfs.spec +++ b/gvfs.spec @@ -1,7 +1,7 @@ Summary: Backends for the gio framework in GLib Name: gvfs Version: 1.3.4 -Release: 1%{?dist} +Release: 2%{?dist} License: LGPLv2+ Group: System Environment/Libraries URL: http://www.gtk.org @@ -41,7 +41,8 @@ Patch4: gvfs-1.3.4-dont-strip-mount-prefix-for-local-paths.patch Patch8: gvfs-1.2.2-dnssd-deadlock.patch # https://bugzilla.redhat.com/show_bug.cgi?id=504339 Patch9: gvfs-1.2.3-sftp-40sec-timeout.patch - +# http://bugzilla.gnome.org/show_bug.cgi?id=591005 +Patch10: 0001-Add-AFC-backend.patch %description The gvfs package provides backend implementations for the gio @@ -119,7 +120,16 @@ This package provides support for reading and writing files on PTP based cameras (Picture Transfer Protocol) and MTP based media players (Media Transfer Protocol) to applications using gvfs. +%package afc +Summary: AFC support for gvfs +Group: System Environment/Libraries +Requires: %{name} = %{version}-%{release} +Requires: usbmuxd +BuildRequires: libiphone-devel >= 0.9.2 libgudev-devel +%description afc +This package provides support for reading files on Apple iPhones and +and iPod Touches to applications using gvfs. %prep @@ -128,6 +138,7 @@ media players (Media Transfer Protocol) to applications using gvfs. %patch4 -p1 -b .mount-prefix %patch8 -p1 -b .dnssd-deadlock %patch9 -p1 -b .sftp-timeout +%patch10 -p1 -b .afc %build @@ -272,8 +283,18 @@ update-desktop-database &> /dev/null ||: %{_datadir}/dbus-1/services/org.gtk.Private.GPhoto2VolumeMonitor.service %{_datadir}/gvfs/remote-volume-monitors/gphoto2.monitor +%files afc +%defattr(-, root, root, -) +%{_libexecdir}/gvfsd-afc +%{_datadir}/gvfs/mounts/afc.mount +%{_libexecdir}/gvfs-afc-volume-monitor +%{_datadir}/dbus-1/services/org.gtk.Private.AFCVolumeMonitor.service +%{_datadir}/gvfs/remote-volume-monitors/afc.monitor %changelog +* Tue Aug 11 2009 Bastien Nocera 1.3.4-2 +- Add AFC backend + * Mon Aug 10 2009 Matthias Clasen - 1.3.4-1 - Update to 1.3.4