Index: configure.ac
===================================================================
--- configure.ac	(revision 1371)
+++ configure.ac	(working copy)
@@ -240,6 +240,41 @@
 
 AM_CONDITIONAL(USE_HAL, [test "$msg_hal" = "yes"])
 
+dnl *****************************************************
+dnl *** Check if we should build with obexftp backend ***
+dnl *****************************************************
+AC_ARG_ENABLE(obexftp, [  --disable-obexftp           build without ObexFTP backend])
+msg_obexftp=no
+OBEXFTP_LIBS=
+OBEXFTP_CFLAGS=
+
+if test "x$enable_obexftp" != "xno"; then
+  PKG_CHECK_EXISTS(dbus-glib-1 bluez >= 3.12, msg_obexftp=yes)
+
+  dnl Make sure we have expat
+  AC_CHECK_LIB(expat, XML_ParserCreate_MM,
+               [ AC_CHECK_HEADERS(expat.h, have_expat=true, have_expat=false) ],
+               have_expat=false)
+
+  if test "x$msg_obexftp" == "xyes" -a "x$have_expat" == "xtrue"; then
+    PKG_CHECK_MODULES(OBEXFTP, dbus-glib-1 bluez >= 3.12)
+    AC_SUBST(OBEXFTP_LIBS)
+    AC_SUBST(OBEXFTP_CFLAGS)
+
+    msg_obexftp=yes
+    AC_DEFINE(HAVE_OBEXFTP, 1, [Define to 1 if ObexFTP is going to be built])
+    XML_CFLAGS=""
+    XML_LIBS="-lexpat"
+  else
+    msg_obexftp=no
+  fi
+fi
+
+AC_SUBST(XML_LIBS)
+AC_SUBST(XML_CFLAGS)
+
+AM_CONDITIONAL(USE_OBEXFTP, [test "$msg_obexftp" = "yes"])
+
 dnl *************************
 dnl *** Check for gphoto2 ***
 dnl *************************
@@ -438,6 +473,7 @@
 echo "gvfs configuration summary:"
 echo "
         HTTP/WebDAV support           $msg_http
+        ObexFTP support               $msg_obexftp
 	Samba support:	              $msg_samba
 	FUSE support:                 $msg_fuse
         CDDA support:                 $msg_cdda
