[Telepathy-commits] [telepathy-salut/master] Added Marco's file transfer code.

Jonny Lamb jonny.lamb at collabora.co.uk
Fri Nov 21 03:45:57 PST 2008


20080715185636-8ed0e-06775a31a2c94b716db41375af32db8248b9e8d3.gz
---
 configure.ac                              |    7 +-
 extensions/Channel_Type_File_Transfer.xml |   52 ++-
 lib/gibber/Makefile.am                    |   28 +-
 lib/gibber/gibber-debug.c                 |    1 +
 lib/gibber/gibber-debug.h                 |    1 +
 lib/gibber/gibber-file-transfer.c         |  365 +++++++++++
 lib/gibber/gibber-file-transfer.h         |  127 ++++
 lib/gibber/gibber-namespaces.h            |    6 +
 lib/gibber/gibber-oob-file-transfer.c     |  781 +++++++++++++++++++++++
 lib/gibber/gibber-oob-file-transfer.h     |   70 +++
 src/Makefile.am                           |    8 +-
 src/debug.c                               |    1 +
 src/debug.h                               |    1 +
 src/file-transfer-mixin.c                 |  696 +++++++++++++++++++++
 src/file-transfer-mixin.h                 |  130 ++++
 src/salut-connection.c                    |    8 +
 src/salut-ft-channel.c                    |  953 +++++++++++++++++++++++++++++
 src/salut-ft-channel.h                    |   75 +++
 src/salut-ft-manager.c                    |  407 ++++++++++++
 src/salut-ft-manager.h                    |   64 ++
 src/salut.c                               |    1 +
 21 files changed, 3772 insertions(+), 10 deletions(-)
 create mode 100644 lib/gibber/gibber-file-transfer.c
 create mode 100644 lib/gibber/gibber-file-transfer.h
 create mode 100644 lib/gibber/gibber-oob-file-transfer.c
 create mode 100644 lib/gibber/gibber-oob-file-transfer.h
 create mode 100644 src/file-transfer-mixin.c
 create mode 100644 src/file-transfer-mixin.h
 create mode 100644 src/salut-ft-channel.c
 create mode 100644 src/salut-ft-channel.h
 create mode 100644 src/salut-ft-manager.c
 create mode 100644 src/salut-ft-manager.h

diff --git a/configure.ac b/configure.ac
index 28d71af..4c4c60b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -136,7 +136,7 @@ dnl GTK docs
 GTK_DOC_CHECK
 
 dnl Check for Glib
-PKG_CHECK_MODULES(GLIB, [glib-2.0 >= 2.16, gobject-2.0 >= 2.16])
+PKG_CHECK_MODULES(GLIB, [glib-2.0 >= 2.16, gobject-2.0 >= 2.16, gthread-2.0 >= 2.4])
 
 AC_SUBST(GLIB_CFLAGS)
 AC_SUBST(GLIB_LIBS)
@@ -236,6 +236,11 @@ PKG_CHECK_MODULES(AVAHI, [ avahi-gobject ])
 AC_SUBST(AVAHI_CFLAGS)
 AC_SUBST(AVAHI_LIBS)
 
+dnl Check for libsoup
+PKG_CHECK_MODULES(LIBSOUP, [libsoup-2.2])
+AC_SUBST(AVAHI_CFLAGS)
+AC_SUBST(AVAHI_LIBS)
+
 dnl glibc backtrace functions
 AC_CHECK_FUNCS(backtrace backtrace_symbols_fd)
 AC_CHECK_HEADERS(execinfo.h)
diff --git a/extensions/Channel_Type_File_Transfer.xml b/extensions/Channel_Type_File_Transfer.xml
index 3d6a8c4..5593d11 100644
--- a/extensions/Channel_Type_File_Transfer.xml
+++ b/extensions/Channel_Type_File_Transfer.xml
@@ -12,7 +12,7 @@ version 2.1 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.
+Library 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
@@ -63,6 +63,34 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
       </tp:enumvalue>
     </tp:enum>
 
+    <tp:enum name="FileTransfer_CloseReason">
+      <tp:enumvalue suffix="Success" value="0">
+        <tp:docstring>
+          The file transfer was completed successfully.
+        </tp:docstring>
+      </tp:enumvalue>
+      <tp:enumvalue suffix="LocalStopped" value="1">
+        <tp:docstring>
+          The file transfer was closed by the local user.
+        </tp:docstring>
+      </tp:enumvalue>
+      <tp:enumvalue suffix="RemoteStopped" value="2">
+        <tp:docstring>
+          The file transfer was closed by the remote user.
+        </tp:docstring>
+      </tp:enumvalue>
+      <tp:enumvalue suffix="LocalError" value="3">
+        <tp:docstring>
+          The file transfer was closed because of a local error.
+        </tp:docstring>
+      </tp:enumvalue>
+      <tp:enumvalue suffix="RemoteError" value="4">
+        <tp:docstring>
+          The file transfer was closed because of a remote error.
+        </tp:docstring>
+      </tp:enumvalue>
+    </tp:enum>
+
 
     <method name="ListFileTransfers">
       <arg direction="out" type="a(uuuusa{sv})">
@@ -126,6 +154,11 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
           FileTransfer_Direction enum.
         </tp:docstring>
       </arg>
+      <arg name="state" type="u">
+        <tp:docstring>
+          The new file transfer's state.
+        </tp:docstring>
+      </arg>
       <arg name="filename" type="s">
         <tp:docstring>
           The filename of the file that is to be transmitted, for displaying.
@@ -136,11 +169,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
           The new file's additional information.
         </tp:docstring>
       </arg>
-      <arg name="state" type="u">
-        <tp:docstring>
-          The new file transfer's state.
-        </tp:docstring>
-      </arg>
     </signal>
 
     <method name="AcceptFile">
@@ -180,6 +208,12 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
           The ID of the file transfer to close.
         </tp:docstring>
       </arg>
+      <arg direction="in" name="reason" type="u">
+        <tp:docstring>
+          The reason why the file transfer is being closed; see the
+          FileTransfer_CloseReason enumeration.
+        </tp:docstring>
+      </arg>
     </method>
 
     <signal name="FileTransferClosed">
@@ -193,6 +227,12 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
           The ID of the file transfer that was closed.
         </tp:docstring>
       </arg>
+      <arg name="reason" type="u">
+        <tp:docstring>
+          The reason why the file transfer was closed; see the
+          FileTransfer_CloseReason enumeration.
+        </tp:docstring>
+      </arg>
     </signal>
 
     <method name="GetLocalUnixSocketPath">
diff --git a/lib/gibber/Makefile.am b/lib/gibber/Makefile.am
index 7f7c2a0..a72e48d 100644
--- a/lib/gibber/Makefile.am
+++ b/lib/gibber/Makefile.am
@@ -3,6 +3,8 @@ SUBDIRS=examples
 noinst_LTLIBRARIES = libgibber.la
 
 BUILT_SOURCES = \
+  gibber-file-transfer-enumtypes.c \
+  gibber-file-transfer-enumtypes.h \
   signals-marshal.list \
   signals-marshal.h \
   signals-marshal.c
@@ -56,6 +58,10 @@ HANDWRITTEN_SOURCES =             \
   gibber-namespaces.h             \
   gibber-iq-helper.c              \
   gibber-iq-helper.h              \
+  gibber-file-transfer.c          \
+  gibber-file-transfer.h          \
+  gibber-oob-file-transfer.c      \
+  gibber-oob-file-transfer.h      \
   gibber-xmpp-connection-listener.c \
   gibber-xmpp-connection-listener.h \
   gibber-xmpp-error.h             \
@@ -105,7 +111,7 @@ signals-marshal.c: signals-marshal.list
 	glib-genmarshal --body --prefix=_gibber_signals_marshal $< > $@
 
 
-AM_CFLAGS = $(ERROR_CFLAGS) $(GCOV_CFLAGS) @GLIB_CFLAGS@ @LIBXML2_CFLAGS@
+AM_CFLAGS = $(ERROR_CFLAGS) $(GCOV_CFLAGS) @GLIB_CFLAGS@ @LIBXML2_CFLAGS@ @LIBSOUP_CFLAGS@
 
 if HAVE_LIBSSL
   AM_CFLAGS += @LIBSSL_CFLAGS@
@@ -115,7 +121,7 @@ if HAVE_LIBASYNCNS
   AM_CFLAGS += @LIBASYNCNS_CFLAGS@
 endif
 
-AM_LDFLAGS = $(GCOV_LIBS) @GLIB_LIBS@ @LIBXML2_LIBS@ @RESOLV_LIBS@
+AM_LDFLAGS = $(GCOV_LIBS) @GLIB_LIBS@ @LIBXML2_LIBS@ @RESOLV_LIBS@ @LIBSOUP_LIBS@
 
 if HAVE_LIBSSL
   AM_LDFLAGS += @LIBSSL_LIBS@
@@ -124,3 +130,21 @@ endif
 if HAVE_LIBASYNCNS
   AM_LDFLAGS += @LIBASYNCNS_LIBS@
 endif
+
+# rules for making the glib enum objects
+%-enumtypes.h: %.h Makefile.in
+	glib-mkenums \
+		--fhead "#ifndef __$(shell echo $* | tr [:lower:]- [:upper:]_)_ENUM_TYPES_H__\n#define __$(shell echo $* | tr [:lower:]- [:upper:]_)_ENUM_TYPES_H__\n\n#include <glib-object.h>\n\nG_BEGIN_DECLS\n" \
+		--fprod "/* enumerations from \"@filename@\" */\n" \
+		--vhead "GType @enum_name at _get_type (void);\n#define $(shell echo $* | tr [:lower:]- [:upper:]_ | sed 's/_.*//')_TYPE_ at ENUMSHORT@ (@enum_name at _get_type())\n"         \
+		--ftail "G_END_DECLS\n\n#endif /* __$(shell echo $* | tr [:lower:]- [:upper:]_)_ENUM_TYPES_H__ */" \
+		$< > $@
+
+%-enumtypes.c: %.h Makefile.in
+	glib-mkenums \
+		--fhead "#include <$*.h>" \
+		--fprod "\n/* enumerations from \"@filename@\" */" \
+		--vhead "GType\n at enum_name@_get_type (void)\n{\n  static GType etype = 0;\n  if (etype == 0) {\n    static const G at Type@Value values[] = {"     \
+		--vprod "      { @VALUENAME@, \"@VALUENAME@\", \"@VALUENAME@\" }," \
+		--vtail "      { 0, NULL, NULL }\n    };\n    etype = g_ at type@_register_static (\"@EnumName@\", values);\n  }\n  return etype;\n}\n" \
+		$< > $@
diff --git a/lib/gibber/gibber-debug.c b/lib/gibber/gibber-debug.c
index 870b220..df01ade 100644
--- a/lib/gibber/gibber-debug.c
+++ b/lib/gibber/gibber-debug.c
@@ -20,6 +20,7 @@ static GDebugKey keys[] = {
   { "rmulticast-sender", DEBUG_RMULTICAST_SENDER },
   { "muc-connection",    DEBUG_MUC_CONNECTION    },
   { "bytestream",        DEBUG_BYTESTREAM        },
+  { "ft",                DEBUG_FILE_TRANSFER     },
   { "all",               ~0                      },
   { 0, },
 };
diff --git a/lib/gibber/gibber-debug.h b/lib/gibber/gibber-debug.h
index cf3ee5f..94a9c4f 100644
--- a/lib/gibber/gibber-debug.h
+++ b/lib/gibber/gibber-debug.h
@@ -24,6 +24,7 @@ typedef enum
   DEBUG_RMULTICAST_SENDER = 1 << 7,
   DEBUG_MUC_CONNECTION    = 1 << 8,
   DEBUG_BYTESTREAM        = 1 << 9,
+  DEBUG_FILE_TRANSFER     = 1 << 10,
 } DebugFlags;
 
 #define DEBUG_XMPP (DEBUG_XMPP_READER | DEBUG_XMPP_WRITER)
