summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMorten Mjelva <morten.mjelva@gmail.com>2011-02-16 23:37:14 +0100
committerMorten Mjelva <morten.mjelva@gmail.com>2011-06-22 13:07:07 +0200
commitf5db50c2fbf8784af482e450eb20ad119785bf67 (patch)
treeb1f3a6624b1da75908bca44b306b7bbf7abf98ee
parent7a9a30157c677642defe92624e4e83846751d678 (diff)
Added client-side TpFileTransferChannel object
-rw-r--r--telepathy-glib/Makefile.am2
-rw-r--r--telepathy-glib/automatic-proxy-factory.c10
-rw-r--r--telepathy-glib/file-transfer-channel.c1300
-rw-r--r--telepathy-glib/file-transfer-channel.h110
-rw-r--r--telepathy-glib/introspection.am1
5 files changed, 1423 insertions, 0 deletions
diff --git a/telepathy-glib/Makefile.am b/telepathy-glib/Makefile.am
index 54a42b85..6029c3c4 100644
--- a/telepathy-glib/Makefile.am
+++ b/telepathy-glib/Makefile.am
@@ -65,6 +65,7 @@ our_headers = \
enums.h \
errors.h \
exportable-channel.h \
+ file-transfer-channel.h \
gnio-util.h \
group-mixin.h \
gtypes.h \
@@ -197,6 +198,7 @@ libtelepathy_glib_internal_la_SOURCES = \
debug-internal.h \
errors.c \
exportable-channel.c \
+ file-transfer-channel.c \
gnio-util.c \
group-mixin.c \
gtypes.c \
diff --git a/telepathy-glib/automatic-proxy-factory.c b/telepathy-glib/automatic-proxy-factory.c
index 36c53cb2..e10ae890 100644
--- a/telepathy-glib/automatic-proxy-factory.c
+++ b/telepathy-glib/automatic-proxy-factory.c
@@ -41,6 +41,10 @@
* %TP_IFACE_CHANNEL_INTERFACE_MESSAGES;</para>
* </listitem>
* <listitem>
+ * <para>a #TpFileTransferChannel, if the channel is of type
+ * %TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER;</para>
+ * </listitem>
+ * <listitem>
* <para>a plain #TpChannel, otherwise</para>
* </listitem>
* </itemizedlist>
@@ -92,6 +96,7 @@
#include <telepathy-glib/interfaces.h>
#include <telepathy-glib/stream-tube-channel.h>
#include <telepathy-glib/text-channel.h>
+#include <telepathy-glib/file-transfer-channel.h>
#include <telepathy-glib/util.h>
#define DEBUG_FLAG TP_DEBUG_CLIENT
@@ -144,6 +149,11 @@ tp_automatic_proxy_factory_create_channel_impl (
DEBUG ("channel %s doesn't implement Messages so we can't create "
"a TpTextChannel", path);
}
+ else if (!tp_strdiff (chan_type, TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER))
+ {
+ return TP_CHANNEL (tp_file_transfer_channel_new (conn, path, properties,
+ error));
+ }
return tp_channel_new_from_properties (conn, path, properties, error);
}
diff --git a/telepathy-glib/file-transfer-channel.c b/telepathy-glib/file-transfer-channel.c
new file mode 100644
index 00000000..e916a658
--- /dev/null
+++ b/telepathy-glib/file-transfer-channel.c
@@ -0,0 +1,1300 @@
+/*
+ * file-transfer-channel.h - high level API for Chan.I.FileTransfer
+ *
+ * Copyright (C) 2010 Morten Mjelva <morten.mjelva@gmail.com>
+ *
+ * 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-channel
+ * @title: TpFileTransferChannel
+ * @short_description: proxy object for a file transfer channel
+ *
+ * #TpFileTransferChannel is a sub-class of #TpChannel providing convenient
+ * API to send and receive files.
+ */
+
+/**
+ * TpFileTransferChannel:
+ *
+ * Data structure representing a #TpFileTransferChannel.
+ *
+ * Since: 0.15.UNRELEASED
+ */
+
+/**
+ * TpFileTransferChannelClass:
+ *
+ * The class of a #TpFileTransferChannel.
+ *
+ * Since: 0.15.UNRELEASED
+ */
+
+#include <config.h>
+
+#include "telepathy-glib/file-transfer-channel.h"
+
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/gnio-util.h>
+#include <telepathy-glib/gtypes.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/proxy-subclass.h>
+#include <telepathy-glib/proxy-internal.h>
+#include <telepathy-glib/util-internal.h>
+#include <telepathy-glib/util.h>
+
+#define DEBUG_FLAG TP_DEBUG_CHANNEL
+#include "telepathy-glib/debug-internal.h"
+
+#include "_gen/signals-marshal.h"
+
+#include <stdio.h>
+#include <glib.h>
+#include <glib/gstdio.h>
+
+#ifdef HAVE_GIO_UNIX
+#include <gio/gunixsocketaddress.h>
+#include <gio/gunixconnection.h>
+#endif /* HAVE_GIO_UNIX */
+
+G_DEFINE_TYPE (TpFileTransferChannel, tp_file_transfer_channel, TP_TYPE_CHANNEL)
+
+struct _TpFileTransferChannelPrivate
+{
+ /* Exposed properties */
+ const gchar *content_type;
+ GDateTime *date;
+ const gchar *description;
+ const gchar *filename;
+ gsize size;
+ TpFileTransferState state;
+ TpFileTransferStateChangeReason state_reason;
+ gsize transferred_bytes;
+ GFile *file;
+
+ /* Hidden properties */
+ GHashTable *available_socket_types;
+ const gchar *content_hash;
+ TpFileHashType content_hash_type;
+ goffset initial_offset;
+
+ /* Accepting side */
+ GSocket*client_socket;
+ /* The access_control_param we passed to Accept */
+ GValue *access_control_param;
+
+ /* Offering side */
+ GSocketService *service;
+ GSocketAddress *address;
+
+ TpSocketAddressType socket_type;
+ TpSocketAccessControl access_control;
+ GSimpleAsyncResult *result;
+};
+
+enum /* properties */
+{
+ PROP_CONTENT_TYPE = 1,
+ PROP_DATE,
+ PROP_DESCRIPTION,
+ PROP_FILENAME,
+ PROP_SIZE,
+ PROP_STATE,
+ PROP_TRANSFERRED_BYTES,
+ PROP_FILE,
+ N_PROPS
+};
+
+static void
+operation_failed (TpFileTransferChannel *self,
+ const GError *error)
+{
+ g_simple_async_result_set_from_error (self->priv->result, error);
+
+ g_simple_async_result_complete (self->priv->result);
+ tp_clear_object (&self->priv->result);
+}
+
+static void
+incoming_splice_done_cb (GObject *output,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ //TpFileTransferChannel *self = (TpFileTransferChannel *) user_data;
+ gssize size;
+ GError *error = NULL;
+
+ size = g_output_stream_splice_finish (G_OUTPUT_STREAM (output), result,
+ &error);
+}
+
+static void
+client_socket_connected (TpFileTransferChannel *self)
+{
+ GSocketConnection *conn;
+ GFileOutputStream *out;
+ GInputStream *in;
+ GError *error = NULL;
+
+ conn = g_socket_connection_factory_create_connection (
+ self->priv->client_socket);
+ if (conn == NULL)
+ {
+ DEBUG ("Failed to create client connection: %s", error->message);
+ operation_failed (self, error);
+ return;
+ }
+
+ DEBUG ("File transfer socket connected");
+
+#ifdef HAVE_GIO_UNIX
+ if (self->priv->access_control == TP_SOCKET_ACCESS_CONTROL_CREDENTIALS)
+ {
+ guchar byte;
+
+ byte = g_value_get_uchar (self->priv->access_control_param);
+
+ /* FIXME: we should an async version of this API (bgo #629503) */
+ if (!tp_unix_connection_send_credentials_with_byte (
+ conn, byte, NULL, &error))
+ {
+ DEBUG ("Failed to send credentials: %s", error->message);
+
+ operation_failed (self, error);
+ g_clear_error (&error);
+ return;
+ }
+ }
+#endif
+
+ /* Get an input/output streams from the SocketConnection and the GFile */
+ in = g_io_stream_get_input_stream (G_IO_STREAM (conn));
+
+ out = g_file_replace (self->priv->file, NULL, FALSE,
+ G_FILE_CREATE_REPLACE_DESTINATION, NULL, &error);
+ if (out == NULL)
+ {
+ DEBUG ("Failed to get output stream: %s", error->message);
+ operation_failed (self, error);
+ return;
+ }
+
+ g_output_stream_splice_async (G_OUTPUT_STREAM (out), in, G_OUTPUT_STREAM_SPLICE_NONE, 0, NULL,
+ incoming_splice_done_cb, self);
+
+ DEBUG ("Leaving client_socket_connected");
+}
+
+static gboolean
+client_socket_cb (GSocket *socket,
+ GIOCondition condition,
+ TpFileTransferChannel *self)
+{
+ GError *error = NULL;
+
+ if (!g_socket_check_connect_result (socket, &error))
+ {
+ DEBUG ("Failed to connect to socket: %s", error->message);
+
+ operation_failed (self, error);
+ g_error_free (error);
+ return FALSE;
+ }
+
+ DEBUG ("Client socket connected after pending");
+ client_socket_connected (self);
+
+ return FALSE;
+}
+
+
+/* Callbacks */
+
+static void
+tp_file_transfer_channel_state_changed_cb (TpChannel *proxy,
+ guint state,
+ guint reason,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ TpFileTransferChannel *self = (TpFileTransferChannel *) proxy;
+
+ self->priv->state = state;
+ self->priv->state_reason = reason;
+ g_object_notify (G_OBJECT (self), "state");
+}
+
+static void
+tp_file_transfer_channel_initial_offset_defined_cb (TpChannel *proxy,
+ guint64 initial_offset,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ TpFileTransferChannel *self = (TpFileTransferChannel *) proxy;
+
+ self->priv->initial_offset = initial_offset;
+ g_object_notify (G_OBJECT (self), "initial-offset");
+}
+
+static void
+tp_file_transfer_channel_transferred_bytes_changed_cb (TpChannel *proxy,
+ guint64 count,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ TpFileTransferChannel *self = (TpFileTransferChannel *) proxy;
+
+ self->priv->transferred_bytes = count;
+ g_object_notify (G_OBJECT (self), "transferred-bytes");
+}
+
+static void
+tp_file_transfer_channel_uri_defined_cb (TpChannel *proxy,
+ const gchar *uri,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ TpFileTransferChannel *self = (TpFileTransferChannel *) proxy;
+
+ self->priv->file = g_file_new_for_uri (uri);
+ g_object_notify (G_OBJECT (self), "file");
+}
+
+static void
+tp_file_transfer_channel_prepare_core_cb (TpProxy *proxy,
+ GHashTable *properties,
+ const GError *error,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ TpFileTransferChannel *self = (TpFileTransferChannel *) proxy;
+ GSimpleAsyncResult *result = user_data;
+ const gchar *uri;
+
+ if (error != NULL)
+ {
+ g_simple_async_result_set_from_error (result, error);
+ goto out;
+ }
+
+ self->priv->state = tp_asv_get_uint32 (properties, "State", NULL);
+ self->priv->transferred_bytes = tp_asv_get_uint64 (properties,
+ "TransferredBytes", NULL);
+ self->priv->initial_offset = tp_asv_get_uint64 (properties, "InitialOffset",
+ NULL);
+
+ /* URI might already be set from immutable properties */
+ uri = tp_asv_get_string (properties, "URI");
+ if (self->priv->file == NULL && uri != NULL)
+ self->priv->file = g_file_new_for_uri (uri);
+
+out:
+ g_simple_async_result_complete (result);
+}
+
+static void
+accept_file_cb (TpChannel *proxy,
+ const GValue *addressv,
+ const GError *in_error,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ TpFileTransferChannel *self = (TpFileTransferChannel *) proxy;
+ GSocketAddress *remote_address;
+ GError *error = NULL;
+
+ DEBUG ("Entering accept_file_cb");
+
+ if (in_error != NULL)
+ {
+ DEBUG ("Failed to accept file: %s", in_error->message);
+
+ operation_failed (self, error);
+ return;
+ }
+
+ remote_address = tp_g_socket_address_from_variant (self->priv->socket_type,
+ addressv, &error);
+ if (remote_address == NULL)
+ {
+ DEBUG ("Failed to convert address: %s", error->message);
+ operation_failed (self, error);
+ g_error_free (error);
+ return;
+ }
+
+ /* TODO: Async? */
+ g_socket_set_blocking (self->priv->client_socket, FALSE);
+
+ /* g_socket_connect returns true on successful connection */
+ if (g_socket_connect (self->priv->client_socket, remote_address, NULL,
+ &error))
+ {
+ DEBUG ("Client socket connected immediately");
+ client_socket_connected (self);
+ goto out;
+ }
+ else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_PENDING))
+ {
+ /* The connection is pending */
+ GSource *source;
+
+ source = g_socket_create_source (self->priv->client_socket, G_IO_OUT,
+ NULL);
+
+ g_source_attach (source, g_main_context_get_thread_default ());
+ g_source_set_callback (source, (GSourceFunc) client_socket_cb, self,
+ NULL);
+
+ g_error_free (error);
+ g_source_unref (source);
+ }
+ else
+ {
+ DEBUG ("Failed to connect to socket: %s:", error->message);
+
+ operation_failed (self, error);
+ g_error_free (error);
+ }
+
+/* TODO: Don't return until the file transfer completes */
+
+out:
+ g_object_unref (remote_address);
+// g_simple_async_result_complete (self->priv->result);
+}
+
+static void
+provide_file_cb (TpChannel *proxy,
+ const GValue *addressv,
+ const GError *in_error,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ TpFileTransferChannel *self = (TpFileTransferChannel *) proxy;
+ GSocketAddress *remote_address;
+ GError *error = NULL;
+
+ DEBUG ("Entering provide_file_cb");
+
+ if (in_error != NULL)
+ {
+ DEBUG ("Failed to offer file: %s", error->message);
+
+ operation_failed (self, error);
+ return;
+ }
+
+ remote_address = tp_g_socket_address_from_variant (self->priv->socket_type,
+ addressv, &error);
+ /* FIXME: Isn't really offered (but at least we haven't crashed) */
+ DEBUG ("File offered");
+
+ g_simple_async_result_complete (self->priv->result);
+ tp_clear_object (&self->priv->result);
+}
+
+static void
+service_incoming_cb (GSocketService *service,
+ GSocketConnection *conn,
+ GObject *source_object,
+ gpointer user_data)
+{
+ TpFileTransferChannel *self = (TpFileTransferChannel *) user_data;
+ guchar byte = 0;
+
+ DEBUG ("New incoming connection");
+
+#ifdef HAVE_GIO_UNIX
+ /* Check the credentials if needed */
+ if (self->priv->access_control == TP_SOCKET_ACCESS_CONTROL_CREDENTIALS)
+ {
+ GCredentials *creds;
+ uid_t uid;
+ GError *error = NULL;
+
+ /* Should be async */
+ creds = tp_unix_connection_receive_credentials_with_byte (
+ conn, &byte, NULL, &error);
+ if (creds == NULL)
+ {
+ DEBUG ("Failed to receive credentials: %s", error->message);
+
+ g_error_free (error);
+ return;
+ }
+
+ uid = g_credentials_get_unix_user (creds, &error);
+ g_object_unref (creds);
+
+ if (uid != geteuid ())
+ {
+ DEBUG ("Wrong credentials received (user: %u)", uid);
+
+ return;
+ }
+ }
+#endif /* HAVE_GIO_UNIX */
+}
+
+/* Private methods */
+
+static void
+tp_file_transfer_channel_prepare_core_async (TpProxy *proxy,
+ const TpProxyFeature *feature,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ TpFileTransferChannel *self = (TpFileTransferChannel *) proxy;
+ TpChannel *channel = (TpChannel *) proxy;
+ GSimpleAsyncResult *result;
+
+ tp_cli_channel_type_file_transfer_connect_to_file_transfer_state_changed (
+ channel, tp_file_transfer_channel_state_changed_cb,
+ NULL, NULL, NULL, NULL);
+ tp_cli_channel_type_file_transfer_connect_to_initial_offset_defined (
+ channel, tp_file_transfer_channel_initial_offset_defined_cb,
+ NULL, NULL, NULL, NULL);
+ tp_cli_channel_type_file_transfer_connect_to_transferred_bytes_changed (
+ channel, tp_file_transfer_channel_transferred_bytes_changed_cb,
+ NULL, NULL, NULL, NULL);
+ tp_cli_channel_type_file_transfer_connect_to_uri_defined (
+ channel, tp_file_transfer_channel_uri_defined_cb,
+ NULL, NULL, NULL, NULL);
+
+ result = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
+ tp_file_transfer_channel_prepare_core_async);
+
+ tp_cli_dbus_properties_call_get_all (self, -1,
+ TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER,
+ tp_file_transfer_channel_prepare_core_cb,
+ result, g_object_unref,
+ NULL);
+}
+
+static void
+tp_file_transfer_channel_constructed (GObject *obj)
+{
+ TpFileTransferChannel *self = (TpFileTransferChannel *) obj;
+ GHashTable *properties;
+ const gchar *uri;
+
+ G_OBJECT_CLASS (tp_file_transfer_channel_parent_class)->constructed (obj);
+
+ properties = tp_channel_borrow_immutable_properties (TP_CHANNEL (self));
+
+ self->priv->content_type = tp_asv_get_string (properties,
+ TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_CONTENT_TYPE);
+ self->priv->filename = tp_asv_get_string (properties,
+ TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_FILENAME);
+ self->priv->size = tp_asv_get_uint64 (properties,
+ TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_SIZE, FALSE);
+ self->priv->content_hash_type = tp_asv_get_uint32 (properties,
+ TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_CONTENT_HASH_TYPE, FALSE);
+ self->priv->content_hash = tp_asv_get_string (properties,
+ TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_CONTENT_HASH);
+ self->priv->description = tp_asv_get_string (properties,
+ TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_DESCRIPTION);
+ self->priv->date = g_date_time_new_from_unix_utc (tp_asv_get_int64
+ (properties, TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_DATE, FALSE));
+ self->priv->available_socket_types = tp_asv_get_boxed (properties,
+ TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_AVAILABLE_SOCKET_TYPES,
+ TP_HASH_TYPE_SUPPORTED_SOCKET_MAP);
+
+ /* URI might be immutable */
+ uri = tp_asv_get_string (properties, TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_URI);
+ if (uri != NULL)
+ self->priv->file = g_file_new_for_uri (uri);
+}
+
+static void
+tp_file_transfer_channel_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ TpFileTransferChannel *self = (TpFileTransferChannel *) object;
+
+ switch (property_id)
+ {
+ case PROP_CONTENT_TYPE:
+ g_value_set_string (value, self->priv->content_type);
+ break;
+
+ case PROP_DATE:
+ g_value_set_boxed (value, self->priv->date);
+ break;
+
+ case PROP_DESCRIPTION:
+ g_value_set_string (value, self->priv->description);
+ break;
+
+ case PROP_FILENAME:
+ g_value_set_string (value, self->priv->filename);
+ break;
+
+ case PROP_SIZE:
+ g_value_set_uint64 (value, self->priv->size);
+ break;
+
+ case PROP_STATE:
+ g_value_set_uint (value, self->priv->state);
+ break;
+
+ case PROP_TRANSFERRED_BYTES:
+ g_value_set_uint64 (value, self->priv->transferred_bytes);
+ break;
+
+ case PROP_FILE:
+ g_value_set_object (value, self->priv->file);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+enum /* features */
+{
+ FEAT_CORE,
+ N_FEAT
+};
+
+static const TpProxyFeature *
+tp_file_transfer_channel_list_features (TpProxyClass *cls G_GNUC_UNUSED)
+{
+ static TpProxyFeature features[N_FEAT + 1] = { { 0 } };
+
+ if (G_LIKELY (features[0].name != 0))
+ return features;
+
+ features[FEAT_CORE].name = TP_FILE_TRANSFER_CHANNEL_FEATURE_CORE;
+ features[FEAT_CORE].core = TRUE;
+ features[FEAT_CORE].prepare_async =
+ tp_file_transfer_channel_prepare_core_async;
+
+ /* Assert that the terminator at the end is present */
+ g_assert (features[N_FEAT].name == 0);
+
+ return features;
+}
+
+static void
+tp_file_transfer_channel_finalize (GObject *obj)
+{
+ TpFileTransferChannel *self = (TpFileTransferChannel *) obj;
+ tp_clear_pointer (&self->priv->date, g_date_time_unref);
+ g_clear_object (&self->priv->file);
+
+ if (self->priv->service != NULL)
+ {
+ g_socket_service_stop (self->priv->service);
+ tp_clear_object (&self->priv->service);
+ }
+
+ if (self->priv->address != NULL)
+ {
+#ifdef HAVE_GIO_UNIX
+ /* Check if we need to remove our temp file */
+ if (G_IS_UNIX_SOCKET_ADDRESS (self->priv->address))
+ {
+ const gchar *path;
+
+ path = g_unix_socket_address_get_path (
+ G_UNIX_SOCKET_ADDRESS (self->priv->address));
+ g_unlink (path);
+ }
+#endif /* HAVE_GIO_UNIX */
+ g_object_unref (self->priv->address);
+ self->priv->address = NULL;
+ }
+
+ tp_clear_pointer (&self->priv->access_control_param, tp_g_value_slice_free);
+ tp_clear_object (&self->priv->client_socket);
+
+ G_OBJECT_CLASS (tp_file_transfer_channel_parent_class)->finalize (obj);
+}
+
+/*
+static void
+tp_file_transfer_channel_dispose (GObject *obj)
+{
+ G_OBJECT_CLASS (tp_file_transfer_channel_parent_class)->dispose (obj);
+}
+*/
+
+static void
+tp_file_transfer_channel_class_init (TpFileTransferChannelClass *klass)
+{
+ GParamSpec *param_spec;
+ TpProxyClass *proxy_class = (TpProxyClass *) klass;
+ GObjectClass *object_class = (GObjectClass *) klass;
+
+ object_class->constructed = tp_file_transfer_channel_constructed;
+ object_class->get_property = tp_file_transfer_channel_get_property;
+ //object_class->dispose = tp_file_transfer_channel_dispose;
+ object_class->finalize = tp_file_transfer_channel_finalize;
+
+ proxy_class->list_features = tp_file_transfer_channel_list_features;
+
+ /* Properties */
+
+ /**
+ * TpFileTransferChannel:content-type:
+ *
+ * A string containing the content type of the file to be transferred.
+ *
+ * Since: 0.15.UNRELEASED
+ */
+ param_spec = g_param_spec_string ("content-type",
+ "ContentType",
+ "The ContentType property of this channel",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONTENT_TYPE,
+ param_spec);
+
+ /**
+ * TpFileTransferChannel:date
+ *
+ * A #GDateTime holding the last modification time of the file to be
+ * transferred.
+ *
+ * Since 0.15.UNRELEASED
+ *
+ */
+ param_spec = g_param_spec_boxed ("date",
+ "Date",
+ "The Date property of this channel",
+ G_TYPE_DATE_TIME,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_DATE,
+ param_spec);
+
+ /**
+ * TpFileTransferChannel:description
+ *
+ * A string holding the description of the file transfer.
+ *
+ * Since 0.15.UNRELEASED
+ *
+ */
+ param_spec = g_param_spec_string ("description",
+ "Description",
+ "The Description property of this channel",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_DESCRIPTION,
+ param_spec);
+
+ /**
+ * TpFileTransferChannel:file
+ *
+ * For incoming, this property may be set to the location where the file
+ * will be saved once the transfer starts. The feature
+ * %TP_FILE_TRANSFER_CHANNEL_FEATURE_CORE must already be prepared for this
+ * property to have a meaningful value, and to receive change notification.
+ * Once the initial value is set, this can no be changed.
+ *
+ * For outgoing, this property may be set to the location of the file being
+ * sent. The feature %TP_FILE_TRANSFER_CHANNEL_FEATURE_CORE does not have
+ * to be prepared and there is no change notification.
+ *
+ * Since: 0.15.UNRELEASED
+ */
+ param_spec = g_param_spec_object ("file" ,
+ "File",
+ "A GFile corresponding to the URI property of this channel",
+ G_TYPE_FILE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_FILE,
+ param_spec);
+
+ /**
+ * TpFileTransferChannel:filename
+ *
+ * A string containing the filename of the file to be transferred.
+ *
+ * Since 0.15.UNRELEASED
+ */
+ param_spec = g_param_spec_string ("filename",
+ "Filename",
+ "The Filename property of this channel",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_FILENAME,
+ param_spec);
+
+ /**
+ * TpFileTransferChannel:size
+ *
+ * A 64-bit guint holding the size of the file to be transferred.
+ *
+ * Since 0.15.UNRELEASED
+ */
+ param_spec = g_param_spec_uint64 ("size",
+ "Size",
+ "The Size property of this channel",
+ 0, G_MAXUINT64, 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_SIZE,
+ param_spec);
+
+ /**
+ * TpFileTransferChannel:state
+ *
+ * A TpFileTransferState holding the state of the file transfer.
+ *
+ * Since 0.15.UNRELEASED
+ */
+ param_spec = g_param_spec_uint ("state",
+ "State",
+ "The TpFileTransferState of the channel",
+ 0, NUM_TP_FILE_TRANSFER_STATES, TP_FILE_TRANSFER_STATE_NONE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_STATE,
+ param_spec);
+
+ /**
+ * TpFileTransferChannel:transferred-bytes
+ *
+ * A 64-bit guint holding the number of bytes transferred so far in this
+ * file transfer.
+ *
+ * Since: 0.15.UNRELEASED
+ */
+ param_spec = g_param_spec_uint64 ("transferred-bytes",
+ "TransferredBytes",
+ "The TransferredBytes property of this channel",
+ 0, G_MAXUINT64, 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_TRANSFERRED_BYTES,
+ param_spec);
+
+ g_type_class_add_private (object_class, sizeof
+ (TpFileTransferChannelPrivate));
+}
+
+static void
+tp_file_transfer_channel_init (TpFileTransferChannel *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self),
+ TP_TYPE_FILE_TRANSFER_CHANNEL, TpFileTransferChannelPrivate);
+}
+
+/**
+ * TP_FILE_TRANSFER_CHANNEL_FEATURE_CORE:
+ *
+ * Expands to a call to a function that returns a quark for the "core"
+ * feature on a #TpFileTransferChannel.
+ *
+ * When this feature is prepared, the basic properties of the
+ * FileTransferChannel have been retrieved and are available for use, and
+ * change notification has been set up.
+ *
+ * One can ask for a feature to be prepared using the tp_proxy_prepare_async()
+ * function, and waiting for it to trigger the callback.
+ *
+ * Since: 0.15.UNRELEASED
+ */
+
+GQuark
+tp_file_transfer_channel_get_feature_quark_core (void)
+{
+ return g_quark_from_static_string ("tp-file-transfer-channel-feature-core");
+}
+
+
+/* Public methods */
+
+/**
+ * tp_file_transfer_channel_new:
+ * @conn: a #TpConnection; may not be %NULL
+ * @object_path: the object path of the channel; may not be %NULL
+ * @immutable_properties: (transfer none) (element-type utf8 GObject.Value):
+ * the immutable properties of the channel,
+ * as signalled by the NewChannel D-Bus signal or returned by the
+ * CreateChannel and EnsureChannel D-Bus methods: a mapping from
+ * strings (D-Bus interface name + "." + property name) to #GValue instances
+ * @error: used to indicate the error if %NULL is returned
+ *
+ * Convenient function to create a new #TpFileTransferChannel
+ *
+ * Returns: (transfer full): a newly created #TpFileTransferChannel
+ *
+ * Since: 0.15.UNRELEASED
+ */
+TpFileTransferChannel *
+tp_file_transfer_channel_new (TpConnection *conn,
+ const gchar *object_path,
+ const GHashTable *immutable_properties,
+ GError **error)
+{
+ TpProxy *conn_proxy = (TpProxy *) conn;
+
+ g_return_val_if_fail (TP_IS_CONNECTION (conn), NULL);
+ g_return_val_if_fail (object_path != NULL, NULL);
+ g_return_val_if_fail (immutable_properties != NULL, NULL);
+
+ if (!tp_dbus_check_valid_object_path (object_path, error))
+ return NULL;
+
+ return g_object_new (TP_TYPE_FILE_TRANSFER_CHANNEL,
+ "connection", conn,
+ "dbus-daemon", conn_proxy->dbus_daemon,
+ "bus-name", conn_proxy->bus_name,
+ "object-path", object_path,
+ "handle-type", (guint) TP_UNKNOWN_HANDLE_TYPE,
+ "channel-properties", immutable_properties,
+ NULL);
+}
+
+/**
+ * tp_file_transfer_channel_accept_file_async:
+ * @self: a #TpFileTransferChannel
+ * @file: a #GFile
+ * @offset: Offset from the start of @file where transfer begins
+ * @callback: a callback to call when the transfer has been accepted
+ * @user_data: data to pass to @callback
+ *
+ * Accept an offered file transfer. Once the accept has been processed,
+ * @callback will be called. You can then call
+ * tp_file_transfer_channel_accept_file_finish() to get the result of
+ * the operation.
+ *
+ * Since: 0.15.UNRELEASED
+ */
+void
+tp_file_transfer_channel_accept_file_async (TpFileTransferChannel *self,
+ GFile *file,
+ guint64 offset,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GHashTable *properties;
+ GHashTable *supported_sockets;
+ GError *error = NULL;
+
+ g_return_if_fail (TP_IS_FILE_TRANSFER_CHANNEL (self));
+ g_return_if_fail (G_IS_FILE (file));
+
+ if (self->priv->access_control_param != NULL)
+ {
+ g_simple_async_report_error_in_idle (G_OBJECT (self), callback,
+ user_data, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Can't accept already accepted transfer");
+
+ return;
+ }
+
+ if (self->priv->state != TP_FILE_TRANSFER_STATE_PENDING)
+ {
+ g_simple_async_report_error_in_idle (G_OBJECT (self), callback,
+ user_data, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Can't accept a transfer that isn't pending");
+
+ return;
+ }
+
+ if (tp_channel_get_requested (TP_CHANNEL (self)))
+ {
+ g_simple_async_report_error_in_idle (G_OBJECT (self), callback,
+ user_data, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Can't accept outgoing transfer");
+
+ return;
+ }
+
+ self->priv->result = g_simple_async_result_new (G_OBJECT (self), callback,
+ user_data, tp_file_transfer_channel_accept_file_async);
+
+ properties = tp_channel_borrow_immutable_properties (TP_CHANNEL (self));
+ supported_sockets = tp_asv_get_boxed (properties,
+ TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_AVAILABLE_SOCKET_TYPES,
+ TP_HASH_TYPE_SUPPORTED_SOCKET_MAP);
+
+ if (!_tp_set_socket_address_type_and_access_control_type (supported_sockets,
+ &self->priv->socket_type, &self->priv->access_control, &error))
+ {
+ operation_failed (self, error);
+
+ g_clear_error (&error);
+ return;
+ }
+
+ DEBUG ("Using socket type %u with access control %u",
+ self->priv->socket_type, self->priv->access_control);
+
+ self->priv->client_socket =
+ _tp_create_client_socket (self->priv->socket_type, &error);
+ if (self->priv->client_socket == NULL)
+ {
+ DEBUG ("Failed to create socket: %s", error->message);
+
+ operation_failed (self, error);
+ g_clear_error (&error);
+ return;
+ }
+
+ switch (self->priv->access_control)
+ {
+ case TP_SOCKET_ACCESS_CONTROL_LOCALHOST:
+ /* Dummy value */
+ self->priv->access_control_param = tp_g_value_slice_new_uint (0);
+ break;
+
+ case TP_SOCKET_ACCESS_CONTROL_PORT:
+ {
+ GSocketAddress *addr;
+ guint16 port;
+
+ addr = g_socket_get_local_address (self->priv->client_socket,
+ &error);
+ if (addr == NULL)
+ {
+ DEBUG ("Failed to get address of local socket: %s",
+ error->message);
+
+ operation_failed (self, error);
+ g_error_free (error);
+ return;
+ }
+
+ port = g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS
+ (addr));
+ self->priv->access_control_param = tp_g_value_slice_new_uint (port);
+
+ g_object_unref (addr);
+ }
+ break;
+
+ case TP_SOCKET_ACCESS_CONTROL_CREDENTIALS:
+ self->priv->access_control_param = tp_g_value_slice_new_byte (
+ g_random_int_range (0, G_MAXUINT8));
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+
+ /* FIXME: What if an approver already set a file? */
+ g_clear_object (&self->priv->file);
+ self->priv->file = g_object_ref (file);
+
+ /* Call accept */
+ tp_cli_channel_type_file_transfer_call_accept_file (TP_CHANNEL (self), -1,
+ self->priv->socket_type,
+ self->priv->access_control,
+ self->priv->access_control_param,
+ self->priv->initial_offset,
+ accept_file_cb,
+ NULL,
+ NULL,
+ G_OBJECT (self));
+}
+
+/**
+ * tp_file_transfer_channel_accept_file_finish:
+ * @self: a #TpFileTransferChannel
+ * @result: a #GAsyncResult
+ * @error: a #GError to fill
+ *
+ * Finishes a file transfer accept.
+ *
+ * Since: 0.15.UNRELEASED
+ */
+gboolean
+tp_file_transfer_channel_accept_file_finish (TpFileTransferChannel *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ DEBUG ("Entering tp_file_transfer_channel_accept_file_finish");
+ _tp_implement_finish_void (self, tp_file_transfer_channel_accept_file_async)
+}
+
+/**
+ * tp_file_transfer_channel_offer_file_async:
+ * @self: a #TpFileTransferChannel
+ * @file: a #GFile
+ * @callback: a callback to call when the transfer has been accepted
+ * @user_data: data to pass to @callback
+ *
+ * Offer a file transfer. Once the offer has been sent, @callback will be
+ * called. You can then call tp_file_transfer_channel_offer_file_finish()
+ * to get the result of the operation.
+ *
+ * Since: 0.15.UNRELEASED
+ */
+void
+tp_file_transfer_channel_offer_file_async (TpFileTransferChannel *self,
+ GFile *file,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GHashTable *properties;
+ GHashTable *supported_sockets;
+ GError *error = NULL;
+
+ DEBUG ("Entering tp_file_transfer_channel_offer_file_async");
+
+ g_return_if_fail (TP_IS_FILE_TRANSFER_CHANNEL (self));
+ g_return_if_fail (G_IS_FILE (file));
+ g_return_if_fail (tp_channel_get_requested (TP_CHANNEL (self)));
+
+ self->priv->file = g_object_ref (file);
+
+ self->priv->result = g_simple_async_result_new (G_OBJECT (self), callback,
+ user_data, tp_file_transfer_channel_offer_file_async);
+
+ properties = tp_channel_borrow_immutable_properties (TP_CHANNEL (self));
+ supported_sockets = tp_asv_get_boxed (properties,
+ TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_AVAILABLE_SOCKET_TYPES,
+ TP_HASH_TYPE_SUPPORTED_SOCKET_MAP);
+
+ if (!_tp_set_socket_address_type_and_access_control_type (supported_sockets,
+ &self->priv->socket_type, &self->priv->access_control, &error))
+ {
+ operation_failed (self, error);
+
+ g_clear_error (&error);
+ return;
+ }
+
+ DEBUG ("Using socket type %u with access control %u",
+ self->priv->socket_type, self->priv->access_control);
+
+ self->priv->service = g_socket_service_new ();
+
+ switch (self->priv->socket_type)
+ {
+#ifdef HAVE_GIO_UNIX
+ case TP_SOCKET_ADDRESS_TYPE_UNIX:
+ case TP_SOCKET_ADDRESS_TYPE_ABSTRACT_UNIX:
+ {
+ self->priv->address = _tp_create_temp_unix_socket (
+ self->priv->service, &error);
+
+ if (self->priv->address == NULL)
+ {
+ operation_failed (self, error);
+
+ g_clear_error (&error);
+ return;
+ }
+
+ break;
+ }
+#endif /* HAVE_GIO_UNIX */
+
+ case TP_SOCKET_ADDRESS_TYPE_IPV6:
+ case TP_SOCKET_ADDRESS_TYPE_IPV4:
+ {
+ GInetAddress *localhost;
+ GSocketAddress *in_address;
+
+ localhost = g_inet_address_new_loopback (
+ self->priv->socket_type == TP_SOCKET_ADDRESS_TYPE_IPV4 ?
+ G_SOCKET_FAMILY_IPV4 : G_SOCKET_FAMILY_IPV6);
+
+ in_address = g_inet_socket_address_new (localhost, 0);
+
+ g_socket_listener_add_address (
+ G_SOCKET_LISTENER (self->priv->service), in_address,
+ G_SOCKET_TYPE_STREAM, G_SOCKET_PROTOCOL_DEFAULT,
+ NULL, &self->priv->address, &error);
+
+ g_object_unref (localhost);
+ g_object_unref (in_address);
+
+ if (error != NULL)
+ {
+ operation_failed (self, error);
+
+ g_clear_error (&error);
+ return;
+ }
+
+ break;
+ }
+
+ default:
+ /* should have already errored */
+ g_assert_not_reached ();
+ break;
+ }
+
+ tp_g_signal_connect_object (self->priv->service, "incoming",
+ G_CALLBACK (service_incoming_cb), self, 0);
+
+ g_socket_service_start (self->priv->service);
+
+ DEBUG ("Calling ProvideFile");
+
+ /* Call provide */
+ tp_cli_channel_type_file_transfer_call_provide_file (TP_CHANNEL (self), -1,
+ self->priv->socket_type,
+ self->priv->access_control,
+ self->priv->access_control_param,
+ provide_file_cb,
+ NULL, NULL, NULL);
+}
+
+/**
+ * tp_file_transfer_channel_offer_file_finish:
+ * @self: a #TpFileTransferChannel
+ * @result: a #GAsyncResult
+ * @error: a #GError to fill
+ *
+ * Finishes a file transfer offer.
+ *
+ * Returns: %TRUE if the file has been successfully offered, or %FALSE.
+ *
+ * Since: 0.15.UNRELEASED
+ */
+gboolean
+tp_file_transfer_channel_offer_file_finish (TpFileTransferChannel *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ DEBUG ("Entering tp_file_transfer_channel_offer_file_finish");
+ _tp_implement_finish_void (self, tp_file_transfer_channel_offer_file_async)
+}
+
+
+/* Property accessors */
+
+/**
+ * tp_file_transfer_channel_get_content_type
+ * @self: a #TpFileTransferChannel
+ *
+ * Returns: the value of the content-type property
+ *
+ * Since: 0.15.UNRELEASED
+ */
+const char *
+tp_file_transfer_channel_get_content_type (TpFileTransferChannel *self)
+{
+ g_return_val_if_fail (TP_IS_FILE_TRANSFER_CHANNEL (self), NULL);
+
+ return self->priv->content_type;
+}
+
+/**
+ * tp_file_transfer_channel_get_date
+ * @self: a #TpFileTransferChannel
+ *
+ * Returns: a #GDateTime with the value of the date property. The returned
+ * object should be unreffed with #g_date_time_unref().
+ *
+ * Since: 0.15.UNRELEASED
+ */
+GDateTime *
+tp_file_transfer_channel_get_date (TpFileTransferChannel *self)
+{
+ g_return_val_if_fail (TP_IS_FILE_TRANSFER_CHANNEL (self), NULL);
+
+ return self->priv->date;
+}
+
+/**
+ * tp_file_transfer_channel_get_description
+ * @self: a #TpFileTransferChannel
+ *
+ * Returns: the value of the description property or en empty string.
+ *
+ * Since: 0.15.UNRELEASED
+ */
+const gchar *
+tp_file_transfer_channel_get_description (TpFileTransferChannel *self)
+{
+ g_return_val_if_fail (TP_IS_FILE_TRANSFER_CHANNEL (self), NULL);
+
+ return self->priv->description;
+}
+
+/**
+ * tp_file_transfer_channel_get_filename
+ * @self: a #TpFileTransferChannel
+ *
+ * Returns: the value of the filename property
+ *
+ * Since: 0.15.UNRELEASED
+ */
+const gchar *
+tp_file_transfer_channel_get_filename (TpFileTransferChannel *self)
+{
+ g_return_val_if_fail (TP_IS_FILE_TRANSFER_CHANNEL (self), NULL);
+
+ return self->priv->filename;
+}
+
+/**
+ * tp_file_transfer_channel_get_size
+ * @self: a #TpFileTransferChannel
+ *
+ * Returns: the value of the size property
+ *
+ * Since: 0.15.UNRELEASED
+ */
+guint64
+tp_file_transfer_channel_get_size (TpFileTransferChannel *self)
+{
+ g_return_val_if_fail (TP_IS_FILE_TRANSFER_CHANNEL (self), 0);
+
+ return self->priv->size;
+}
+
+/**
+ * tp_file_transfer_channel_get_state
+ * @self: a #TpFileTransferChannel
+ * @reason: (out): a #TpFileTransferStateChangeReason, or %NULL
+ *
+ * If @reason is not %NULL it is set to the reason why
+ * #TpFileTransferChannel:state changed to its current value.
+ *
+ * Returns: the value of the #TpFileTransferState:state property
+ *
+ * Since: 0.15.UNRELEASED
+ */
+TpFileTransferState
+tp_file_transfer_channel_get_state (TpFileTransferChannel *self,
+ TpFileTransferStateChangeReason *reason)
+{
+ g_return_val_if_fail (TP_IS_FILE_TRANSFER_CHANNEL (self),
+ TP_FILE_TRANSFER_STATE_NONE);
+
+ if (reason != NULL)
+ *reason = self->priv->state_reason;
+
+ return self->priv->state;
+}
+
+/**
+ * tp_file_transfer_channel_get_transferred_bytes
+ * @self: a #TpFileTransferChannel
+ *
+ * Returns: the value of the transferred-bytes property
+ *
+ * Since: 0.15.UNRELEASED
+ */
+guint64
+tp_file_transfer_channel_get_transferred_bytes (TpFileTransferChannel *self)
+{
+ g_return_val_if_fail (TP_IS_FILE_TRANSFER_CHANNEL (self), 0);
+
+ return self->priv->transferred_bytes;
+}
diff --git a/telepathy-glib/file-transfer-channel.h b/telepathy-glib/file-transfer-channel.h
new file mode 100644
index 00000000..14691b21
--- /dev/null
+++ b/telepathy-glib/file-transfer-channel.h
@@ -0,0 +1,110 @@
+/*
+ * file-transfer-channel.h - high level API for File Transfer channels
+ *
+ * Copyright (C) 2010 Morten Mjelva <morten.mjelva@gmail.com>
+ *
+ * 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_CHANNEL_H__
+#define __TP_FILE_TRANSFER_CHANNEL_H__
+
+#include <telepathy-glib/channel.h>
+
+G_BEGIN_DECLS
+
+
+#define TP_TYPE_FILE_TRANSFER_CHANNEL (tp_file_transfer_channel_get_type ())
+#define TP_FILE_TRANSFER_CHANNEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TP_TYPE_FILE_TRANSFER_CHANNEL, TpFileTransferChannel))
+#define TP_FILE_TRANSFER_CHANNEL_CLASS(obj) (G_TYPE_CHECK_CLASS_CAST ((obj), TP_TYPE_FILE_TRANSFER_CHANNEL, TpFileTransferChannelClass))
+#define TP_IS_FILE_TRANSFER_CHANNEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TP_TYPE_FILE_TRANSFER_CHANNEL))
+#define TP_IS_FILE_TRANSFER_CHANNEL_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE ((obj), TP_TYPE_FILE_TRANSFER_CHANNEL))
+#define TP_FILE_TRANSFER_CHANNEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TP_TYPE_FILE_TRANSFER_CHANNEL, TpFileTransferChannelClass))
+
+typedef struct _TpFileTransferChannel TpFileTransferChannel;
+typedef struct _TpFileTransferChannelClass TpFileTransferChannelClass;
+typedef struct _TpFileTransferChannelPrivate TpFileTransferChannelPrivate;
+
+struct _TpFileTransferChannel
+{
+ /*<private>*/
+ TpChannel parent;
+ TpFileTransferChannelPrivate *priv;
+};
+
+struct _TpFileTransferChannelClass
+{
+ /*<private>*/
+ TpChannelClass parent_class;
+ GCallback _padding[8];
+};
+
+#define TP_FILE_TRANSFER_CHANNEL_FEATURE_CORE tp_file_transfer_channel_get_feature_quark_core ()
+GQuark tp_file_transfer_channel_get_feature_quark_core (void) G_GNUC_CONST;
+
+GType tp_file_transfer_channel_get_type (void);
+
+/* Methods */
+
+TpFileTransferChannel *tp_file_transfer_channel_new (TpConnection *conn,
+ const gchar *object_path,
+ const GHashTable *immutable_properties,
+ GError **error);
+
+void tp_file_transfer_channel_accept_file_async (TpFileTransferChannel *self,
+ GFile *file,
+ guint64 offset,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean tp_file_transfer_channel_accept_file_finish (TpFileTransferChannel *self,
+ GAsyncResult *result,
+ GError **error);
+
+void tp_file_transfer_channel_offer_file_async (TpFileTransferChannel *self,
+ GFile *file,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean tp_file_transfer_channel_offer_file_finish (TpFileTransferChannel *self,
+ GAsyncResult *result,
+ GError **error);
+
+/* Property accessors */
+
+const char *tp_file_transfer_channel_get_content_type (
+ TpFileTransferChannel *self);
+
+GDateTime *tp_file_transfer_channel_get_date (TpFileTransferChannel *self);
+
+TpFileTransferState tp_file_transfer_channel_get_state (TpFileTransferChannel *self,
+ TpFileTransferStateChangeReason *reason);
+
+const gchar *tp_file_transfer_channel_get_description (
+ TpFileTransferChannel *self);
+
+const gchar *tp_file_transfer_channel_get_filename (
+ TpFileTransferChannel *self);
+
+guint64 tp_file_transfer_channel_get_size (TpFileTransferChannel *self);
+
+guint64 tp_file_transfer_channel_get_transferred_bytes (
+ TpFileTransferChannel *self);
+
+const gchar *tp_file_transfer_channel_get_uri (TpFileTransferChannel *self);
+
+G_END_DECLS
+
+#endif
diff --git a/telepathy-glib/introspection.am b/telepathy-glib/introspection.am
index f75e1584..8e36e5bf 100644
--- a/telepathy-glib/introspection.am
+++ b/telepathy-glib/introspection.am
@@ -52,6 +52,7 @@ INTROSPECTION_FILES = \
$(srcdir)/stream-tube-channel.c $(srcdir)/stream-tube-channel.h \
$(srcdir)/stream-tube-connection.c $(srcdir)/stream-tube-connection.h \
$(srcdir)/text-channel.c $(srcdir)/text-channel.h \
+ $(srcdir)/file-transfer-channel.c $(srcdir)/file-transfer-channel.h \
$(srcdir)/client-message.c $(srcdir)/client-message.h \
$(srcdir)/message.c $(srcdir)/message.h \
$(srcdir)/signalled-message.c $(srcdir)/signalled-message.h \