Index: daemon/obexftp-marshal.list
===================================================================
--- daemon/obexftp-marshal.list	(revision 0)
+++ daemon/obexftp-marshal.list	(revision 0)
@@ -0,0 +1,3 @@
+VOID:STRING,STRING,INT
+VOID:STRING,STRING
+VOID:STRING,STRING,UINT64
Index: daemon/obexftp.mount.in
===================================================================
--- daemon/obexftp.mount.in	(revision 0)
+++ daemon/obexftp.mount.in	(revision 0)
@@ -0,0 +1,4 @@
+[Mount]
+Type=obex
+Exec=@libexecdir@/gvfsd-obexftp
+AutoMount=false
Index: daemon/gvfsbackendobexftp-fl-parser.c
===================================================================
--- daemon/gvfsbackendobexftp-fl-parser.c	(revision 0)
+++ daemon/gvfsbackendobexftp-fl-parser.c	(revision 0)
@@ -0,0 +1,400 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2004-2005 Nokia Corporation.
+ * Copyright (C) 2008 Bastien Nocera <hadess@hadess.net> (gio port)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; version 2 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <gio/gio.h>
+#include <expat.h>
+
+#include "gvfsbackendobexftp-fl-parser.h"
+
+#define d(x)
+
+typedef struct {
+	GError **error;
+	GList   *elements;
+
+	gint     depth;
+} ParserData;
+
+/* Static functions declaration */
+static void       fl_parser_start_node_cb     (void                *data,
+					       const char          *el,
+					       const char         **attr);
+static void       fl_parser_end_node_cb       (void                *data,
+					       const char          *el);
+static XML_Parser fl_parser_create_context    (ParserData          *data);
+static gboolean   fl_parser_fill_file_info    (GFileInfo           *file_info,
+					       const char         **attr);
+static void       fl_parser_free_parser_data  (ParserData          *data,
+					       gboolean             free_list);
+
+
+/* Function implementations */
+static void 
+fl_parser_start_node_cb (void        *user_data,
+			 const char  *node_name,
+			 const char **attr)
+{
+	ParserData *data;
+	GFileInfo  *info;
+	
+	data = (ParserData *) user_data;
+	
+	data->depth++;
+	
+	d(g_print ("%d: %s\n", data->depth, node_name));
+
+	if (data->depth > 2) {
+		g_set_error (data->error,  
+			     G_MARKUP_ERROR,  
+			     G_MARKUP_ERROR_INVALID_CONTENT,  
+			     "Don't expect node '%s' as child of 'file', 'folder' or 'parent-folder'",  
+			     node_name); 
+		return;
+	}
+	else if (data->depth == 1) {
+		if (strcmp (node_name, "folder-listing") != 0) {
+			g_set_error (data->error,  
+				     G_MARKUP_ERROR,  
+				     G_MARKUP_ERROR_INVALID_CONTENT,  
+				     "Expected 'folder-listing', got '%s'",  
+				     node_name);  
+			return;
+		}
+
+		return;
+	}
+
+	if (strcmp (node_name, "parent-folder") == 0) {
+		/* Just ignore parent-folder items */
+		return;
+	}
+	
+	info = g_file_info_new ();
+
+	if (strcmp (node_name, "file") == 0) {
+		g_file_info_set_file_type (info, G_FILE_TYPE_REGULAR);
+	}
+	else if (strcmp (node_name, "folder") == 0) {
+		g_file_info_set_file_type (info, G_FILE_TYPE_DIRECTORY);
+		g_file_info_set_content_type (info, "x-directory/normal");
+	} else {
+		g_set_error (data->error,
+			     G_MARKUP_ERROR,
+			     G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+			     "Unknown element '%s'",
+			     node_name);
+		return;
+	}
+
+	if (!fl_parser_fill_file_info (info, attr)) {
+		d(g_print ("Failed to fill GnomeVFSFileInfo from node '%s'\n",
+			   node_name));
+		g_object_unref (info);
+		return;
+	}
+
+	//FIXME
+#if 0
+	if (info->mime_type == NULL) {
+		info->mime_type = g_strdup (
+			gnome_vfs_mime_type_from_name (info->name));
+	}
+#endif
+	/* Permissions on folders in OBEX has different semantics than POSIX.
+	 * In POSIX, if a folder is not writable, it means that it's content
+	 * can't be removed, whereas in OBEX, it just means that the folder
+	 * itself can't be removed. Therefore we must set all folders to RWD and
+	 * handle the error when it happens. */
+	if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) {
+		g_file_info_set_attribute_boolean (info,
+						   G_FILE_ATTRIBUTE_ACCESS_CAN_READ,
+						   TRUE);
+		g_file_info_set_attribute_boolean (info,
+						   G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE,
+						   TRUE);
+	}
+	
+	data->elements = g_list_prepend (data->elements, info);
+}
+
+static void
+fl_parser_end_node_cb (void *user_data, const char *node_name)
+{
+	ParserData *data;
+
+	data = (ParserData *) user_data;
+
+	data->depth--;
+	
+	if (data->depth < 0) {  
+		g_set_error (data->error,  
+			     G_MARKUP_ERROR,  
+			     G_MARKUP_ERROR_INVALID_CONTENT,  
+			     "Closing non-open node '%s'",  
+			     node_name);  
+		return;  
+	} 
+
+	d(g_print ("%d: /%s\n", data->depth, node_name));
+}
+
+static XML_Parser
+fl_parser_create_context (ParserData *data)
+{
+	XML_Parser parser;
+	
+	parser = XML_ParserCreate (NULL);
+	
+	XML_SetElementHandler(parser, 
+			      (XML_StartElementHandler) fl_parser_start_node_cb,
+			      (XML_EndElementHandler) fl_parser_end_node_cb);
+	XML_SetUserData (parser, data);
+
+	return parser;
+}
+
+static gboolean
+fl_parser_fill_file_info (GFileInfo *info, const char **attr)
+{
+	gint i;
+	
+	for (i = 0; attr[i]; ++i) {
+		const gchar *name;
+		const gchar *value;
+
+		name  = attr[i];
+		value = attr[++i];
+		
+		if (strcmp (name, "name") == 0) {
+			/* Apparently someone decided it was a good idea
+			 * to send name="" mem-type="MMC" 
+			 */
+			if (!value || strcmp (value, "") == 0) {
+				return FALSE;
+			}
+
+			g_file_info_set_name (info, value);
+			d(g_print ("Name: '%s'\n", value));
+		}
+		else if (strcmp (name, "size") == 0) {
+			g_file_info_set_size (info, strtoll (value, NULL, 10));
+			d(g_print ("Size: '%"G_GINT64_FORMAT"'\n", g_file_info_get_size (info)));
+		}
+		else if (strcmp (name, "modified") == 0) {
+			GTimeVal time;
+
+			if (g_time_val_from_iso8601 (value, &time) == FALSE)
+				continue;
+			g_file_info_set_modification_time (info, &time);
+			d(g_print ("Modified: '%s' = '%d'\n", 
+				   value, (int)time.tv_sec));
+		}
+		else if (strcmp (name, "created") == 0) {
+			GTimeVal time;
+
+			if (g_time_val_from_iso8601 (value, &time) == FALSE)
+				continue;
+			g_file_info_set_attribute_uint64 (info,
+							  G_FILE_ATTRIBUTE_TIME_CREATED,
+							  time.tv_sec);
+			g_file_info_set_attribute_uint32 (info,
+							  G_FILE_ATTRIBUTE_TIME_CREATED_USEC,
+							  time.tv_usec);
+			d(g_print ("Created: '%s' = '%d'\n", 
+				   value, (int)time.tv_sec));
+		}
+		else if (strcmp (name, "accessed") == 0) {
+			GTimeVal time;
+
+			if (g_time_val_from_iso8601 (value, &time) == FALSE)
+				continue;
+			g_file_info_set_attribute_uint64 (info,
+							  G_FILE_ATTRIBUTE_TIME_ACCESS,
+							  time.tv_sec);
+			g_file_info_set_attribute_uint32 (info,
+							  G_FILE_ATTRIBUTE_TIME_ACCESS_USEC,
+							  time.tv_usec);
+			d(g_print ("Accessed: '%s' = '%d'\n", 
+				   value, (int)time.tv_sec));
+		}
+		else if (strcmp (name, "user-perm") == 0) {
+			/* The permissions don't map well to unix semantics,
+			 * since the user is most likely not the same on both
+			 * sides. We map the user permissions to "other" on the
+			 * local side. D is treated as write, otherwise files
+			 * can't be deleted through the module, even if it
+			 * should be possible.
+			 */
+			if (strstr (value, "R")) {
+				g_file_info_set_attribute_boolean (info,
+								   G_FILE_ATTRIBUTE_ACCESS_CAN_READ,
+								   TRUE);
+			}
+			if (strstr (value, "W") || strstr (value, "D")) {
+				g_file_info_set_attribute_boolean (info,
+								   G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE,
+								   TRUE);
+			}
+		}
+		else if (strcmp (name, "group-perm") == 0) {
+			/* Ignore for now */
+			d(g_print ("Group permissions: '%s'\n", value));
+		}
+		else if (strcmp (name, "other-perm") == 0) {
+			/* Ignore for now */
+			d(g_print ("Other permissions: '%s'\n", value));
+		}
+		else if (strcmp (name, "owner") == 0) {
+			/* Ignore for now */
+			d(g_print ("Owner: '%s'\n", value));
+		}
+		else if (strcmp (name, "group") == 0) {
+			/* Ignore for now */
+			d(g_print ("Group: '%s'\n", value));
+		}
+		else if (strcmp (name, "type") == 0) {
+			g_file_info_set_content_type (info, value);
+			d(g_print ("Mime-Type: '%s'\n", value));
+		}
+		else if (strcmp (name, "xml:lang") == 0) {
+			d(g_print ("Lang: '%s'\n", value));
+		}
+		else if (strcmp (name, "mem-type") == 0) {
+			guint device;
+
+			if (value == NULL || value[0] == '\0')
+				continue;
+
+			device = om_mem_type_id_from_string (value);
+			g_file_info_set_attribute_uint32 (info,
+							 G_FILE_ATTRIBUTE_UNIX_RDEV,
+							 device);
+			d(g_print ("Mem-Type: '%s' (%d)\n",
+				   value, device));
+		}
+		else {
+			d(g_print ("Unknown Attribute: %s = %s\n",
+				   name, value));
+		}
+	}
+
+	if (g_file_info_get_name (info) == NULL) { /* Required attribute */
+		/* Set error */
+		return FALSE;
+	}
+	
+	return TRUE;
+}
+
+static void
+fl_parser_free_parser_data (ParserData *data, gboolean free_list)
+{
+	if (free_list) {
+		g_list_foreach (data->elements, (GFunc) g_object_unref, NULL);
+		g_list_free (data->elements);
+		data->elements = NULL;
+	}
+
+	g_free (data);
+}
+
+gboolean
+gvfsbackendobexftp_fl_parser_parse (const gchar *buf, gint len, GList **elements,
+				    GError **error)
+{
+	ParserData *data;
+	XML_Parser  parser;
+
+	data = g_new0 (ParserData, 1);
+	data->error = error;
+	data->elements = NULL;
+	data->depth = 0;
+
+	parser = fl_parser_create_context (data);
+	if (!parser) {
+		g_free (data);
+		return FALSE;
+	}
+
+	if (XML_Parse (parser, buf, len, TRUE) == 0) {
+		XML_ParserFree (parser);
+		fl_parser_free_parser_data (data, TRUE);
+
+		if (*error == NULL) {
+			g_set_error (error,
+				     G_MARKUP_ERROR,
+				     G_MARKUP_ERROR_INVALID_CONTENT,
+				     "Couldn't parse the incoming data");
+		}
+		return FALSE;
+	}
+
+	XML_ParserFree (parser);
+	
+	*elements = data->elements;
+
+	fl_parser_free_parser_data (data, FALSE);
+		
+	return TRUE;
+}
+
+static GPtrArray *mem_types = NULL;
+static GHashTable *mem_types_ht = NULL;
+
+guint
+om_mem_type_id_from_string (const gchar *memtype)
+{
+	guint mem_id;
+	gchar *value;
+
+	if (memtype == NULL || memtype[0] == '\0')
+		return 0;
+
+	if (mem_types_ht != NULL) {
+		mem_id = GPOINTER_TO_UINT (g_hash_table_lookup
+					   (mem_types_ht, memtype));
+		if (mem_id != 0)
+			return mem_id;
+	} else {
+		mem_types = g_ptr_array_new ();
+		/* Insert a dummy entry, so that we don't use 0 as a mem_id */
+		g_ptr_array_add (mem_types, NULL);
+		mem_types_ht = g_hash_table_new (g_str_hash, g_str_equal);
+	}
+	value = g_strdup (memtype);
+	mem_id = mem_types->len;
+	g_ptr_array_add (mem_types, value);
+	g_hash_table_insert (mem_types_ht, value, GUINT_TO_POINTER (mem_id));
+	return mem_id;
+}
+
+const gchar *
+om_mem_type_id_to_string (guint mem_id)
+{
+	if (mem_types == NULL || mem_id >= mem_types->len)
+		return NULL;
+	else
+		return g_ptr_array_index (mem_types, mem_id);
+}
Index: daemon/gvfsbackendobexftp-fl-parser.h
===================================================================
--- daemon/gvfsbackendobexftp-fl-parser.h	(revision 0)
+++ daemon/gvfsbackendobexftp-fl-parser.h	(revision 0)
@@ -0,0 +1,38 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2004 Nokia Corporation.
+ * Copyright (C) 2008 Bastien Nocera <hadess@hadess.net> (gio port)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; version 2 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GVFSBACKENDOBEXFTP_FL_PARSER_H__
+#define __GVFSBACKENDOBEXFTP_FL_PARSER_H__
+
+#include <glib.h>
+
+gboolean     gvfsbackendobexftp_fl_parser_parse             (const gchar *buf,
+							     gint         len,
+							     GList      **elements,
+							     GError     **error);
+void         gvfsbackendobexftp_fl_parser_free_element_list (GSList      *elements);
+
+
+
+guint        om_mem_type_id_from_string     (const gchar *memtype);
+const gchar *om_mem_type_id_to_string       (guint        mem_id);
+
+#endif /* __GVFSBACKENDOBEXFTP_FL_PARSER_H__ */
Index: daemon/gvfsbackendobexftp.c
===================================================================
--- daemon/gvfsbackendobexftp.c	(revision 0)
+++ daemon/gvfsbackendobexftp.c	(revision 0)
@@ -0,0 +1,1253 @@
+/* GIO - GLib Input, Output and Streaming Library
+ * 
+ * Copyright (C) 2006-2008 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Bastien Nocera <hadess@hadess.net>
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+
+#include <glib/gstdio.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include <dbus/dbus-glib.h>
+#include <bluetooth/bluetooth.h>
+
+#include "gvfsbackendobexftp.h"
+#include "gvfsbackendobexftp-fl-parser.h"
+#include "gvfsbackendobexftp-cap-parser.h"
+#include "obexftp-marshal.h"
+
+#include "gvfsjobopenforread.h"
+#include "gvfsjobread.h"
+#include "gvfsjobseekread.h"
+#include "gvfsjobmount.h"
+#include "gvfsjobopenforwrite.h"
+#include "gvfsjobwrite.h"
+#include "gvfsjobclosewrite.h"
+#include "gvfsjobseekwrite.h"
+#include "gvfsjobsetdisplayname.h"
+#include "gvfsjobqueryinfo.h"
+#include "gvfsjobqueryfsinfo.h"
+#include "gvfsjobqueryattributes.h"
+#include "gvfsjobenumerate.h"
+#include "gvfsdaemonprotocol.h"
+
+#define BDADDR_LEN 17
+
+struct _GVfsBackendObexftp
+{
+  GVfsBackend parent_instance;
+
+  /* The bdaddr is checked to be valid in try_mount */
+  char *bdaddr;
+
+  DBusGConnection *connection;
+  DBusGProxy *manager_proxy;
+  DBusGProxy *session_proxy;
+
+  /* The current job for some actions:
+   * GVfsJobOpenForRead */
+  GVfsJob *current_job;
+
+  /* Whether we got a user-cancellation */
+  gboolean user_cancelled;
+};
+
+typedef struct {
+    char *source;
+    char *target;
+    goffset size;
+    int fd;
+} ObexFTPOpenHandle;
+
+G_DEFINE_TYPE (GVfsBackendObexftp, g_vfs_backend_obexftp, G_VFS_TYPE_BACKEND);
+
+static gchar *
+get_device_name (const char *bdaddr)
+{
+  DBusGConnection *connection;
+  DBusGProxy *manager;
+  gchar *name, **adapters;
+  guint i;
+
+  name = NULL;
+
+  connection = dbus_g_bus_get (DBUS_BUS_SYSTEM, NULL);
+  if (connection == NULL)
+        return NULL;
+
+  manager = dbus_g_proxy_new_for_name (connection, "org.bluez",
+                                       "/org/bluez", "org.bluez.Manager");
+  if (manager == NULL)
+    {
+      dbus_g_connection_unref (connection);
+      return NULL;
+    }
+
+  if (dbus_g_proxy_call (manager, "ListAdapters", NULL, G_TYPE_INVALID, G_TYPE_STRV, &adapters, G_TYPE_INVALID) == FALSE)
+    {
+      g_object_unref (manager);
+      dbus_g_connection_unref (connection);
+      return NULL;
+    }
+
+  for (i = 0; adapters[i] != NULL; i++)
+    {
+      DBusGProxy *adapter;
+
+      adapter = dbus_g_proxy_new_for_name (connection, "org.bluez",
+                                           adapters[i], "org.bluez.Adapter");
+      if (dbus_g_proxy_call (adapter, "GetRemoteName", NULL,
+                             G_TYPE_STRING, bdaddr, G_TYPE_INVALID,
+                             G_TYPE_STRING, &name, G_TYPE_INVALID) != FALSE)
+        {
+          if (name != NULL && name[0] != '\0')
+            {
+              g_object_unref (adapter);
+              break;
+            }
+        }
+      g_object_unref (adapter);
+    }
+
+  g_object_unref (manager);
+  dbus_g_connection_unref (connection);
+
+  return name;
+}
+
+static void
+g_vfs_backend_obexftp_finalize (GObject *object)
+{
+  GVfsBackendObexftp *backend;
+
+  backend = G_VFS_BACKEND_OBEXFTP (object);
+
+  if (backend->session_proxy != NULL)
+        g_object_unref (backend->session_proxy);
+  g_free (backend->bdaddr);
+
+  if (G_OBJECT_CLASS (g_vfs_backend_obexftp_parent_class)->finalize)
+        (*G_OBJECT_CLASS (g_vfs_backend_obexftp_parent_class)->finalize) (object);
+}
+
+static void
+g_vfs_backend_obexftp_init (GVfsBackendObexftp *backend)
+{
+  GError *err = NULL;
+
+  backend->connection = dbus_g_bus_get (DBUS_BUS_SESSION, &err);
+  if (backend->connection == NULL) {
+      g_printerr ("Connecting to session bus failed: %s\n", err->message);
+      g_error_free (err);
+      return;
+  }
+
+  backend->manager_proxy = dbus_g_proxy_new_for_name (backend->connection,
+                                                      "org.openobex",
+                                                      "/org/openobex",
+                                                      "org.openobex.Manager");
+}
+
+static gboolean
+same_paths (const char *filename, const char *current_path)
+{
+  GFile *req, *cur;
+  gboolean retval;
+
+  req = g_file_new_for_path (filename);
+  cur = g_file_new_for_path (current_path);
+  retval = g_file_equal (req, cur);
+  g_object_unref (req);
+  g_object_unref (cur);
+
+  return retval;
+}
+
+static gboolean
+do_change_directory (GVfsBackendObexftp *op_backend,
+                     const char *filename,
+                     GError **error)
+{
+  char *current_path, **req_components;
+  guint i;
+
+  if (dbus_g_proxy_call (op_backend->session_proxy, "GetCurrentPath", error,
+                         G_TYPE_INVALID,
+                         G_TYPE_STRING, &current_path, G_TYPE_INVALID) == FALSE)
+    {
+      g_message ("GetCurrentPath failed");
+      return FALSE;
+    }
+
+  if (same_paths (filename, current_path) == TRUE)
+    {
+      g_free (current_path);
+      return TRUE;
+    }
+
+  /* Are we already at the root? */
+  if (strcmp (current_path, "/") != 0)
+    {
+      if (dbus_g_proxy_call (op_backend->session_proxy, "ChangeCurrentFolderToRoot", error,
+                             G_TYPE_INVALID,
+                             G_TYPE_INVALID) == FALSE)
+        {
+          g_message ("ChangeCurrentFolderToRoot failed");
+          return FALSE;
+        }
+    }
+  g_free (current_path);
+
+  /* If we asked for the root, we're done */
+  if (strcmp (filename, "/") == 0)
+        return TRUE;
+
+  req_components = g_strsplit (filename, "/", -1);
+  for (i = 0; req_components[i] != NULL; i++)
+    {
+      if (*req_components[i] == '\0')
+            continue;
+      if (dbus_g_proxy_call (op_backend->session_proxy, "ChangeCurrentFolder", error,
+                             G_TYPE_STRING, req_components[i], G_TYPE_INVALID,
+                             G_TYPE_INVALID) == FALSE)
+        {
+          g_message ("ChangeCurrentFolder failed");
+          g_strfreev (req_components);
+          return FALSE;
+        }
+    }
+
+  g_strfreev (req_components);
+
+  return TRUE;
+}
+
+static gboolean
+do_query_file_info_helper (GVfsBackend *backend,
+                           const char *filename,
+                           GFileInfo *info,
+                           GError **error)
+{
+  GVfsBackendObexftp *op_backend = G_VFS_BACKEND_OBEXFTP (backend);
+  char *parent, *basename, *files;
+  GList *elements, *l;
+  gboolean found;
+
+  g_print ("+ do_query_file_info_helper, filename: %s\n", filename);
+
+  if (strcmp (filename, "/") == 0)
+    {
+      /* That happens when you want '/'
+       * and we don't have any info about it :( */
+      g_file_info_set_file_type (info, G_FILE_TYPE_DIRECTORY);
+      g_file_info_set_content_type (info, "x-directory/normal");
+      g_file_info_set_name (info, "/");
+      return TRUE;
+    }
+
+  parent = g_path_get_dirname (filename);
+  if (do_change_directory (op_backend, parent, error) == FALSE)
+    {
+      g_free (parent);
+      return FALSE;
+    }
+  g_free (parent);
+
+  files = NULL;
+  if (dbus_g_proxy_call (op_backend->session_proxy, "RetrieveFolderListing", error,
+                         G_TYPE_INVALID,
+                         G_TYPE_STRING, &files, G_TYPE_INVALID) == FALSE)
+    {
+      return FALSE;
+    }
+
+  if (gvfsbackendobexftp_fl_parser_parse (files, strlen (files), &elements, error) == FALSE)
+    {
+      g_free (files);
+      return FALSE;
+    }
+  g_free (files);
+
+  basename = g_path_get_basename (filename);
+  found = FALSE;
+
+  for (l = elements; l != NULL; l = l->next)
+    {
+      if (strcmp (basename, g_file_info_get_name (l->data)) == 0)
+        {
+          g_file_info_copy_into (l->data, info);
+          found = TRUE;
+          break;
+        }
+    }
+
+  if (found == FALSE)
+    {
+      g_set_error (error, G_IO_ERROR,
+                   G_IO_ERROR_NOT_FOUND,
+                   g_strerror (ENOENT));
+    }
+
+  g_free (basename);
+  g_list_foreach (elements, (GFunc)g_object_unref, NULL);
+  g_list_free (elements);
+
+  g_print ("- do_query_file_info_helper\n");
+
+  return found;
+}
+
+static void
+error_occurred_cb (DBusGProxy *proxy, const gchar *error_name, const gchar *error_message, gpointer user_data)
+{
+  GVfsBackendObexftp *op_backend = G_VFS_BACKEND_OBEXFTP (user_data);
+
+  g_message("ErrorOccurred");
+  g_message("Error name: %s", error_name);
+  g_message("Error message: %s", error_message);
+
+  if (strcmp (error_name, "org.openobex.Error.LinkError") == 0)
+    {
+      g_message ("link lost to remote device");
+      _exit (1);
+    }
+
+  if (op_backend->current_job == NULL)
+    {
+      g_message ("No current job, exiting");
+      _exit (1);
+    }
+
+  if (G_VFS_IS_JOB_OPEN_FOR_READ (op_backend->current_job))
+    {
+      ObexFTPOpenHandle *handle = (ObexFTPOpenHandle *) (G_VFS_JOB_OPEN_FOR_READ (op_backend->current_job)->backend_handle);
+      GError *error;
+
+      g_unlink (handle->target);
+      close (handle->fd);
+      g_free (handle->source);
+      g_free (handle);
+      error = g_error_new (DBUS_GERROR,
+                           DBUS_GERROR_REMOTE_EXCEPTION,
+                           error_message);
+      g_vfs_job_failed_from_error (G_VFS_JOB (op_backend->current_job), error);
+      g_error_free (error);
+      op_backend->current_job = NULL;
+    }
+  else
+    {
+      _exit (1);
+    }
+}
+
+static void
+cancelled_cb (DBusGProxy *proxy, gpointer user_data)
+{
+  GVfsBackendObexftp *op_backend = G_VFS_BACKEND_OBEXFTP (user_data);
+
+  g_message ("transfer got cancelled");
+
+  if (op_backend->user_cancelled != FALSE)
+    {
+      g_message ("user cancelled the operation");
+      op_backend->user_cancelled = FALSE;
+      return;
+    }
+
+  g_message ("remote device cancelled the operation, exiting");
+  _exit (1);
+}
+
+static void
+disconnected_cb (DBusGProxy *proxy, gpointer user_data)
+{
+  g_message ("disconnected_cb");
+
+  _exit (1);
+}
+
+static void
+closed_cb (DBusGProxy *proxy, gpointer user_data)
+{
+  g_message ("closed_cb");
+
+  _exit (1);
+}
+
+static void session_created_cb (DBusGProxy *proxy,
+                                DBusGProxyCall *call,
+                                GVfsJobMount *job)
+{
+  GVfsBackendObexftp *op_backend = G_VFS_BACKEND_OBEXFTP (G_VFS_JOB (job)->backend_data);
+  GError *error = NULL;
+  const gchar *path = NULL;
+  char *display_name, *server;
+  GMountSpec *obexftp_mount_spec;
+
+  if (dbus_g_proxy_end_call (op_backend->manager_proxy, call, &error,
+                             DBUS_TYPE_G_OBJECT_PATH, &path, G_TYPE_INVALID) == FALSE)
+    {
+      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+      g_error_free (error);
+      return;
+    }
+
+  g_message ("session_created_cb");
+
+  op_backend->session_proxy = dbus_g_proxy_new_for_name (op_backend->connection,
+                                                         "org.openobex",
+                                                         path,
+                                                         "org.openobex.Session");
+
+  display_name = get_device_name (op_backend->bdaddr);
+  g_vfs_backend_set_display_name (G_VFS_BACKEND  (op_backend),
+                                  display_name ? display_name : op_backend->bdaddr);
+  g_free (display_name);
+  g_vfs_backend_set_icon_name (G_VFS_BACKEND (op_backend), "folder-remote");
+
+  obexftp_mount_spec = g_mount_spec_new ("obex");
+  server = g_strdup_printf ("[%s]", op_backend->bdaddr);
+  g_mount_spec_set (obexftp_mount_spec, "host", server);
+  g_free (server);
+  g_vfs_backend_set_mount_spec (G_VFS_BACKEND (op_backend), obexftp_mount_spec);
+  g_mount_spec_unref (obexftp_mount_spec);
+
+  g_vfs_job_succeeded (G_VFS_JOB (job));
+
+  dbus_g_proxy_add_signal(op_backend->session_proxy, "ErrorOccurred",
+                          G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INVALID);
+  dbus_g_proxy_connect_signal(op_backend->session_proxy, "ErrorOccurred",
+                              G_CALLBACK(error_occurred_cb), op_backend, NULL);
+
+  dbus_g_proxy_add_signal(op_backend->session_proxy, "Cancelled",
+                          G_TYPE_INVALID);
+  dbus_g_proxy_connect_signal(op_backend->session_proxy, "Cancelled",
+                              G_CALLBACK(cancelled_cb), op_backend, NULL);
+
+  dbus_g_proxy_add_signal(op_backend->session_proxy, "Disconnected",
+                          G_TYPE_INVALID);
+  dbus_g_proxy_connect_signal(op_backend->session_proxy, "Disconnected",
+                              G_CALLBACK(disconnected_cb), op_backend, NULL);
+
+  dbus_g_proxy_add_signal(op_backend->session_proxy, "Closed",
+                          G_TYPE_INVALID);
+  dbus_g_proxy_connect_signal(op_backend->session_proxy, "Closed",
+                              G_CALLBACK(closed_cb), op_backend, NULL);
+
+  dbus_g_proxy_add_signal(op_backend->session_proxy, "TransferStarted",
+                          G_TYPE_STRING, G_TYPE_STRING, G_TYPE_UINT64, G_TYPE_INVALID);
+  dbus_g_proxy_add_signal(op_backend->session_proxy, "TransferCompleted",
+                          G_TYPE_INVALID);
+}
+
+static gboolean
+try_mount (GVfsBackend *backend,
+           GVfsJobMount *job,
+           GMountSpec *mount_spec,
+           GMountSource *mount_source,
+           gboolean is_automount)
+{
+  GVfsBackendObexftp *op_backend = G_VFS_BACKEND_OBEXFTP (backend);
+  const char *device;
+  char *bdaddr;
+
+  g_message ("try_mount");
+
+  device = g_mount_spec_get (mount_spec, "host");
+
+  if (device == NULL || strlen (device) != BDADDR_LEN + 2)
+    {
+      g_vfs_job_failed (G_VFS_JOB (job),
+                        G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
+                        _("Invalid mount spec"));
+      return TRUE;
+    }
+
+  /* Strip the brackets */
+  bdaddr = g_strndup (device + 1, 17);
+  if (bachk (bdaddr) < 0)
+    {
+      g_free (bdaddr);
+      g_vfs_job_failed (G_VFS_JOB (job),
+                        G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
+                        _("Invalid mount spec"));
+      return TRUE;
+    }
+
+  op_backend->bdaddr = bdaddr;
+
+  /* FIXME, Have a way for the mount to be cancelled, see:
+   * http://bugs.muiline.com/view.php?id=51 */
+
+  dbus_g_proxy_begin_call (op_backend->manager_proxy, "CreateBluetoothSession",
+                           (DBusGProxyCallNotify) session_created_cb, job, NULL,
+                           G_TYPE_STRING, bdaddr, G_TYPE_STRING, "ftp",
+                           G_TYPE_INVALID);
+
+  g_vfs_job_set_backend_data (G_VFS_JOB (job), backend, NULL);
+
+  return TRUE;
+}
+
+static void
+transfer_started_cb (DBusGProxy *proxy, const gchar *filename,
+                     const gchar *local_path, guint64 byte_count, gpointer user_data)
+{
+  GVfsBackendObexftp *op_backend = G_VFS_BACKEND_OBEXFTP (user_data);
+  ObexFTPOpenHandle *handle = (ObexFTPOpenHandle *) (G_VFS_JOB_OPEN_FOR_READ (op_backend->current_job)->backend_handle);
+
+  g_message ("transfer of %s to %s started", filename, local_path);
+
+  /* ods and us both have an fd to the file, we can remove it now */
+  g_unlink (local_path);
+  g_free (handle->target);
+  handle->target = NULL;
+
+  dbus_g_proxy_disconnect_signal(op_backend->session_proxy, "TransferStarted",
+                                 G_CALLBACK(transfer_started_cb), op_backend);
+  g_vfs_job_open_for_read_set_can_seek (G_VFS_JOB_OPEN_FOR_READ (op_backend->current_job), FALSE);
+  g_vfs_job_succeeded (op_backend->current_job);
+  op_backend->current_job = NULL;
+}
+
+static void 
+do_open_for_read (GVfsBackend *backend,
+                  GVfsJobOpenForRead *job,
+                  const char *filename)
+{
+  GVfsBackendObexftp *op_backend = G_VFS_BACKEND_OBEXFTP (backend);
+  GError *error = NULL;
+  ObexFTPOpenHandle *handle;
+  char *target, *basename;
+  GFileInfo *info;
+  goffset size;
+  int fd;
+
+  g_message ("do_open_for_read");
+
+  /* Change into the directory and cache the file size */
+  info = g_file_info_new ();
+  if (do_query_file_info_helper (backend, filename, info, &error) == FALSE)
+    {
+      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+      g_error_free (error);
+      g_object_unref (info);
+      return;
+    }
+  size = g_file_info_get_size (info);
+  g_object_unref (info);
+
+  if (g_vfs_job_is_cancelled (G_VFS_JOB (job)))
+    {
+      op_backend->user_cancelled = TRUE;
+      return;
+    }
+
+  fd = g_file_open_tmp ("gvfsobexftp-tmp-XXXXXX",
+                        &target, &error);
+  if (fd < 0)
+    {
+      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+      g_error_free (error);
+      return;
+    }
+
+  if (g_vfs_job_is_cancelled (G_VFS_JOB (job)))
+    {
+      op_backend->user_cancelled = TRUE;
+      return;
+    }
+
+  op_backend->current_job = G_VFS_JOB (job);
+  dbus_g_proxy_connect_signal(op_backend->session_proxy, "TransferStarted",
+                              G_CALLBACK(transfer_started_cb), op_backend, NULL);
+
+  basename = g_path_get_basename (filename);
+  if (dbus_g_proxy_call (op_backend->session_proxy, "CopyRemoteFile", &error,
+                         G_TYPE_STRING, basename,
+                         G_TYPE_STRING, target,
+                         G_TYPE_INVALID,
+                         G_TYPE_INVALID) == FALSE)
+    {
+      g_message ("CopyRemoteFile failed");
+
+      g_free (basename);
+      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+      g_error_free (error);
+
+      dbus_g_proxy_disconnect_signal(op_backend->session_proxy, "TransferStarted",
+                                     G_CALLBACK(transfer_started_cb), op_backend);
+      op_backend->current_job = NULL;
+
+      /* Close the target */
+      g_unlink (target);
+      g_free (target);
+      close (fd);
+      return;
+    }
+
+  handle = g_new0 (ObexFTPOpenHandle, 1);
+  handle->source = g_strdup (filename);
+  handle->target = target;
+  handle->fd = fd;
+  handle->size = size;
+  g_vfs_job_open_for_read_set_handle (job, handle);
+
+  g_message ("filename: %s (%s) copying to %s", filename, basename, target);
+
+  g_free (basename);
+}
+
+static int
+is_busy (DBusGProxy *session_proxy, GVfsJobRead *job)
+{
+  GError *error = NULL;
+  gboolean busy;
+
+  if (dbus_g_proxy_call (session_proxy, "IsBusy", &error,
+                         G_TYPE_INVALID,
+                         G_TYPE_BOOLEAN, &busy, G_TYPE_INVALID) == FALSE)
+    {
+      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+      g_error_free (error);
+      return -1;
+    }
+
+  return busy;
+}
+
+static void
+do_read (GVfsBackend *backend,
+         GVfsJobRead *job,
+         GVfsBackendHandle handle,
+         char *buffer,
+         gsize bytes_requested)
+{
+  GVfsBackendObexftp *op_backend = G_VFS_BACKEND_OBEXFTP (backend);
+  ObexFTPOpenHandle *backend_handle = (ObexFTPOpenHandle *) handle;
+  ssize_t bytes_read = 0;
+  gboolean busy = TRUE;
+
+  op_backend->current_job = G_VFS_JOB (job);
+
+  while (bytes_read == 0 && busy != FALSE)
+    {
+      bytes_read = read (backend_handle->fd, buffer, bytes_requested);
+      if (bytes_read != 0)
+            break;
+
+      if (g_vfs_job_is_cancelled (G_VFS_JOB (job)))
+        {
+          op_backend->user_cancelled = TRUE;
+          dbus_g_proxy_call (session_proxy, "Cancel", &error,
+                             G_TYPE_INVALID, G_TYPE_INVALID);
+          return;
+        }
+
+      busy = is_busy (op_backend->session_proxy, job);
+      if (busy < 0)
+            return;
+
+      g_usleep (G_USEC_PER_SEC / 100);
+    }
+
+  if (bytes_read < 0)
+    {
+      g_vfs_job_failed_from_errno (G_VFS_JOB (job), errno);
+    }
+  else if (bytes_read == 0)
+    {
+      g_vfs_job_read_set_size (job, 0);
+      g_vfs_job_succeeded (G_VFS_JOB (job));
+    }
+  else
+    {
+      g_vfs_job_read_set_size (job, bytes_read);
+      g_vfs_job_succeeded (G_VFS_JOB (job));
+    }
+}
+
+static void
+do_seek_on_read (GVfsBackend *backend,
+                 GVfsJobSeekRead *job,
+                 GVfsBackendHandle handle,
+                 goffset    offset,
+                 GSeekType  type)
+{
+  g_vfs_job_failed_from_errno (G_VFS_JOB (job), ENOSYS);
+}
+
+static void
+do_close_read (GVfsBackend *backend,
+               GVfsJobCloseRead *job,
+               GVfsBackendHandle handle)
+{
+  GVfsBackendObexftp *op_backend = G_VFS_BACKEND_OBEXFTP (backend);
+  ObexFTPOpenHandle *backend_handle = (ObexFTPOpenHandle *) handle;
+
+  op_backend->current_job = G_VFS_JOB (job);
+
+  g_message ("close read");
+
+  close (backend_handle->fd);
+  g_free (backend_handle->source);
+  g_free (backend_handle);
+
+  g_vfs_job_succeeded (G_VFS_JOB (job));
+  op_backend->current_job = NULL;
+}
+
+static void
+do_create (GVfsBackend *backend,
+           GVfsJobOpenForWrite *job,
+           const char *filename,
+           GFileCreateFlags flags)
+{
+  g_vfs_job_failed_from_errno (G_VFS_JOB (job), ENOSYS);
+}
+
+static void
+do_append_to (GVfsBackend *backend,
+              GVfsJobOpenForWrite *job,
+              const char *filename,
+              GFileCreateFlags flags)
+{
+  g_vfs_job_failed_from_errno (G_VFS_JOB (job), ENOSYS);
+}
+
+
+static void
+do_replace (GVfsBackend *backend,
+            GVfsJobOpenForWrite *job,
+            const char *filename,
+            const char *etag,
+            gboolean make_backup,
+            GFileCreateFlags flags)
+{
+  g_vfs_job_failed_from_errno (G_VFS_JOB (job), ENOSYS);
+}
+
+
+static void
+do_write (GVfsBackend *backend,
+          GVfsJobWrite *job,
+          GVfsBackendHandle _handle,
+          char *buffer,
+          gsize buffer_size)
+{
+  g_vfs_job_failed_from_errno (G_VFS_JOB (job), ENOSYS);
+}
+
+static void
+do_seek_on_write (GVfsBackend *backend,
+                  GVfsJobSeekWrite *job,
+                  GVfsBackendHandle _handle,
+                  goffset    offset,
+                  GSeekType  type)
+{
+  g_vfs_job_failed_from_errno (G_VFS_JOB (job), ENOSYS);
+}
+
+static void
+do_close_write (GVfsBackend *backend,
+                GVfsJobCloseWrite *job,
+                GVfsBackendHandle _handle)
+{
+  g_vfs_job_failed_from_errno (G_VFS_JOB (job), ENOSYS);
+}
+
+static void
+do_query_info (GVfsBackend *backend,
+               GVfsJobQueryInfo *job,
+               const char *filename,
+               GFileQueryInfoFlags flags,
+               GFileInfo *info,
+               GFileAttributeMatcher *matcher)
+{
+  GError *error = NULL;
+
+  g_print ("+ do_query_info, filename: %s\n", filename);
+
+  if (do_query_file_info_helper (backend, filename, info, &error) == FALSE)
+    {
+      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+      g_error_free (error);
+      return;
+    }
+
+  g_vfs_job_succeeded (G_VFS_JOB (job));
+
+  g_print ("- do_query_info\n");
+
+  return;
+}
+
+static void
+do_query_fs_info (GVfsBackend *backend,
+                  GVfsJobQueryFsInfo *job,
+                  const char *filename,
+                  GFileInfo *info,
+                  GFileAttributeMatcher *attribute_matcher)
+{
+  GVfsBackendObexftp *op_backend = G_VFS_BACKEND_OBEXFTP (backend);
+  OvuCaps *caps;
+  OvuCapsMemory *memory;
+  GError *error = NULL;
+  char *caps_str;
+  const char *mem_type;
+  GList *l;
+  gboolean has_free_memory;
+
+  g_print ("+ do_query_fs_info, filename: %s\n", filename);
+
+  /* Get the capabilities */
+  if (dbus_g_proxy_call (op_backend->session_proxy, "GetCapability", &error,
+                         G_TYPE_INVALID,
+                         G_TYPE_STRING, &caps_str, G_TYPE_INVALID) == FALSE)
+    {
+      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+      g_error_free (error);
+      return;
+    }
+
+  if (g_vfs_job_is_cancelled (G_VFS_JOB (job)))
+    {
+      op_backend->user_cancelled = TRUE;
+      g_free (caps_str);
+      return;
+    }
+
+  /* No caps from the server? */
+  if (caps_str == NULL)
+    {
+      g_vfs_job_failed_from_errno (G_VFS_JOB (job), ENOSYS);
+      return;
+    }
+
+  g_message ("caps: %s", caps_str);
+
+  caps = ovu_caps_parser_parse (caps_str, strlen (caps_str), &error);
+  g_free (caps_str);
+  if (caps == NULL)
+    {
+      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+      g_error_free (error);
+      return;
+    }
+
+  /* Check whether we have no free space available */
+  has_free_memory = FALSE;
+  for (l = ovu_caps_get_memory_entries (caps); l != NULL; l = l->next)
+    {
+      if (ovu_caps_memory_has_free (l->data) != FALSE)
+        {
+          has_free_memory = TRUE;
+          break;
+        }
+    }
+  if (has_free_memory == FALSE)
+    {
+      ovu_caps_free (caps);
+      g_vfs_job_failed_from_errno (G_VFS_JOB (job), ENOSYS);
+      return;
+    }
+
+  /* Check whether we have only one memory type */
+  l = ovu_caps_get_memory_entries (caps);
+  if (g_list_length (l) == 1)
+    {
+      memory = l->data;
+      goto set_info_from_memory;
+    }
+
+  if (do_query_file_info_helper (backend, filename, info, &error) == FALSE)
+    {
+      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+      ovu_caps_free (caps);
+      return;
+    }
+
+  if (g_vfs_job_is_cancelled (G_VFS_JOB (job)))
+    {
+      op_backend->user_cancelled = TRUE;
+      ovu_caps_free (caps);
+      return;
+    }
+
+  mem_type = NULL;
+  if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_UNIX_RDEV) != FALSE)
+    {
+      guint rdev;
+      rdev = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_RDEV);
+      mem_type = om_mem_type_id_to_string (rdev);
+    }
+  memory = ovu_caps_get_memory_type (caps, mem_type);
+
+set_info_from_memory:
+  if (memory != NULL && ovu_caps_memory_has_free (memory) != FALSE)
+    {
+      g_file_info_set_attribute_uint64 (info,
+                                        G_FILE_ATTRIBUTE_FILESYSTEM_FREE,
+                                        ovu_caps_memory_get_free (memory));
+      if (ovu_caps_memory_has_used (memory) != FALSE)
+        {
+          g_file_info_set_attribute_uint64 (info,
+                                            G_FILE_ATTRIBUTE_FILESYSTEM_SIZE,
+                                            ovu_caps_memory_get_free (memory)
+                                            + ovu_caps_memory_get_used (memory));
+        }
+    }
+  ovu_caps_free (caps);
+
+  g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE, "obexftp");
+
+  g_vfs_job_succeeded (G_VFS_JOB (job));
+
+  g_print ("- do_query_fs_info\n");
+}
+
+static void
+do_enumerate (GVfsBackend *backend,
+              GVfsJobEnumerate *job,
+              const char *filename,
+              GFileAttributeMatcher *matcher,
+              GFileQueryInfoFlags flags)
+{
+  GVfsBackendObexftp *op_backend = G_VFS_BACKEND_OBEXFTP (backend);
+  GError *error = NULL;
+  char *files;
+  GList *elements = NULL;
+
+  g_print ("+ do_enumerate, filename: %s\n", filename);
+
+  if (do_change_directory (op_backend, filename, &error) == FALSE)
+    {
+      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+      g_error_free (error);
+      return;
+    }
+
+  files = NULL;
+  if (dbus_g_proxy_call (op_backend->session_proxy, "RetrieveFolderListing", &error,
+                         G_TYPE_INVALID,
+                         G_TYPE_STRING, &files, G_TYPE_INVALID) == FALSE)
+    {
+      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+      g_error_free (error);
+      return;
+    }
+
+  g_message ("files: %s", files);
+
+  if (gvfsbackendobexftp_fl_parser_parse (files, strlen (files), &elements, &error) == FALSE)
+    {
+      g_message ("gvfsbackendobexftp_fl_parser_parse failed");
+      g_free (files);
+      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+      g_error_free (error);
+      return;
+    }
+  g_free (files);
+
+  g_vfs_job_succeeded (G_VFS_JOB (job));
+
+  g_vfs_job_enumerate_add_infos (job, elements);
+
+  g_list_foreach (elements, (GFunc)g_object_unref, NULL);
+  g_list_free (elements);
+  g_vfs_job_enumerate_done (job);
+
+  g_print ("- do_enumerate\n");
+}
+
+static void
+do_set_display_name (GVfsBackend *backend,
+                     GVfsJobSetDisplayName *job,
+                     const char *filename,
+                     const char *display_name)
+{
+  g_print ("+ do_set_display_name, filename: %s, display_name: %s\n",
+           filename, display_name);
+  g_vfs_job_failed_from_errno (G_VFS_JOB (job), ENOSYS);
+  g_print ("- do_set_display_name\n");
+}
+
+static void
+do_delete (GVfsBackend *backend,
+           GVfsJobDelete *job,
+           const char *filename)
+{
+  GVfsBackendObexftp *op_backend = G_VFS_BACKEND_OBEXFTP (backend);
+  char *basename, *parent;
+  GError *error = NULL;
+  GFileInfo *info;
+
+  g_print ("+ do_delete, filename: %s\n", filename);
+
+  /* Check whether we have a directory */
+  info = g_file_info_new ();
+  if (do_query_file_info_helper (backend, filename, info, &error) == FALSE)
+    {
+      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+      g_object_unref (info);
+      return;
+    }
+
+  if (g_vfs_job_is_cancelled (G_VFS_JOB (job)))
+    {
+      op_backend->user_cancelled = TRUE;
+      g_object_unref (info);
+      return;
+    }
+
+  /* Get the listing of the directory, and abort if it's not empty */
+  if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
+    {
+      GList *elements;
+      char *files;
+      guint len;
+
+      g_object_unref (info);
+
+      if (do_change_directory (op_backend, filename, &error) == FALSE)
+        {
+          g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+          g_error_free (error);
+          return;
+        }
+
+      if (g_vfs_job_is_cancelled (G_VFS_JOB (job)))
+        {
+          op_backend->user_cancelled = TRUE;
+          return;
+        }
+
+      files = NULL;
+      if (dbus_g_proxy_call (op_backend->session_proxy, "RetrieveFolderListing", &error,
+                             G_TYPE_INVALID,
+                             G_TYPE_STRING, &files, G_TYPE_INVALID) == FALSE)
+        {
+          g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+          g_error_free (error);
+          return;
+        }
+
+      if (gvfsbackendobexftp_fl_parser_parse (files, strlen (files), &elements, &error) == FALSE)
+        {
+          g_message ("gvfsbackendobexftp_fl_parser_parse failed");
+          g_free (files);
+          g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+          g_error_free (error);
+          return;
+        }
+      g_free (files);
+
+      len = g_list_length (elements);
+      g_list_foreach (elements, (GFunc)g_object_unref, NULL);
+      g_list_free (elements);
+
+      if (len != 0)
+        {
+          g_set_error (&error, G_IO_ERROR,
+                       G_IO_ERROR_NOT_EMPTY,
+                       g_strerror (ENOTEMPTY));
+          g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+          g_error_free (error);
+          return;
+        }
+    }
+  else
+    {
+      g_object_unref (info);
+    }
+
+  basename = g_path_get_basename (filename);
+  if (strcmp (basename, G_DIR_SEPARATOR_S) == 0
+      || strcmp (basename, ".") == 0)
+    {
+      g_free (basename);
+      g_vfs_job_failed_from_errno (G_VFS_JOB (job), EPERM);
+      return;
+    }
+
+  if (g_vfs_job_is_cancelled (G_VFS_JOB (job)))
+    {
+      op_backend->user_cancelled = TRUE;
+      g_free (basename);
+      return;
+    }
+
+  parent = g_path_get_dirname (filename);
+  if (do_change_directory (op_backend, parent, &error) == FALSE)
+    {
+      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+      g_free (basename);
+      g_free (parent);
+      g_error_free (error);
+      return;
+    }
+  g_free (parent);
+
+  if (g_vfs_job_is_cancelled (G_VFS_JOB (job)))
+    {
+      op_backend->user_cancelled = TRUE;
+      g_free (basename);
+      return;
+    }
+
+  if (dbus_g_proxy_call (op_backend->session_proxy, "DeleteRemoteFile", &error,
+                         G_TYPE_STRING, basename, G_TYPE_INVALID,
+                         G_TYPE_INVALID) == FALSE)
+    {
+      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+      g_error_free (error);
+      return;
+    }
+  g_free (basename);
+
+  g_vfs_job_succeeded (G_VFS_JOB (job));
+
+  g_print ("- do_delete\n");
+}
+
+static void
+do_make_directory (GVfsBackend *backend,
+                   GVfsJobMakeDirectory *job,
+                   const char *filename)
+{
+  GVfsBackendObexftp *op_backend = G_VFS_BACKEND_OBEXFTP (backend);
+  char *basename, *parent;
+  GError *error = NULL;
+  GFileInfo *info;
+
+  g_print ("+ do_make_directory, filename: %s\n", filename);
+
+  /* Check if the folder already exists */
+  info = g_file_info_new ();
+  if (do_query_file_info_helper (backend, filename, info, &error) != FALSE)
+    {
+      g_object_unref (info);
+      g_vfs_job_failed_from_errno (G_VFS_JOB (job), EEXIST);
+      return;
+    }
+  g_object_unref (info);
+  if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) == FALSE)
+    {
+      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+      return;
+    }
+
+  if (g_vfs_job_is_cancelled (G_VFS_JOB (job)))
+    {
+      op_backend->user_cancelled = TRUE;
+      return;
+    }
+
+  parent = g_path_get_dirname (filename);
+  if (do_change_directory (op_backend, parent, &error) == FALSE)
+    {
+      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+      g_error_free (error);
+      return;
+    }
+  g_free (parent);
+
+  if (g_vfs_job_is_cancelled (G_VFS_JOB (job)))
+    {
+      op_backend->user_cancelled = TRUE;
+      return;
+    }
+
+  basename = g_path_get_basename (filename);
+  if (dbus_g_proxy_call (op_backend->session_proxy, "CreateFolder", &error,
+                         G_TYPE_STRING, basename, G_TYPE_INVALID,
+                         G_TYPE_INVALID) == FALSE)
+    {
+      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+      g_error_free (error);
+      return;
+    }
+  g_free (basename);
+
+  g_vfs_job_succeeded (G_VFS_JOB (job));
+
+  g_print ("+ do_make_directory\n");
+}
+
+static void
+do_move (GVfsBackend *backend,
+         GVfsJobMove *job,
+         const char *source,
+         const char *destination,
+         GFileCopyFlags flags,
+         GFileProgressCallback progress_callback,
+         gpointer progress_callback_data)
+{
+  g_vfs_job_failed_from_errno (G_VFS_JOB (job), ENOSYS);
+}
+
+static void
+g_vfs_backend_obexftp_class_init (GVfsBackendObexftpClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  GVfsBackendClass *backend_class = G_VFS_BACKEND_CLASS (klass);
+
+  gobject_class->finalize = g_vfs_backend_obexftp_finalize;
+
+  backend_class->try_mount = try_mount;
+  backend_class->open_for_read = do_open_for_read;
+  backend_class->read = do_read;
+  backend_class->seek_on_read = do_seek_on_read;
+  backend_class->close_read = do_close_read;
+  backend_class->create = do_create;
+  backend_class->append_to = do_append_to;
+  backend_class->replace = do_replace;
+  backend_class->write = do_write;
+  backend_class->seek_on_write = do_seek_on_write;
+  backend_class->close_write = do_close_write;
+  backend_class->query_info = do_query_info;
+  backend_class->query_fs_info = do_query_fs_info;
+  backend_class->enumerate = do_enumerate;
+  backend_class->set_display_name = do_set_display_name;
+  backend_class->delete = do_delete;
+  backend_class->make_directory = do_make_directory;
+  backend_class->move = do_move;
+
+  dbus_g_object_register_marshaller (obexftp_marshal_VOID__STRING_STRING_INT,
+                                     G_TYPE_NONE, G_TYPE_STRING,
+                                     G_TYPE_STRING, G_TYPE_INT, G_TYPE_INVALID);
+  dbus_g_object_register_marshaller (obexftp_marshal_VOID__STRING_STRING,
+                                     G_TYPE_NONE, G_TYPE_STRING,
+                                     G_TYPE_STRING, G_TYPE_INVALID);
+  /* TransferStarted */
+  dbus_g_object_register_marshaller(obexftp_marshal_VOID__STRING_STRING_UINT64,
+                                    G_TYPE_NONE, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_UINT64, G_TYPE_INVALID);
+}
+
+/*
+ * vim: sw=2 ts=8 cindent expandtab cinoptions=f0,>4,n2,{2,(0,^-2,t0 ai
+ */
Index: daemon/gvfsbackendobexftp.h
===================================================================
--- daemon/gvfsbackendobexftp.h	(revision 0)
+++ daemon/gvfsbackendobexftp.h	(revision 0)
@@ -0,0 +1,50 @@
+/* GIO - GLib Input, Output and Streaming Library
+ * 
+ * Copyright (C) 2006-2007 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Alexander Larsson <alexl@redhat.com>
+ */
+
+#ifndef __G_VFS_BACKEND_OBEXFTP_H__
+#define __G_VFS_BACKEND_OBEXFTP_H__
+
+#include <gvfsbackend.h>
+#include <gmountspec.h>
+
+G_BEGIN_DECLS
+
+#define G_VFS_TYPE_BACKEND_OBEXFTP         (g_vfs_backend_obexftp_get_type ())
+#define G_VFS_BACKEND_OBEXFTP(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), G_VFS_TYPE_BACKEND_OBEXFTP, GVfsBackendObexftp))
+#define G_VFS_BACKEND_OBEXFTP_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), G_VFS_TYPE_BACKEND_OBEXFTP, GVfsBackendObexftpClass))
+#define G_VFS_IS_BACKEND_OBEXFTP(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_VFS_TYPE_BACKEND_OBEXFTP))
+#define G_VFS_IS_BACKEND_OBEXFTP_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), G_VFS_TYPE_BACKEND_OBEXFTP))
+#define G_VFS_BACKEND_OBEXFTP_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_VFS_TYPE_BACKEND_OBEXFTP, GVfsBackendObexftpClass))
+
+typedef struct _GVfsBackendObexftp        GVfsBackendObexftp;
+typedef struct _GVfsBackendObexftpClass   GVfsBackendObexftpClass;
+
+struct _GVfsBackendObexftpClass
+{
+  GVfsBackendClass parent_class;
+};
+
+GType g_vfs_backend_obexftp_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* __G_VFS_BACKEND_OBEXFTP_H__ */
Index: daemon/Makefile.am
===================================================================
--- daemon/Makefile.am	(revision 1371)
+++ daemon/Makefile.am	(working copy)
@@ -8,6 +8,7 @@
 	-I$(top_srcdir)/common			\
 	-I$(top_builddir)			\
 	$(GLIB_CFLAGS) $(DBUS_CFLAGS) 		\