diff --git a/lib/gibber/gibber-file-transfer.c b/lib/gibber/gibber-file-transfer.c
new file mode 100644
index 0000000..ac469fb
--- /dev/null
+++ b/lib/gibber/gibber-file-transfer.c
@@ -0,0 +1,365 @@
+/*
+ * gibber-file-transfer.c - Source for GibberFileTransfer
+ * Copyright (C) 2007 Marco Barisione <marco at barisione.org>
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "gibber-file-transfer.h"
+#include "gibber-oob-file-transfer.h"
+
+#define DEBUG_FLAG DEBUG_FILE_TRANSFER
+#include "gibber-debug.h"
+
+#include "signals-marshal.h"
+#include "gibber-file-transfer-enumtypes.h"
+
+
+G_DEFINE_TYPE(GibberFileTransfer, gibber_file_transfer, G_TYPE_OBJECT)
+
+/* signal enum */
+enum
+{
+  REMOTE_ACCEPTED,
+  FINISHED,
+  ERROR,
+  LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+/* properties */
+enum
+{
+  PROP_ID = 1,
+  PROP_SELF_ID,
+  PROP_PEER_ID,
+  PROP_FILENAME,
+  PROP_DIRECTION,
+  PROP_CONNECTION,
+  LAST_PROPERTY
+};
+
+/* private structure */
+struct _GibberFileTransferPrivate
+{
+  GibberXmppConnection *connection;
+};
+
+
+GQuark
+gibber_file_transfer_error_quark (void)
+{
+  static GQuark error_quark = 0;
+
+  if (error_quark == 0)
+    error_quark =
+        g_quark_from_static_string ("gibber-file-transfer-error-quark");
+
+  return error_quark;
+}
+
+static void
+gibber_file_transfer_init (GibberFileTransfer *self)
+{
+  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GIBBER_TYPE_FILE_TRANSFER,
+      GibberFileTransferPrivate);
+}
+
+static void
+gibber_file_transfer_get_property (GObject *object,
+                                   guint property_id,
+                                   GValue *value,
+                                   GParamSpec *pspec)
+{
+  GibberFileTransfer *self = GIBBER_FILE_TRANSFER (object);
+
+  switch (property_id)
+    {
+      case PROP_ID:
+        g_value_set_string (value, self->id);
+        break;
+      case PROP_SELF_ID:
+        g_value_set_string (value, self->self_id);
+        break;
+      case PROP_PEER_ID:
+        g_value_set_string (value, self->peer_id);
+        break;
+      case PROP_FILENAME:
+        g_value_set_string (value, self->filename);
+        break;
+      case PROP_DIRECTION:
+        g_value_set_enum (value, self->direction);
+        break;
+      case PROP_CONNECTION:
+        g_value_set_object (value, self->priv->connection);
+        break;
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+        break;
+    }
+}
+
+static gchar *
+generate_id (void)
+{
+  static guint id_num = 0;
+
+  return g_strdup_printf ("gibber-file-transfer-%d", id_num++);
+}
+
+static void received_stanza_cb (GibberXmppConnection *conn,
+    GibberXmppStanza *stanza, gpointer user_data);
+
+static void
+gibber_file_transfer_set_property (GObject *object,
+                                   guint property_id,
+                                   const GValue *value,
+                                   GParamSpec *pspec)
+{
+  GibberFileTransfer *self = GIBBER_FILE_TRANSFER (object);
+
+  switch (property_id)
+    {
+      case PROP_ID:
+        self->id = g_value_dup_string (value);
+        if (self->id == NULL)
+          self->id = generate_id ();
+        break;
+      case PROP_SELF_ID:
+        self->self_id = g_value_dup_string (value);
+        if (self->self_id == NULL)
+          g_critical ("'self-id' cannot be NULL");
+        break;
+      case PROP_PEER_ID:
+        self->peer_id = g_value_dup_string (value);
+        if (self->peer_id == NULL)
+          g_critical ("'peer-id' cannot be NULL");
+        break;
+      case PROP_FILENAME:
+        self->filename = g_value_dup_string (value);
+        break;
+      case PROP_DIRECTION:
+        self->direction = g_value_get_enum (value);
+        break;
+      case PROP_CONNECTION:
+        self->priv->connection = g_value_get_object (value);
+        if (self->priv->connection != NULL)
+          g_signal_connect (self->priv->connection, "received-stanza",
+              G_CALLBACK (received_stanza_cb), self);
+        break;
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+        break;
+    }
+}
+
+static void gibber_file_transfer_finalize (GObject *object);
+
+static void
+gibber_file_transfer_class_init (GibberFileTransferClass *gibber_file_transfer_class)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (gibber_file_transfer_class);
+  GParamSpec *param_spec;
+
+  g_type_class_add_private (gibber_file_transfer_class, sizeof (GibberFileTransferPrivate));
+
+  object_class->finalize = gibber_file_transfer_finalize;
+
+  object_class->get_property = gibber_file_transfer_get_property;
+  object_class->set_property = gibber_file_transfer_set_property;
+
+  param_spec = g_param_spec_string ("id", "ID for the file transfer",
+      "The ID used tpo indentify the file transfer", NULL,
+      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+      G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
+  g_object_class_install_property (object_class, PROP_ID, param_spec);
+
+  param_spec = g_param_spec_string ("self-id",
+      "Self ID",
+      "The ID that identifies the local user", NULL,
+      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+      G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
+  g_object_class_install_property (object_class, PROP_SELF_ID, param_spec);
+
+  param_spec = g_param_spec_string ("peer-id",
+      "Peer ID",
+      "The ID that identifies the remote user", NULL,
+      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+      G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
+  g_object_class_install_property (object_class, PROP_PEER_ID, param_spec);
+
+  param_spec = g_param_spec_string ("filename",
+      "File name",
+      "The name of the transferred file", "new-file",
+      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+      G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
+  g_object_class_install_property (object_class, PROP_FILENAME, param_spec);
+
+  param_spec = g_param_spec_enum ("direction",
+      "Direction", "File transfer direction",
+      GIBBER_TYPE_FILE_TRANSFER_DIRECTION,
+      GIBBER_FILE_TRANSFER_DIRECTION_OUTGOING,
+      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+      G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
+  g_object_class_install_property (object_class, PROP_DIRECTION, param_spec);
+
+  param_spec = g_param_spec_object ("connection",
+      "GibberXmppConnection object", "Gibber Connection used to send stanzas",
+      GIBBER_TYPE_XMPP_CONNECTION,
+      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+      G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
+  g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
+
+  signals[REMOTE_ACCEPTED] = g_signal_new ("remote-accepted",
+      G_OBJECT_CLASS_TYPE (gibber_file_transfer_class),
+      G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+      0, NULL, NULL,
+      g_cclosure_marshal_VOID__VOID,
+      G_TYPE_NONE, 0);
+
+  signals[FINISHED] = g_signal_new ("finished",
+      G_OBJECT_CLASS_TYPE (gibber_file_transfer_class),
+      G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+      0, NULL, NULL,
+      g_cclosure_marshal_VOID__VOID,
+      G_TYPE_NONE, 0);
+
+  signals[ERROR] = g_signal_new ("error",
+      G_OBJECT_CLASS_TYPE (gibber_file_transfer_class),
+      G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+      0, NULL, NULL,
+      _gibber_signals_marshal_VOID__UINT_INT_STRING,
+      G_TYPE_NONE, 3, G_TYPE_UINT, G_TYPE_INT, G_TYPE_STRING);
+}
+
+static void
+gibber_file_transfer_finalize (GObject *object)
+{
+  GibberFileTransfer *self = GIBBER_FILE_TRANSFER (object);
+
+  g_free (self->id);
+  g_free (self->self_id);
+  g_free (self->peer_id);
+  g_free (self->filename);
+
+  G_OBJECT_CLASS (gibber_file_transfer_parent_class)->finalize (object);
+}
+
+static void
+received_stanza_cb (GibberXmppConnection *conn,
+                    GibberXmppStanza *stanza,
+                    gpointer user_data)
+{
+  GibberFileTransfer *self = user_data;
+  const gchar *id;
+
+  id = gibber_xmpp_node_get_attribute (stanza->node, "id");
+  if (id != NULL && strcmp (id, self->id) == 0)
+    GIBBER_FILE_TRANSFER_GET_CLASS (self)->received_stanza (self, stanza);
+}
+
+gboolean
+gibber_file_transfer_is_file_offer (GibberXmppStanza *stanza)
+{
+  /* FIXME put the known backends in a list and stop when the first one
+   * can handle the stanza */
+  return gibber_oob_file_transfer_is_file_offer (stanza);
+}
+
+GibberFileTransfer *
+gibber_file_transfer_new_from_stanza (GibberXmppStanza *stanza,
+                                      GibberXmppConnection *connection)
+{
+  /* FIXME put the known backends in a list and stop when the first one
+   * can handle the stanza */
+  GibberFileTransfer *ft;
+
+  ft = gibber_oob_file_transfer_new_from_stanza (stanza, connection);
+  /* it's not possible to have an outgoing transfer created from
+   * a stanza */
+  g_assert (ft == NULL ||
+      ft->direction == GIBBER_FILE_TRANSFER_DIRECTION_INCOMING);
+
+  return ft;
+}
+
+void
+gibber_file_transfer_offer (GibberFileTransfer *self)
+{
+  GibberFileTransferClass *cls = GIBBER_FILE_TRANSFER_GET_CLASS (self);
+
+  g_return_if_fail (self->direction ==
+      GIBBER_FILE_TRANSFER_DIRECTION_OUTGOING);
+
+  cls->offer (self);
+}
+
+void
+gibber_file_transfer_send (GibberFileTransfer *self,
+                           GIOChannel *src)
+{
+  GibberFileTransferClass *cls = GIBBER_FILE_TRANSFER_GET_CLASS (self);
+
+  g_return_if_fail (self->direction ==
+      GIBBER_FILE_TRANSFER_DIRECTION_OUTGOING);
+
+  g_io_channel_set_buffered (src, FALSE);
+  cls->send (self, src);
+}
+
+void
+gibber_file_transfer_receive (GibberFileTransfer *self,
+                              GIOChannel *dest)
+{
+  GibberFileTransferClass *cls = GIBBER_FILE_TRANSFER_GET_CLASS (self);
+
+  g_return_if_fail (self->direction ==
+      GIBBER_FILE_TRANSFER_DIRECTION_INCOMING);
+
+  g_io_channel_set_buffered (dest, FALSE);
+  cls->receive (self, dest);
+}
+
+void
+gibber_file_transfer_cancel (GibberFileTransfer *self,
+                             guint error_code)
+{
+  GibberFileTransferClass *cls = GIBBER_FILE_TRANSFER_GET_CLASS (self);
+
+  cls->cancel (self, error_code);
+}
+
+void 
+gibber_file_transfer_emit_error (GibberFileTransfer *self,
+                                 GError *error)
+{
+  DEBUG("File transfer error: %s", error->message);
+  g_signal_emit(self, signals[ERROR], 0, error->domain, error->code,
+      error->message);
+}
+
+gboolean
+gibber_file_transfer_send_stanza (GibberFileTransfer *self,
+                                  GibberXmppStanza *stanza,
+                                  GError **error)
+{
+  return gibber_xmpp_connection_send (self->priv->connection, stanza, error);
+}
diff --git a/lib/gibber/gibber-file-transfer.h b/lib/gibber/gibber-file-transfer.h
new file mode 100644
index 0000000..fafbe43
--- /dev/null
+++ b/lib/gibber/gibber-file-transfer.h
@@ -0,0 +1,127 @@
+/*
+ * gibber-file-transfer.h - Header for GibberFileTransfer
+ * Copyright (C) 2007 Marco Barisione <marco at barisione.org>
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef __GIBBER_FILE_TRANSFER_H__
+#define __GIBBER_FILE_TRANSFER_H__
+
+#include <glib.h>
+#include <glib-object.h>
+#include "gibber-xmpp-stanza.h"
+#include "gibber-xmpp-connection.h"
+
+G_BEGIN_DECLS
+
+typedef enum
+{
+  GIBBER_FILE_TRANSFER_DIRECTION_INCOMING,
+  GIBBER_FILE_TRANSFER_DIRECTION_OUTGOING
+} GibberFileTransferDirection;
+
+typedef enum
+{
+  GIBBER_FILE_TRANSFER_ERROR_NOT_CONNECTED,
+  GIBBER_FILE_TRANSFER_ERROR_NOT_FOUND,
+  GIBBER_FILE_TRANSFER_ERROR_NOT_ACCEPTABLE
+} GibberFileTransferError;
+
+#define GIBBER_FILE_TRANSFER_ERROR gibber_file_transfer_error_quark ()
+
+GQuark gibber_file_transfer_error_quark (void);
+
+typedef struct _GibberFileTransfer GibberFileTransfer;
+typedef struct _GibberFileTransferClass GibberFileTransferClass;
+
+struct _GibberFileTransferClass
+{
+  GObjectClass parent_class;
+
+  void (*offer) (GibberFileTransfer *ft);
+  void (*send) (GibberFileTransfer *ft,
+                GIOChannel *src);
+  void (*receive) (GibberFileTransfer *ft,
+                   GIOChannel *dest);
+  void (*cancel) (GibberFileTransfer *ft,
+                  guint error_code);
+  void (*received_stanza) (GibberFileTransfer *ft,
+                           GibberXmppStanza *stanza);
+};
+
+typedef struct _GibberFileTransferPrivate GibberFileTransferPrivate;
+
+struct _GibberFileTransfer
+{
+  GObject parent;
+
+  GibberFileTransferPrivate *priv;
+
+  /*< public >*/
+  gchar *id;
+
+  gchar *self_id;
+  gchar *peer_id;
+
+  gchar *filename;
+
+  GibberFileTransferDirection direction;
+
+  guint64 size;
+};
+
+GType gibber_file_transfer_get_type(void);
+
+/* TYPE MACROS */
+#define GIBBER_TYPE_FILE_TRANSFER \
+  (gibber_file_transfer_get_type ())
+#define GIBBER_FILE_TRANSFER(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIBBER_TYPE_FILE_TRANSFER, \
+                               GibberFileTransfer))
+#define GIBBER_FILE_TRANSFER_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST ((klass), GIBBER_TYPE_FILE_TRANSFER, \
+                            GibberFileTransferClass))
+#define GIBBER_IS_FILE_TRANSFER (obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIBBER_TYPE_FILE_TRANSFER))
+#define GIBBER_IS_FILE_TRANSFER_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE ((klass), GIBBER_TYPE_FILE_TRANSFER))
+#define GIBBER_FILE_TRANSFER_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), GIBBER_TYPE_FILE_TRANSFER, \
+                              GibberFileTransferClass))
+
+
+gboolean gibber_file_transfer_is_file_offer (GibberXmppStanza *stanza);
+GibberFileTransfer *gibber_file_transfer_new_from_stanza (
+    GibberXmppStanza *stanza, GibberXmppConnection *connection);
+
+void gibber_file_transfer_offer (GibberFileTransfer *self);
+void gibber_file_transfer_send (GibberFileTransfer *self, GIOChannel *src);
+void gibber_file_transfer_receive (GibberFileTransfer *self, GIOChannel *dest);
+void gibber_file_transfer_cancel (GibberFileTransfer *self, guint error_code);
+
+
+/* these functions should only be used by backends */
+/* FIXME move to a private header if gibber becomes a public library */
+
+gboolean gibber_file_transfer_send_stanza (GibberFileTransfer *self,
+    GibberXmppStanza *stanza, GError **error);
+
+void gibber_file_transfer_emit_error (GibberFileTransfer *self, GError *error);
+
+
+G_END_DECLS
+
+#endif /* #ifndef __GIBBER_FILE_TRANSFER_H__*/
diff --git a/lib/gibber/gibber-namespaces.h b/lib/gibber/gibber-namespaces.h
index e5ddf31..512bd84 100644
--- a/lib/gibber/gibber-namespaces.h
+++ b/lib/gibber/gibber-namespaces.h
@@ -50,4 +50,10 @@
 #define GIBBER_TELEPATHY_NS_CLIQUE \
   (const gchar *)"http://telepathy.freedesktop.org/xmpp/clique"
 
+#define GIBBER_XMPP_NS_OOB \
+  (const gchar *)"jabber:iq:oob"
+
+#define GIBBER_XMPP_NS_STANZAS \
+  (const gchar *)"urn:ietf:params:xml:ns:xmpp-stanzas"
+
 #endif /* #ifndef __GIBBER_NAMESPACES_H__ */
