summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog19
-rw-r--r--client/test-uri-utils.c3
-rw-r--r--configure.ac36
-rw-r--r--daemon/Makefile.am33
-rw-r--r--daemon/gvfsbackendobexftp-cap-parser.c596
-rw-r--r--daemon/gvfsbackendobexftp-cap-parser.h54
-rw-r--r--daemon/gvfsbackendobexftp-fl-parser.c400
-rw-r--r--daemon/gvfsbackendobexftp-fl-parser.h38
-rw-r--r--daemon/gvfsbackendobexftp.c1268
-rw-r--r--daemon/gvfsbackendobexftp.h50
-rw-r--r--daemon/obexftp-marshal.list2
-rw-r--r--daemon/obexftp.mount.in4
-rw-r--r--po/ChangeLog5
-rw-r--r--po/POTFILES.in1
14 files changed, 2507 insertions, 2 deletions
diff --git a/ChangeLog b/ChangeLog
index 5c4403ef..d3d473e7 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,22 @@
+2008-02-27 Bastien Nocera <hadess@hadess.net>
+
+ * client/test-uri-utils.c: add test for obex URIs
+ * configure.ac: Require expat for the obexftp backend
+ * daemon/Makefile.am:
+ * daemon/gvfsbackendobexftp-cap-parser.c:
+ * daemon/gvfsbackendobexftp-cap-parser.h:
+ * daemon/gvfsbackendobexftp-fl-parser.c:
+ * daemon/gvfsbackendobexftp-fl-parser.h:
+ Added ObexFTP folder listing and capability parser from
+ gnome-vfs-obexftp, ported to gio
+
+ * daemon/gvfsbackendobexftp.c:
+ * daemon/gvfsbackendobexftp.h:
+ * daemon/obexftp-marshal.list:
+ * daemon/obexftp.mount.in: Add read-only ObexFTP backend
+
+ (Closes: #509621)
+
2008-02-27 Luca Ferretti <elle.uca@libero.it>
* daemon/gvfsbackendcdda.c: (do_query_info):
diff --git a/client/test-uri-utils.c b/client/test-uri-utils.c
index ce7b2524..52045671 100644
--- a/client/test-uri-utils.c
+++ b/client/test-uri-utils.c
@@ -13,7 +13,8 @@ typedef struct {
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:FF:FF:FF:FF:FF]/MMC/foo.jpg", "[00:FF:FF:FF:FF:FF]", -1 }
};
int main (int argc, char **argv)
diff --git a/configure.ac b/configure.ac
index d528f8ac..8e74ea1f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -240,6 +240,41 @@ AC_SUBST(HAL_CFLAGS)
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
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
diff --git a/daemon/Makefile.am b/daemon/Makefile.am
index 689cd9c0..e08642b1 100644
--- a/daemon/Makefile.am
+++ b/daemon/Makefile.am
@@ -8,6 +8,7 @@ INCLUDES = \
-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 @@ mount_DATA += network.mount
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_CPPFLAGS = \
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 \
diff --git a/daemon/gvfsbackendobexftp-cap-parser.c b/daemon/gvfsbackendobexftp-cap-parser.c
new file mode 100644
index 00000000..8e67aef1
--- /dev/null
+++ b/daemon/gvfsbackendobexftp-cap-parser.c
@@ -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;
+}
diff --git a/daemon/gvfsbackendobexftp-cap-parser.h b/daemon/gvfsbackendobexftp-cap-parser.h
new file mode 100644
index 00000000..75a7011d
--- /dev/null
+++ b/daemon/gvfsbackendobexftp-cap-parser.h
@@ -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__ */
+
diff --git a/daemon/gvfsbackendobexftp-fl-parser.c b/daemon/gvfsbackendobexftp-fl-parser.c
new file mode 100644
index 00000000..a768c593
--- /dev/null
+++ b/daemon/gvfsbackendobexftp-fl-parser.c
@@ -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);
+}
diff --git a/daemon/gvfsbackendobexftp-fl-parser.h b/daemon/gvfsbackendobexftp-fl-parser.h
new file mode 100644
index 00000000..7867dccf
--- /dev/null
+++ b/daemon/gvfsbackendobexftp-fl-parser.h
@@ -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__ */
diff --git a/daemon/gvfsbackendobexftp.c b/daemon/gvfsbackendobexftp.c
new file mode 100644
index 00000000..e56fdcfe
--- /dev/null
+++ b/daemon/gvfsbackendobexftp.c
@@ -0,0 +1,1268 @@
+/* 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
+
+#define ASYNC_SUCCESS 1
+#define ASYNC_PENDING 0
+#define ASYNC_ERROR -1
+
+struct _GVfsBackendObexftp
+{
+ GVfsBackend parent_instance;
+
+ char *display_name;
+
+ DBusGConnection *connection;
+ DBusGProxy *manager_proxy;
+ DBusGProxy *session_proxy;
+
+ GCond *cond;
+ GMutex *mutex;
+ int status;
+ gboolean doing_io;
+ GError *error;
+};
+
+typedef struct {
+ char *source;
+ 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);
+
+ g_free (backend->display_name);
+
+ if (backend->session_proxy != NULL)
+ g_object_unref (backend->session_proxy);
+ g_mutex_free (backend->mutex);
+ g_cond_free (backend->cond);
+
+ 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->mutex = g_mutex_new ();
+ backend->cond = g_cond_new ();
+ backend->manager_proxy = dbus_g_proxy_new_for_name (backend->connection,
+ "org.openobex",
+ "/org/openobex",
+ "org.openobex.Manager");
+}
+
+static gboolean
+_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 (strcmp (filename, current_path) == 0)
+ {
+ 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
+_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 ("+ _query_file_info_helper, filename: %s\n", filename);
+
+ if (strcmp (filename, "/") == 0)
+ {
+ char *display;
+
+ /* 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, "/");
+ display = g_strdup_printf (_("%s on %s"), "/", op_backend->display_name);
+ g_file_info_set_display_name (info, display);
+ g_free (display);
+ return TRUE;
+ }
+
+ parent = g_path_get_dirname (filename);
+ if (_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 ("- _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);
+ }
+
+ /* Something is waiting on us */
+ g_mutex_lock (op_backend->mutex);
+ if (op_backend->doing_io)
+ {
+ op_backend->status = ASYNC_ERROR;
+ op_backend->error = g_error_new (DBUS_GERROR,
+ DBUS_GERROR_REMOTE_EXCEPTION,
+ error_message);
+ g_cond_signal (op_backend->cond);
+ g_mutex_unlock (op_backend->mutex);
+ return;
+ }
+ g_mutex_unlock (op_backend->mutex);
+
+ g_message ("Unhandled error, file a bug");
+ _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");
+
+ g_mutex_lock (op_backend->mutex);
+ op_backend->status = ASYNC_ERROR;
+ g_cond_signal (op_backend->cond);
+ g_mutex_unlock (op_backend->mutex);
+}
+
+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
+do_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;
+ GError *error = NULL;
+ const gchar *path = NULL;
+ char *server, *bdaddr;
+ GMountSpec *obexftp_mount_spec;
+
+ g_print ("+ do_mount\n");
+
+ 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;
+ }
+
+ /* 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;
+ }
+
+ /* FIXME, Have a way for the mount to be cancelled, see:
+ * http://bugs.muiline.com/view.php?id=51 */
+
+ if (dbus_g_proxy_call (op_backend->manager_proxy, "CreateBluetoothSession", &error,
+ G_TYPE_STRING, bdaddr, G_TYPE_STRING, "ftp", G_TYPE_INVALID,
+ DBUS_TYPE_G_OBJECT_PATH, &path, G_TYPE_INVALID) == FALSE)
+ {
+ g_free (bdaddr);
+ g_error_free (error);
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ return;
+ }
+
+ g_vfs_job_set_backend_data (G_VFS_JOB (job), backend, NULL);
+ g_print (" do_mount: %s mounted\n", bdaddr);
+
+ op_backend->session_proxy = dbus_g_proxy_new_for_name (op_backend->connection,
+ "org.openobex",
+ path,
+ "org.openobex.Session");
+
+ op_backend->display_name = get_device_name (bdaddr);
+ if (!op_backend->display_name)
+ op_backend->display_name = g_strdup (bdaddr);
+
+ g_vfs_backend_set_display_name (G_VFS_BACKEND (op_backend),
+ op_backend->display_name);
+ g_vfs_backend_set_icon_name (G_VFS_BACKEND (op_backend), "bluetooth");
+
+ obexftp_mount_spec = g_mount_spec_new ("obex");
+ server = g_strdup_printf ("[%s]", 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);
+
+ 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);
+
+ g_free (bdaddr);
+
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+
+ g_print ("- do_mount\n");
+}
+
+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);
+
+ g_message ("transfer of %s to %s started", filename, local_path);
+
+ g_mutex_lock (op_backend->mutex);
+ op_backend->status = ASYNC_SUCCESS;
+ g_cond_signal (op_backend->cond);
+ g_mutex_unlock (op_backend->mutex);
+}
+
+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, success;
+
+ g_print ("+ do_open_for_read, filename: %s\n", filename);
+
+ g_mutex_lock (op_backend->mutex);
+ op_backend->doing_io = TRUE;
+
+ /* Change into the directory and cache the file size */
+ info = g_file_info_new ();
+ if (_query_file_info_helper (backend, filename, info, &error) == FALSE)
+ {
+ op_backend->doing_io = FALSE;
+ g_mutex_unlock (op_backend->mutex);
+ 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->doing_io = FALSE;
+ g_mutex_unlock (op_backend->mutex);
+ error = g_error_new_literal (G_IO_ERROR,
+ G_IO_ERROR_CANCELLED,
+ _("Operation was cancelled"));
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ return;
+ }
+
+ fd = g_file_open_tmp ("gvfsobexftp-tmp-XXXXXX",
+ &target, &error);
+ if (fd < 0)
+ {
+ op_backend->doing_io = FALSE;
+ g_mutex_unlock (op_backend->mutex);
+ 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->doing_io = FALSE;
+ g_mutex_unlock (op_backend->mutex);
+ error = g_error_new_literal (G_IO_ERROR,
+ G_IO_ERROR_CANCELLED,
+ _("Operation was cancelled"));
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ return;
+ }
+
+ op_backend->status = ASYNC_PENDING;
+
+ 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);
+
+ /* Close the target */
+ g_unlink (target);
+ g_free (target);
+ close (fd);
+
+ op_backend->doing_io = FALSE;
+ g_mutex_unlock (op_backend->mutex);
+ return;
+ }
+
+ /* Wait for TransferStarted or ErrorOccurred to have happened */
+ while (op_backend->status == ASYNC_PENDING)
+ g_cond_wait (op_backend->cond, op_backend->mutex);
+ success = op_backend->status;
+ dbus_g_proxy_disconnect_signal(op_backend->session_proxy, "TransferStarted",
+ G_CALLBACK(transfer_started_cb), op_backend);
+
+ /* We either got success or an async error */
+ g_assert (success != ASYNC_PENDING);
+
+ g_message ("filename: %s (%s) copying to %s (retval %d)", filename, basename, target, success);
+ g_free (basename);
+
+ g_unlink (target);
+ g_free (target);
+ op_backend->status = ASYNC_PENDING;
+
+ if (success == ASYNC_ERROR)
+ {
+ op_backend->doing_io = FALSE;
+ g_mutex_unlock (op_backend->mutex);
+ g_free (basename);
+ close (fd);
+ g_vfs_job_failed_from_error (G_VFS_JOB (job),
+ op_backend->error);
+ g_error_free (op_backend->error);
+ op_backend->error = NULL;
+ return;
+ }
+
+ handle = g_new0 (ObexFTPOpenHandle, 1);
+ handle->source = g_strdup (filename);
+ handle->fd = fd;
+ handle->size = size;
+ g_vfs_job_open_for_read_set_handle (job, handle);
+
+ g_print ("- do_open_for_read, filename: %s\n", filename);
+
+ g_vfs_job_open_for_read_set_can_seek (G_VFS_JOB_OPEN_FOR_READ (job), FALSE);
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+
+ op_backend->doing_io = FALSE;
+ g_mutex_unlock (op_backend->mutex);
+}
+
+static int
+is_busy (DBusGProxy *session_proxy, GVfsJob *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 (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;
+
+ 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)))
+ {
+ GError *error;
+
+ error = g_error_new_literal (G_IO_ERROR,
+ G_IO_ERROR_CANCELLED,
+ _("Operation was cancelled"));
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ return;
+ }
+
+ busy = is_busy (op_backend->session_proxy, G_VFS_JOB (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_close_read (GVfsBackend *backend,
+ GVfsJobCloseRead *job,
+ GVfsBackendHandle handle)
+{
+ GVfsBackendObexftp *op_backend = G_VFS_BACKEND_OBEXFTP (backend);
+ ObexFTPOpenHandle *backend_handle = (ObexFTPOpenHandle *) handle;
+ int busy;
+
+ g_print ("+ do_close_read\n");
+
+ busy = is_busy (op_backend->session_proxy, G_VFS_JOB (job));
+ if (busy < 0) {
+ g_message ("busy error");
+ return;
+ }
+
+ g_mutex_lock (op_backend->mutex);
+
+ if (busy > 0)
+ {
+ op_backend->status = ASYNC_PENDING;
+
+ if (dbus_g_proxy_call (op_backend->session_proxy, "Cancel", NULL,
+ G_TYPE_INVALID, G_TYPE_INVALID) != FALSE)
+ {
+ while (op_backend->status == ASYNC_PENDING)
+ g_cond_wait (op_backend->cond, op_backend->mutex);
+ }
+ }
+
+ g_mutex_unlock (op_backend->mutex);
+
+ close (backend_handle->fd);
+ g_free (backend_handle->source);
+ g_free (backend_handle);
+
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+
+ g_print ("- do_close_read\n");
+}
+
+static void
+do_query_info (GVfsBackend *backend,
+ GVfsJobQueryInfo *job,
+ const char *filename,
+ GFileQueryInfoFlags flags,
+ GFileInfo *info,
+ GFileAttributeMatcher *matcher)
+{
+ GVfsBackendObexftp *op_backend = G_VFS_BACKEND_OBEXFTP (backend);
+ GError *error = NULL;
+
+ g_print ("+ do_query_info, filename: %s\n", filename);
+
+ g_mutex_lock (op_backend->mutex);
+
+ if (_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_mutex_unlock (op_backend->mutex);
+
+ 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);
+
+ g_mutex_lock (op_backend->mutex);
+
+ /* 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_mutex_unlock (op_backend->mutex);
+ 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)))
+ {
+ g_mutex_unlock (op_backend->mutex);
+ error = g_error_new_literal (G_IO_ERROR,
+ G_IO_ERROR_CANCELLED,
+ _("Operation was cancelled"));
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ g_free (caps_str);
+ return;
+ }
+
+ /* No caps from the server? */
+ if (caps_str == NULL)
+ {
+ /* Best effort, don't error out */
+ g_mutex_unlock (op_backend->mutex);
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+ return;
+ }
+
+ caps = ovu_caps_parser_parse (caps_str, strlen (caps_str), &error);
+ g_free (caps_str);
+ if (caps == NULL)
+ {
+ g_mutex_unlock (op_backend->mutex);
+ 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)
+ {
+ g_mutex_unlock (op_backend->mutex);
+ /* Best effort, don't error out */
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+ 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 (_query_file_info_helper (backend, filename, info, &error) == FALSE)
+ {
+ g_mutex_unlock (op_backend->mutex);
+ 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)))
+ {
+ g_mutex_unlock (op_backend->mutex);
+ error = g_error_new_literal (G_IO_ERROR,
+ G_IO_ERROR_CANCELLED,
+ _("Operation was cancelled"));
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ 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_mutex_unlock (op_backend->mutex);
+
+ 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);
+
+ g_mutex_lock (op_backend->mutex);
+
+ if (_change_directory (op_backend, filename, &error) == FALSE)
+ {
+ g_mutex_unlock (op_backend->mutex);
+ 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_mutex_unlock (op_backend->mutex);
+ 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_mutex_unlock (op_backend->mutex);
+ 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_mutex_unlock (op_backend->mutex);
+
+ g_print ("- do_enumerate\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);
+
+ g_mutex_lock (op_backend->mutex);
+
+ /* Check whether we have a directory */
+ info = g_file_info_new ();
+ if (_query_file_info_helper (backend, filename, info, &error) == FALSE)
+ {
+ g_mutex_unlock (op_backend->mutex);
+ 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)))
+ {
+ g_mutex_unlock (op_backend->mutex);
+ error = g_error_new_literal (G_IO_ERROR,
+ G_IO_ERROR_CANCELLED,
+ _("Operation was cancelled"));
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ 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 (_change_directory (op_backend, filename, &error) == FALSE)
+ {
+ g_mutex_unlock (op_backend->mutex);
+ 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)))
+ {
+ g_mutex_unlock (op_backend->mutex);
+ error = g_error_new_literal (G_IO_ERROR,
+ G_IO_ERROR_CANCELLED,
+ _("Operation was cancelled"));
+ 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_mutex_unlock (op_backend->mutex);
+ 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_mutex_unlock (op_backend->mutex);
+ 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_mutex_unlock (op_backend->mutex);
+ 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_mutex_unlock (op_backend->mutex);
+ 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)))
+ {
+ g_mutex_unlock (op_backend->mutex);
+ error = g_error_new_literal (G_IO_ERROR,
+ G_IO_ERROR_CANCELLED,
+ _("Operation was cancelled"));
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ g_free (basename);
+ return;
+ }
+
+ parent = g_path_get_dirname (filename);
+ if (_change_directory (op_backend, parent, &error) == FALSE)
+ {
+ g_mutex_unlock (op_backend->mutex);
+ 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)))
+ {
+ g_mutex_unlock (op_backend->mutex);
+ error = g_error_new_literal (G_IO_ERROR,
+ G_IO_ERROR_CANCELLED,
+ _("Operation was cancelled"));
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ 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_mutex_unlock (op_backend->mutex);
+ 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_mutex_unlock (op_backend->mutex);
+
+ 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);
+
+ g_mutex_lock (op_backend->mutex);
+
+ /* Check if the folder already exists */
+ info = g_file_info_new ();
+ if (_query_file_info_helper (backend, filename, info, &error) != FALSE)
+ {
+ g_mutex_unlock (op_backend->mutex);
+ 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_mutex_unlock (op_backend->mutex);
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ return;
+ }
+
+ if (g_vfs_job_is_cancelled (G_VFS_JOB (job)))
+ {
+ g_mutex_unlock (op_backend->mutex);
+ error = g_error_new_literal (G_IO_ERROR,
+ G_IO_ERROR_CANCELLED,
+ _("Operation was cancelled"));
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ return;
+ }
+
+ parent = g_path_get_dirname (filename);
+ if (_change_directory (op_backend, parent, &error) == FALSE)
+ {
+ g_mutex_unlock (op_backend->mutex);
+ 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)))
+ {
+ g_mutex_unlock (op_backend->mutex);
+ error = g_error_new_literal (G_IO_ERROR,
+ G_IO_ERROR_CANCELLED,
+ _("Operation was cancelled"));
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ 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_mutex_unlock (op_backend->mutex);
+ 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_mutex_unlock (op_backend->mutex);
+
+ g_print ("+ do_make_directory\n");
+}
+
+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->mount = do_mount;
+ backend_class->open_for_read = do_open_for_read;
+ backend_class->read = do_read;
+ backend_class->close_read = do_close_read;
+ backend_class->query_info = do_query_info;
+ backend_class->query_fs_info = do_query_fs_info;
+ backend_class->enumerate = do_enumerate;
+ backend_class->delete = do_delete;
+ backend_class->make_directory = do_make_directory;
+
+ /* ErrorOccurred */
+ 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
+ */
diff --git a/daemon/gvfsbackendobexftp.h b/daemon/gvfsbackendobexftp.h
new file mode 100644
index 00000000..434749bb
--- /dev/null
+++ b/daemon/gvfsbackendobexftp.h
@@ -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__ */
diff --git a/daemon/obexftp-marshal.list b/daemon/obexftp-marshal.list
new file mode 100644
index 00000000..985a3218
--- /dev/null
+++ b/daemon/obexftp-marshal.list
@@ -0,0 +1,2 @@
+VOID:STRING,STRING
+VOID:STRING,STRING,UINT64
diff --git a/daemon/obexftp.mount.in b/daemon/obexftp.mount.in
new file mode 100644
index 00000000..96ade114
--- /dev/null
+++ b/daemon/obexftp.mount.in
@@ -0,0 +1,4 @@
+[Mount]
+Type=obex
+Exec=@libexecdir@/gvfsd-obexftp
+AutoMount=false
diff --git a/po/ChangeLog b/po/ChangeLog
index 45245345..5b2379c6 100644
--- a/po/ChangeLog
+++ b/po/ChangeLog
@@ -1,3 +1,8 @@
+2008-02-27 Bastien Nocera <hadess@hadess.net>
+
+ * POTFILES.in: update for the obexftp backend, no
+ new strings though
+
2008-02-27 Inaki Larranaga Murgoitio <dooteo@euskalgnu.org>
* eu.po: Updated Basque translation.
diff --git a/po/POTFILES.in b/po/POTFILES.in
index a1fa4d98..3a1985e5 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -21,6 +21,7 @@ daemon/gvfsbackendgphoto2.c
daemon/gvfsbackendhttp.c
daemon/gvfsbackendlocaltest.c
daemon/gvfsbackendnetwork.c
+daemon/gvfsbackendobexftp.c
daemon/gvfsbackendsftp.c
daemon/gvfsbackendsmb.c
daemon/gvfsbackendsmbbrowse.c