+	$(OBEXFTP_CFLAGS) $(XML_CFLAGS)		\
 	$(KEYRING_CFLAGS)			\
 	-DDBUS_API_SUBJECT_TO_CHANGE		\
 	-DLIBEXEC_DIR=\"$(libexecdir)/\" 	\
@@ -69,13 +70,27 @@
 libexec_PROGRAMS += gvfsd-network
 endif
 
+mount_in_files += obexftp.mount.in
+if USE_OBEXFTP
+mount_DATA += obexftp.mount
+libexec_PROGRAMS += gvfsd-obexftp
+BUILT_SOURCES = obexftp-marshal.c obexftp-marshal.h
+
+obexftp-marshal.h: obexftp-marshal.list
+	glib-genmarshal $< --prefix=obexftp_marshal --header > $@
+
+obexftp-marshal.c: obexftp-marshal.list
+	echo "#include \"obexftp-marshal.h\"" > $@ && glib-genmarshal $< --prefix=obexftp_marshal --body >> $@
+
+endif
+
 mount_in_files += dns-sd.mount.in
 if HAVE_AVAHI
 mount_DATA += dns-sd.mount
 libexec_PROGRAMS += gvfsd-dnssd
 endif
 
-EXTRA_DIST = gvfs-daemon.service.in $(mount_in_files)
+EXTRA_DIST = gvfs-daemon.service.in $(mount_in_files) obexftp-marshal.list
 
 DISTCLEANFILES = gvfs-daemon.service $(mount_DATA)
 