diff --git a/lib/gibber/gibber-oob-file-transfer.c b/lib/gibber/gibber-oob-file-transfer.c
new file mode 100644
index 0000000..6f2b2c5
--- /dev/null
+++ b/lib/gibber/gibber-oob-file-transfer.c
@@ -0,0 +1,781 @@
+/*
+ * gibber-oob-file-transfer.c - Source for GibberOobFileTransfer
+ * Copyright (C) 2007 Marco Barisione <marco at barisione.org>
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <libsoup/soup.h>
+#include <libsoup/soup-server.h>
+#include <libsoup/soup-server-message.h>
+
+#include "gibber-xmpp-stanza.h"
+#include "gibber-oob-file-transfer.h"
+#include "gibber-fd-transport.h"
+#include "gibber-namespaces.h"
+
+#define DEBUG_FLAG DEBUG_FILE_TRANSFER
+#include "gibber-debug.h"
+
+
+G_DEFINE_TYPE(GibberOobFileTransfer, gibber_oob_file_transfer, GIBBER_TYPE_FILE_TRANSFER)
+
+/* private structure */
+struct _GibberOobFileTransferPrivate
+{
+  /* HTTP server used to send files (only when sending files) */
+  SoupServer *server;
+  /* object used to send file chunks (only when sending files) */
+  SoupMessage *msg;
+  /* The unescaped served path passed to libsoup, i.e.
+   * "/salut-ft-12/hello world" (only when sending files) */
+  gchar *served_name;
+  /* The full escaped URL, such as
+   * "http://192.168.1.2/salut-ft-12/hello%20world" */
+  gchar *url;
+  /* Input/output channel */
+  GIOChannel *channel;
+};
+
+static void
+gibber_oob_file_transfer_init (GibberOobFileTransfer *self)
+{
+  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+      GIBBER_TYPE_OOB_FILE_TRANSFER, GibberOobFileTransferPrivate);
+}
+
+static void gibber_oob_file_transfer_finalize (GObject *object);
+static void gibber_oob_file_transfer_offer (GibberFileTransfer *ft);
+static void gibber_oob_file_transfer_send (GibberFileTransfer *ft,
+    GIOChannel *src);
+static void gibber_oob_file_transfer_receive (GibberFileTransfer *ft,
+    GIOChannel *dest);
+static void gibber_oob_file_transfer_cancel (GibberFileTransfer *ft,
+    guint error_code);
+static void gibber_oob_file_transfer_received_stanza (GibberFileTransfer *ft,
+    GibberXmppStanza *stanza);
+
+static void
+gibber_oob_file_transfer_class_init (GibberOobFileTransferClass *gibber_oob_file_transfer_class)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (gibber_oob_file_transfer_class);
+  GibberFileTransferClass *ft_class =
+    GIBBER_FILE_TRANSFER_CLASS(gibber_oob_file_transfer_class);
+
+  g_type_class_add_private (gibber_oob_file_transfer_class,
+      sizeof (GibberOobFileTransferPrivate));
+
+  object_class->finalize = gibber_oob_file_transfer_finalize;
+
+  ft_class->offer = gibber_oob_file_transfer_offer;
+  ft_class->send = gibber_oob_file_transfer_send;
+  ft_class->receive = gibber_oob_file_transfer_receive;
+  ft_class->cancel = gibber_oob_file_transfer_cancel;
+  ft_class->received_stanza = gibber_oob_file_transfer_received_stanza;
+}
+
+static void
+gibber_oob_file_transfer_finalize (GObject *object)
+{
+  GibberOobFileTransfer *self = GIBBER_OOB_FILE_TRANSFER (object);
+
+  if (self->priv->server != NULL)
+    g_object_unref (G_OBJECT (self->priv->server));
+
+  if (self->priv->msg)
+    g_object_unref (G_OBJECT (self->priv->msg));
+
+  g_free (self->priv->served_name);
+  g_free (self->priv->url);
+
+  G_OBJECT_CLASS (gibber_oob_file_transfer_parent_class)->finalize (object);
+}
+
+gboolean
+gibber_oob_file_transfer_is_file_offer (GibberXmppStanza *stanza)
+{
+  GibberStanzaType type;
+  GibberStanzaSubType sub_type;
+  GibberXmppNode *query;
+  GibberXmppNode *url;
+
+  gibber_xmpp_stanza_get_type_info (stanza, &type, &sub_type);
+  if (type != GIBBER_STANZA_TYPE_IQ ||
+      sub_type != GIBBER_STANZA_SUB_TYPE_SET)
+    {
+      return FALSE;
+    }
+
+  query = gibber_xmpp_node_get_child (stanza->node, "query");
+  if (query == NULL)
+    return FALSE;
+
+  url = gibber_xmpp_node_get_child (query, "url");
+  if (url == NULL || url->content == NULL || strcmp (url->content, "") == 0)
+    return FALSE;
+
+  return TRUE;
+}
+
+static gchar *
+escape_filename (const char *unescaped);
+static gchar *
+unescape_filename (const char *escaped);
+
+GibberFileTransfer *
+gibber_oob_file_transfer_new_from_stanza (GibberXmppStanza *stanza,
+                                          GibberXmppConnection *connection)
+{
+  GibberOobFileTransfer *self;
+  GibberXmppNode *query;
+  GibberXmppNode *url_node;
+  const gchar *self_id;
+  const gchar *peer_id;
+  const gchar *type;
+  const gchar *id;
+  const gchar *size;
+  gchar *url;
+  gchar *filename;
+
+  if (strcmp (stanza->node->name, "iq") != 0)
+    return NULL;
+
+  peer_id = gibber_xmpp_node_get_attribute (stanza->node, "from");
+  self_id = gibber_xmpp_node_get_attribute (stanza->node, "to");
+  if (peer_id == NULL || self_id == NULL)
+    return NULL;
+
+  type = gibber_xmpp_node_get_attribute (stanza->node, "type");
+  if (type == NULL || strcmp (type, "set") != 0)
+    return NULL;
+
+  id = gibber_xmpp_node_get_attribute (stanza->node, "id");
+  if (id == NULL)
+    return NULL;
+
+  query = gibber_xmpp_node_get_child (stanza->node, "query");
+  if (query == NULL)
+    return NULL;
+
+  url_node = gibber_xmpp_node_get_child (query, "url");
+  if (url_node == NULL || url_node->content == NULL)
+    return NULL;
+
+  /* The file name is extracted from the address */
+  url = g_strdup (url_node->content);
+  g_strstrip (url);
+  filename = g_strrstr (url, "/");
+  if (filename == NULL)
+    {
+      g_free (url);
+      return NULL;
+    }
+  filename++; /* move after the last "/" */
+  filename = unescape_filename (filename);
+ 
+  self = g_object_new (GIBBER_TYPE_OOB_FILE_TRANSFER,
+      "id", id,
+      "self-id", self_id,
+      "peer-id", peer_id,
+      "filename", filename,
+      "connection", connection,
+      "direction", GIBBER_FILE_TRANSFER_DIRECTION_INCOMING,
+      NULL);
+
+  size = gibber_xmpp_node_get_attribute (url_node, "size");
+  if (size != NULL)
+    GIBBER_FILE_TRANSFER (self)->size = g_ascii_strtoull (size, NULL, 0);
+
+  self->priv->url = url;
+
+  g_free (filename);
+
+  return GIBBER_FILE_TRANSFER (self);
+}
+
+/*
+ * Data received from the HTTP server.
+ */
+static void
+http_client_chunk_cb (SoupMessage *msg,
+                      gpointer user_data)
+{
+  GibberOobFileTransfer *self = user_data;
+
+  /* FIXME make async */
+  g_io_channel_write_chars (self->priv->channel, msg->response.body,
+      msg->response.length, NULL, NULL);
+}
+
+/*
+ * Received all the file from the HTTP server.
+ */
+static void
+http_client_finished_chunks_cb (SoupMessage *msg,
+                                gpointer user_data)
+{
+  GibberOobFileTransfer *self = user_data;
+  GibberXmppStanza *stanza;
+  GError *error = NULL;
+
+  /* disconnect from the "got_chunk" signal */
+  g_signal_handlers_disconnect_by_func (msg, http_client_chunk_cb, user_data);
+
+  DEBUG("Finished HTTP chunked file transfer");
+  g_io_channel_unref (self->priv->channel);
+  self->priv->channel = NULL;
+
+  if (msg->status_code != 200)
+    {
+      GError *error = NULL;
+      const gchar *reason_phrase;
+
+      if (msg->reason_phrase != NULL)
+        reason_phrase = msg->reason_phrase;
+      else
+        reason_phrase = "Unknown HTTP error";
+      g_set_error (&error, GIBBER_FILE_TRANSFER_ERROR,
+          GIBBER_FILE_TRANSFER_ERROR_NOT_FOUND, reason_phrase);
+      gibber_file_transfer_emit_error (GIBBER_FILE_TRANSFER (self), error);
+      return;
+    }
+
+  stanza = gibber_xmpp_stanza_new ("iq");
+  gibber_xmpp_node_set_attribute (stanza->node, "type", "result");
+  gibber_xmpp_node_set_attribute (stanza->node, "from",
+      GIBBER_FILE_TRANSFER (self)->self_id);
+  gibber_xmpp_node_set_attribute (stanza->node, "to",
+      GIBBER_FILE_TRANSFER (self)->peer_id);
+  gibber_xmpp_node_set_attribute (stanza->node, "id",
+      GIBBER_FILE_TRANSFER (self)->id);
+
+  if (gibber_file_transfer_send_stanza (GIBBER_FILE_TRANSFER (self), stanza,
+        &error))
+    {
+      g_signal_emit_by_name (self, "finished");
+    }
+  else
+    {
+      gibber_file_transfer_emit_error (GIBBER_FILE_TRANSFER (self), error);
+    }
+}
+
+static void
+gibber_oob_file_transfer_receive (GibberFileTransfer *ft,
+                                  GIOChannel *dest)
+{
+  GibberOobFileTransfer *self = GIBBER_OOB_FILE_TRANSFER (ft);
+  SoupSession *session;
+  SoupMessage *msg;
+
+  session = soup_session_async_new ();
+  msg = soup_message_new (SOUP_METHOD_GET, self->priv->url);
+  if (msg == NULL)
+    {
+      GError *error = NULL;
+
+      gibber_file_transfer_cancel (ft, 404);
+      g_set_error (&error, GIBBER_FILE_TRANSFER_ERROR,
+          GIBBER_FILE_TRANSFER_ERROR_NOT_FOUND, "Couldn't get the file");
+      gibber_file_transfer_emit_error (GIBBER_FILE_TRANSFER (self), error);
+
+      return;
+    }
+
+  self->priv->channel = g_io_channel_ref (dest);
+
+  soup_message_set_flags (msg, SOUP_MESSAGE_OVERWRITE_CHUNKS);
+  g_signal_connect (msg, "got_chunk", G_CALLBACK (http_client_chunk_cb), self);
+  soup_session_queue_message (session, msg, http_client_finished_chunks_cb,
+      self);
+}
+
+static GibberXmppStanza *
+create_transfer_offer (GibberOobFileTransfer *self,
+                       GError **error)
+{
+  GibberXmppConnection *connection;
+  GibberFdTransport *transport;
+
+  /* local host name */
+  gchar host_name[50];
+  struct sockaddr name_addr;
+  socklen_t name_addr_len = sizeof (name_addr);
+
+  GibberXmppStanza *stanza;
+  GibberXmppNode *query_node;
+  GibberXmppNode *url_node;
+
+  gchar *filename_escaped;
+  gchar *url;
+  gchar *served_name;
+
+  g_object_get (GIBBER_FILE_TRANSFER (self), "connection", &connection, NULL);
+  transport = GIBBER_FD_TRANSPORT (connection->transport);
+  if (transport == NULL)
+    {
+      g_set_error (error, GIBBER_FILE_TRANSFER_ERROR,
+          GIBBER_FILE_TRANSFER_ERROR_NOT_CONNECTED, "Null transport");
+      return NULL;
+    }
+
+  getsockname (transport->fd, &name_addr, &name_addr_len);
+  g_object_unref (connection);
+  getnameinfo (&name_addr, name_addr_len, host_name, sizeof (host_name), NULL,
+      0, NI_NUMERICHOST);
+
+  filename_escaped = escape_filename (GIBBER_FILE_TRANSFER (self)->filename);
+  url = g_strdup_printf ("http://%s:%d/%s/%s", host_name,
+      soup_server_get_port (self->priv->server),
+      GIBBER_FILE_TRANSFER (self)->id, filename_escaped);
+  g_free (filename_escaped);
+  served_name = g_strdup_printf ("/%s/%s", GIBBER_FILE_TRANSFER (self)->id,
+      GIBBER_FILE_TRANSFER (self)->filename);
+
+  stanza = gibber_xmpp_stanza_new ("iq");
+  gibber_xmpp_node_set_attribute (stanza->node, "type", "set");
+  gibber_xmpp_node_set_attribute (stanza->node, "id",
+      GIBBER_FILE_TRANSFER (self)->id);
+  gibber_xmpp_node_set_attribute (stanza->node, "from",
+      GIBBER_FILE_TRANSFER (self)->self_id);
+  gibber_xmpp_node_set_attribute (stanza->node, "to",
+      GIBBER_FILE_TRANSFER (self)->peer_id);
+
+  query_node = gibber_xmpp_node_add_child_ns (stanza->node, "query",
+      GIBBER_XMPP_NS_OOB);
+
+  url_node = gibber_xmpp_node_add_child_with_content (query_node, "url", url);
+  gibber_xmpp_node_set_attribute (url_node, "type", "file");
+  /* FIXME 0 could be a valid size */
+  if (GIBBER_FILE_TRANSFER (self)->size > 0)
+    {
+      gchar *size_str = g_strdup_printf ("%" G_GUINT64_FORMAT,
+          GIBBER_FILE_TRANSFER (self)->size);
+      gibber_xmpp_node_set_attribute (url_node, "size", size_str);
+      g_free (size_str);
+    }
+
+  self->priv->url = url;
+  self->priv->served_name = served_name;
+
+  return stanza;
+}
+
+/*
+ * Data is available from the channel so we can send it.
+ */
+static gboolean
+input_channel_readable_cb (GIOChannel *source,
+                           GIOCondition condition,
+                           gpointer user_data)
+{
+  GibberOobFileTransfer *self = user_data;
+  GIOStatus status;
+  gchar *buff;
+  gsize bytes_read;
+
+#define BUFF_SIZE 4096
+
+  if (condition & G_IO_IN)
+    {
+      buff = g_malloc (BUFF_SIZE);
+      status = g_io_channel_read_chars (source, buff, BUFF_SIZE,
+          &bytes_read, NULL);
+      switch (status)
+        {
+        case G_IO_STATUS_NORMAL:
+          soup_message_add_chunk (self->priv->msg, SOUP_BUFFER_SYSTEM_OWNED,
+              buff, bytes_read);
+          soup_message_io_unpause (self->priv->msg);
+          DEBUG("Data available, writing a %d bytes chunk", bytes_read);
+          return FALSE;
+        case G_IO_STATUS_AGAIN:
+          DEBUG("Data available, try again");
+          g_free (buff);
+          return TRUE;
+        case G_IO_STATUS_EOF:
+          DEBUG("EOF received on input");
+          break;
+        default:
+          DEBUG ("Read from the channel failed");
+      }
+      g_free (buff);
+    }
+
+#undef BUFF_SIZE
+
+  DEBUG("Closing HTTP chunked transfer");
+  soup_message_add_final_chunk (self->priv->msg);
+  soup_message_io_unpause (self->priv->msg);
+
+  g_io_channel_unref (self->priv->channel);
+  self->priv->channel = NULL;
+
+  soup_server_remove_handler (self->priv->server, self->priv->served_name);
+
+  return FALSE;
+}
+
+static void
+http_server_cb (SoupServerContext *context,
+                SoupMessage *msg,
+                gpointer user_data)
+{
+  const SoupUri *uri = soup_message_get_uri (msg);
+  GibberOobFileTransfer *self = user_data;
+  const gchar *accept_encoding;
+
+  if (context->method_id != SOUP_METHOD_ID_GET)
+    {
+      DEBUG ("A HTTP client tried to use an unsupported method");
+      soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
+      return;
+    }
+
+  if (strcmp (uri->path, self->priv->served_name) != 0)
+    {
+      soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND);
+      return;
+    }
+
+  DEBUG ("Serving '%s'", uri->path);
+
+  soup_message_set_status (msg, SOUP_STATUS_OK);
+  soup_server_message_set_encoding (SOUP_SERVER_MESSAGE (msg),
+      SOUP_TRANSFER_CHUNKED);
+  self->priv->msg = g_object_ref (msg);
+
+  /* iChat accepts only AppleSingle encoding, i.e. file's contents and
+   * attributes are stored in the same stream */
+  accept_encoding = soup_message_get_header (msg->request_headers,
+      "Accept-Encoding");
+  if (accept_encoding != NULL && strcmp (accept_encoding, "AppleSingle") == 0)
+    {
+      DEBUG ("Using AppleSingle encoding");
+
+      /* FIXME this is not working at the moment */
+      /* the header contains a magic number (4 bytes), a version number
+       * (4 bytes), a filler (16 bytes, all zeros) and the number of
+       * entries (2 bytes) */
+      static gchar buff[26] = {0};
+      if (buff[1] == 0)
+        {
+          /* magic number */
+          ((gint32*) buff)[0] = htonl (0x51600);
+          /* version */
+          ((gint32*) buff)[1] = htonl (0x20000);
+        }
+
+      soup_message_add_header (msg->response_headers, "Content-encoding",
+          "AppleSingle");
+
+      soup_message_add_chunk (self->priv->msg, SOUP_BUFFER_STATIC, buff,
+          sizeof (buff));
+      soup_message_io_unpause (self->priv->msg);
+    }
+
+  g_signal_emit_by_name (self, "remote-accepted");
+}
+
+static void
+gibber_oob_file_transfer_offer (GibberFileTransfer *ft)
+{
+  GibberOobFileTransfer *self = GIBBER_OOB_FILE_TRANSFER (ft);
+  GibberXmppStanza *stanza;
+  GError *error = NULL;
+
+  /* start the server if not running */
+  /* FIXME we should have only a single server */
+  if (self->priv->server == NULL)
+    {
+      self->priv->server = soup_server_new (NULL, NULL);
+      soup_server_run_async (self->priv->server);
+    }
+
+  stanza = create_transfer_offer (self, &error);
+  if (stanza == NULL)
+    {
+      gibber_file_transfer_emit_error (GIBBER_FILE_TRANSFER (self), error);
+      return;
+    }
+
+  soup_server_add_handler (self->priv->server, self->priv->served_name, NULL,
+      http_server_cb, NULL, self);
+
+  if (!gibber_file_transfer_send_stanza (GIBBER_FILE_TRANSFER (self),
+        stanza, &error))
+    {
+      gibber_file_transfer_emit_error (GIBBER_FILE_TRANSFER (self), error);
+    }
+}
+
+static void
+http_server_wrote_chunk_cb (SoupMessage *msg,
+                            gpointer user_data)
+{
+  GibberOobFileTransfer *self = user_data;
+
+  DEBUG("Chunk written, adding a watch to get more input");
+  if (self->priv->channel)
+    {
+      g_io_add_watch (self->priv->channel, G_IO_IN | G_IO_HUP,
+          input_channel_readable_cb, self);
+    }
+}
+
+static void
+gibber_oob_file_transfer_send (GibberFileTransfer *ft,
+                               GIOChannel *src)
+{
+  GibberOobFileTransfer *self = GIBBER_OOB_FILE_TRANSFER (ft);
+
+  DEBUG("Starting HTTP chunked file transfer");
+  self->priv->channel = src;
+  g_io_channel_ref (src);
+  g_signal_connect (self->priv->msg, "wrote-chunk",
+      G_CALLBACK (http_server_wrote_chunk_cb), self);
+  http_server_wrote_chunk_cb (self->priv->msg, self);
+}
+
+static void
+gibber_oob_file_transfer_cancel (GibberFileTransfer *ft,
+                                 guint error_code)
+{
+  GibberOobFileTransfer *self = GIBBER_OOB_FILE_TRANSFER (ft);
+  GibberXmppStanza *stanza;
+  GibberXmppNode *query;
+  GibberXmppNode *error_node;
+  GibberXmppNode *error_desc;
+
+  stanza = gibber_xmpp_stanza_new ("iq");
+  gibber_xmpp_node_set_attribute (stanza->node, "type", "error");
+  gibber_xmpp_node_set_attribute (stanza->node, "from", ft->self_id);
+  gibber_xmpp_node_set_attribute (stanza->node, "to", ft->peer_id);
+  gibber_xmpp_node_set_attribute (stanza->node, "id", ft->id);
+
+  query = gibber_xmpp_node_add_child_ns (stanza->node, "query",
+      GIBBER_XMPP_NS_OOB);
+  gibber_xmpp_node_add_child_with_content (query, "url", self->priv->url);
+
+  error_node = gibber_xmpp_node_add_child (stanza->node, "error");
+  switch (error_code)
+    {
+      case 404:
+        gibber_xmpp_node_set_attribute (error_node, "code", "404");
+        gibber_xmpp_node_set_attribute (error_node, "type", "cancel");
+        error_desc = gibber_xmpp_node_add_child_ns (error_node, "not-found",
+            GIBBER_XMPP_NS_STANZAS);
+        break;
+      case 406:
+        gibber_xmpp_node_set_attribute (error_node, "code", "406");
+        gibber_xmpp_node_set_attribute (error_node, "type", "modify");
+        error_desc = gibber_xmpp_node_add_child_ns (error_node,
+            "not-acceptable", GIBBER_XMPP_NS_STANZAS);
+        break;
+      default:
+        g_assert_not_reached ();
+    }
+
+  gibber_file_transfer_send_stanza (ft, stanza, NULL);
+}
+
+static void
+gibber_oob_file_transfer_received_stanza (GibberFileTransfer *ft,
+                                          GibberXmppStanza *stanza)
+{
+  GibberOobFileTransfer *self = GIBBER_OOB_FILE_TRANSFER (ft);
+  const gchar *type;
+  GibberXmppNode *error_node;
+
+  if (strcmp (stanza->node->name, "iq") != 0)
+    return;
+
+  type = gibber_xmpp_node_get_attribute (stanza->node, "type");
+  if (type == NULL)
+    return;
+
+  if (strcmp (type, "result") == 0)
+    {
+      g_signal_emit_by_name (self, "finished");
+      return;
+    }
+
+  error_node = gibber_xmpp_node_get_child (stanza->node, "error");
+  if (error_node != NULL)
+    {
+      GError *error = NULL;
+      const gchar *error_code_str;
+      guint error_code;
+      const gchar *error_descr;
+
+      /* FIXME copy the error handling code from gabble */
+      error_code_str = gibber_xmpp_node_get_attribute (error_node, "code");
+      if (g_ascii_strtoll (error_code_str, NULL, 10) == 406)
+        {
+          error_code = GIBBER_FILE_TRANSFER_ERROR_NOT_ACCEPTABLE;
+          error_descr = "Remote user stopped the transfer";
+        }
+      else
+        {
+          error_code = GIBBER_FILE_TRANSFER_ERROR_NOT_FOUND;
+          error_descr = "Remote user is not able to retrieve the file";
+        }
+
+      g_set_error (&error, GIBBER_FILE_TRANSFER_ERROR, error_code,
+          error_descr);
+      gibber_file_transfer_emit_error (GIBBER_FILE_TRANSFER (self), error);
+      return;
+    }
+}
+
+
+/*
+ * Escape/unescape file names according to RFC-2396, copied and modified
+ * from glib/gconvert.c.
+ *
+ * Original copyright:
+ *   Copyright Red Hat Inc., 2000
+ *   Authors: Havoc Pennington, Owen Taylor
+ */
+
+static const gboolean acceptable[96] =
+{
+  /*     !      "      #      $      %      &      '      (      )      *   */
+  FALSE, TRUE,  FALSE, FALSE, TRUE,  FALSE, TRUE,  TRUE,  TRUE,  TRUE,  TRUE,
+  /* +   ,      -      .      /      0      1      2      3      4      5   */
+  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,
+  /* 6   7      8      9      :      ;      <      =      >      ?      @   */
+  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  FALSE, FALSE, TRUE,  FALSE, FALSE, TRUE,
+  /* A   B      C      D      E      F      G      H      I      J      K   */
+  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,
+  /* L   M      N      O      P      Q      R      S      T      U      V   */
+  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,
+  /* W   X      Y      Z      [      \      ]      ^      _      `      a   */
+  TRUE,  TRUE,  TRUE,  TRUE,  FALSE, FALSE, FALSE, FALSE, TRUE,  FALSE, TRUE,
+  /* b   c      d      e      f      g      h      i      j      k      l   */
+  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,
+  /* m   n      o      p      q      r      s      t      u      v      w   */
+  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,
+  /* x   y      z      {      |      }      ~      DEL */
+  TRUE,  TRUE,  TRUE,  FALSE, FALSE, FALSE, TRUE,  FALSE
+};
+
+static const gchar hex[16] = "0123456789ABCDEF";
+
+static gchar *
+escape_filename (const gchar *unescaped)
+{
+  const gchar *p;
+  gchar *q;
+  gchar *result;
+  int c;
+  gint unacceptable;
+
+#define ACCEPTABLE(a) ((a) >= 32 && (a) < 128 && acceptable[(a) - 32])
+
+  unacceptable = 0;
+  for (p = unescaped; *p != '\0'; p++)
+    {
+      c = (guchar) *p;
+      if (!ACCEPTABLE (c))
+        unacceptable++;
+    }
+
+  result = g_malloc (p - unescaped + unacceptable * 2 + 1);
+
+  for (q = result, p = unescaped; *p != '\0'; p++)
+    {
+      c = (guchar) *p;
+
+      if (!ACCEPTABLE (c))
+        {
+          *q++ = '%'; /* means hex coming */
+          *q++ = hex[c >> 4];
+          *q++ = hex[c & 15];
+        }
+      else
+        {
+          *q++ = *p;
+        }
+    }
+
+#undef ACCEPTABLE
+
+  *q = '\0';
+
+  return result;
+}
+
+static int
+unescape_character (const char *scanner)
+{
+  int first_digit;
+  int second_digit;
+
+  first_digit = g_ascii_xdigit_value (scanner[0]);
+  if (first_digit < 0)
+    return -1;
+
+  second_digit = g_ascii_xdigit_value (scanner[1]);
+  if (second_digit < 0)
+    return -1;
+
+  return (first_digit << 4) | second_digit;
+}
+
+static gchar *
+unescape_filename (const char *escaped)
+{
+  int len;
+  const gchar *in, *in_end;
+  gchar *out, *result;
+  int c;
+
+  len = strlen (escaped);
+
+  result = g_malloc (len + 1);
+
+  out = result;
+  for (in = escaped, in_end = escaped + len; in < in_end; in++)
+    {
+      c = *in;
+
+      if (c == '%')
+        {
+          /* catch partial escape sequences past the end of the substring */
+          if (in + 3 > in_end)
+            break;
+
+          c = unescape_character (in + 1);
+          /* catch bad escape sequences and NUL characters */
+          if (c <= 0)
+            break;
+
+          in += 2;
+        }
+
+      *out++ = c;
+    }
+
+  *out = '\0';
+
+  return result;
+}
+
diff --git a/lib/gibber/gibber-oob-file-transfer.h b/lib/gibber/gibber-oob-file-transfer.h
new file mode 100644
index 0000000..d254177
--- /dev/null
+++ b/lib/gibber/gibber-oob-file-transfer.h
@@ -0,0 +1,70 @@
+/*
+ * gibber-oob-file-transfer.h - Header for GibberOobFileTransfer
+ * Copyright (C) 2007 Marco Barisione <marco at barisione.org>
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef __GIBBER_OOB_FILE_TRANSFER_H__
+#define __GIBBER_OOB_FILE_TRANSFER_H__
+
+#include <glib.h>
+#include <glib-object.h>
+#include "gibber-xmpp-connection.h"
+#include "gibber-file-transfer.h"
+
+G_BEGIN_DECLS
+
+typedef struct _GibberOobFileTransfer GibberOobFileTransfer;
+typedef struct _GibberOobFileTransferClass GibberOobFileTransferClass;
+
+struct _GibberOobFileTransferClass
+{
+    GibberFileTransferClass parent_class;
+};
+
+typedef struct _GibberOobFileTransferPrivate GibberOobFileTransferPrivate;
+
+struct _GibberOobFileTransfer {
+    GibberFileTransfer parent;
+
+    GibberOobFileTransferPrivate *priv;
+};
+
+GType gibber_oob_file_transfer_get_type(void);
+
+/* TYPE MACROS */
+#define GIBBER_TYPE_OOB_FILE_TRANSFER \
+  (gibber_oob_file_transfer_get_type ())
+#define GIBBER_OOB_FILE_TRANSFER(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIBBER_TYPE_OOB_FILE_TRANSFER, GibberOobFileTransfer))
+#define GIBBER_OOB_FILE_TRANSFER_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST ((klass), GIBBER_TYPE_OOB_FILE_TRANSFER, GibberOobFileTransferClass))
+#define GIBBER_IS_OOB_FILE_TRANSFER(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIBBER_TYPE_OOB_FILE_TRANSFER))
+#define GIBBER_IS_OOB_FILE_TRANSFER_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE ((klass), GIBBER_TYPE_OOB_FILE_TRANSFER))
+#define GIBBER_OOB_FILE_TRANSFER_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), GIBBER_TYPE_OOB_FILE_TRANSFER, GibberOobFileTransferClass))
+
+
+gboolean gibber_oob_file_transfer_is_file_offer (GibberXmppStanza *stanza);
+GibberFileTransfer *gibber_oob_file_transfer_new_from_stanza (
+    GibberXmppStanza *stanza, GibberXmppConnection *connection);
+
+
+G_END_DECLS
+
+#endif /* #ifndef __GIBBER_OOB_FILE_TRANSFER_H__*/
diff --git a/src/Makefile.am b/src/Makefile.am
index 8c4dde1..0fd8086 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -26,6 +26,10 @@ CORE_SOURCES =                                          \
     salut-muc-manager.c                                 \
     salut-muc-manager.h                                 \
     salut-muc-channel.c                                 \
