diff --git a/configure.ac b/configure.ac
index 5d1e910..e34a6d0 100644
--- a/configure.ac
+++ b/configure.ac
@@ -211,6 +211,7 @@ GNOME_KEYRING_REQUIRED=2.26
 LIBGRSS_REQUIRED=0.3
 REST_REQUIRED=0.6
 NETWORK_MANAGER_REQUIRED=0.8
+LIBGPOD_REQUIRED=0.8
 
 # 3.6.11 for sqlite_backup API
 # 3.6.16 to fix test failures
@@ -407,6 +408,14 @@ if test -n $THUNDERBIRD; then
    AC_SUBST(THUNDERBIRD)
 fi
 
+# Check requirements for tracker-miner-apple-device
+TRACKER_MINER_APPLE_DEVICE_REQUIRED="glib-2.0    >= $GLIB_REQUIRED
+                                     libgpod-1.0 >= $LIBGPOD_REQUIRED"
+
+PKG_CHECK_MODULES(TRACKER_MINER_APPLE_DEVICE, [$TRACKER_MINER_APPLE_DEVICE_REQUIRED],
+                  [have_tracker_miner_apple_device=yes],
+                  [have_tracker_miner_apple_device=no])
+
 # Check requirements for tracker-miner-rss
 TRACKER_MINER_RSS_REQUIRED="glib-2.0     >= $GLIB_REQUIRED
                             libgrss-0    >= $LIBGRSS_REQUIRED"
@@ -1103,6 +1112,29 @@ fi
 AM_CONDITIONAL(HAVE_MEEGOTOUCH, test "x$have_meegotouch" = "xyes")
 
 ##################################################################