@@ -183,6 +198,22 @@
 
 gvfsd_smb_browse_LDADD = $(SAMBA_LIBS)  $(GCONF_LIBS) $(libraries)
 
+gvfsd_obexftp_SOURCES = \
+	gvfsbackendobexftp.c gvfsbackendobexftp.h \
+	obexftp-marshal.c obexftp-marshal.h \
+	gvfsbackendobexftp-fl-parser.c gvfsbackendobexftp-fl-parser.h \
+	gvfsbackendobexftp-cap-parser.c gvfsbackendobexftp-cap-parser.h \
+	daemon-main.c daemon-main.h \
+	daemon-main-generic.c
+
+gvfsd_obexftp_CPPFLAGS = \
+	-DBACKEND_HEADER=gvfsbackendobexftp.h \
+	-DDEFAULT_BACKEND_TYPE=obex \
+	-DMAX_JOB_THREADS=1 \
+	-DBACKEND_TYPES='"obex", G_VFS_TYPE_BACKEND_OBEXFTP,'
+
+gvfsd_obexftp_LDADD = $(OBEXFTP_LIBS) $(XML_LIBS) $(libraries)
+
 gvfsd_ftp_SOURCES = \
 	gvfsbackendftp.c gvfsbackendftp.h \
 	daemon-main.c daemon-main.h \