+    salut-ft-manager.c                                  \
+    salut-ft-manager.h                                  \
+    salut-ft-channel.c                                  \
+    salut-ft-channel.h                                  \
     salut-muc-channel.h                                 \
     salut-contact.h                                     \
     salut-contact.c                                     \
@@ -53,7 +57,9 @@ CORE_SOURCES =                                          \
     salut-util.h                                        \
     salut-util.c                                        \
     debug.c                                             \
-    debug.h
+    debug.h                                             \
+    file-transfer-mixin.c                               \
+    file-transfer-mixin.h
 
 if ENABLE_DBUS_TUBES
   CORE_SOURCES +=                                       \
diff --git a/src/debug.c b/src/debug.c
index f274beb..733e923 100644
--- a/src/debug.c
+++ b/src/debug.c
@@ -36,6 +36,7 @@ GDebugKey keys[] = {
   { "bytestream-manager",       DEBUG_BYTESTREAM_MGR },
   { "discovery",      DEBUG_DISCO },
   { "olpc-activity",      DEBUG_OLPC_ACTIVITY },
+  { "ft",             DEBUG_FT },
   { "all",            ~0 },
   { 0, },
 };
diff --git a/src/debug.h b/src/debug.h
index 8ecc18f..5418c75 100644
--- a/src/debug.h
+++ b/src/debug.h
@@ -30,6 +30,7 @@ typedef enum
   DEBUG_XCM            = 1 << 15,
   DEBUG_DISCOVERY      = 1 << 16,
   DEBUG_OLPC_ACTIVITY  = 1 << 17,
+  DEBUG_FT             = 1 << 18,
 } DebugFlags;
 
 void debug_set_flags_from_env ();
