[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