Index: daemon/gvfsbackendobexftp-cap-parser.c
===================================================================
--- daemon/gvfsbackendobexftp-cap-parser.c	(revision 0)
+++ daemon/gvfsbackendobexftp-cap-parser.c	(revision 0)
@@ -0,0 +1,596 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2004-2005 Nokia Corporation.
+ * Copyright (C) 2008 Bastien Nocera <hadess@hadess.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; version 2 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+#include <expat.h>
+
+#include "gvfsbackendobexftp-cap-parser.h"
+
+#define d(x)
+
+struct _OvuCaps {
+	GList *memory_entries;
+
+	/* FIXME: Add "Services" and "Inbox" data here later. */
+};
+
+struct _OvuCapsMemory {
+        gchar   *type;
+        goffset  free;
+        goffset  used;
+        guint    has_free : 1;
+	guint    has_used : 1;
+        guint    case_sensitive : 1;
+};
+
+typedef enum {
+	PARSER_STATE_INVALID,
+
+	PARSER_STATE_START,
+	PARSER_STATE_CAPABILITY,
+
+	PARSER_STATE_GENERAL,
+
+	PARSER_STATE_MEMORY,
+	PARSER_STATE_MEMORY_TYPE,
+	PARSER_STATE_MEMORY_LOCATION,
+	PARSER_STATE_MEMORY_FREE,
+	PARSER_STATE_MEMORY_USED,
+	PARSER_STATE_MEMORY_SHARED,
+	PARSER_STATE_MEMORY_FILESIZE,
+	PARSER_STATE_MEMORY_FOLDERSIZE,
+	PARSER_STATE_MEMORY_FILELEN,
+	PARSER_STATE_MEMORY_FOLDERLEN,
+	PARSER_STATE_MEMORY_CASE,
+	PARSER_STATE_MEMORY_EXT,
+
+	PARSER_STATE_INBOX,
+	PARSER_STATE_SERVICE,
+
+	PARSER_STATE_SKIP
+} ParserState;
+
+
+typedef struct {
+	GList             *state;
+
+	GList             *memory_entries;
+
+	gchar             *memory_type;
+	goffset   memory_free;
+	goffset   memory_used;
+	gboolean           memory_has_free;
+	gboolean           memory_has_used;
+	gboolean           memory_case_sensitive;
+
+	GError           **error;
+} ParserData;
+
+static void    cap_parser_start_node_cb   (void                 *user_data,
+					   const char           *node_name,
+					   const char          **attr);
+
+static void    cap_parser_end_node_cb     (void                 *user_data,
+					   const char           *node_name);
+static void    cap_parser_text_cb         (void                 *user_data,
+					   const XML_Char       *s,
+                                           int                   len);
+static XML_Parser
+cap_parser_create_parser                  (ParserData           *data);
+
+
+static void
+cap_parser_push_state (ParserData *data, ParserState state)
+{
+	data->state = g_list_prepend (data->state,
+				      GINT_TO_POINTER (state));
+}
+
+static ParserState
+cap_parser_pop_state (ParserData *data)
+{
+	ParserState state;
+
+	if (!data->state) {
+		return PARSER_STATE_INVALID;
+	}
+
+	state = GPOINTER_TO_INT (data->state->data);
+	data->state = g_list_delete_link (data->state, data->state);
+
+	return state;
+}
+
+static ParserState
+cap_parser_peek_state (ParserData *data)
+{
+	if (!data->state) {
+		return PARSER_STATE_START;
+	}
+
+	return GPOINTER_TO_INT (data->state->data);
+}
+
+static const char *
+cap_parser_get_attribute_value (const char  *name, const char **attr)
+{
+	gint i = 0;
+
+	while (attr[i]) {
+		if (strcmp (name, attr[i]) == 0) {
+			return attr[i + 1];
+		}
+		i += 2;
+	}
+
+	return "";
+}
+
+static void
+cap_parser_start_node_cb (void        *user_data,
+			  const char  *node_name,
+			  const char **attr)
+{
+	ParserData  *data;
+	ParserState  state;
+	const gchar *version;
+
+	data = (ParserData *) user_data;
+
+	state = cap_parser_peek_state (data);
+
+	switch (state) {
+	case PARSER_STATE_START:
+		if (strcmp (node_name, "Capability") != 0) {
+			g_set_error (data->error,
+				     G_MARKUP_ERROR,
+				     G_MARKUP_ERROR_INVALID_CONTENT,
+				     "Outermost element must be a <Capability>, not <%s>",
+				     node_name);
+			return;
+		}
+
+		version = cap_parser_get_attribute_value ("version", attr);
+		/* Assume an empty version is fine */
+		if (strcmp (version, "1.0") != 0 && version[0] != '\0') {
+			g_warning ("Version expected is '1.0', not '%s'\n", version);
+		}
+
+		cap_parser_push_state (data, PARSER_STATE_CAPABILITY);
+		break;
+
+	case PARSER_STATE_CAPABILITY:
+		if (strcmp (node_name, "General") == 0) {
+			cap_parser_push_state (data, PARSER_STATE_GENERAL);
+		}
+		else if (strcmp (node_name, "Inbox") == 0) {
+			cap_parser_push_state (data, PARSER_STATE_INBOX);
+		}
+		else if (strcmp (node_name, "Service") == 0) {
+			cap_parser_push_state (data, PARSER_STATE_SERVICE);
+		} else {
+			g_set_error (data->error,
+				     G_MARKUP_ERROR,
+				     G_MARKUP_ERROR_INVALID_CONTENT,
+				     "Don't expect node '%s' as child of 'Cap'",
+				     node_name);
+			return;
+		}
+		break;
+
+	case PARSER_STATE_GENERAL:
+		if (strcmp (node_name, "Memory") == 0) {
+			cap_parser_push_state (data, PARSER_STATE_MEMORY);
+		}
+		else if (strcmp (node_name, "Manufacturer") == 0 ||
+			 strcmp (node_name, "Model") == 0 ||
+			 strcmp (node_name, "SN") == 0 ||
+			 strcmp (node_name, "OEM") == 0 ||
+			 strcmp (node_name, "SW") == 0 ||
+			 strcmp (node_name, "FW") == 0 ||
+			 strcmp (node_name, "HW") == 0 ||
+			 strcmp (node_name, "Language") == 0 ||
+			 strcmp (node_name, "Ext") == 0) {
+
+			/* Skip these for now. */
+			cap_parser_push_state (data, PARSER_STATE_SKIP);
+		} else {
+			g_set_error (data->error,
+				     G_MARKUP_ERROR,
+				     G_MARKUP_ERROR_INVALID_CONTENT,
+				     "Don't expect node '%s' as child of 'General'",
+				     node_name);
+			return;
+		}
+
+		break;
+
+	case PARSER_STATE_MEMORY:
+		if (strcmp (node_name, "MemType") == 0) {
+			cap_parser_push_state (data, PARSER_STATE_MEMORY_TYPE);
+		}
+		else if (strcmp (node_name, "Location") == 0) {
+			cap_parser_push_state (data, PARSER_STATE_MEMORY_LOCATION);
+		}
+		else if (strcmp (node_name, "Free") == 0) {
+			cap_parser_push_state (data, PARSER_STATE_MEMORY_FREE);
+		}
+		else if (strcmp (node_name, "Used") == 0) {
+			cap_parser_push_state (data, PARSER_STATE_MEMORY_USED);
+		}
+		else if (strcmp (node_name, "Shared") == 0) {
+			cap_parser_push_state (data, PARSER_STATE_MEMORY_SHARED);
+		}
+		else if (strcmp (node_name, "FileSize") == 0) {
+			cap_parser_push_state (data, PARSER_STATE_MEMORY_FILESIZE);
+		}
+		else if (strcmp (node_name, "FolderSize") == 0) {
+			cap_parser_push_state (data, PARSER_STATE_MEMORY_FOLDERSIZE);
+		}
+		else if (strcmp (node_name, "FileNLen") == 0) {
+			cap_parser_push_state (data, PARSER_STATE_MEMORY_FILELEN);
+		}
+		else if (strcmp (node_name, "FolderNLen") == 0) {
+			cap_parser_push_state (data, PARSER_STATE_MEMORY_FOLDERLEN);
+		}
+		else if (strcmp (node_name, "CaseSenN") == 0) {
+			cap_parser_push_state (data, PARSER_STATE_MEMORY_CASE);
+			data->memory_case_sensitive = TRUE;
+		}
+		else if (strcmp (node_name, "Ext") == 0) {
+			cap_parser_push_state (data, PARSER_STATE_MEMORY_EXT);
+		} else {
+			g_set_error (data->error,
+				     G_MARKUP_ERROR,
+				     G_MARKUP_ERROR_INVALID_CONTENT,
+				     "Don't expect node '%s' as child of 'Memory'",
+				     node_name);
+			return;
+		}
+		break;
+
+	case PARSER_STATE_INBOX:
+	case PARSER_STATE_SERVICE:
+		/* Skip these for now. */
+		cap_parser_push_state (data, PARSER_STATE_SKIP);
+		break;
+
+	case PARSER_STATE_SKIP:
+		cap_parser_push_state (data, PARSER_STATE_SKIP);
+		break;
+
+	default:
+		g_warning ("Node not handled: '%s'\n", node_name);
+		cap_parser_push_state (data, PARSER_STATE_SKIP);
+		break;
+	}
+}
+
+static void
+cap_parser_reset_memory (ParserData *data)
+{
+	g_free (data->memory_type);
+	data->memory_type = NULL;
+	data->memory_free = 0;
+	data->memory_used = 0;
+	data->memory_has_free = FALSE;
+	data->memory_has_used = FALSE;
+	data->memory_case_sensitive = FALSE;
+}
+
+static void
+cap_parser_end_node_cb (void *user_data, const char *node_name)
+{
+	ParserData    *data;
+	ParserState    state;
+	OvuCapsMemory *memory;
+
+	data = (ParserData *) user_data;
+
+	state = cap_parser_pop_state (data);
+
+	switch (state) {
+	case PARSER_STATE_INVALID:
+		return;
+
+	case PARSER_STATE_MEMORY:
+		memory = ovu_caps_memory_new (data->memory_type,
+					      data->memory_free,
+					      data->memory_used,
+					      data->memory_has_free,
+					      data->memory_has_used,
+					      data->memory_case_sensitive);
+
+		data->memory_entries = g_list_prepend (data->memory_entries,
+						       memory);
+		cap_parser_reset_memory (data);
+		break;
+
+	case PARSER_STATE_CAPABILITY:
+		data->memory_entries = g_list_reverse (data->memory_entries);
+		break;
+
+	default:
+		break;
+	}
+}
+
+/* Parse a long, return -1 if input is not strictly valid or null. */
+static goffset
+parse_long (const gchar *str, gboolean *success)
+{
+	gchar *endptr;
+	glong  l;
+
+	*success = TRUE;
+
+	if (!str) {
+		*success = FALSE;
+		return 0;
+	}
+
+	l = strtol (str, &endptr, 10);
+	if (endptr[0] != '\0' || l < 0) {
+		*success = FALSE;
+		l = 0;
+	}
+
+	return l;
+}
+
+static void
+cap_parser_text_cb (void           *user_data,
+		    const XML_Char *s,
+		    int             len)
+{
+	ParserData  *data;
+	ParserState  state;
+	gchar       *tmp;
+
+	data = (ParserData *) user_data;
+
+	/* text is not null terminated. */
+	tmp = g_strndup (s, len);
+
+	state = cap_parser_peek_state (data);
+
+	switch (state) {
+	case PARSER_STATE_MEMORY_TYPE:
+		data->memory_type = g_strdup (tmp);
+		break;
+	case PARSER_STATE_MEMORY_FREE:
+		data->memory_free = parse_long (tmp, &data->memory_has_free);
+		break;
+	case PARSER_STATE_MEMORY_USED:
+		data->memory_used = parse_long (tmp, &data->memory_has_used);
+		break;
+
+	default:
+		break;
+	}
+
+	g_free (tmp);
+}
+
+static XML_Parser
+cap_parser_create_parser (ParserData *data)
+{
+	XML_Parser parser;
+
+	parser = XML_ParserCreate (NULL);
+
+	XML_SetElementHandler (parser,
+			       cap_parser_start_node_cb,
+			       cap_parser_end_node_cb);
+
+	XML_SetCharacterDataHandler (parser, cap_parser_text_cb);
+
+	XML_SetUserData (parser, data);
+
+	return parser;
+}
+
+static void
+cap_parser_free (ParserData *data, gboolean free_data)
+{
+	cap_parser_reset_memory (data);
+
+	if (free_data) {
+		g_list_foreach (data->memory_entries,
+				(GFunc) ovu_caps_memory_free, NULL);
+	}
+
+	g_free (data);
+}
+
+OvuCaps *
+ovu_caps_parser_parse (const gchar  *buf,
+		       gint          len,
+		       GError      **error)
+{
+	ParserData *data;
+	XML_Parser  parser;
+	OvuCaps    *caps;
+
+	data = g_new0 (ParserData, 1);
+
+	data->error = error;
+	parser = cap_parser_create_parser (data);
+
+	if (XML_Parse (parser, buf, len, TRUE) == 0) {
+		caps = NULL;
+
+		if (*error == NULL) {
+			g_set_error (error,
+				     G_MARKUP_ERROR,
+				     G_MARKUP_ERROR_INVALID_CONTENT,
+				     "Couldn't parse the incoming data");
+		}
+
+		cap_parser_free (data, TRUE);
+	} else {
+		caps = g_new0 (OvuCaps, 1);
+		caps->memory_entries = data->memory_entries;
+		
+		cap_parser_free (data, FALSE);
+	}
+
+	XML_ParserFree (parser);
+
+	return caps;
+}
+
+OvuCapsMemory *
+ovu_caps_memory_new (const gchar      *type,
+		     goffset  free,
+		     goffset  used,
+		     gboolean          has_free,
+		     gboolean          has_used,
+		     gboolean          case_sensitive)
+{
+	OvuCapsMemory *memory;
+
+	memory = g_new0 (OvuCapsMemory, 1);
+
+	memory->type = g_strdup (type);
+	memory->free = free;
+	memory->used = used;
+	memory->has_free = has_free;
+	memory->has_used = has_used;
+	memory->case_sensitive = case_sensitive;
+
+	return memory;
+}
+
+void
+ovu_caps_memory_free (OvuCapsMemory *memory)
+{
+	g_free (memory->type);
+	g_free (memory);
+}
+
+gboolean
+ovu_caps_memory_equal (OvuCapsMemory *m1, OvuCapsMemory *m2)
+{
+	if (strcmp (m1->type, m2->type) != 0) {
+		d(g_print ("type mismatch: %s %s\n",
+			   m1->type, m2->type));
+		return FALSE;
+	}
+
+	if (m1->free != m2->free) {
+		d(g_print ("free mismatch: %d %d\n",
+			   (int) m1->free, (int) m2->free));
+		return FALSE;
+	}
+
+	if (m1->used != m2->used) {
+		d(g_print ("used mismatch: %d %d\n",
+			   (int) m1->used, (int) m2->used));
+		return FALSE;
+	}
+
+	if (m1->case_sensitive != m2->case_sensitive) {
+		d(g_print ("case mismatch: %d %d\n",
+			   m1->case_sensitive,
+			   m2->case_sensitive));
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+void
+ovu_caps_free (OvuCaps *caps)
+{
+	g_list_foreach (caps->memory_entries,
+			(GFunc) ovu_caps_memory_free, NULL);
+
+	g_list_free (caps->memory_entries);
+
+	g_free (caps);
+}
+
+GList *
+ovu_caps_get_memory_entries (OvuCaps *caps)
+{
+	g_return_val_if_fail (caps != NULL, NULL);
+
+	return caps->memory_entries;
+}
+
+OvuCapsMemory *
+ovu_caps_get_memory_type (OvuCaps     *caps,
+			  const gchar *mem_type)
+{
+	GList *tmp;
+
+	g_return_val_if_fail (caps != NULL, NULL);
+
+	for (tmp = caps->memory_entries; tmp != NULL; tmp = tmp->next) {
+		OvuCapsMemory *memory = tmp->data;
+
+		/* treat a NULL memory type as matching anything */
+		if (mem_type == NULL || (memory->type != NULL &&
+					 !strcmp(mem_type, memory->type)))
+			return memory;
+	}
+	return NULL;
+}
+
+const gchar *
+ovu_caps_memory_get_type (OvuCapsMemory *memory)
+{
+	return memory->type;
+}
+
+goffset
+ovu_caps_memory_get_used (OvuCapsMemory *memory)
+{
+	return memory->used;
+}
+
+goffset
+ovu_caps_memory_get_free (OvuCapsMemory *memory)
+{
+	return memory->free;
+}
+
+gboolean
+ovu_caps_memory_has_used (OvuCapsMemory *memory)
+{
+	return memory->has_used;
+}
+
+gboolean
+ovu_caps_memory_has_free (OvuCapsMemory *memory)
+{
+	return memory->has_free;
+}
+
+gboolean
+ovu_caps_memory_get_case_sensitive (OvuCapsMemory *memory)
+{
+	return memory->case_sensitive;
+}
Index: daemon/gvfsbackendobexftp-cap-parser.h
===================================================================
--- daemon/gvfsbackendobexftp-cap-parser.h	(revision 0)
+++ daemon/gvfsbackendobexftp-cap-parser.h	(revision 0)
@@ -0,0 +1,54 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2004 Nokia Corporation.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; version 2 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __OVU_CAP_PARSER_H__
+#define __OVU_CAP_PARSER_H__
+
+#include <glib.h>
+
+typedef struct _OvuCaps       OvuCaps;
+typedef struct _OvuCapsMemory OvuCapsMemory;
+
+OvuCaps *     ovu_caps_parser_parse       (const gchar       *buf,
+					   gint               len,
+					   GError           **error);
+
+GList *          ovu_caps_get_memory_entries        (OvuCaps         *caps);
+OvuCapsMemory   *ovu_caps_get_memory_type           (OvuCaps         *caps,
+						     const gchar     *mem_type);
+void             ovu_caps_free                      (OvuCaps         *caps);
+OvuCapsMemory *  ovu_caps_memory_new                (const gchar     *type,
+						     goffset          free,
+						     goffset          used,
+						     gboolean         has_free,
+						     gboolean         has_used,
+						     gboolean         case_sensitive);
+void             ovu_caps_memory_free               (OvuCapsMemory   *memory);
+gboolean         ovu_caps_memory_equal              (OvuCapsMemory   *m1,
+						     OvuCapsMemory   *m2);
+const gchar *    ovu_caps_memory_get_type           (OvuCapsMemory   *memory);
+goffset          ovu_caps_memory_get_used           (OvuCapsMemory   *memory);
+goffset          ovu_caps_memory_get_free           (OvuCapsMemory   *memory);
+gboolean         ovu_caps_memory_has_used           (OvuCapsMemory   *memory);
+gboolean         ovu_caps_memory_has_free           (OvuCapsMemory   *memory);
+gboolean         ovu_caps_memory_get_case_sensitive (OvuCapsMemory   *memory);
+
+#endif /* __OVU_CAP_PARSER_H__ */
+
Index: po/POTFILES.in
===================================================================
--- po/POTFILES.in	(revision 1371)
+++ po/POTFILES.in	(working copy)
@@ -21,6 +21,7 @@
 daemon/gvfsbackendhttp.c
 daemon/gvfsbackendlocaltest.c
 daemon/gvfsbackendnetwork.c
+daemon/gvfsbackendobexftp.c
 daemon/gvfsbackendsftp.c
 daemon/gvfsbackendsmb.c
 daemon/gvfsbackendsmbbrowse.c
Index: client/test-uri-utils.c
===================================================================
--- client/test-uri-utils.c	(revision 1371)
+++ client/test-uri-utils.c	(working copy)
@@ -13,7 +13,8 @@
 static TestURIs uris[] = {
 	{ "https://[2001:0db8:85a3:08d3:1319:8a2e:0370:7344]:443/", "[2001:0db8:85a3:08d3:1319:8a2e:0370:7344]", 443 },
 	{ "http://test:443/", "test", 443 },
-	{ "http://test/", "test", -1 }
+	{ "http://test/", "test", -1 },
+	{ "obex://[00:12:D2:79:B7:33]/MMC/foo.jpg", "[00:12:D2:79:B7:33]", -1 }
 };
 
 int main (int argc, char **argv)