diff --git a/src/file-transfer-mixin.c b/src/file-transfer-mixin.c
new file mode 100644
index 0000000..bcccad0
--- /dev/null
+++ b/src/file-transfer-mixin.c
@@ -0,0 +1,696 @@
+/*
+ * file-transfer-mixin.c - Source for TpFileTransfertMixin
+ * Copyright (C) 2007 Marco Barisione <marco at barisione.org>
+ * Copyright (C) 2006, 2007 Collabora Ltd.
+ * Copyright (C) 2006, 2007 Nokia Corporation
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+/**
+ * SECTION:file-transfer-mixin
+ * @title: TpFileTransferMixin
+ * @short_description: a mixin implementation of the file transfer channel type
+ * @see_also: #SalutSvcChannelTypeFileTransfer
+ *
+ * This mixin can be added to a channel GObject class to implement the file
+ * transfer channel type in a general way. It implements the list of transfers
+ * and manages the Unix sockets, so the implementation should only need to
+ * implement OfferFile, AcceptFile and CloseFileTransfer.
+ *
+ * To use the file transfer mixin, include a #TpFileTransferMixinClass
+ * somewhere in your class structure and a #TpFileTransferMixin somewhere in
+ * your instance structure, and call tp_file_transfer_mixin_class_init() from
+ * your class_init function, tp_file_transfer_mixin_init() from your init
+ * function or constructor, and tp_file_transfer_mixin_finalize() from your
+ * dispose or finalize function.
+ *
+ * To use the file transfer mixin as the implementation of
+ * #SalutSvcFileTransferInterface, in the function you pass to
+ * G_IMPLEMENT_INTERFACE, you should first call
+ * tp_file_transfer_mixin_iface_init(), then call
+ * salut_svc_channel_type_text_implement_*() to register your implementations
+ * of OfferFile, AcceptFile and CloseFileTransfer.
+ */
+
+/*#include <telepathy-glib/file-transfer-mixin.h>*/
+#include "file-transfer-mixin.h"
+
+#include <glib/gstdio.h>
+#include <dbus/dbus-glib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <telepathy-glib/enums.h>
+#include <telepathy-glib/errors.h>
+
+/*#define DEBUG_FLAG TP_DEBUG_FT*/
+#define DEBUG_FLAG DEBUG_FT
+
+/*#include "internal-debug.h"*/
+#include "debug.h"
+
+#define TP_TYPE_PENDING_TRANSFERS_STRUCT \
+  (dbus_g_type_get_struct ("GValueArray", \
+      G_TYPE_UINT, \
+      G_TYPE_UINT, \
+      G_TYPE_UINT, \
+      G_TYPE_UINT, \
+      G_TYPE_STRING, \
+      dbus_g_type_get_map ("GHashTable", G_TYPE_STRING, G_TYPE_VALUE), \
+      G_TYPE_INVALID))
+
+struct _TpFileTransferMixinPrivate
+{
+  TpHandleRepoIface *contacts_repo;
+  guint transfer_id;
+  GHashTable *transfers;
+  gchar *local_path;
+};
+
+/*
+ * _Transfer:
+ * @initiator: The handle of the contact who initiated the file transfer
+ * @direction: The file transfer's direction
+ * @state: The file transfer's state
+ * @filename: The filename of the file that is to be transmitted
+ * @information: The file's additional information
+ * @contacts_repo: The contacts repo used to unref initiator
+ * @user_data: User data associated to the transfer
+ *
+ * Represents a file transfer.
+ */
+typedef struct
+{
+  TpHandle initiator;
+  TpFileTransferDirection direction;
+  TpFileTransferState state;
+  gchar *filename;
+  GHashTable *information;
+  TpHandleRepoIface *contacts_repo;
+  gpointer user_data;
+} _Transfer;
+
+static _Transfer *
+_transfer_new (TpHandleRepoIface *contacts_repo)
+{
+  _Transfer *transfer = g_new0 (_Transfer, 1);
+  transfer->contacts_repo = contacts_repo;
+  return transfer;
+}
+
+static void
+_transfer_free (_Transfer *transfer)
+{
+  if (transfer == NULL)
+    return;
+
+  tp_handle_unref (transfer->contacts_repo, transfer->initiator);
+  g_free (transfer->filename);
+  g_hash_table_unref (transfer->information);
+  g_free (transfer);
+}
+
+/**
+ * tp_file_transfer_mixin_class_get_offset_quark:
+ *
+ * <!--no documentation beyond Returns: needed-->
+ *
+ * Returns: the quark used for storing mixin offset on a GObjectClass
+ */
+GQuark
+tp_file_transfer_mixin_class_get_offset_quark ()
+{
+  static GQuark offset_quark = 0;
+  if (!offset_quark)
+    offset_quark = g_quark_from_static_string ("TpFileTransferMixinClassOffsetQuark");
+  return offset_quark;
+}
+
+/**
+ * tp_file_transfer_mixin_get_offset_quark:
+ *
+ * <!--no documentation beyond Returns: needed-->
+ *
+ * Returns: the quark used for storing mixin offset on a GObject
+ */
+GQuark
+tp_file_transfer_mixin_get_offset_quark ()
+{
+  static GQuark offset_quark = 0;
+  if (!offset_quark)
+    offset_quark = g_quark_from_static_string ("TpFileTransferMixinOffsetQuark");
+  return offset_quark;
+}
+
+
+/**
+ * tp_file_transfer_mixin_class_init:
+ * @obj_cls: The class of the implementation that uses this mixin
+ * @offset: The byte offset of the TpFileTransferMixinClass within the class
+ * structure
+ *
+ * Initialize the file transfer mixin. Should be called from the
+ * implementation's class_init function like so:
+ *
+ * <informalexample><programlisting>
+ * tp_file_transfer_mixin_class_init ((GObjectClass *)klass,
+ *                                    G_STRUCT_OFFSET (SomeObjectClass,
+ *                                                     file_transfer_mixin));
+ * </programlisting></informalexample>
+ */
+void
+tp_file_transfer_mixin_class_init (GObjectClass *obj_cls,
+                                   glong offset)
+{
+  TpFileTransferMixinClass *mixin_cls;
+
+  g_assert (G_IS_OBJECT_CLASS (obj_cls));
+
+  g_type_set_qdata (G_OBJECT_CLASS_TYPE (obj_cls),
+      TP_FILE_TRANSFER_MIXIN_CLASS_OFFSET_QUARK,
+      GINT_TO_POINTER (offset));
+
+  mixin_cls = TP_FILE_TRANSFER_MIXIN_CLASS (obj_cls);
+}
+
+
+/**
+ * tp_file_transfer_mixin_init:
+ * @obj: An instance of the implementation that uses this mixin
+ * @offset: The byte offset of the TpFileTransferMixin within the object structure
+ * @contacts_repo: The connection's %TP_HANDLE_TYPE_CONTACT repository
+ *
+ * Initialize the file transfer mixin. Should be called from the
+ * implementation's instance init function like so:
+ *
+ * <informalexample><programlisting>
+ * tp_file_transfer_mixin_init ((GObject *)self,
+ *                              G_STRUCT_OFFSET (SomeObject,
+ *                                               file_transfer_mixin),
+ *                              self->contact_repo);
+ * </programlisting></informalexample>
+ */
+void
+tp_file_transfer_mixin_init (GObject *obj,
+                             glong offset,
+                             TpHandleRepoIface *contacts_repo)
+{
+  TpFileTransferMixin *mixin;
+
+  g_assert (G_IS_OBJECT (obj));
+
+  g_type_set_qdata (G_OBJECT_TYPE (obj),
+                    TP_FILE_TRANSFER_MIXIN_OFFSET_QUARK,
+                    GINT_TO_POINTER (offset));
+
+  mixin = TP_FILE_TRANSFER_MIXIN (obj);
+
+  mixin->priv = g_slice_new0 (TpFileTransferMixinPrivate);
+
+  mixin->priv->transfers = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+                                      NULL, (GDestroyNotify)_transfer_free);
+  mixin->priv->contacts_repo = contacts_repo;
+  mixin->priv->transfer_id = 0;
+}
+
+/**
+ * tp_file_transfer_mixin_finalize:
+ * @obj: An object with this mixin.
+ *
+ * Free resources held by the file transfer mixin.
+ */
+void
+tp_file_transfer_mixin_finalize (GObject *obj)
+{
+  TpFileTransferMixin *mixin = TP_FILE_TRANSFER_MIXIN (obj);
+
+  DEBUG ("%p", obj);
+
+  /* free any data held directly by the object here */
+
+  if (mixin->priv->local_path != NULL)
+    {
+      g_rmdir (mixin->priv->local_path);
+      g_free (mixin->priv->local_path);
+    }
+
+  g_hash_table_unref (mixin->priv->transfers);
+
+  g_slice_free (TpFileTransferMixinPrivate, mixin->priv);
+}
+
+gboolean
+tp_file_transfer_mixin_set_state (GObject *obj,
+                                  guint id,
+                                  TpFileTransferState state,
+                                  GError **error)
+{
+  TpFileTransferMixin *mixin = TP_FILE_TRANSFER_MIXIN (obj);
+  _Transfer *transfer;
+
+  transfer = g_hash_table_lookup (mixin->priv->transfers, GINT_TO_POINTER (id));
+  if (transfer != NULL)
+    {
+      transfer->state = state;
+      salut_svc_channel_type_file_transfer_emit_file_transfer_state_changed (
+              obj, id, state);
+      return TRUE;
+    }
+  else
+    {
+      DEBUG ("invalid transfer id %u", id);
+      g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+                   "invalid transfer id %u", id);
+      return FALSE;
+    }
+}
+
+TpFileTransferState
+tp_file_transfer_mixin_get_state (GObject *obj,
+                                  guint id,
+                                  GError **error)
+{
+  TpFileTransferMixin *mixin = TP_FILE_TRANSFER_MIXIN (obj);
+  _Transfer *transfer;
+
+  transfer = g_hash_table_lookup (mixin->priv->transfers, GINT_TO_POINTER (id));
+  if (transfer != NULL)
+    {
+      return transfer->state;
+    }
+  else
+    {
+      DEBUG ("invalid transfer id %u", id);
+      g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+                   "invalid transfer id %u", id);
+      return FALSE;
+    }
+}
+
+gboolean
+tp_file_transfer_mixin_set_user_data (GObject *obj,
+                                      guint id,
+                                      gpointer user_data)
+{
+  TpFileTransferMixin *mixin = TP_FILE_TRANSFER_MIXIN (obj);
+  _Transfer *transfer;
+
+  transfer = g_hash_table_lookup (mixin->priv->transfers, GINT_TO_POINTER (id));
+  if (transfer != NULL)
+    {
+      transfer->user_data = user_data;
+      return FALSE;
+    }
+  else
+    {
+      return FALSE;
+    }
+}
+
+gpointer
+tp_file_transfer_mixin_get_user_data (GObject *obj,
+                                      guint id)
+{
+  TpFileTransferMixin *mixin = TP_FILE_TRANSFER_MIXIN (obj);
+  _Transfer *transfer;
+
+  transfer = g_hash_table_lookup (mixin->priv->transfers, GINT_TO_POINTER (id));
+  return transfer != NULL ? transfer->user_data : NULL;
+}
+
+/**
+ * tp_file_transfer_mixin_add_transfer:
+ *
+ * @obj: An object with the file transfer mixin
+ * @initiator: The handle of the contact who initiated the file transfer
+ * @direction: The file transfer's direction
+ * @state: The file transfer's state
+ * @filename: The filename of the file that is to be transmitted, for
+ * displaying
+ * @information: The file's additional information
+ * @user_data: user data to associate to this transfer
+ *
+ * Add a file transfer.
+ * This function does not emit NewFileTransfer, you have to emit it on your
+ * own using tp_file_transfer_mixin_emit_new_file_transfer().
+ *
+ * Returns: the ID of the new file transfer.
+ */
+guint
+tp_file_transfer_mixin_add_transfer (GObject *obj,
+                                     TpHandle initiator,
+                                     TpFileTransferDirection direction,
+                                     TpFileTransferState state,
+                                     const char *filename,
+                                     GHashTable *information,
+                                     gpointer user_data)
+{
+  /* FIXME do we need state? if the transfer is outgoing the transfer can
+   * only be remote pending, else local pending. */
+  TpFileTransferMixin *mixin = TP_FILE_TRANSFER_MIXIN (obj);
+  _Transfer *transfer;
+  guint id = mixin->priv->transfer_id++;
+
+  tp_handle_ref (mixin->priv->contacts_repo, initiator);
+
+  transfer = _transfer_new (mixin->priv->contacts_repo);
+  transfer->initiator = initiator;
+  transfer->direction = direction;
+  transfer->state = state;
+  transfer->filename = g_strdup (filename);
+  transfer->information = g_hash_table_ref (information);
+  transfer->user_data = user_data;
+
+  g_hash_table_insert (mixin->priv->transfers, GINT_TO_POINTER (id), transfer);
+
+  DEBUG ("new file transfer %u", id);
+
+  return id;
+}
+
+/**
+ * tp_file_transfer_mixin_emit_new_file_transfer:
+ *
+ * @obj: An object with the file transfer mixin
+ * @id: The ID of the file transfer
+ *
+ * Emit NewFileTransfer for the file transfer with ID @id.
+ *
+ * Returns: TRUE if successful, FALSE if an error was thrown.
+ */
+gboolean
+tp_file_transfer_mixin_emit_new_file_transfer (GObject *obj,
+                                               guint id,
+                                               GError **error)
+{
+  TpFileTransferMixin *mixin = TP_FILE_TRANSFER_MIXIN (obj);
+  _Transfer *transfer;
+
+  transfer = g_hash_table_lookup (mixin->priv->transfers, GINT_TO_POINTER (id));
+  if (transfer == NULL)
+    {
+      DEBUG ("invalid transfer id %u", id);
+      g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+                   "invalid transfer id %u", id);
+      return FALSE;
+    }
+
+  DEBUG ("emitting NewFileTransfer for id %u", id);
+
+  salut_svc_channel_type_file_transfer_emit_new_file_transfer (obj, id,
+          transfer->initiator, transfer->direction, transfer->state,
+          transfer->filename, transfer->information);
+
+  return TRUE;
+}
+
+static GValue *
+get_file_transfer (guint id,
+                   _Transfer *transfer)
+{
+  GValue *ret;
+
+  ret = g_new0 (GValue, 1);
+  g_value_init (ret, TP_TYPE_PENDING_TRANSFERS_STRUCT);
+  g_value_take_boxed (ret,
+      dbus_g_type_specialized_construct (TP_TYPE_PENDING_TRANSFERS_STRUCT));
+  dbus_g_type_struct_set (ret,
+      0, id,
+      1, transfer->initiator,
+      2, transfer->direction,
+      3, transfer->state,
+      4, transfer->filename,
+      5, transfer->information,
+      G_MAXUINT);
+
+  return ret;
+}
+
+gboolean
+tp_file_transfer_mixin_get_file_transfer (GObject *obj,
+                                          guint id,
+                                          GValue **ret,
+                                          GError **error)
+{
+  TpFileTransferMixin *mixin = TP_FILE_TRANSFER_MIXIN (obj);
+  _Transfer *transfer;
+
+  transfer = g_hash_table_lookup (mixin->priv->transfers, GINT_TO_POINTER (id));
+  if (transfer == NULL)
+    {
+      DEBUG ("invalid transfer id %u", id);
+      g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+                   "invalid transfer id %u", id);
+      return FALSE;
+    }
+
+  *ret = get_file_transfer (id, transfer);
+  return TRUE;
+}
+
+static void
+list_file_transfers_hash_cb (gpointer key,
+                             gpointer value,
+                             gpointer user_data)
+{
+  guint id = GPOINTER_TO_INT (key);
+  _Transfer *transfer = (_Transfer *) value;
+  GPtrArray *transfers = user_data;
+  GValue *val;
+
+  val = get_file_transfer (id, transfer);
+  g_ptr_array_add (transfers, g_value_get_boxed (val));
+  g_free (val);
+}
+
+/**
+ * tp_file_transfer_mixin_list_file_transfers:
+ *
+ * @obj: An object with this mixin
+ * @ret: Used to return a pointer to a new GPtrArray of D-Bus structures
+ * @error: Used to return a pointer to a GError detailing any error
+ *         that occurred, D-Bus will throw the error only if this
+ *         function returns false.
+ *
+ * Implements D-Bus method ListFileTransfers
+ * on interface org.freedesktop.Telepathy.Channel.Type.FileTransfer
+ *
+ * Returns: TRUE if successful, FALSE if an error was thrown.
+ */
+gboolean
+tp_file_transfer_mixin_list_file_transfers (GObject *obj,
+                                            GPtrArray **ret,
+                                            GError **error)
+{
+  TpFileTransferMixin *mixin = TP_FILE_TRANSFER_MIXIN (obj);
+  guint count;
+  GPtrArray *transfers;
+
+  count = g_hash_table_size (mixin->priv->transfers);
+  transfers = g_ptr_array_sized_new (count);
+  g_hash_table_foreach (mixin->priv->transfers,
+                        list_file_transfers_hash_cb, transfers);
+
+  *ret = transfers;
+  return TRUE;
+}
+
+static void
+tp_file_transfer_mixin_list_file_transfers_async (SalutSvcChannelTypeFileTransfer *iface,
+                                                  DBusGMethodInvocation *context)
+{
+  GPtrArray *ret;
+  GError *error = NULL;
+
+  if (tp_file_transfer_mixin_list_file_transfers (G_OBJECT (iface), &ret,
+      &error))
+    {
+      salut_svc_channel_type_file_transfer_return_from_list_file_transfers (
+          context, ret);
+      g_ptr_array_free (ret, TRUE);
+    }
+  else
+    {
+      dbus_g_method_return_error (context, error);
+      g_error_free (error);
+    }
+}
+
+static void
+create_socket_path (TpFileTransferMixin *mixin)
+{
+  gint fd;
+  gchar *tmp_path = NULL;
+
+  while (tmp_path == NULL)
+    {
+      fd = g_file_open_tmp ("tp-ft-XXXXXX", &tmp_path, NULL);
+      close (fd);
+      g_unlink (tmp_path);
+      if (g_mkdir (tmp_path, 0700) < 0)
+        {
+          g_free (tmp_path);
+          tmp_path = NULL;
+        }
+    }
+
+  mixin->priv->local_path = tmp_path;
+}
+
+static gchar *
+get_local_unix_socket_path (TpFileTransferMixin *mixin,
+                            guint id)
+{
+  gchar *id_str;
+  gchar *path;
+
+  id_str = g_strdup_printf ("id-%d", id);
+  path = g_build_filename (mixin->priv->local_path, id_str, NULL);
+  g_free (id_str);
+
+  return path;
+}
+
+/**
+ * tp_file_transfer_mixin_get_local_unix_path:
+ *
+ * @obj: An object with this mixin
+ * @id: The ID of the file transfer to get an path for
+ * @ret: Used to return a pointer to a new string containing the Unix
+ * socket path
+ * @error: Used to return a pointer to a GError detailing any error
+ *         that occurred, D-Bus will throw the error only if this
+ *         function returns false.
+ *
+ * Implements D-Bus method GetLocalUnixSocketPath
+ * on interface org.freedesktop.Telepathy.Channel.Type.FileTransfer
+ *
+ * Returns: TRUE if successful, FALSE if an error was thrown.
+ */
+gboolean
+tp_file_transfer_mixin_get_local_unix_socket_path (GObject *obj,
+                                                   guint id,
+                                                   gchar **ret,
+                                                   GError **error)
+{
+  TpFileTransferMixin *mixin = TP_FILE_TRANSFER_MIXIN (obj);
+
+  if (mixin->priv->local_path == NULL)
+    create_socket_path (mixin);
+
+  *ret = get_local_unix_socket_path (mixin, id);
+
+  return TRUE;
+}
+
+static void
+tp_file_transfer_mixin_get_local_unix_socket_path_async (SalutSvcChannelTypeFileTransfer *iface,
+                                                         guint id,
+                                                         DBusGMethodInvocation *context)
+{
+  GError *error = NULL;
+  gchar *path;
+
+  if (tp_file_transfer_mixin_get_local_unix_socket_path (G_OBJECT (iface), id, &path,
+      &error))
+    {
+      salut_svc_channel_type_file_transfer_return_from_get_local_unix_socket_path (
+          context, path);
+      g_free (path);
+    }
+  else
+    {
+      dbus_g_method_return_error (context, error);
+      g_error_free (error);
+    }
+}
+
+/**
+ * tp_file_transfer_mixin_close_file_transfer:
+ *
+ * @obj: An object with this mixin
+ * @id: The ID of the file transfer to close
+ * @error: Used to return a pointer to a GError detailing any error
+ *         that occurred.
+ *
+ * Close the file transfer with ID @id and emit FileTransferClosed.
+ * Call this function from your implementation of the CloseFileTransfer
+ * method on interface org.freedesktop.Telepathy.Channel.Type.FileTransfer.
+ *
+ * Returns: TRUE if successful, FALSE if an error was thrown.
+ */
+gboolean
+tp_file_transfer_mixin_close_file_transfer (GObject *obj,
+                                            guint id,
+                                            SalutFileTransferCloseReason reason,
+                                            GError **error)
+{
+  TpFileTransferMixin *mixin = TP_FILE_TRANSFER_MIXIN (obj);
+  _Transfer *transfer;
+
+  transfer = g_hash_table_lookup (mixin->priv->transfers,
+                                  GINT_TO_POINTER (id));
+  if (transfer != NULL)
+    {
+      if (mixin->priv->local_path != NULL)
+        {
+          gchar *local_socket;
+          local_socket = get_local_unix_socket_path (mixin, id);
+          g_unlink (local_socket);
+          g_free (local_socket);
+        }
+      g_hash_table_remove (mixin->priv->transfers, GINT_TO_POINTER (id));
+      salut_svc_channel_type_file_transfer_emit_file_transfer_closed (obj,
+          id, reason);
+      return TRUE;
+    }
+  else
+    {
+      DEBUG ("invalid transfer id %u", id);
+      g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+          "invalid transfer id %u", id);
+      return FALSE;
+    }
+}
+
+/**
+ * tp_file_transfer_mixin_iface_init:
+ * @g_iface: A pointer to the #SalutSvcChannelTypeFileTransferClass in an object
+ * class
+ * @iface_data: Ignored
+ *
+ * Fill in this mixin's ListFileTransfers and GetLocalUnixSocketPath
+ * implementations in the given interface vtable.
+ * In addition to calling this function during interface initialization, the
+ * implementor is expected to call
+ * salut_svc_channel_type_text_implement_offer_file(),
+ * salut_svc_channel_type_text_implement_accept_file() and
+ * salut_svc_channel_type_text_implement_close_file_transfer() providing
+ * implementations for OfferFile, AcceptFile and CloseFileTransfer.
+ */
+void
+tp_file_transfer_mixin_iface_init (gpointer g_iface, gpointer iface_data)
+{
+  SalutSvcChannelTypeFileTransferClass *klass =
+      (SalutSvcChannelTypeFileTransferClass *)g_iface;
+
+#define IMPLEMENT(x) salut_svc_channel_type_file_transfer_implement_##x (klass,\
+    tp_file_transfer_mixin_##x##_async)
+  IMPLEMENT (list_file_transfers);
+  IMPLEMENT (get_local_unix_socket_path);
+  /* OfferFile, AcceptFile and CloseFileTransfer not implemented here */
+#undef IMPLEMENT
+}
diff --git a/src/file-transfer-mixin.h b/src/file-transfer-mixin.h
new file mode 100644
index 0000000..b0462d3
--- /dev/null
+++ b/src/file-transfer-mixin.h
@@ -0,0 +1,130 @@
+/*
+ * text-file-transfer-mixin.h - Header for TpFileTransferMixin
+ * Copyright (C) 2007 Marco Barisione <marco at barisione.org>
+ * Copyright (C) 2006, 2007 Collabora Ltd.
+ * Copyright (C) 2006, 2007 Nokia Corporation
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef __TP_FILE_TRANSFER_MIXIN_H__
+#define __TP_FILE_TRANSFER_MIXIN_H__
+
+#include <telepathy-glib/handle-repo.h>
+#include <telepathy-glib/svc-channel.h>
+#include <telepathy-glib/util.h>
+
+#include <extensions/_gen/svc.h>
+#include <extensions/_gen/enums.h>
+
+G_BEGIN_DECLS
+
+/* FIXME these should be automatically generated */
+#define TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER "org.freedesktop.Telepathy.Channel.Type.FileTransfer"
+
+typedef enum {
+  TP_FILE_TRANSFER_DIRECTION_INCOMING,
+  TP_FILE_TRANSFER_DIRECTION_OUTGOING
+} TpFileTransferDirection;
+typedef enum {
+  TP_FILE_TRANSFER_STATE_LOCAL_PENDING,
+  TP_FILE_TRANSFER_STATE_REMOTE_PENDING,
+  TP_FILE_TRANSFER_STATE_OPEN
+} TpFileTransferState;
+
+typedef struct _TpFileTransferMixinClass TpFileTransferMixinClass;
+typedef struct _TpFileTransferMixinClassPrivate TpFileTransferMixinClassPrivate;
+typedef struct _TpFileTransferMixin TpFileTransferMixin;
+typedef struct _TpFileTransferMixinPrivate TpFileTransferMixinPrivate;
+
+/**
+ * TpFileTransferMixinClass:
+ *
+ * Structure to be included in the class structure of objects that
+ * use this mixin. Initialize it with tp_file_transfer_mixin_class_init().
+ *
+ * There are no public fields.
+ */
+struct _TpFileTransferMixinClass {
+  /*<private>*/
+  TpFileTransferMixinClassPrivate *priv;
+};
+
+/**
+ * TpFileTransferMixin:
+ *
+ * Structure to be included in the instance structure of objects that
+ * use this mixin. Initialize it with tp_file_transfer_mixin_init().
+ *
+ * There are no public fields.
+ */
+struct _TpFileTransferMixin {
+  /*<private>*/
+  TpFileTransferMixinPrivate *priv;
+};
+
+/* TYPE MACROS */
+#define TP_FILE_TRANSFER_MIXIN_CLASS_OFFSET_QUARK \
+  (tp_file_transfer_mixin_class_get_offset_quark ())
+#define TP_FILE_TRANSFER_MIXIN_CLASS_OFFSET(o) \
+  (GPOINTER_TO_UINT (g_type_get_qdata (G_OBJECT_CLASS_TYPE (o), \
+                                       TP_FILE_TRANSFER_MIXIN_CLASS_OFFSET_QUARK)))
+#define TP_FILE_TRANSFER_MIXIN_CLASS(o) \
+  ((TpFileTransferMixinClass *) tp_mixin_offset_cast (o, \
+    TP_FILE_TRANSFER_MIXIN_CLASS_OFFSET (o)))
+
+#define TP_FILE_TRANSFER_MIXIN_OFFSET_QUARK (tp_file_transfer_mixin_get_offset_quark ())
+#define TP_FILE_TRANSFER_MIXIN_OFFSET(o) \
+  (GPOINTER_TO_UINT (g_type_get_qdata (G_OBJECT_TYPE (o), \
+                                       TP_FILE_TRANSFER_MIXIN_OFFSET_QUARK)))
+#define TP_FILE_TRANSFER_MIXIN(o) \
+  ((TpFileTransferMixin *) tp_mixin_offset_cast (o, TP_FILE_TRANSFER_MIXIN_OFFSET (o)))
+
+GQuark tp_file_transfer_mixin_class_get_offset_quark (void);
+GQuark tp_file_transfer_mixin_get_offset_quark (void);
+
+void tp_file_transfer_mixin_class_init (GObjectClass *obj_cls, glong offset);
+
+void tp_file_transfer_mixin_init (GObject *obj, glong offset,
+    TpHandleRepoIface *contacts_repo);
+void tp_file_transfer_mixin_finalize (GObject *obj);
+void tp_file_transfer_mixin_iface_init (gpointer g_iface, gpointer iface_data);
+
+gboolean tp_file_transfer_mixin_set_user_data (GObject *obj, guint id,
+    gpointer user_data);
+gpointer tp_file_transfer_mixin_get_user_data (GObject *obj, guint id);
+
+gboolean tp_file_transfer_mixin_set_state (GObject *obj, guint id,
+    TpFileTransferState state, GError **error);
+TpFileTransferState tp_file_transfer_mixin_get_state (GObject *obj, guint id,
+    GError **error);
+
+guint tp_file_transfer_mixin_add_transfer (GObject *obj, TpHandle initiator,
+    TpFileTransferDirection direction, TpFileTransferState state,
+    const char *filename, GHashTable *information, gpointer user_data);
+gboolean tp_file_transfer_mixin_emit_new_file_transfer (GObject *obj, guint id,
+    GError **error);
+gboolean tp_file_transfer_mixin_get_file_transfer (GObject *obj, guint id,
+    GValue **ret, GError **error);
+gboolean tp_file_transfer_mixin_list_file_transfers (GObject *obj,
+    GPtrArray **ret, GError **error);
+gboolean tp_file_transfer_mixin_get_local_unix_socket_path (GObject *obj,
+    guint id, gchar **ret, GError **error);
+gboolean tp_file_transfer_mixin_close_file_transfer (GObject *obj, guint id,
+    SalutFileTransferCloseReason reason, GError **error);
+
+G_END_DECLS
+
+#endif /* #ifndef __TP_FILE_TRANSFER_MIXIN_H__ */
diff --git a/src/salut-connection.c b/src/salut-connection.c
index 9b45913..84244b2 100644
--- a/src/salut-connection.c
+++ b/src/salut-connection.c
@@ -37,6 +37,7 @@
 #include "salut-contact-channel.h"
 #include "salut-im-manager.h"
 #include "salut-muc-manager.h"