+# Check for tracker-miner-apple-device
+##################################################################
+
+AC_ARG_ENABLE(miner_apple_device,
+              AS_HELP_STRING([--enable-miner-apple-device],
+                             [enable Apple device miner [[default=auto]]]),,
+                             [enable_tracker_miner_apple_device=auto])
+
+enable_tracker_miner_apple_device=$enable_miner_apple_device
+
+if test "x$enable_tracker_miner_apple_device" = "xyes"; then
+   if test "x$have_tracker_miner_apple_device" != "xyes"; then
+      AC_MSG_ERROR([Couldn't find tracker-miner-apple_device dependencies ($TRACKER_MINER_APPLE_DEVICE_REQUIRED).])
+   fi
+else
+   if test "x$enable_tracker_miner_apple_device" = "xno"; then
+      have_tracker_miner_apple_device="no  (disabled)"
+   fi
+fi
+
+AM_CONDITIONAL(HAVE_TRACKER_MINER_APPLE_DEVICE, test "x$have_tracker_miner_apple_device" = "xyes")
+
+##################################################################
 # Check for tracker-miner-flickr
 ##################################################################
 
@@ -2116,6 +2148,7 @@ AC_CONFIG_FILES([
 	src/Makefile
 	src/miners/Makefile
 	src/miners/fs/Makefile
+	src/miners/apple-device/Makefile
 	src/miners/rss/Makefile
 	src/miners/flickr/Makefile
 	src/tracker-store/Makefile
@@ -2274,6 +2307,7 @@ Metadata Extractors:
 Data Miners:
 
 	Applications:                           yes (MeeGo support: $have_meegotouch)
+	Apple mobile devices:                   $have_tracker_miner_apple_device
 	RSS:                                    $have_tracker_miner_rss
 	Evolution:                              $have_tracker_miner_evolution ($tracker_miner_evolution_install_dir)
         Thunderbird:                            $have_tracker_miner_thunderbird ($tracker_miner_thunderbird_install_dir)
diff --git a/data/.gitignore b/data/.gitignore
index 8348853..84e38b8 100644
--- a/data/.gitignore
+++ b/data/.gitignore
@@ -1,4 +1,5 @@
 *.pc
+tracker-miner-apple-device.desktop.in
 tracker-miner-fs.desktop.in
 tracker-status-icon.desktop.in
 tracker-store.desktop.in
diff --git a/data/Makefile.am b/data/Makefile.am
index 69c1092..b3a486f 100644
--- a/data/Makefile.am
+++ b/data/Makefile.am
@@ -11,6 +11,10 @@ tracker-miner-fs.desktop.in: tracker-miner-fs.desktop.in.in
 	@sed -e "s|@libexecdir[@]|${libexecdir}|"      \
 	     -e "s|@VERSION[@]|${VERSION}|" $< > $@
 
+tracker-miner-apple-device.desktop.in: tracker-miner-apple-device.desktop.in.in
+	@$(SED) -e "s|@libexecdir[@]|${libexecdir}|"   \
+	     -e "s|@VERSION[@]|${VERSION}|" $< > $@
+
 tracker-miner-flickr.desktop.in: tracker-miner-flickr.desktop.in.in
 	@$(SED) -e "s|@libexecdir[@]|${libexecdir}|"   \
 	     -e "s|@VERSION[@]|${VERSION}|" $< > $@
@@ -19,16 +23,18 @@ tracker-miner-rss.desktop.in: tracker-miner-rss.desktop.in.in
 	@$(SED) -e "s|@libexecdir[@]|${libexecdir}|"   \
 	     -e "s|@VERSION[@]|${VERSION}|" $< > $@
 
-desktop_in_in_files =                                  \
+desktop_in_in_files =                              \
 	tracker-store.desktop.in.in                    \
 	tracker-miner-fs.desktop.in.in                 \
+	tracker-miner-apple-device.desktop.in.in       \
 	tracker-miner-flickr.desktop.in.in             \
 	tracker-miner-rss.desktop.in.in
 
 # Second take those new .in files and translate them
-desktop_in_files =                                     \
+desktop_in_files =                                 \
 	tracker-store.desktop.in                       \
 	tracker-miner-fs.desktop.in                    \
+	tracker-miner-apple-device.desktop.in          \
 	tracker-miner-flickr.desktop.in                \
 	tracker-miner-rss.desktop.in
 
@@ -38,6 +44,10 @@ autostart_DATA =                                       \
 	tracker-store.desktop                          \
 	tracker-miner-fs.desktop
 
+if HAVE_TRACKER_MINER_APPLE_DEVICE
+autostart_DATA += tracker-miner-apple-device.desktop
+endif
+
 if HAVE_TRACKER_MINER_RSS
 autostart_DATA += tracker-miner-rss.desktop
 endif
diff --git a/data/dbus/Makefile.am b/data/dbus/Makefile.am
index a809137..fd6908a 100644
--- a/data/dbus/Makefile.am
+++ b/data/dbus/Makefile.am
@@ -18,6 +18,7 @@ config_noinst = \
 # Services
 service_in_files = \
 	org.freedesktop.Tracker1.service.in \
+	org.freedesktop.Tracker1.Miner.AppleDevice.service.in \
 	org.freedesktop.Tracker1.Miner.Applications.service.in \
 	org.freedesktop.Tracker1.Miner.EMails.service.in \
 	org.freedesktop.Tracker1.Miner.Files.service.in \
@@ -39,6 +40,10 @@ service_DATA = \
 	org.freedesktop.Tracker1.Writeback.service
 
 
+if HAVE_TRACKER_MINER_APPLE_DEVICE
+service_DATA += org.freedesktop.Tracker1.Miner.AppleDevice.service
+endif
+
 if HAVE_TRACKER_MINER_EVOLUTION
 service_DATA += org.freedesktop.Tracker1.Miner.EMails.service
 endif
diff --git a/data/dbus/org.freedesktop.Tracker1.Miner.AppleDevice.service.in b/data/dbus/org.freedesktop.Tracker1.Miner.AppleDevice.service.in
new file mode 100644
index 0000000..e3b069a
--- /dev/null
+++ b/data/dbus/org.freedesktop.Tracker1.Miner.AppleDevice.service.in
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name=org.freedesktop.Tracker1.Miner.AppleDevice
+Exec=@libexecdir@/tracker-miner-apple-device
diff --git a/data/miners/Makefile.am b/data/miners/Makefile.am
index be36aa4..dc583da 100644
--- a/data/miners/Makefile.am
+++ b/data/miners/Makefile.am
@@ -1,6 +1,7 @@
 include $(top_srcdir)/Makefile.decl
 
 desktop_in_in_files =                                  \
+	tracker-miner-apple-device.desktop.in          \
 	tracker-miner-applications.desktop.in.in       \
 	tracker-miner-evolution.desktop.in.in          \
 	tracker-miner-files.desktop.in.in              \
@@ -8,6 +9,7 @@ desktop_in_in_files =                                  \
 	tracker-miner-rss.desktop.in.in
 
 desktop_in_files =                                     \
+	tracker-miner-apple-device.desktop.in          \
 	tracker-miner-applications.desktop.in          \
 	tracker-miner-evolution.desktop.in             \
 	tracker-miner-files.desktop.in                 \
@@ -20,6 +22,10 @@ tracker_miners_DATA =                                  \
 	tracker-miner-applications.desktop             \
 	tracker-miner-files.desktop
 
+if HAVE_TRACKER_MINER_APPLE_DEVICE
+tracker_miners_DATA += tracker-miner-apple-device.desktop
+endif
+
 if HAVE_TRACKER_MINER_EVOLUTION
 tracker_miners_DATA += tracker-miner-evolution.desktop
 endif
diff --git a/data/miners/tracker-miner-apple-device.desktop.in.in b/data/miners/tracker-miner-apple-device.desktop.in.in
new file mode 100644
index 0000000..aa2e202
--- /dev/null
+++ b/data/miners/tracker-miner-apple-device.desktop.in.in
@@ -0,0 +1,8 @@
+[Desktop Entry]
+Encoding=UTF-8
+_Name=Apple device miner
+_Comment=Processes music on Apple iPod and iPhone devices
+Icon=multimedia-player
+DBusName=org.freedesktop.Tracker1.Miner.AppleDevice
+DBusPath=/org/freedesktop/Tracker1/Miner/AppleDevice
+
diff --git a/data/tracker-miner-apple-device.desktop.in.in b/data/tracker-miner-apple-device.desktop.in.in
new file mode 100644
index 0000000..06f7f0f
--- /dev/null
+++ b/data/tracker-miner-apple-device.desktop.in.in
@@ -0,0 +1,18 @@
+[Desktop Entry]
+_Name=Tracker Miner for Apple devices
+_Comment=Processes music on Apple iPod and iPhone devices
+Icon=multimedia-player
+Exec=@libexecdir@/tracker-miner-apple-device
+Terminal=false
+Type=Application
+Categories=Utility;
+X-GNOME-Autostart-enabled=true
+X-KDE-autostart-after=panel
+X-KDE-StartupNotify=false
+X-KDE-UniqueApplet=true
+NoDisplay=true
+OnlyShowIn=GNOME;KDE;XFCE;
+X-GNOME-Bugzilla-Bugzilla=GNOME
+X-GNOME-Bugzilla-Product=tracker
+X-GNOME-Bugzilla-Component=Miners
+X-GNOME-Bugzilla-Version=@VERSION@
diff --git a/po/POTFILES.in b/po/POTFILES.in
index b881f4e..0286efe 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -1,6 +1,7 @@
 # List of source files containing translatable strings.
 # Please keep this file sorted alphabetically.
 [encoding: UTF-8]
+data/miners/tracker-miner-apple-device.desktop.in.in
 data/miners/tracker-miner-applications.desktop.in.in
 data/miners/tracker-miner-evolution.desktop.in.in
 data/miners/tracker-miner-files.desktop.in.in
@@ -12,6 +13,7 @@ data/gschemas/org.freedesktop.Tracker.FTS.gschema.xml.in
 data/gschemas/org.freedesktop.Tracker.Miner.Files.gschema.xml.in
 data/gschemas/org.freedesktop.Tracker.Store.gschema.xml.in
 data/gschemas/org.freedesktop.Tracker.Writeback.gschema.xml.in
+data/tracker-miner-apple-device.desktop.in.in
 data/tracker-miner-flickr.desktop.in.in
 data/tracker-miner-fs.desktop.in.in
 data/tracker-miner-rss.desktop.in.in
diff --git a/po/POTFILES.skip b/po/POTFILES.skip
index a3c8a0a..05e3185 100644
--- a/po/POTFILES.skip
+++ b/po/POTFILES.skip
@@ -1,5 +1,6 @@
 # List of source files that should NOT be translated.
 # Please keep this file sorted alphabetically.
+data/tracker-miner-apple-device.desktop.in
 data/tracker-miner-flickr.desktop.in
 data/tracker-miner-fs.desktop.in
 data/tracker-miner-rss.desktop.in
diff --git a/src/miners/Makefile.am b/src/miners/Makefile.am
index 0cf41ea..a822fed 100644
--- a/src/miners/Makefile.am
+++ b/src/miners/Makefile.am
@@ -2,6 +2,10 @@ include $(top_srcdir)/Makefile.decl
 
 SUBDIRS = fs
 
+if HAVE_TRACKER_MINER_APPLE_DEVICE
+SUBDIRS += apple-device
+endif
+
 if HAVE_TRACKER_MINER_RSS
 SUBDIRS += rss
 endif
diff --git a/src/miners/apple-device/.gitignore b/src/miners/apple-device/.gitignore
new file mode 100644
index 0000000..fee8b69
--- /dev/null
+++ b/src/miners/apple-device/.gitignore
@@ -0,0 +1 @@
+tracker-miner-apple-device
diff --git a/src/miners/apple-device/Makefile.am b/src/miners/apple-device/Makefile.am
new file mode 100644
index 0000000..a384081
--- /dev/null
+++ b/src/miners/apple-device/Makefile.am
@@ -0,0 +1,23 @@
+include $(top_srcdir)/Makefile.decl
+
+AM_CPPFLAGS =                                      \
+	$(BUILD_CFLAGS)                                \
+	-DSHAREDIR=\""$(datadir)"\"                    \
+	-DLOCALEDIR=\""$(localedir)"\"                 \
+	-DLIBEXEC_PATH=\""$(libexecdir)"\"             \
+	-I$(top_srcdir)/src                            \
+	-I$(top_builddir)/src                          \
+	$(TRACKER_MINER_APPLE_DEVICE_CFLAGS)
+
+libexec_PROGRAMS = tracker-miner-apple-device
+
+tracker_miner_apple_device_SOURCES =               \
+	tracker-miner-apple-device.h                   \
+	tracker-miner-apple-device.c
+
+tracker_miner_apple_device_LDADD =                 \
+	$(top_builddir)/src/libtracker-sparql-backend/libtracker-sparql-@TRACKER_API_VERSION@.la \
+	$(top_builddir)/src/libtracker-miner/libtracker-miner-@TRACKER_API_VERSION@.la \
+	$(top_builddir)/src/libtracker-common/libtracker-common.la \
+	$(BUILD_LIBS)                                  \
+	$(TRACKER_MINER_APPLE_DEVICE_LIBS)
diff --git a/src/miners/apple-device/tracker-miner-apple-device.c b/src/miners/apple-device/tracker-miner-apple-device.c
new file mode 100644
index 0000000..3a77dd8
--- /dev/null
+++ b/src/miners/apple-device/tracker-miner-apple-device.c
@@ -0,0 +1,1128 @@
+/*
+ * Copyright (C) 2011, Codethink Ltd. <samthursfield@codethink.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ */
+
+/* Apple iPod/iPhone miner for Tracker.
+ *
+ * Current capabilities: importing the available music on the device.
+ *
+ * Dependencies: libgpod
+ *
+ * Technical details
+ *
+ *  - All Apple devices appear as GVFS mounts; the iPhone/iPod Touch through the
+ *    AFC backend.
+ *
+ *  - Access to the iTunes database is provided by libgpod.
+ *
+ *  - Currently ignores:
+ *      disc numbering
+ *      album art
+ *      the distinction between music and podcasts, videos etc.
+ */
+
+#include <gpod/itdb.h>
+#include <glib/gi18n.h>
+
+#include <libtracker-common/tracker-common.h>
+#include <libtracker-sparql/tracker-sparql.h>
+
+#include "config.h"
+#include "tracker-miner-apple-device.h"
+
+#include <math.h>
+#include <string.h>
+
+#define MINER_NAME "AppleDevice"
+
+/* Number of outstanding D-Bus calls we are allowed */
+#define UPDATE_TASK_LIMIT     10
+
+#define PROCESSING_INTERVAL   200   /* ms */
+
+typedef struct {
+	GDBusConnection *connection;
+
+	/* For our TrackerSparqlConnection calls .. but not used */
+	GCancellable *cancellable;
+
+	gboolean paused;
+	gboolean stopped;
+	gboolean dry_run;
+	gchar *last_status;
+
+	GTimer *status_update_timer;
+	gint process_device_source_id;
+
+	GQueue *device_queue;
+	GQueue *update_task_queue;
+} TrackerMinerAppleDevicePrivate;
+
+typedef struct {
+	gchar *urn;
+	gchar *uuid;
+	gchar *mount_point;
+	Itdb_iTunesDB *itunes_db;
+
+	GList *track_cursor;
+	gint   tracks_processed;
+	gint   tracks_count;
+} Device;
+
+typedef struct {
+	TrackerMinerAppleDevice *owner;
+	TrackerSparqlBuilder    *contents;
+} UpdateTask;
+
+enum {
+	PROP_0,
+	PROP_DRY_RUN
+};
+
+static void     tracker_miner_apple_device_class_init          (TrackerMinerAppleDeviceClass *klass);
+static void     tracker_miner_apple_device_init                (TrackerMinerAppleDevice *object);
+static void     tracker_miner_apple_device_initable_iface_init (GInitableIface *iface);
+static gboolean tracker_miner_apple_device_initable_init       (GInitable *initable, GCancellable *cancellable, GError **error);
+static void     tracker_miner_apple_device_finalize            (GObject *object);
+static void     tracker_miner_apple_device_get_property        (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
+static void     tracker_miner_apple_device_set_property        (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
+
+static void     mount_added_cb    (GVolumeMonitor *volume_monitor, GMount *mount, gpointer user_data);
+static void     mount_removed_cb  (GVolumeMonitor *volume_monitor, GMount *mount, gpointer user_data);
+
+static void     run_processing    (TrackerMinerAppleDevice *self);
+static void     stop_processing   (TrackerMinerAppleDevice *self);
+static gboolean process_device_cb (gpointer data);
+
+static void     miner_started (TrackerMiner *miner);
+static void     miner_stopped (TrackerMiner *miner);
+static void     miner_paused  (TrackerMiner *miner);
+static void     miner_resumed (TrackerMiner *miner);
+
+#define TRACKER_MINER_APPLE_DEVICE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), TRACKER_TYPE_MINER_APPLE_DEVICE, TrackerMinerAppleDevicePrivate))
+
+static GInitableIface *tracker_miner_apple_device_initable_parent_iface;
+
+G_DEFINE_TYPE_WITH_CODE (TrackerMinerAppleDevice, tracker_miner_apple_device, TRACKER_TYPE_MINER,
+                         G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
+                                                tracker_miner_apple_device_initable_iface_init));
+
+static void
+tracker_miner_apple_device_class_init (TrackerMinerAppleDeviceClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	TrackerMinerClass *miner_class = TRACKER_MINER_CLASS (klass);
+
+	object_class->finalize = tracker_miner_apple_device_finalize;
+	object_class->get_property = tracker_miner_apple_device_get_property;
+	object_class->set_property = tracker_miner_apple_device_set_property;
+
+	miner_class->started = miner_started;
+	miner_class->stopped = miner_stopped;
+	miner_class->paused  = miner_paused;
+	miner_class->resumed = miner_resumed;
+
+	g_object_class_install_property (object_class,
+	                                 PROP_DRY_RUN,
+	                                 g_param_spec_boolean ("dry-run",
+	                                                       "Dry run",
+	                                                       "If TRUE, runs in test mode",
+	                                                       FALSE,
+	                                                       G_PARAM_READWRITE));
+
+	g_type_class_add_private (object_class, sizeof (TrackerMinerAppleDevicePrivate));
+}
+
+static void
+tracker_miner_apple_device_init (TrackerMinerAppleDevice *object)
+{
+	TrackerMinerAppleDevicePrivate *priv;
+
+	g_message ("Initializing...");
+
+	priv = TRACKER_MINER_APPLE_DEVICE_GET_PRIVATE (object);
+
+	priv->cancellable = g_cancellable_new ();
+
+	priv->paused = FALSE;
+	priv->stopped = FALSE;
+	priv->dry_run = FALSE;
+
+	priv->status_update_timer = g_timer_new ();
+	priv->process_device_source_id = 0;
+
+	priv->device_queue = g_queue_new ();
+	priv->update_task_queue = g_queue_new ();
+}
+
+static void
+tracker_miner_apple_device_initable_iface_init (GInitableIface *iface)
+{
+	tracker_miner_apple_device_initable_parent_iface = g_type_interface_peek_parent (iface);
+	iface->init = tracker_miner_apple_device_initable_init;
+}
+
+static gboolean
+tracker_miner_apple_device_initable_init (GInitable     *initable,
+                                          GCancellable  *cancellable,
+                                          GError       **error)
+{
+	TrackerMinerAppleDevice        *self;
+	TrackerMinerAppleDevicePrivate *priv;
+	GVolumeMonitor *volume_monitor;
+	GList          *mount_list;
+	GList          *node;
+	GError         *inner_error = NULL;
+
+	self = TRACKER_MINER_APPLE_DEVICE (initable);
+	priv = TRACKER_MINER_APPLE_DEVICE_GET_PRIVATE (self);
+
+	/* Chain up parent's initable callback before calling child's one */
+	if (!tracker_miner_apple_device_initable_parent_iface->init (initable, cancellable, &inner_error)) {
+		g_propagate_error (error, inner_error);
+		return FALSE;
+	}
+
+	priv->connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &inner_error);
+
+	if (! priv->connection) {
+		g_propagate_error (error, inner_error);
+		return FALSE;
+	}
+
+	/* Look through available devices and watch for future changes */
+	volume_monitor = g_volume_monitor_get ();
+
+	g_signal_connect (volume_monitor, "mount-added", G_CALLBACK (mount_added_cb), self);
+	g_signal_connect (volume_monitor, "mount-removed", G_CALLBACK (mount_removed_cb), self);
+
+	mount_list = g_volume_monitor_get_mounts (volume_monitor);
+
+	for (node = mount_list; node; node = node->next) {
+		GMount *mount = G_MOUNT (node->data);
+
+		mount_added_cb (volume_monitor, mount, self);
+
+		g_object_unref (mount);
+	}
+
+	g_list_free (mount_list);
+
+	return TRUE;
+}
+
+static void
+tracker_miner_apple_device_finalize (GObject *object)
+{
+	TrackerMinerAppleDevicePrivate *priv;
+
+	priv = TRACKER_MINER_APPLE_DEVICE_GET_PRIVATE (object);
+
+	/* Blocks until we are free to shut down */
+	stop_processing (TRACKER_MINER_APPLE_DEVICE (object));
+
+	g_timer_destroy (priv->status_update_timer);
+
+	g_queue_free (priv->update_task_queue);
+	g_queue_free (priv->device_queue);
+
+	priv->stopped = TRUE;
+	g_free (priv->last_status);
+
+	g_object_unref (priv->connection);
+	g_object_unref (priv->cancellable);
+
+	G_OBJECT_CLASS (tracker_miner_apple_device_parent_class)->finalize (object);
+}
+
+static void
+tracker_miner_apple_device_get_property (GObject    *object,
+                                         guint       prop_id,
+                                         GValue     *value,
+                                         GParamSpec *pspec)
+{
+	TrackerMinerAppleDevicePrivate *priv;
+
+	priv = TRACKER_MINER_APPLE_DEVICE_GET_PRIVATE (object);
+
+	switch (prop_id) {
+	case PROP_DRY_RUN:
+		g_value_set_boolean (value, priv->dry_run);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+tracker_miner_apple_device_set_property (GObject      *object,
+                                         guint         prop_id,
+                                         const GValue *value,
+                                         GParamSpec   *pspec)
+{
+	TrackerMinerAppleDevicePrivate *priv;
+
+	priv = TRACKER_MINER_APPLE_DEVICE_GET_PRIVATE (object);
+
+	switch (prop_id) {
+	case PROP_DRY_RUN:
+		priv->dry_run = g_value_get_boolean (value);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static Device *
+device_new (const gchar   *name,
+            const gchar   *mount_point,
+            Itdb_iTunesDB *itunes_db)
+{
+	Device *device;
+
+	const Itdb_IpodInfo *device_info;
+	gchar *serial_number;
+	gchar *uuid_source;
+
+	device = g_slice_new (Device);
+	device->mount_point = g_strdup (mount_point);
+	device->itunes_db = itunes_db;
+
+	device->track_cursor = itunes_db->tracks;
+	device->tracks_processed = 0;
+	device->tracks_count = itdb_tracks_number (itunes_db);
+
+	/* Come up with a UUID for the device. Note that the GMount doesn't
+	 * ever seem to have a UUID, neither for the file system nor the
+	 * AFC backends. In this case TrackerStorage will hash the mount
+	 * point, but we might be able to do slightly better.
+	 */
+
+	serial_number = itdb_device_get_sysinfo (itunes_db->device, "pszSerialNumber");
+
+	if (serial_number != NULL) {
+		device_info = itdb_device_get_ipod_info (itunes_db->device);
+		uuid_source = g_strdup_printf ("%s.%i.%i.%s",
+		                               device_info->model_number,
+		                               device_info->ipod_model,
+		                               device_info->ipod_generation,
+		                               serial_number);
+
+		g_message ("Got device ID: %s %s\n",
+		           device_info->model_number,
+		           serial_number);
+	} else {
+		/* :( */
+		g_warning ("No serial number or unique ID discovered for '%s'", name);
+		uuid_source = g_strdup (device->mount_point);
+	}
+
+	device->uuid = g_compute_checksum_for_string (G_CHECKSUM_MD5, uuid_source, -1);
+	device->urn = g_strdup_printf ("urn:nepomuk:datasource:%s", device->uuid);
+
+	g_free (uuid_source);
+	g_free (serial_number);
+
+	return device;
+}
+
+static void
+device_free (Device *device)
+{
+	itdb_free (device->itunes_db);
+	g_free (device->mount_point);
+	g_free (device->uuid);
+	g_free (device->urn);
+	g_slice_free (Device, device);
+}
+
+static void
+device_register_tracker_volume (TrackerMinerAppleDevice *self,
+                                Device                  *device)
+{
+	TrackerMinerAppleDevicePrivate *priv;
+	gchar *update;
+	GError *error = NULL;
+
+	priv = TRACKER_MINER_APPLE_DEVICE_GET_PRIVATE (self);
+
+	update = g_strdup_printf ("INSERT { "
+	                          "  <%s> a tracker:Volume "
+	                          "}",
+	                          device->urn);
+
+	tracker_sparql_connection_update (tracker_miner_get_connection (TRACKER_MINER (self)),
+	                                  update,
+	                                  G_PRIORITY_DEFAULT,
+	                                  priv->cancellable,
+	                                  &error);
+
+	g_free (update);
+
+	if (error != NULL)
+		g_warning ("Unable to update tracker:Volume information: %s", error->message);
+}
+
+static gint
+device_lookup_foreach (gconstpointer a,
+                       gconstpointer b)
+{
+	Device *device = (Device *) a;
+	const gchar *mount_point = (const gchar *) b;
+
+	return (strcmp (mount_point, device->mount_point));
+}
+
+static Device *
+device_lookup (TrackerMinerAppleDevice *self,
+               const gchar             *mount_point)
+{
+	TrackerMinerAppleDevicePrivate *priv;
+	GList *node;
+
+	priv = TRACKER_MINER_APPLE_DEVICE_GET_PRIVATE (self);
+
+	node = g_queue_find_custom (priv->device_queue, mount_point, device_lookup_foreach);
+
+	return node != NULL ? node->data : NULL;
+}
+
+static void
+device_add (TrackerMinerAppleDevice *self,
+            const gchar             *name,
+            const gchar             *uri,
+            const gchar             *mount_point)
+{
+	TrackerMinerAppleDevicePrivate *priv;
+	Itdb_iTunesDB *itunes_db;
+	Device        *device;
+	GError        *error = NULL;
+
+	priv = TRACKER_MINER_APPLE_DEVICE_GET_PRIVATE (self);
+
+	/* Parse the database directly in the mount-added handler - it only takes
+	 * a few seconds.
+	 */
+	itunes_db = itdb_parse (mount_point, &error);
+
+	if (error != NULL) {
+		g_critical ("Unable to open iTunes database on device %s: %s",
+		            name, error->message);
+		return;
+	}
+
+	device = device_new (name, mount_point, itunes_db);
+
+	/* Add/update the tracker:Volume info inside the data store.
+	 *
+	 * FIXME: currently, tracker-miner-fs will also create a tracker:Volume
+	 * for our device, but we should be able to coexist. The long term
+	 * solution is to make TrackerStorage usable by all miners, and allow
+	 * miners to 'claim' removable media of whatever sort.
+	 */
+
+	device_register_tracker_volume (self, device);
+
+	g_queue_push_tail (priv->device_queue, device);
+
+	run_processing (self);
+}
+
+static void
+device_remove (TrackerMinerAppleDevice *self,
+               Device                  *device)
+{
+	TrackerMinerAppleDevicePrivate *priv;
+
+	priv = TRACKER_MINER_APPLE_DEVICE_GET_PRIVATE (self);
+
+	g_queue_remove (priv->device_queue, device);
+	device_free (device);
+
+	if (g_queue_is_empty (priv->device_queue)) {
+		stop_processing (self);
+		g_object_set (self, "status", "Idle", "progress", 1.0, NULL);
+	}
+}
+
+static gchar *
+device_get_track_url (Device     *device,
+                      Itdb_Track *track)
+{
+	gchar *track_relative_path;
+	gchar *track_absolute_path;
+	gchar *track_url;
+
+	track_relative_path = g_strdup (track->ipod_path);
+	itdb_filename_ipod2fs (track_relative_path);
+	track_absolute_path = g_build_filename (device->mount_point, track_relative_path, NULL);
+
+	track_url = g_filename_to_uri (track_absolute_path, NULL, NULL);
+
+	g_free (track_relative_path);
+	g_free (track_absolute_path);
+
+	return track_url;
+}
+
+static void
+register_mount (TrackerMinerAppleDevice *self,
+                GMount                  *mount)
+{
+	TrackerMinerAppleDevicePrivate *priv;
+	GFile  *root;
+	gchar  *name;
+	gchar  *uri;
+	gchar  *mount_point;
+	gchar  *itunes_dir;
+
+	priv = TRACKER_MINER_APPLE_DEVICE_GET_PRIVATE (self);
+
+	root = g_mount_get_root (mount);
+	name = g_mount_get_name (mount);
+	uri = g_file_get_uri (root), 
+	mount_point = g_file_get_path (root);
+	itunes_dir = itdb_get_itunes_dir (mount_point);
+
+	/* Test if the mount has an iTunes database; get  */
+	if (itunes_dir != NULL && g_file_test (itunes_dir, G_FILE_TEST_IS_DIR) == TRUE)
+		device_add (self, name, uri, mount_point);
+
+	g_free (itunes_dir);
+	g_free (mount_point);
+	g_free (name);
+	g_free (uri);
+	g_object_unref (root);
+}
+
+/* This hackery is due to the fact that we may receive mount-added before the
+ * device has actually settled, so we can't tell whether it's an Apple device
+ * yet.
+ *
+ * FIXME: this is really a GIO bug, it's pointless to send 'mount-added' when
+ * the mount is not yet available. Also note this bug is of course very hard
+ * to reproduce, it only appears when no code is there to trap the error.
+ */
+
+typedef struct {
+	TrackerMinerAppleDevice *self;
+	GMount *mount;
+	int call_count;
+} DeviceWaitClosure;
+
+static gboolean
+mount_added_wait_cb (gpointer user_data)
+{
+	DeviceWaitClosure *data = user_data;
+	GFile    *root;
+	gchar    *mount_point;
+	gboolean  device_available = FALSE;
+
+	root = g_mount_get_root (data->mount);
+	mount_point = g_file_get_path (root);
+
+	g_message ("In wait callback for %s ..", mount_point);
+
+	if (mount_point != NULL)
+		device_available = g_file_test (mount_point, G_FILE_TEST_IS_DIR);
+
+	if (device_available)
+		register_mount (data->self, data->mount);
+
+	g_free (mount_point);
+	g_object_unref (root);
+
+	/* Quit if successful, or if we've tried more than 5 times */
+	if (device_available || (data->call_count ++) > 5) {
+		g_object_unref (data->mount);
+		g_object_unref (data->self);
+		g_slice_free (DeviceWaitClosure, data);
+
+		return FALSE;
+	}
+
+	/* Otherwise, let's have another go ... */
+	return TRUE;
+}
+
+static void
+mount_added_cb (GVolumeMonitor *volume_monitor,
+                GMount         *mount,
+                gpointer        user_data)
+{
+	TrackerMinerAppleDevice *self;
+	GFile *root;
+	gchar *uri;
+	gchar *mount_point;
+
+	self = TRACKER_MINER_APPLE_DEVICE (user_data);
+
+	root = g_mount_get_root (mount);
+	uri = g_file_get_uri (root);
+	mount_point = g_file_get_path (root);
+
+	if (mount_point == NULL || device_lookup (self, mount_point) != NULL) {
+		g_free (mount_point);
+		g_free (uri);
+		g_object_unref (root);
+		return;
+	}
+
+	if (g_file_test (mount_point, G_FILE_TEST_IS_DIR) == TRUE)
+		register_mount (TRACKER_MINER_APPLE_DEVICE (user_data), mount);
+	else {
+		DeviceWaitClosure *data = g_slice_new (DeviceWaitClosure);
+		data->self = g_object_ref (TRACKER_MINER_APPLE_DEVICE (user_data));
+		data->mount = g_object_ref (mount);
+		data->call_count = 0;
+
+		g_timeout_add (250, mount_added_wait_cb, data);
+	}
+
+	g_free (mount_point);
+	g_object_unref (root);
+}
+
+
+static void
+mount_removed_cb (GVolumeMonitor *volume_monitor, GMount *mount, gpointer user_data)
+{
+	TrackerMinerAppleDevice *self;
+	Device *device;
+	GFile *root;
+	gchar *uri;
+
+	self = TRACKER_MINER_APPLE_DEVICE (user_data);
+	root = g_mount_get_root (mount);
+	uri = g_file_get_uri (root);
+
+	device = device_lookup (self, uri);
+
+	if (device != NULL)
+		device_remove (self, device);
+}
+
+
+static void
+run_processing (TrackerMinerAppleDevice *self)
+{
+	TrackerMinerAppleDevicePrivate *priv;
+
+	priv = TRACKER_MINER_APPLE_DEVICE_GET_PRIVATE (self);
+
+	if (g_queue_is_empty (priv->device_queue)) {
+		g_object_set (self, "progress", 1.0, "status", "Idle", NULL);
+		return;
+	}
+
+	if (priv->process_device_source_id == 0)
+		priv->process_device_source_id = g_timeout_add (PROCESSING_INTERVAL, process_device_cb, self);
+}
+
+static void
+stop_processing (TrackerMinerAppleDevice *self)
+{
+	TrackerMinerAppleDevicePrivate *priv;
+
+	priv = TRACKER_MINER_APPLE_DEVICE_GET_PRIVATE (self);
+
+	if (priv->process_device_source_id != 0) {
+		g_source_remove (priv->process_device_source_id);
+		priv->process_device_source_id = 0;
+	}
+
+	while (! g_queue_is_empty (priv->update_task_queue)) {
+		/* Block while the updates queue empties, we might be about to finalize.
+		 * Thanks to the fact that miners run as independent processes we can
+		 * get away with this sort of brute force synchronisation.
+		 */
+		g_message ("Waiting for %i updates to complete...",
+		           g_queue_get_length (priv->update_task_queue));
+		g_usleep (1 * G_USEC_PER_SEC);
+	}
+}
+
+static void
+update_processing_status (TrackerMinerAppleDevice *self)
+{
+	TrackerMinerAppleDevicePrivate *priv;
+	gint    total_tracks_processed = 0;
+	gint    total_tracks = 0;
+	gint    i;
+	gdouble progress;
+
+	priv = TRACKER_MINER_APPLE_DEVICE_GET_PRIVATE (self);
+
+	for (i = 0; i < g_queue_get_length (priv->device_queue); i++) {
+		Device *device = g_queue_peek_nth (priv->device_queue, i);
+
+		total_tracks += device->tracks_count;
+		total_tracks_processed += device->tracks_processed;
+	}
+
+	if (total_tracks == 0)
+		progress = 1.0;
+	else
+		progress = (gdouble)total_tracks_processed / (gdouble)total_tracks;
+
+	g_object_set (self, "status", "Processing…", "progress", progress, NULL);
+
+	g_message ("\t-- Processed %i / %i tracks from %i devices",
+	           total_tracks_processed,
+	           total_tracks,
+	           g_queue_get_length (priv->device_queue));
+}
+
+static void
+update_complete_cb (GObject      *source_object,
+                    GAsyncResult *res,
+                    gpointer      user_data)
+{
+	TrackerMinerAppleDevicePrivate *priv;
+	UpdateTask *task = user_data;
+	GError *error = NULL;
+
+	priv = TRACKER_MINER_APPLE_DEVICE_GET_PRIVATE (task->owner);
+
+	tracker_sparql_connection_update_finish
+	  (TRACKER_SPARQL_CONNECTION (source_object), res, &error);
+
+	if (error != NULL) {
+		/* Some D-Bus errors simply indicate that we are pushing data too
+		 * quickly. If we handled those by lowering the query limit, we
+		 * could perhaps push data faster wherever it was possible.
+		 */
+		g_critical ("Error inserting data: %s", error->message);
+		g_error_free (error);
+	}
+
+	g_queue_remove (priv->update_task_queue, task);
+
+	g_object_unref (task->contents);
+	g_slice_free (UpdateTask, task);
+}
+
+static void
+execute_update (TrackerMinerAppleDevice *self,
+                TrackerSparqlBuilder    *sparql)
+{
+	TrackerMinerAppleDevicePrivate *priv;
+	UpdateTask *task;
+
+	priv = TRACKER_MINER_APPLE_DEVICE_GET_PRIVATE (self);
+
+	if (tracker_sparql_builder_get_length (sparql) == 0)
+		return;
+
+	task = g_slice_new (UpdateTask);
+	task->owner = self;
+	task->contents = g_object_ref (sparql);
+
+	g_queue_push_tail (priv->update_task_queue, task);
+
+	tracker_sparql_connection_update_async (tracker_miner_get_connection (TRACKER_MINER (self)),
+	                                        tracker_sparql_builder_get_result (sparql),
+	                                        G_PRIORITY_DEFAULT,
+	                                        priv->cancellable,
+	                                        update_complete_cb,
+	                                        task); 
+}
+
+/* process_track:
+ * 
+ * Build SPARQL INSERT query for the track. This code is based on that
+ * from tracker-extract-mp3 ... and could perhaps be merged into some
+ * libtracker-extract function that takes generic music info and
+ * generates the query there.
+ */
+static void
+process_track (TrackerMinerAppleDevice *self,
+               Device                  *device,
+               Itdb_Track              *track)
+{
+	TrackerMinerAppleDevicePrivate *priv;
+	TrackerSparqlBuilder *preupdate;
+	TrackerSparqlBuilder *metadata;
+
+	gchar *track_urn;
+	gchar *track_url;
+	gchar *artist_urn = NULL;
+	gchar *composer_urn = NULL;
+	gchar *album_artist_urn = NULL;
+	gchar *album_urn = NULL;
+
+	priv = TRACKER_MINER_APPLE_DEVICE_GET_PRIVATE (self);
+
+	preupdate = tracker_sparql_builder_new_update ();
+
+	track_urn = tracker_sparql_get_uuid_urn ();
+	metadata = tracker_sparql_builder_new_update ();
+
+	track_url = device_get_track_url (device, track);
+
+	if (track->artist != NULL) {
+		artist_urn = tracker_sparql_escape_uri_printf ("urn:artist:%s", track->artist);
+
+		tracker_sparql_builder_insert_open (preupdate, NULL);
+
+		tracker_sparql_builder_subject_iri (preupdate, artist_urn);
+		tracker_sparql_builder_predicate (preupdate, "a");
+		tracker_sparql_builder_object (preupdate, "nmm:Artist");
+		tracker_sparql_builder_predicate (preupdate, "nmm:artistName");
+		tracker_sparql_builder_object_unvalidated (preupdate, track->artist);
+
+		tracker_sparql_builder_insert_close (preupdate);
+	}
+
+	if (track->composer != NULL) {
+		composer_urn = tracker_sparql_escape_uri_printf ("urn:artist:%s", track->composer);
+
+		tracker_sparql_builder_insert_open (preupdate, NULL);
+
+		tracker_sparql_builder_subject_iri (preupdate, composer_urn);
+		tracker_sparql_builder_predicate (preupdate, "a");
+		tracker_sparql_builder_object (preupdate, "nmm:Artist");
+		tracker_sparql_builder_predicate (preupdate, "nmm:artistName");
+		tracker_sparql_builder_object_unvalidated (preupdate, track->composer);
+
+		tracker_sparql_builder_insert_close (preupdate);
+	}
+
+	if (track->albumartist != NULL &&
+		(track->artist == NULL || strcmp (track->artist, track->albumartist) != 0)) {
+		album_artist_urn = tracker_sparql_escape_uri_printf ("urn:artist:%s", track->albumartist);
+
+		tracker_sparql_builder_insert_open (preupdate, NULL);
+
+		tracker_sparql_builder_subject_iri (preupdate, album_artist_urn);
+		tracker_sparql_builder_predicate (preupdate, "a");
+		tracker_sparql_builder_object (preupdate, "nmm:Artist");
+		tracker_sparql_builder_predicate (preupdate, "nmm:artistName");
+		tracker_sparql_builder_object_unvalidated (preupdate, track->albumartist);
+
+		tracker_sparql_builder_insert_close (preupdate);
+	}
+
+	if (track->album) {
+		album_urn = tracker_sparql_escape_uri_printf ("urn:album:%s", track->album);
+
+		tracker_sparql_builder_insert_open (preupdate, NULL);
+
+		tracker_sparql_builder_subject_iri (preupdate, album_urn);
+		tracker_sparql_builder_predicate (preupdate, "a");
+		tracker_sparql_builder_object (preupdate, "nmm:MusicAlbum");
+		/* FIXME: nmm:albumTitle is now deprecated
+		 * tracker_sparql_builder_predicate (preupdate, "nie:title");
+		 */
+		tracker_sparql_builder_predicate (preupdate, "nmm:albumTitle");
+		tracker_sparql_builder_object_unvalidated (preupdate, track->album);
+
+		if (album_artist_urn != NULL) {
+			tracker_sparql_builder_predicate (preupdate, "nmm:albumArtist");
+			tracker_sparql_builder_object_iri (preupdate, album_artist_urn);
+		} else if (artist_urn != NULL) {
+			tracker_sparql_builder_predicate (preupdate, "nmm:albumArtist");
+			tracker_sparql_builder_object_iri (preupdate, artist_urn);
+		}
+
+		tracker_sparql_builder_insert_close (preupdate);
+
+		if (track->tracks > 0) {
+			tracker_sparql_builder_delete_open (preupdate, NULL);
+			tracker_sparql_builder_subject_iri (preupdate, album_urn);
+			tracker_sparql_builder_predicate (preupdate, "nmm:albumTrackCount");
+			tracker_sparql_builder_object_variable (preupdate, "unknown");
+			tracker_sparql_builder_delete_close (preupdate);
+			tracker_sparql_builder_where_open (preupdate);
+			tracker_sparql_builder_subject_iri (preupdate, album_urn);
+			tracker_sparql_builder_predicate (preupdate, "nmm:albumTrackCount");
+			tracker_sparql_builder_object_variable (preupdate, "unknown");
+			tracker_sparql_builder_where_close (preupdate);
+
+			tracker_sparql_builder_insert_open (preupdate, NULL);
+
+			tracker_sparql_builder_subject_iri (preupdate, album_urn);
+			tracker_sparql_builder_predicate (preupdate, "nmm:albumTrackCount");
+			tracker_sparql_builder_object_int64 (preupdate, track->tracks);
+
+			tracker_sparql_builder_insert_close (preupdate);
+		}
+	}
+
+	tracker_sparql_builder_insert_open (metadata, NULL);
+	tracker_sparql_builder_subject_iri (metadata, track_urn);
+	tracker_sparql_builder_predicate (metadata, "a");
+	tracker_sparql_builder_object (metadata, "nie:DataObject");
+	tracker_sparql_builder_object (metadata, "nfo:Audio");
+	tracker_sparql_builder_object (metadata, "nmm:MusicPiece");
+
+	tracker_sparql_builder_predicate (metadata, "nie:dataSource");
+	tracker_sparql_builder_object_iri (metadata, device->urn);
+
+	tracker_sparql_builder_predicate (metadata, "nie:title");
+	tracker_sparql_builder_object_string (metadata, track->title != NULL ? track->title : track_url);
+
+	tracker_sparql_builder_predicate (metadata, "nie:url");
+	tracker_sparql_builder_object_string (metadata, track_url);
+
+	if (track->comment != NULL) {
+		tracker_sparql_builder_predicate (metadata, "nie:comment");
+		tracker_sparql_builder_object_string (metadata, track->comment);
+	}
+
+	tracker_sparql_builder_predicate (metadata, "nfo:averageBitrate");
+	tracker_sparql_builder_object_int64 (metadata, track->bitrate);
+	tracker_sparql_builder_predicate (metadata, "nfo:duration");
+	tracker_sparql_builder_object_int64 (metadata, ceil (track->tracklen / 1000.0));
+
+	if (track->genre != NULL) {
+		tracker_sparql_builder_predicate (metadata, "nfo:genre");
+		tracker_sparql_builder_object_string (metadata, track->genre);
+	}
+
+	tracker_sparql_builder_predicate (metadata, "nfo:sampleRate");
+	tracker_sparql_builder_object_int64 (metadata, track->samplerate);
+
+	if (artist_urn != NULL) {
+		tracker_sparql_builder_predicate (metadata, "nmm:performer");
+		tracker_sparql_builder_object_iri (metadata, artist_urn);
+	}
+
+	if (composer_urn != NULL) {
+		tracker_sparql_builder_predicate (metadata, "nmm:composer");
+		tracker_sparql_builder_object_iri (metadata, composer_urn);
+	}
+
+	if (album_urn != NULL) {
+		tracker_sparql_builder_predicate (metadata, "nmm:musicAlbum");
+		tracker_sparql_builder_object_iri (metadata, album_urn);
+	}
+
+	if (track->track_nr > 0) {
+		tracker_sparql_builder_predicate (metadata, "nmm:trackNumber");
+		tracker_sparql_builder_object_int64 (metadata, track->track_nr);
+	}
+
+	tracker_sparql_builder_insert_close (metadata);
+
+	g_free (track_url);
+	g_free (track_urn);
+	g_free (artist_urn);
+	g_free (composer_urn);
+	g_free (album_urn);
+	g_free (album_artist_urn);
+
+	if (priv->dry_run) {
+		g_print ("\n + %s\n", tracker_sparql_builder_get_result (preupdate));
+		g_print ("\n * %s\n", tracker_sparql_builder_get_result (metadata));
+	} else {
+		execute_update (self, preupdate);
+		execute_update (self, metadata);
+	}
+
+	g_object_unref (preupdate);
+	g_object_unref (metadata);
+}
+
+static gboolean
+process_device_cb (gpointer data)
+{
+	TrackerMinerAppleDevice *self;
+	TrackerMinerAppleDevicePrivate *priv;
+	Device  *device;
+
+	self = TRACKER_MINER_APPLE_DEVICE (data);
+	priv = TRACKER_MINER_APPLE_DEVICE_GET_PRIVATE (self);
+
+	if (g_queue_is_empty (priv->device_queue)) {
+		priv->process_device_source_id = 0;
+		return FALSE;
+	}
+
+	if (g_timer_elapsed (priv->status_update_timer, NULL) > 1.0) {
+		g_timer_reset (priv->status_update_timer);
+
+		update_processing_status (self);
+	}
+
+	device = g_queue_peek_head (priv->device_queue);
+
+	while (g_queue_get_length (priv->update_task_queue) < UPDATE_TASK_LIMIT) {
+		/* Process as many tracks as we can, rate-limited by
+		 * tracker-store's D-Bus interface */
+		if (device->track_cursor == NULL)
+			break;
+
+		process_track (self, device, device->track_cursor->data);
+
+		device->track_cursor = device->track_cursor->next;
+		device->tracks_processed ++;
+	}
+
+	if (device->track_cursor == NULL) {
+		g_queue_pop_head (priv->device_queue);
+		device_free (device);
+	}
+
+	if (g_queue_is_empty (priv->device_queue)) {
+		/* We're done */
+		g_object_set (self, "status", "Idle", "progress", 1.0, NULL);
+		g_message ("All devices processed.\n");
+		priv->process_device_source_id = 0;
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+
+static void
+miner_started (TrackerMiner *miner)
+{
+	TrackerMinerAppleDevicePrivate *priv;
+
+	priv = TRACKER_MINER_APPLE_DEVICE_GET_PRIVATE (miner);
+
+	g_object_set (miner, "progress", 0.0, "status", "Initializing", NULL);
+
+	run_processing (TRACKER_MINER_APPLE_DEVICE (miner));
+}
+
+static void
+miner_stopped (TrackerMiner *miner)
+{
+	stop_processing (TRACKER_MINER_APPLE_DEVICE (miner));
+
+	g_object_set (miner, "progress", 1.0, "status", "Idle", NULL);
+}
+
+static void
+miner_paused (TrackerMiner *miner)
+{
+	TrackerMinerAppleDevicePrivate *priv;
+
+	priv = TRACKER_MINER_APPLE_DEVICE_GET_PRIVATE (miner);
+
+	stop_processing (TRACKER_MINER_APPLE_DEVICE (miner));
+
+	g_free (priv->last_status);
+	g_object_get (miner, "status", &priv->last_status, NULL);
+
+	g_object_set (miner, "status", "Paused", NULL);
+}
+
+static void
+miner_resumed (TrackerMiner *miner)
+{
+	TrackerMinerAppleDevicePrivate *priv;
+
+	priv = TRACKER_MINER_APPLE_DEVICE_GET_PRIVATE (miner);
+
+	g_object_set (miner, "status", priv->last_status ? priv->last_status : "Idle", NULL);
+
+	if (! g_queue_is_empty (priv->device_queue))
+		run_processing (TRACKER_MINER_APPLE_DEVICE (miner));
+}
+
+
+TrackerMinerAppleDevice *
+tracker_miner_apple_device_new (GError **error)
+{
+	return g_initable_new (TRACKER_TYPE_MINER_APPLE_DEVICE,
+	                       NULL,
+	                       error,
+	                       "name", MINER_NAME,
+	                       NULL);
+}
+
+
+int
+main (int argc, char **argv)
+{
+	TrackerMinerAppleDevice *miner;
+	GMainLoop *loop;
+	GError    *error = NULL;
+	int i;
+
+	g_type_init ();
+
+	g_set_application_name ("tracker-miner-apple-device");
+
+	setlocale (LC_ALL, "");
+
+	bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
+	bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+	textdomain (GETTEXT_PACKAGE);
+	tzset ();
+
+	tracker_log_init (2, NULL);
+
+	miner = tracker_miner_apple_device_new (&error);
+	if (!miner) {
+		g_printerr ("Cannot create new Apple device miner: '%s', exiting...\n",
+		            error ? error->message : "unknown error");
+		return EXIT_FAILURE;
+	};
+
+	/* Command-line arguments - for testing and debugging only */
+	i = 0;
+	while ((++ i) < argc) {
+		if (strcmp (argv[i], "--dry-run") == 0)
+			/* --dry-run: Don't actually commit anything to the tracker store */
+			g_object_set (G_OBJECT (miner), "dry-run", TRUE, NULL);
+
+		else
+		if (strcmp (argv[i], "--directory") == 0) {
+			/* --directory PATH: Read PATH as if it was a mounted Apple device */
+			char *path,
+			     *absolute_path,
+			     *uri;
+
+			path = argv[++ i];
+
+			if (g_path_is_absolute (path))
+				absolute_path = g_strdup (path);
+			else
+				absolute_path = g_build_filename (g_get_current_dir (), path, NULL);
+
+			uri = g_filename_to_uri (absolute_path, NULL, NULL);
+
+			device_add (miner, absolute_path, uri, absolute_path);
+
+			g_free (uri);
+			g_free (absolute_path);
+		}
+
+		else
+			g_print ("Unrecognised argument: %s\n", argv[i]);
+	}
+
+	tracker_miner_start (TRACKER_MINER (miner));
+
+	loop = g_main_loop_new (NULL, FALSE);
+	g_main_loop_run (loop);
+
+	tracker_log_shutdown ();
+
+	g_main_loop_unref (loop);
+	g_object_unref (miner);
+
+	return EXIT_SUCCESS;
+}
\ No newline at end of file
diff --git a/src/miners/apple-device/tracker-miner-apple-device.h b/src/miners/apple-device/tracker-miner-apple-device.h
new file mode 100644
index 0000000..20605ed
--- /dev/null
+++ b/src/miners/apple-device/tracker-miner-apple-device.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2011, Codethink Ltd. <samthursfield@codethink.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ */
+
+#ifndef __TRACKER_MINER_APPLE_DEVICE_H
+#define __TRACKER_MINER_APPLE_DEVICE_H
+
+#include <libtracker-miner/tracker-miner.h>
+
+G_BEGIN_DECLS
+
+#define TRACKER_TYPE_MINER_APPLE_DEVICE         (tracker_miner_apple_device_get_type())
+#define TRACKER_MINER_APPLE_DEVICE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), TRACKER_TYPE_MINER_APPLE_DEVICE, TrackerMinerAppleDevice))
+#define TRACKER_MINER_APPLE_DEVICE_CLASS(c)     (G_TYPE_CHECK_CLASS_CAST ((c), TRACKER_TYPE_MINER_APPLE_DEVICE, TrackerMinerAppleDeviceClass))
+#define TRACKER_IS_MINER_APPLE_DEVICE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), TRACKER_TYPE_MINER_APPLE_DEVICE))
+#define TRACKER_IS_MINER_APPLE_DEVICE_CLASS(c)  (G_TYPE_CHECK_CLASS_TYPE ((c),  TRACKER_TYPE_MINER_APPLE_DEVICE))
+#define TRACKER_MINER_APPLE_DEVICE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), TRACKER_TYPE_MINER_APPLE_DEVICE, TrackerMinerAppleDeviceClass))
+
+typedef struct _TrackerMinerAppleDevice TrackerMinerAppleDevice;
+typedef struct _TrackerMinerAppleDeviceClass TrackerMinerAppleDeviceClass;
+
+struct _TrackerMinerAppleDevice {
+	TrackerMiner parent;
+};
+
+struct _TrackerMinerAppleDeviceClass {
+	TrackerMinerClass parent;
+};
+
+GType                    tracker_miner_apple_device_get_type (void) G_GNUC_CONST;
+TrackerMinerAppleDevice *tracker_miner_apple_device_new      (GError **error);
+
+G_END_DECLS
+
+#endif
\ No newline at end of file