+#include "salut-ft-manager.h"
 #include "salut-contact.h"
 #include "salut-self.h"
 #include "salut-xmpp-connection-manager.h"
@@ -187,6 +188,9 @@ struct _SalutConnectionPrivate
   /* MUC channel manager */
   SalutMucManager *muc_manager;
 
+  /* FT channel manager */
+  SalutFtManager *ft_manager;
+
   /* Tubes channel manager */
   /* XXX disabled while private tubes aren't implemented */
   /* SalutTubesManager *tubes_manager; */
@@ -2817,11 +2821,15 @@ salut_connection_create_channel_factories (TpBaseConnection *base)
   priv->muc_manager = salut_discovery_client_create_muc_manager (
       priv->discovery_client, self, priv->xmpp_connection_manager);
 
+  priv->ft_manager = salut_ft_manager_new (self, priv->contact_manager,
+      priv->xmpp_connection_manager);
+
   /*
   priv->tubes_manager = salut_tubes_manager_new (self, priv->contact_manager);
   */
 
   g_ptr_array_add (factories, priv->muc_manager);
+  g_ptr_array_add (factories, priv->ft_manager);
   /*
   g_ptr_array_add (factories, priv->tubes_manager);
   */
diff --git a/src/salut-ft-channel.c b/src/salut-ft-channel.c
new file mode 100644
index 0000000..58253f2
--- /dev/null
+++ b/src/salut-ft-channel.c
@@ -0,0 +1,953 @@
+/*
+ * salut-ft-channel.c - Source for SalutFtChannel
+ * Copyright (C) 2007 Marco Barisione <marco at barisione.org>
+ * Copyright (C) 2005, 2007 Collabora Ltd.
+ *   @author: Sjoerd Simons <sjoerd at luon.net>
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include <glib/gstdio.h>
+#include <dbus/dbus-glib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#define DEBUG_FLAG DEBUG_FT
+#include "debug.h"
+
+#include "salut-ft-channel.h"
+#include "signals-marshal.h"
+
+#include "salut-connection.h"
+#include "salut-im-manager.h"
+#include "salut-contact.h"
+
+#include <gibber/gibber-xmpp-stanza.h>
+#include <gibber/gibber-file-transfer.h>
+#include <gibber/gibber-oob-file-transfer.h>
+
+#include <telepathy-glib/channel-iface.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/dbus.h>
+
+
+static void
+channel_iface_init (gpointer g_iface, gpointer iface_data);
+static void
+file_transfer_iface_init (gpointer g_iface, gpointer iface_data);
+
+G_DEFINE_TYPE_WITH_CODE (SalutFtChannel, salut_ft_channel, G_TYPE_OBJECT,
+    G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL, channel_iface_init);
+    G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_IFACE, NULL);
+    G_IMPLEMENT_INTERFACE (SALUT_TYPE_SVC_CHANNEL_TYPE_FILE_TRANSFER,
+                           file_transfer_iface_init);
+);
+
+/* signal enum */
+/*
+enum
+{
+    RECEIVED_STANZA,
+    LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+*/
+
+/* properties */
+enum
+{
+  PROP_OBJECT_PATH = 1,
+  PROP_CHANNEL_TYPE,
+  PROP_HANDLE_TYPE,
+  PROP_HANDLE,
+  PROP_CONTACT,
+  PROP_CONNECTION,
+  PROP_XMPP_CONNECTION_MANAGER,
+  LAST_PROPERTY
+};
+
+/* private structure */
+struct _SalutFtChannelPrivate {
+  gboolean dispose_has_run;
+  gboolean closed;
+  gchar *object_path;
+  TpHandle handle;
+  SalutContact *contact;
+  SalutConnection *connection;
+  SalutXmppConnectionManager *xmpp_connection_manager;
+  GibberXmppConnection *xmpp_connection;
+  /* hash table used to convert from string id to numerical id */
+  GHashTable *name_to_id;
+};
+
+static void
+salut_ft_channel_do_close (SalutFtChannel *self)
+{
+  if (self->priv->closed)
+    return;
+
+  DEBUG ("Emitting closed signal for %s", self->priv->object_path);
+  tp_svc_channel_emit_closed (self);
+  self->priv->closed = TRUE;
+}
+
+static void
+salut_ft_channel_init (SalutFtChannel *obj)
+{
+  obj->priv = G_TYPE_INSTANCE_GET_PRIVATE (obj, SALUT_TYPE_FT_CHANNEL,
+      SalutFtChannelPrivate);
+
+  /* allocate any data required by the object here */
+  obj->priv->object_path = NULL;
+  obj->priv->connection = NULL;
+  obj->priv->xmpp_connection_manager = NULL;
+  obj->priv->contact = NULL;
+}
+
+static void
+salut_ft_channel_get_property (GObject    *object,
+                               guint       property_id,
+                               GValue     *value,
+                               GParamSpec *pspec)
+{
+  SalutFtChannel *self = SALUT_FT_CHANNEL (object);
+
+  switch (property_id)
+    {
+      case PROP_OBJECT_PATH:
+        g_value_set_string (value, self->priv->object_path);
+        break;
+      case PROP_CHANNEL_TYPE:
+        g_value_set_static_string (value, TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER);
+        break;
+      case PROP_HANDLE_TYPE:
+        g_value_set_uint (value, TP_HANDLE_TYPE_CONTACT);
+        break;
+      case PROP_HANDLE:
+        g_value_set_uint (value, self->priv->handle);
+        break;
+      case PROP_CONTACT:
+        g_value_set_object (value, self->priv->contact);
+        break;
+      case PROP_CONNECTION:
+        g_value_set_object (value, self->priv->connection);
+        break;
+      case PROP_XMPP_CONNECTION_MANAGER:
+        g_value_set_object (value, self->priv->xmpp_connection_manager);
+        break;
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+        break;
+    }
+}
+
+static void
+salut_ft_channel_set_property (GObject *object,
+                               guint property_id,
+                               const GValue *value,
+                               GParamSpec *pspec)
+{
+  SalutFtChannel *self = SALUT_FT_CHANNEL (object);
+  const gchar *tmp;
+
+  switch (property_id)
+    {
+      case PROP_OBJECT_PATH:
+        g_free (self->priv->object_path);
+        self->priv->object_path = g_value_dup_string (value);
+        break;
+      case PROP_HANDLE:
+        self->priv->handle = g_value_get_uint (value);
+        break;
+      case PROP_CONTACT:
+        self->priv->contact = g_value_get_object (value);
+        g_object_ref (self->priv->contact);
+        break;
+      case PROP_CONNECTION:
+        self->priv->connection = g_value_get_object (value);
+        break;
+      case PROP_HANDLE_TYPE:
+        g_assert (g_value_get_uint (value) == 0
+                  || g_value_get_uint (value) == TP_HANDLE_TYPE_CONTACT);
+        break;
+      case PROP_CHANNEL_TYPE:
+        tmp = g_value_get_string (value);
+        g_assert (tmp == NULL
+                  || !tp_strdiff (g_value_get_string (value),
+                         TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER));
+        break;
+      case PROP_XMPP_CONNECTION_MANAGER:
+        self->priv->xmpp_connection_manager = g_value_get_object (value);
+        g_object_ref (self->priv->xmpp_connection_manager);
+        break;
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+        break;
+    }
+}
+
+static GObject *
+salut_ft_channel_constructor (GType type, guint n_props,
+                              GObjectConstructParam *props)
+{
+  GObject *obj;
+  SalutFtChannel *self;
+  DBusGConnection *bus;
+  TpBaseConnection *base_conn;
+  TpHandleRepoIface *contact_repo;
+
+  /* Parent constructor chain */
+  obj = G_OBJECT_CLASS (salut_ft_channel_parent_class)->
+          constructor (type, n_props, props);
+
+  self = SALUT_FT_CHANNEL (obj);
+
+  /* Ref our handle */
+  base_conn = TP_BASE_CONNECTION (self->priv->connection);
+
+  contact_repo = tp_base_connection_get_handles (base_conn,
+      TP_HANDLE_TYPE_CONTACT);
+
+  tp_handle_ref (contact_repo, self->priv->handle);
+
+  /* Initialize file transfer mixin */
+  tp_file_transfer_mixin_init (obj,
+      G_STRUCT_OFFSET (SalutFtChannel, file_transfer), contact_repo);
+
+  /* Initialize the hash table used to convert from the id name
+   * to the numerical id. */
+  self->priv->name_to_id = g_hash_table_new_full (g_str_hash, g_str_equal,
+      NULL, NULL);
+
+  /* Connect to the bus */
+  bus = tp_get_bus ();
+  dbus_g_connection_register_g_object (bus, self->priv->object_path, obj);
+
+  return obj;
+}
+
+static void
+salut_ft_channel_dispose (GObject *object);
+static void
+salut_ft_channel_finalize (GObject *object);
+static gboolean
+do_close_file_transfer (SalutFtChannel *self, guint id,
+    SalutFileTransferCloseReason reason, GError **error);
+
+static void
+salut_ft_channel_class_init (SalutFtChannelClass *salut_ft_channel_class)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (salut_ft_channel_class);
+  GParamSpec *param_spec;
+
+  g_type_class_add_private (salut_ft_channel_class,
+      sizeof (SalutFtChannelPrivate));
+
+  object_class->dispose = salut_ft_channel_dispose;
+  object_class->finalize = salut_ft_channel_finalize;
+
+  object_class->constructor = salut_ft_channel_constructor;
+  object_class->get_property = salut_ft_channel_get_property;
+  object_class->set_property = salut_ft_channel_set_property;
+
+  g_object_class_override_property (object_class, PROP_OBJECT_PATH,
+                                    "object-path");
+  g_object_class_override_property (object_class, PROP_CHANNEL_TYPE,
+                                    "channel-type");
+  g_object_class_override_property (object_class, PROP_HANDLE_TYPE,
+                                    "handle-type");
+  g_object_class_override_property (object_class, PROP_HANDLE, "handle");
+
+  param_spec = g_param_spec_object ("contact",
+                                    "SalutContact object",
+                                    "Salut Contact to which this channel"
+                                    "is dedicated",
+                                    SALUT_TYPE_CONTACT,
+                                    G_PARAM_CONSTRUCT_ONLY |
+                                    G_PARAM_READWRITE |
+                                    G_PARAM_STATIC_NICK |
+                                    G_PARAM_STATIC_BLURB);
+  g_object_class_install_property (object_class, PROP_CONTACT, param_spec);
+
+  param_spec = g_param_spec_object ("connection",
+                                    "SalutConnection object",
+                                    "Salut Connection that owns the"
+                                    "connection for this IM channel",
+                                    SALUT_TYPE_CONNECTION,
+                                    G_PARAM_CONSTRUCT_ONLY |
+                                    G_PARAM_READWRITE |
+                                    G_PARAM_STATIC_NICK |
+                                    G_PARAM_STATIC_BLURB);
+  g_object_class_install_property (object_class,
+                                   PROP_CONNECTION, param_spec);
+
+  param_spec = g_param_spec_object (
+      "xmpp-connection-manager",
+      "SalutXmppConnectionManager object",
+      "Salut XMPP Connection manager used for this FT channel",
+      SALUT_TYPE_XMPP_CONNECTION_MANAGER,
+      G_PARAM_CONSTRUCT_ONLY |
+      G_PARAM_READWRITE |
+      G_PARAM_STATIC_NICK |
+      G_PARAM_STATIC_BLURB);
+  g_object_class_install_property (object_class, PROP_XMPP_CONNECTION_MANAGER,
+      param_spec);
+
+  tp_file_transfer_mixin_class_init (object_class,
+                                     G_STRUCT_OFFSET (SalutFtChannelClass,
+                                                      file_transfer_class));
+}
+
+void
+salut_ft_channel_dispose (GObject *object)
+{
+  SalutFtChannel *self = SALUT_FT_CHANNEL (object);
+  TpBaseConnection *base_conn = TP_BASE_CONNECTION (self->priv->connection);
+  TpHandleRepoIface *handle_repo = tp_base_connection_get_handles (base_conn,
+      TP_HANDLE_TYPE_CONTACT);
+
+  if (self->priv->dispose_has_run)
+    return;
+
+  self->priv->dispose_has_run = TRUE;
+
+  tp_handle_unref (handle_repo, self->priv->handle);
+
+  g_hash_table_unref (self->priv->name_to_id);
+
+  salut_ft_channel_do_close (self);
+
+  if (self->priv->contact)
+    {
+      g_object_unref (self->priv->contact);
+      self->priv->contact = NULL;
+    }
+
+  if (self->priv->xmpp_connection_manager != NULL)
+    {
+      g_object_unref (self->priv->xmpp_connection_manager);
+      self->priv->xmpp_connection_manager = NULL;
+    }
+
+  if (self->priv->xmpp_connection != NULL)
+    {
+      g_object_unref (self->priv->xmpp_connection);
+      self->priv->xmpp_connection = NULL;
+    }
+
+  /* release any references held by the object here */
+
+  if (G_OBJECT_CLASS (salut_ft_channel_parent_class)->dispose)
+    G_OBJECT_CLASS (salut_ft_channel_parent_class)->dispose (object);
+}
+
+static void
+salut_ft_channel_finalize (GObject *object)
+{
+  SalutFtChannel *self = SALUT_FT_CHANNEL (object);
+
+  /* free any data held directly by the object here */
+  g_free (self->priv->object_path);
+
+  tp_file_transfer_mixin_finalize (G_OBJECT (self));
+
+  G_OBJECT_CLASS (salut_ft_channel_parent_class)->finalize (object);
+}
+
+
+/**
+ * salut_ft_channel_close
+ *
+ * Implements DBus method Close
+ * on interface org.freedesktop.Telepathy.Channel
+ */
+static void
+salut_ft_channel_close (TpSvcChannel *iface,
+                        DBusGMethodInvocation *context)
+{
+  salut_ft_channel_do_close (SALUT_FT_CHANNEL (iface));
+  tp_svc_channel_return_from_close (context);
+}
+
+/**
+ * salut_ft_channel_get_channel_type
+ *
+ * Implements DBus method GetChannelType
+ * on interface org.freedesktop.Telepathy.Channel
+ */
+static void
+salut_ft_channel_get_channel_type (TpSvcChannel *iface,
+                                   DBusGMethodInvocation *context)
+{
+  tp_svc_channel_return_from_get_channel_type (context,
+      TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER);
+}
+
+/**
+ * salut_ft_channel_get_handle
+ *
+ * Implements DBus method GetHandle
+ * on interface org.freedesktop.Telepathy.Channel
+ */
+static void
+salut_ft_channel_get_handle (TpSvcChannel *iface,
+                             DBusGMethodInvocation *context)
+{
+  SalutFtChannel *self = SALUT_FT_CHANNEL (iface);
+
+  tp_svc_channel_return_from_get_handle (context, TP_HANDLE_TYPE_CONTACT,
+                                         self->priv->handle);
+}
+
+/**
+ * salut_ft_channel_get_interfaces
+ *
+ * Implements DBus method GetInterfaces
+ * on interface org.freedesktop.Telepathy.Channel
+ */
+static void
+salut_ft_channel_get_interfaces (TpSvcChannel *iface,
+                                 DBusGMethodInvocation *context)
+{
+  const char *interfaces[] = { NULL };
+
+  tp_svc_channel_return_from_get_interfaces (context, interfaces);
+}
+
+static void
+channel_iface_init (gpointer g_iface, gpointer iface_data)
+{
+  TpSvcChannelClass *klass = (TpSvcChannelClass *)g_iface;
+
+#define IMPLEMENT(x) tp_svc_channel_implement_##x (\
+    klass, salut_ft_channel_##x)
+  IMPLEMENT (close);
+  IMPLEMENT (get_channel_type);
+  IMPLEMENT (get_handle);
+  IMPLEMENT (get_interfaces);
+#undef IMPLEMENT
+}
+
+static GibberFileTransfer *
+get_file_transfer (SalutFtChannel *self,
+                   guint id,
+                   GError **error)
+{
+  GibberFileTransfer *ft;
+
+  ft = tp_file_transfer_mixin_get_user_data (G_OBJECT (self), id);
+  if (ft != NULL)
+    {
+      return ft;
+    }
+  else
+    {
+      DEBUG ("Invalid transfer id %u", id);
+      g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+          "Invalid transfer id %u", id);
+      return NULL;
+    }
+}
+
+static void
+error_cb (GibberFileTransfer *ft,
+          guint domain,
+          gint code,
+          const gchar *message,
+          SalutFtChannel *self)
+{
+  guint id;
+  guint reason;
+
+  if (code == GIBBER_FILE_TRANSFER_ERROR_NOT_ACCEPTABLE)
+    reason = SALUT_FILETRANSFER_CLOSEREASON_REMOTESTOPPED;
+  else
+    reason = SALUT_FILETRANSFER_CLOSEREASON_REMOTEERROR;
+
+  id = GPOINTER_TO_INT (g_hash_table_lookup (self->priv->name_to_id, ft->id));
+  do_close_file_transfer (self, id, reason, NULL);
+}
+
+static void
+ft_finished_cb (GibberFileTransfer *ft,
+                SalutFtChannel *self)
+{
+  guint id;
+
+  id = GPOINTER_TO_INT (g_hash_table_lookup (self->priv->name_to_id, ft->id));
+  do_close_file_transfer (self, id, SALUT_FILETRANSFER_CLOSEREASON_SUCCESS, NULL);
+}
+
+static void
+remote_accepted_cb (GibberFileTransfer *ft,
+                    SalutFtChannel *self)
+{
+  guint id;
+
+  id = GPOINTER_TO_INT (g_hash_table_lookup (self->priv->name_to_id, ft->id));
+  tp_file_transfer_mixin_set_state (G_OBJECT (self), id,
+    TP_FILE_TRANSFER_STATE_OPEN, NULL);
+
+  g_signal_connect (ft, "finished", G_CALLBACK (ft_finished_cb), self);
+}
+
+static gboolean
+setup_local_socket (SalutFtChannel *self, guint id);
+
+static void
+send_file_offer (SalutFtChannel *self,
+                 guint id)
+{
+  GValue *val;
+  GValueArray *val_array;
+  GibberFileTransfer *ft;
+  const gchar *filename;
+  GHashTable *information;
+
+  /* retrieve the file name and the additional information */
+  if (!tp_file_transfer_mixin_get_file_transfer (G_OBJECT (self), id,
+        &val, NULL))
+    {
+      DEBUG ("Invalid transfer id %u", id);
+      do_close_file_transfer (self, id,
+          SALUT_FILETRANSFER_CLOSEREASON_LOCALERROR, NULL);
+      return;
+    }
+  val_array = g_value_get_boxed (val);
+  g_free (val);
+  filename = g_value_get_string (g_value_array_get_nth (val_array, 4));
+  information = g_value_get_boxed (g_value_array_get_nth (val_array, 5));
+
+  ft = g_object_new (GIBBER_TYPE_OOB_FILE_TRANSFER,
+      "self-jid", self->priv->connection->name,
+      "peer-jid", self->priv->contact->name,
+      "filename", filename,
+      "connection", self->priv->xmpp_connection,
+      NULL);
+  g_signal_connect (ft, "remote-accepted",
+      G_CALLBACK (remote_accepted_cb), self);
+  g_signal_connect (ft, "error", G_CALLBACK (error_cb), self);
+
+  g_hash_table_insert (self->priv->name_to_id, (gchar *) ft->id,
+      GINT_TO_POINTER (id));
+  tp_file_transfer_mixin_set_user_data (G_OBJECT (self), id, ft);
+
+  setup_local_socket (self, id);
+
+  val = g_hash_table_lookup (information, "size");
+  if (val != NULL)
+    ft->size = g_value_get_uint64 (val);
+
+  gibber_file_transfer_offer (ft);
+
+  tp_file_transfer_mixin_emit_new_file_transfer (G_OBJECT (self), id, NULL);
+}
+
+/* passed as user_data to the callbacl for the "new-connection" signal
+ * emitted by the SalutXmppConnectionManager */
+typedef struct {
+  SalutFtChannel *self;
+  guint id;
+} NewConnectionData;
+
+static void
+xmpp_connection_manager_new_connection_cb (SalutXmppConnectionManager *mgr,
+                                           GibberXmppConnection *connection,
+                                           SalutContact *contact,
+                                           gpointer user_data)
+{
+  NewConnectionData *data = user_data;
+
+  data->self->priv->xmpp_connection = g_object_ref (connection);
+  g_signal_handlers_disconnect_by_func (mgr,
+      xmpp_connection_manager_new_connection_cb, user_data);
+  send_file_offer (data->self, data->id);
+  g_free (data);
+}
+
+static void
+value_free (GValue *value)
+{
+  if (!value)
+    return;
+  g_value_unset (value);
+  g_free (value);
+}
+
+static gboolean
+dup_hash_table_cb (gpointer key,
+                   gpointer value,
+                   gpointer user_data)
+{
+  GHashTable *dest = user_data;
+
+  g_hash_table_insert (dest, key, value);
+  return TRUE;
+}
+
+/**
+ * salut_ft_channel_offer_file
+ *
+ * Implements DBus method OfferFile
+ * on interface org.freedesktop.Telepathy.Channel.Type.FileTransfer
+ */
+static void
+salut_ft_channel_offer_file (SalutSvcChannelTypeFileTransfer *channel,
+                             const gchar *filename,
+                             GHashTable *information,
+                             DBusGMethodInvocation *context)
+{
+  SalutFtChannel *self = SALUT_FT_CHANNEL (channel);
+  TpBaseConnection *base_connection =
+      TP_BASE_CONNECTION (self->priv->connection);
+  GHashTable *information_dup;
+  guint id;
+  GibberXmppConnection *connection = NULL;
+  SalutXmppConnectionManagerRequestConnectionResult request_result;
+  GError *error = NULL;
+
+  /* FIXME dbus calls g_hash_table_destroy() instead of g_hash_table_unref()
+   * so we have to copy the hash table, see freedesktop bug #11396
+   * (https://bugs.freedesktop.org/show_bug.cgi?id=11396) */
+  information_dup = g_hash_table_new_full (g_str_hash, g_str_equal,
+      (GDestroyNotify) g_free, (GDestroyNotify) value_free);
+  g_hash_table_foreach_steal (information, dup_hash_table_cb, information_dup);
+
+  id = tp_file_transfer_mixin_add_transfer (G_OBJECT (channel),
+      base_connection->self_handle, TP_FILE_TRANSFER_DIRECTION_OUTGOING,
+      TP_FILE_TRANSFER_STATE_REMOTE_PENDING, filename, information_dup, NULL);
+
+  request_result = salut_xmpp_connection_manager_request_connection (
+      self->priv->xmpp_connection_manager, self->priv->contact, &connection,
+      &error);
+
+  if (request_result ==
+      SALUT_XMPP_CONNECTION_MANAGER_REQUEST_CONNECTION_RESULT_DONE)
+    {
+      self->priv->xmpp_connection = connection;
+      send_file_offer (self, id);
+    }
+  else if (request_result ==
+           SALUT_XMPP_CONNECTION_MANAGER_REQUEST_CONNECTION_RESULT_PENDING)
+    {
+      NewConnectionData *data = g_new0 (NewConnectionData, 1);
+      data->self = self;
+      data->id = id;
+      g_signal_connect (self->priv->xmpp_connection_manager, "new-connection",
+          G_CALLBACK (xmpp_connection_manager_new_connection_cb), data);
+    }
+  else
+    {
+      DEBUG ("Request connection failed");
+      dbus_g_method_return_error (context, error);
+      g_error_free (error);
+      return;
+    }
+
+  salut_svc_channel_type_file_transfer_return_from_offer_file (context, id);
+}
+
+void
+salut_ft_channel_received_file_offer (SalutFtChannel *self,
+                                      GibberXmppStanza *stanza,
+                                      GibberXmppConnection *conn)
+{
+  GibberFileTransfer *ft;
+  GHashTable *information;
+  guint id;
+
+  ft = gibber_file_transfer_new_from_stanza (stanza, conn);
+  g_signal_connect (ft, "error", G_CALLBACK (error_cb), self);
+
+  DEBUG ("Received file offer with id '%s'", ft->id);
+
+  information = g_hash_table_new_full (g_str_hash, g_str_equal,
+      (GDestroyNotify) g_free, (GDestroyNotify) value_free);
+  GValue *val = g_new0 (GValue, 1);
+  g_value_init (val, G_TYPE_UINT64);
+  g_value_set_uint64 (val, ft->size);
+  g_hash_table_insert (information, g_strdup ("size"), val);
+
+  id = tp_file_transfer_mixin_add_transfer (G_OBJECT (self),
+      self->priv->handle, TP_FILE_TRANSFER_DIRECTION_INCOMING,
+      TP_FILE_TRANSFER_STATE_LOCAL_PENDING, ft->filename,
+      information, ft);
+
+  g_hash_table_insert (self->priv->name_to_id, (gchar *) ft->id,
+      GINT_TO_POINTER (id));
+
+  tp_file_transfer_mixin_emit_new_file_transfer (G_OBJECT (self), id, NULL);
+}
+
+/**
+ * salut_ft_channel_accept_file
+ *
+ * Implements D-Bus method AcceptFile
+ * on interface org.freedesktop.Telepathy.Channel.Type.FileTransfer
+ */
+static void
+salut_ft_channel_accept_file (SalutSvcChannelTypeFileTransfer *iface,
+                              guint id,
+                              DBusGMethodInvocation *context)
+{
+  SalutFtChannel *self = SALUT_FT_CHANNEL (iface);
+  GibberFileTransfer *ft;
+  GError *error = NULL;
+
+  ft = get_file_transfer (self, id, &error);
+  if (ft == NULL)
+    {
+      dbus_g_method_return_error (context, error);
+      g_error_free (error);
+    }
+
+  g_signal_connect (ft, "finished", G_CALLBACK (ft_finished_cb), self);
+
+  setup_local_socket (self, id);
+
+  if (!tp_file_transfer_mixin_set_state (G_OBJECT (self), id,
+        TP_FILE_TRANSFER_STATE_OPEN, &error))
+    {
+      dbus_g_method_return_error (context, error);
+      g_error_free (error);
+      return;
+    }
+
+  salut_svc_channel_type_file_transfer_return_from_accept_file (context);
+}
+
+/**
+ * salut_ft_channel_close_file_transfer
+ *
+ * Implements D-Bus method CloseFileTransfer
+ * on interface org.freedesktop.Telepathy.Channel.Type.FileTransfer
+ */
+static void
+salut_ft_channel_close_file_transfer (SalutSvcChannelTypeFileTransfer *iface,
+                                      guint id,
+                                      SalutFileTransferCloseReason reason,
+                                      DBusGMethodInvocation *context)
+{
+  SalutFtChannel *self = SALUT_FT_CHANNEL (iface);
+  GibberFileTransfer *ft;
+  GError *error = NULL;
+
+  ft = get_file_transfer (self, id, &error);
+  if (ft == NULL)
+    {
+      dbus_g_method_return_error (context, error);
+      g_error_free (error);
+    }
+
+  gibber_file_transfer_cancel (ft, 406);
+
+  /* FIXME use the reason argument when added to the file transfer spec
+   * and check that the passed in argument is not
+   * SALUT_FILETRANSFER_CLOSEREASON_SUCCESS or REMOTEERROR/REMOTESTOPPED as
+   * it doesn't make sense. */
+  if (do_close_file_transfer (self, id,
+        SALUT_FILETRANSFER_CLOSEREASON_LOCALSTOPPED, &error))
+    {
+      salut_svc_channel_type_file_transfer_return_from_close_file_transfer (
+          context);
+    }
+  else
+    {
+      dbus_g_method_return_error (context, error);
+      g_error_free (error);
+    }
+}
+
+static gboolean
+do_close_file_transfer (SalutFtChannel *self,
+                        guint id,
+                        SalutFileTransferCloseReason reason,
+                        GError **error)
+{
+  /* reason is not used at the moment */
+  GibberFileTransfer *ft;
+
+  ft = get_file_transfer (self, id, error);
+  if (ft == NULL)
+    return FALSE;
+
+  g_hash_table_remove (self->priv->name_to_id, ft->id);
+  g_object_unref (ft);
+
+  DEBUG ("Closing file transfer %u with reason %d", id, reason);
+
+  return tp_file_transfer_mixin_close_file_transfer (G_OBJECT (self), id,
+      reason, error);
+}
+
+static void
+file_transfer_iface_init (gpointer g_iface,
+                          gpointer iface_data)
+{
+  SalutSvcChannelTypeFileTransferClass *klass =
+      (SalutSvcChannelTypeFileTransferClass *)g_iface;
+
+  tp_file_transfer_mixin_iface_init (g_iface, iface_data);
+#define IMPLEMENT(x) salut_svc_channel_type_file_transfer_implement_##x (\
+    klass, salut_ft_channel_##x)
+  IMPLEMENT (offer_file);
+  IMPLEMENT (accept_file);
+  IMPLEMENT (close_file_transfer);
+#undef IMPLEMENT
+}
+
+
+/*
+ * Return a GIOChannel for the unix socket returned by
+ * GetLocalUnixSocketPath().
+ */
+static GIOChannel *
+get_socket_channel (SalutFtChannel *self,
+                    guint id)
+{
+  gint fd;
+  gchar *path;
+  size_t path_len;
+  struct sockaddr_un addr;
+  GIOChannel *io_channel;
+
+  if (!tp_file_transfer_mixin_get_local_unix_socket_path (G_OBJECT (self), id,
+        &path, NULL))
+    {
+      DEBUG ("Impossible to get the socket path");
+      return NULL;
+    }
+
+  fd = socket (PF_UNIX, SOCK_STREAM, 0);
+  if (fd < 0)
+    {
+      DEBUG("socket() failed");
+      g_free (path);
+      return NULL;
+    }
+
+  memset (&addr, 0, sizeof (addr));
+  addr.sun_family = AF_UNIX;
+  path_len = strlen (path);
+  strncpy (addr.sun_path, path, path_len);
+  g_unlink (path);
+  g_free (path);
+
+  if (bind (fd, (struct sockaddr*) &addr,
+        G_STRUCT_OFFSET (struct sockaddr_un, sun_path) + path_len) < 0)
+    {
+      DEBUG ("bind failed");
+      close (fd);
+      return NULL;
+    }
+
+  if (listen (fd, 5) < 0)
+    {
+      DEBUG ("listen failed");
+      close (fd);
+      return NULL;
+    }
+
+  io_channel = g_io_channel_unix_new (fd);
+  g_io_channel_set_close_on_unref (io_channel, TRUE);
+  return io_channel;
+}
+
+typedef struct {
+  SalutFtChannel *self;
+  guint id;
+} LocalSocketWatchData;
+
+/*
+ * Some client is connecting to the Unix socket.
+ */
+static gboolean
+accept_local_socket_connection (GIOChannel *source,
+                                GIOCondition condition,
+                                gpointer user_data)
+{
+  LocalSocketWatchData *watch_data = user_data;
+  GibberFileTransfer *ft;
+
+  int new_fd;
+  struct sockaddr_un addr;
+  socklen_t addrlen;
+  GIOChannel *channel;
+
+  if (condition & G_IO_IN)
+    {
+      DEBUG ("Client connected to local socket");
+
+      ft = get_file_transfer (watch_data->self, watch_data->id, NULL);
+      if (ft == NULL)
+        return FALSE;
+
+      addrlen = sizeof (addr);
+      new_fd = accept (g_io_channel_unix_get_fd (source),
+          (struct sockaddr *) &addr, &addrlen);
+      if (new_fd < 0)
+        {
+          DEBUG ("accept() failed");
+          return FALSE;
+        }
+
+      channel = g_io_channel_unix_new (new_fd);
+      g_io_channel_set_close_on_unref (channel, TRUE);
+      g_io_channel_set_encoding (channel, NULL, NULL);
+      if (ft->direction == GIBBER_FILE_TRANSFER_DIRECTION_INCOMING)
+        gibber_file_transfer_receive (ft, channel);
+      else
+        /* FIXME what to do if the chat client connects to the
+         * local socket before receiving the "remote-accepted"
+         * signal? */
+        gibber_file_transfer_send (ft, channel);
+      g_io_channel_unref (channel);
+    }
+
+  g_free (watch_data);
+
+  return FALSE;
+}
+
+static gboolean
+setup_local_socket (SalutFtChannel *self, guint id)
+{
+  GIOChannel *io_channel;
+  LocalSocketWatchData *watch_data;
+
+  io_channel = get_socket_channel (self, id);
+  if (io_channel == NULL)
+    {
+      do_close_file_transfer (self, id,
+          SALUT_FILETRANSFER_CLOSEREASON_LOCALERROR, NULL);
+      return FALSE;
+    }
+
+  watch_data = g_new0 (LocalSocketWatchData, 1);
+  watch_data->self = self;
+  watch_data->id = id;
+  g_io_add_watch (io_channel, G_IO_IN | G_IO_HUP,
+      accept_local_socket_connection, watch_data);
+  g_io_channel_unref (io_channel);
+
+  return TRUE;
+}
+
diff --git a/src/salut-ft-channel.h b/src/salut-ft-channel.h
new file mode 100644
index 0000000..b0a1f75
--- /dev/null
+++ b/src/salut-ft-channel.h
@@ -0,0 +1,75 @@
+/*
+ * salut-ft-channel.h - Header for SalutFtChannel
+ * Copyright (C) 2007 Marco Barisione <marco at barisione.org>
+ * Copyright (C) 2005, 2007 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef __SALUT_FT_CHANNEL_H__
+#define __SALUT_FT_CHANNEL_H__
+
+#include <glib-object.h>
+#include <gibber/gibber-file-transfer.h>
+
+#include <telepathy-glib/text-mixin.h>
+#include "file-transfer-mixin.h"
+
+
+G_BEGIN_DECLS
+
+typedef struct _SalutFtChannel SalutFtChannel;
+typedef struct _SalutFtChannelClass SalutFtChannelClass;
+typedef struct _SalutFtChannelPrivate SalutFtChannelPrivate;
+
+struct _SalutFtChannelClass {
+    GObjectClass parent_class;
+    TpTextMixinClass text_class;
+    TpFileTransferMixinClass file_transfer_class;
+};
+
+struct _SalutFtChannel {
+    GObject parent;
+    TpTextMixin text;
+    TpFileTransferMixin file_transfer;
+
+    SalutFtChannelPrivate *priv;
+};
+
+GType salut_ft_channel_get_type (void);
+
+/* TYPE MACROS */
+#define SALUT_TYPE_FT_CHANNEL \
+  (salut_ft_channel_get_type ())
+#define SALUT_FT_CHANNEL(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj), SALUT_TYPE_FT_CHANNEL, SalutFtChannel))
+#define SALUT_FT_CHANNEL_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass), SALUT_TYPE_FT_CHANNEL, \
+                           SalutFtChannelClass))
+#define SALUT_IS_FT_CHANNEL(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj), SALUT_TYPE_FT_CHANNEL))
+#define SALUT_IS_FT_CHANNEL_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass), SALUT_TYPE_FT_CHANNEL))
+#define SALUT_FT_CHANNEL_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), SALUT_TYPE_FT_CHANNEL, \
+                              SalutFtChannelClass))
+
+void
+salut_ft_channel_received_file_offer (SalutFtChannel *self,
+    GibberXmppStanza *stanza, GibberXmppConnection *conn);
+
+G_END_DECLS
+
+#endif /* #ifndef __SALUT_FT_CHANNEL_H__*/
diff --git a/src/salut-ft-manager.c b/src/salut-ft-manager.c
new file mode 100644
index 0000000..12ba87f
--- /dev/null
+++ b/src/salut-ft-manager.c
@@ -0,0 +1,407 @@
+/*
+ * salut-ft-manager.c - Source for SalutFtManager
+ * Copyright (C) 2007 Marco Barisione <marco at barisione.org>
+ * Copyright (C) 2006 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include <dbus/dbus-glib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <gibber/gibber-file-transfer.h>
+
+#include "salut-ft-manager.h"
+#include "signals-marshal.h"
+
+#include "salut-ft-channel.h"
+#include "salut-contact-manager.h"
+
+#include <telepathy-glib/channel-factory-iface.h>
+#include <telepathy-glib/interfaces.h>
+
+#define DEBUG_FLAG DEBUG_FT
+#include "debug.h"
+
+static void
+salut_ft_manager_factory_iface_init (gpointer *g_iface, gpointer *iface_data);
+
+static SalutFtChannel *
+salut_ft_manager_new_channel (SalutFtManager *mgr, TpHandle handle);
+
+G_DEFINE_TYPE_WITH_CODE (SalutFtManager, salut_ft_manager, G_TYPE_OBJECT,
+    G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_FACTORY_IFACE,
+                           salut_ft_manager_factory_iface_init));
+
+/* signal enum */
+/*
+enum
+{
+    LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+*/
+
+/* private structure */
+typedef struct _SalutFtManagerPrivate SalutFtManagerPrivate;
+
+struct _SalutFtManagerPrivate
+{
+  gboolean dispose_has_run;
+  SalutConnection *connection;
+  SalutXmppConnectionManager *xmpp_connection_manager;
+  SalutContactManager *contact_manager;
+  GHashTable *channels;
+};
+
+#define SALUT_FT_MANAGER_GET_PRIVATE(o) \
+  (G_TYPE_INSTANCE_GET_PRIVATE ((o), SALUT_TYPE_FT_MANAGER, \
+                                SalutFtManagerPrivate))
+
+static void
+salut_ft_manager_init (SalutFtManager *obj)
+{
+  SalutFtManagerPrivate *priv = SALUT_FT_MANAGER_GET_PRIVATE (obj);
+  priv->xmpp_connection_manager = NULL;
+  priv->contact_manager = NULL;
+  priv->connection = NULL;
+
+  /* allocate any data required by the object here */
+  priv->channels = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+                                          NULL, g_object_unref);
+}
+
+static gboolean
+message_stanza_filter (SalutXmppConnectionManager *mgr,
+                       GibberXmppConnection *conn,
+                       GibberXmppStanza *stanza,
+                       SalutContact *contact,
+                       gpointer user_data)
+{
+  return gibber_file_transfer_is_file_offer (stanza);
+}
+
+static void
+message_stanza_callback (SalutXmppConnectionManager *mgr,
+                         GibberXmppConnection *conn,
+                         GibberXmppStanza *stanza,
+                         SalutContact *contact,
+                         gpointer user_data)
+{
+  SalutFtManager *self = SALUT_FT_MANAGER (user_data);
+  SalutFtManagerPrivate *priv = SALUT_FT_MANAGER_GET_PRIVATE (self);
+  SalutFtChannel *chan;
+  TpHandle handle;
+  TpBaseConnection *base_conn = TP_BASE_CONNECTION (priv->connection);
+  TpHandleRepoIface *handle_repo = tp_base_connection_get_handles (base_conn,
+       TP_HANDLE_TYPE_CONTACT);
+
+  handle = tp_handle_lookup (handle_repo, contact->name, NULL, NULL);
+  g_assert (handle != 0);
+
+  chan = g_hash_table_lookup (priv->channels, GUINT_TO_POINTER (handle));
+  if (chan == NULL)
+    chan = salut_ft_manager_new_channel (self, handle);
+
+  salut_ft_channel_received_file_offer (chan, stanza, conn);
+}
+
+static void salut_ft_manager_dispose (GObject *object);
+static void salut_ft_manager_finalize (GObject *object);
+
+static void
+salut_ft_manager_class_init (SalutFtManagerClass *salut_ft_manager_class)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (salut_ft_manager_class);
+
+  g_type_class_add_private (salut_ft_manager_class,
+                            sizeof (SalutFtManagerPrivate));
+
+  object_class->dispose = salut_ft_manager_dispose;
+  object_class->finalize = salut_ft_manager_finalize;
+}
+
+void
+salut_ft_manager_dispose (GObject *object)
+{
+  SalutFtManager *self = SALUT_FT_MANAGER (object);
+  SalutFtManagerPrivate *priv = SALUT_FT_MANAGER_GET_PRIVATE (self);
+  GHashTable *t;
+
+  if (priv->dispose_has_run)
+    return;
+
+  priv->dispose_has_run = TRUE;
+
+  if (priv->xmpp_connection_manager != NULL)
+    {
+      salut_xmpp_connection_manager_remove_stanza_filter (
+          priv->xmpp_connection_manager, NULL,
+          message_stanza_filter, message_stanza_callback, self);
+
+      g_object_unref (priv->xmpp_connection_manager);
+      priv->xmpp_connection_manager = NULL;
+    }
+
+  if (priv->contact_manager != NULL)
+    {
+      g_object_unref (priv->contact_manager);
+      priv->contact_manager = NULL;
+    }
+
+  if (priv->channels)
+    {
+      t = priv->channels;
+      priv->channels = NULL;
+      g_hash_table_destroy (t);
+    }
+
+  /* release any references held by the object here */
+
+  if (G_OBJECT_CLASS (salut_ft_manager_parent_class)->dispose)
+    G_OBJECT_CLASS (salut_ft_manager_parent_class)->dispose (object);
+}
+
+void
+salut_ft_manager_finalize (GObject *object)
+{
+  /*SalutFtManager *self = SALUT_FT_MANAGER (object);*/
+  /*SalutFtManagerPrivate *priv = SALUT_FT_MANAGER_GET_PRIVATE (self);*/
+
+  /* free any data held directly by the object here */
+
+  G_OBJECT_CLASS (salut_ft_manager_parent_class)->finalize (object);
+}
+
+/* Channel Factory interface */
+
+static void
+salut_ft_manager_factory_iface_close_all (TpChannelFactoryIface *iface)
+{
+  GHashTable *t;
+  SalutFtManager *mgr = SALUT_FT_MANAGER (iface);
+  SalutFtManagerPrivate *priv = SALUT_FT_MANAGER_GET_PRIVATE (mgr);
+
+  if (priv->channels)
+    {
+      t = priv->channels;
+      priv->channels = NULL;
+      g_hash_table_destroy (t);
+    }
+}
+
+static void
+salut_ft_manager_factory_iface_connecting (TpChannelFactoryIface *iface)
+{
+}
+
+static void
+salut_ft_manager_factory_iface_connected (TpChannelFactoryIface *iface)
+{
+}
+
+static void
+salut_ft_manager_factory_iface_disconnected (TpChannelFactoryIface *iface)
+{
+  /* FIXME close all channels ? */
+}
+
+struct foreach_data {
+  TpChannelFunc func;
+  gpointer data;
+};
+
+static void
+salut_ft_manager_iface_foreach_one (gpointer key,
+                                    gpointer value,
+                                    gpointer data)
+{
+  TpChannelIface *chan = TP_CHANNEL_IFACE (value);
+  struct foreach_data *f = (struct foreach_data *) data;
+
+  f->func (chan, f->data);
+}
+
+static void
+salut_ft_manager_factory_iface_foreach (TpChannelFactoryIface *iface,
+                                        TpChannelFunc func, gpointer data)
+{
+  SalutFtManager *mgr = SALUT_FT_MANAGER (iface);
+  SalutFtManagerPrivate *priv = SALUT_FT_MANAGER_GET_PRIVATE (mgr);
+  struct foreach_data f;
+  f.func = func;
+  f.data = data;
+
+  g_hash_table_foreach (priv->channels, salut_ft_manager_iface_foreach_one, &f);
+}
+
+static void
+ft_channel_closed_cb (SalutFtChannel *chan, gpointer user_data)
+{
+  SalutFtManager *self = SALUT_FT_MANAGER (user_data);
+  SalutFtManagerPrivate *priv = SALUT_FT_MANAGER_GET_PRIVATE (self);
+  TpHandle handle;
+
+  if (priv->channels)
+    {
+      g_object_get (chan, "handle", &handle, NULL);
+      DEBUG ("Removing channel with handle %d", handle);
+      g_hash_table_remove (priv->channels, GINT_TO_POINTER (handle));
+    }
+}
+
+static SalutFtChannel *
+salut_ft_manager_new_channel (SalutFtManager *mgr,
+                              TpHandle handle)
+{
+  SalutFtManagerPrivate *priv = SALUT_FT_MANAGER_GET_PRIVATE (mgr);
+  TpBaseConnection *base_connection = TP_BASE_CONNECTION (priv->connection);
+  TpHandleRepoIface *handle_repo =
+      tp_base_connection_get_handles (base_connection, TP_HANDLE_TYPE_CONTACT);
+  SalutFtChannel *chan;
+  SalutContact *contact;
+  const gchar *name;
+  gchar *path = NULL;
+
+  g_assert (g_hash_table_lookup (priv->channels, GINT_TO_POINTER (handle))
+            == NULL);
+  DEBUG ("Requested channel for handle: %d", handle);
+
+  contact = salut_contact_manager_get_contact (priv->contact_manager, handle);
+  if (contact == NULL)
+    {
+      return NULL;
+    }
+
+  name = tp_handle_inspect (handle_repo, handle);
+  path = g_strdup_printf ("%s/FtChannel/%u",
+                         base_connection->object_path, handle);
+  chan = g_object_new (SALUT_TYPE_FT_CHANNEL,
+                       "connection", priv->connection,
+                       "contact", contact,
+                       "object-path", path,
+                       "handle", handle,
+                       "xmpp-connection-manager", priv->xmpp_connection_manager,
+                       NULL);
+  g_object_unref (contact);
+  g_free (path);
+  g_hash_table_insert (priv->channels, GINT_TO_POINTER (handle), chan);
+  tp_channel_factory_iface_emit_new_channel (mgr, TP_CHANNEL_IFACE (chan),
+      NULL);
+  g_signal_connect (chan, "closed", G_CALLBACK (ft_channel_closed_cb), mgr);
+
+  return chan;
+}
+
+static TpChannelFactoryRequestStatus
+salut_ft_manager_factory_iface_request (TpChannelFactoryIface *iface,
+                                        const gchar *chan_type,
+                                        TpHandleType handle_type,
+                                        guint handle,
+                                        gpointer request,
+                                        TpChannelIface **ret,
+                                        GError **error)
+{
+  SalutFtManager *mgr = SALUT_FT_MANAGER (iface);
+  SalutFtManagerPrivate *priv = SALUT_FT_MANAGER_GET_PRIVATE (mgr);
+  SalutFtChannel *chan;
+  gboolean created = FALSE;
+  TpBaseConnection *base_connection = TP_BASE_CONNECTION (priv->connection);
+  TpHandleRepoIface *handle_repo =
+      tp_base_connection_get_handles (base_connection, TP_HANDLE_TYPE_CONTACT);
+
+  DEBUG ("File transfer request");
+
+  /* We only support file transfer channels */
+  if (tp_strdiff (chan_type, TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER))
+    {
+      return TP_CHANNEL_FACTORY_REQUEST_STATUS_NOT_IMPLEMENTED;
+    }
+
+  /* And only contact handles */
+  if (handle_type != TP_HANDLE_TYPE_CONTACT)
+    {
+      return TP_CHANNEL_FACTORY_REQUEST_STATUS_NOT_AVAILABLE;
+    }
+
+  /* Must be a valid contact handle */
+  if (!tp_handle_is_valid (handle_repo, TP_HANDLE_TYPE_CONTACT, NULL))
+    {
+      return TP_CHANNEL_FACTORY_REQUEST_STATUS_INVALID_HANDLE;
+    }
+
+  /* Don't support opening a channel to our self handle */
+  if (handle == base_connection->self_handle)
+    {
+       return TP_CHANNEL_FACTORY_REQUEST_STATUS_INVALID_HANDLE;
+    }
+
+  chan = g_hash_table_lookup (priv->channels, GINT_TO_POINTER (handle));
+  if (chan == NULL)
+    {
+      chan = salut_ft_manager_new_channel (mgr, handle);
+      created = TRUE;
+    }
+  *ret = TP_CHANNEL_IFACE (chan);
+
+  return created ? TP_CHANNEL_FACTORY_REQUEST_STATUS_CREATED
+                 : TP_CHANNEL_FACTORY_REQUEST_STATUS_EXISTING;
+}
+
+static void salut_ft_manager_factory_iface_init (gpointer *g_iface,
+                                                 gpointer *iface_data)
+{
+   TpChannelFactoryIfaceClass *klass = (TpChannelFactoryIfaceClass *)g_iface;
+
+   klass->close_all = salut_ft_manager_factory_iface_close_all;
+   klass->connecting = salut_ft_manager_factory_iface_connecting;
+   klass->connected = salut_ft_manager_factory_iface_connected;
+   klass->disconnected = salut_ft_manager_factory_iface_disconnected;
+   klass->foreach = salut_ft_manager_factory_iface_foreach;
+   klass->request = salut_ft_manager_factory_iface_request;
+}
+
+/* public functions */
+SalutFtManager *
+salut_ft_manager_new (SalutConnection *connection,
+                      SalutContactManager *contact_manager,
+                      SalutXmppConnectionManager *xmpp_connection_manager)
+{
+  SalutFtManager *ret = NULL;
+  SalutFtManagerPrivate *priv;
+
+  g_assert (connection != NULL);
+  g_assert (xmpp_connection_manager != NULL);
+
+  ret = g_object_new (SALUT_TYPE_FT_MANAGER, NULL);
+  priv = SALUT_FT_MANAGER_GET_PRIVATE (ret);
+
+  priv->contact_manager = contact_manager;
+  g_object_ref (contact_manager);
+
+  priv->xmpp_connection_manager = xmpp_connection_manager;
+  g_object_ref (xmpp_connection_manager);
+
+  salut_xmpp_connection_manager_add_stanza_filter (
+      priv->xmpp_connection_manager, NULL,
+      message_stanza_filter, message_stanza_callback, ret);
+
+  priv->connection = connection;
+
+  return ret;
+}
diff --git a/src/salut-ft-manager.h b/src/salut-ft-manager.h
new file mode 100644
index 0000000..7195f26
--- /dev/null
+++ b/src/salut-ft-manager.h
@@ -0,0 +1,64 @@
+/*
+ * salut-ft-manager.h - Header for SalutFtManager
+ * Copyright (C) 2007 Marco Barisione <marco at barisione.org>
+ * Copyright (C) 2006 Collabora Ltd.
+ *
+ * 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.1 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef __SALUT_FT_MANAGER_H__
+#define __SALUT_FT_MANAGER_H__
+
+#include <glib-object.h>
+
+#include <salut-connection.h>
+#include <salut-im-manager.h>
+
+G_BEGIN_DECLS
+
+typedef struct _SalutFtManager SalutFtManager;
+typedef struct _SalutFtManagerClass SalutFtManagerClass;
+
+struct _SalutFtManagerClass {
+    GObjectClass parent_class;
+};
+
+struct _SalutFtManager {
+    GObject parent;
+};
+
+GType salut_ft_manager_get_type (void);
+
+/* TYPE MACROS */
+#define SALUT_TYPE_FT_MANAGER \
+  (salut_ft_manager_get_type ())
+#define SALUT_FT_MANAGER(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj), SALUT_TYPE_FT_MANAGER, SalutFtManager))
+#define SALUT_FT_MANAGER_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass), SALUT_TYPE_FT_MANAGER, SalutFtManagerClass))
+#define SALUT_IS_FT_MANAGER(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj), SALUT_TYPE_FT_MANAGER))
+#define SALUT_IS_FT_MANAGER_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass), SALUT_TYPE_FT_MANAGER))
+#define SALUT_FT_MANAGER_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), SALUT_TYPE_FT_MANAGER, SalutFtManagerClass))
+
+SalutFtManager *salut_ft_manager_new (SalutConnection *connection,
+    SalutContactManager *contact_manager,
+    SalutXmppConnectionManager *xmpp_connection_manager);
+
+G_END_DECLS
+
+#endif /* #ifndef __SALUT_FT_MANAGER_H__*/
diff --git a/src/salut.c b/src/salut.c
index 706e2cf..90bd83d 100644
--- a/src/salut.c
+++ b/src/salut.c
@@ -22,6 +22,7 @@ int
 main (int argc, char **argv)
 {
   g_type_init ();
+  g_thread_init (NULL);
   g_set_prgname ("telepathy-salut");
 
   tp_debug_divert_messages (g_getenv ("SALUT_LOGFILE"));
-- 
1.5.6.5




More information about the Telepathy-commits mailing list