summaryrefslogtreecommitdiff
path: root/wocky/wocky-meta-porter.c
diff options
context:
space:
mode:
Diffstat (limited to 'wocky/wocky-meta-porter.c')
-rw-r--r--wocky/wocky-meta-porter.c1696
1 files changed, 1696 insertions, 0 deletions
diff --git a/wocky/wocky-meta-porter.c b/wocky/wocky-meta-porter.c
new file mode 100644
index 0000000..e3a8d5f
--- /dev/null
+++ b/wocky/wocky-meta-porter.c
@@ -0,0 +1,1696 @@
+/*
+ * wocky-meta-porter.c - Source for WockyMetaPorter
+ * Copyright (C) 2011 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 tubesplied 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 "wocky-meta-porter.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "wocky-ll-connection-factory.h"
+#include "wocky-contact-factory.h"
+#include "wocky-c2s-porter.h"
+#include "wocky-utils.h"
+#include "wocky-ll-contact.h"
+#include "wocky-ll-connector.h"
+#include "wocky-loopback-stream.h"
+
+#define DEBUG_FLAG DEBUG_PORTER
+#include "wocky-debug.h"
+
+static void wocky_porter_iface_init (gpointer g_iface,
+ gpointer iface_data);
+
+G_DEFINE_TYPE_WITH_CODE (WockyMetaPorter, wocky_meta_porter, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (WOCKY_TYPE_PORTER,
+ wocky_porter_iface_init));
+
+/* properties */
+enum
+{
+ PROP_JID = 1,
+ PROP_CONTACT_FACTORY,
+ PROP_CONNECTION,
+ PROP_RESOURCE,
+};
+
+/* private structure */
+struct _WockyMetaPorterPrivate
+{
+ gchar *jid;
+ WockyContactFactory *contact_factory;
+ WockyLLConnectionFactory *connection_factory;
+
+ /* owned (gchar *) jid => owned (PorterData *) */
+ GHashTable *porters;
+
+ /* guint handler id => owned (StanzaHandler *) */
+ GHashTable *handlers;
+
+ GSocketService *listener;
+
+ guint16 port;
+
+ guint next_handler_id;
+};
+
+typedef struct
+{
+ WockyMetaPorter *self;
+ WockyContact *contact;
+ /* owned */
+ WockyPorter *porter;
+ /* also owned, for convenience */
+ gchar *jid;
+ guint refcount;
+ guint timeout_id;
+} PorterData;
+
+typedef struct
+{
+ WockyMetaPorter *self;
+ WockyContact *contact;
+
+ /* weak reffed WockyPorter* => handler ID */
+ GHashTable *porters;
+
+ WockyStanzaType type;
+ WockyStanzaSubType sub_type;
+ guint priority;
+ WockyPorterHandlerFunc callback;
+ gpointer user_data;
+ WockyStanza *stanza;
+} StanzaHandler;
+
+GQuark
+wocky_meta_porter_error_quark (void)
+{
+ static GQuark quark = 0;
+
+ if (!quark)
+ quark = g_quark_from_static_string (
+ "wocky_meta_porter_error");
+
+ return quark;
+}
+
+static void register_porter_handlers (WockyMetaPorter *self,
+ WockyPorter *porter, WockyContact *contact);
+
+static void
+porter_data_free (gpointer data)
+{
+ PorterData *p = data;
+
+ if (p->porter != NULL)
+ g_object_unref (p->porter);
+
+ if (p->timeout_id > 0)
+ g_source_remove (p->timeout_id);
+
+ g_free (p->jid);
+
+ g_slice_free (PorterData, data);
+}
+
+static void
+porter_closed_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ WockyPorter *porter = WOCKY_PORTER (source_object);
+ GError *error = NULL;
+ PorterData *data = user_data;
+
+ if (!wocky_porter_close_finish (porter, result, &error))
+ {
+ DEBUG ("Failed to close porter to '%s': %s", data->jid, error->message);
+ g_clear_error (&error);
+ }
+ else
+ {
+ DEBUG ("Closed porter to '%s'", data->jid);
+ }
+
+ porter_data_free (data);
+}
+
+static gboolean
+porter_timeout_cb (gpointer d)
+{
+ PorterData *data = d;
+ WockyMetaPorterPrivate *priv = data->self->priv;
+
+ data->timeout_id = 0;
+
+ g_hash_table_steal (priv->porters, data->contact);
+
+ /* we need to unref this ourselves as we just stole it from the hash
+ * table */
+ g_object_unref (data->contact);
+
+ if (data->porter != NULL)
+ wocky_porter_close_async (data->porter, NULL, porter_closed_cb, data);
+ else
+ porter_data_free (data);
+
+ return FALSE;
+}
+
+static void porter_remote_closed_cb (WockyPorter *porter, PorterData *data);
+
+static void
+porter_closing_cb (WockyPorter *porter,
+ PorterData *data)
+{
+ DEBUG ("porter to '%s' closing, remove it from our records", data->jid);
+
+ if (data->timeout_id > 0)
+ g_source_remove (data->timeout_id);
+
+ data->timeout_id = 0;
+
+ g_signal_handlers_disconnect_by_func (porter,
+ porter_remote_closed_cb, data);
+ g_signal_handlers_disconnect_by_func (porter,
+ porter_closing_cb, data);
+
+ if (data->porter != NULL)
+ g_object_unref (data->porter);
+ data->porter = NULL;
+}
+
+static void
+porter_remote_closed_cb (WockyPorter *porter,
+ PorterData *data)
+{
+ DEBUG ("porter closed by remote, remove it from our records");
+
+ if (data->timeout_id > 0)
+ g_source_remove (data->timeout_id);
+
+ data->timeout_id = 0;
+
+ g_signal_handlers_disconnect_by_func (porter,
+ porter_remote_closed_cb, data);
+ g_signal_handlers_disconnect_by_func (porter,
+ porter_closing_cb, data);
+
+ if (data->porter != NULL)
+ g_object_unref (data->porter);
+ data->porter = NULL;
+}
+
+static void
+maybe_start_timeout (PorterData *data)
+{
+ if (data->refcount == 0)
+ {
+ DEBUG ("Started porter timeout...");
+ data->timeout_id = g_timeout_add_seconds (5, porter_timeout_cb, data);
+ }
+}
+
+static WockyPorter *
+create_porter (WockyMetaPorter *self,
+ WockyXmppConnection *connection,
+ WockyContact *contact)
+{
+ WockyMetaPorterPrivate *priv = self->priv;
+ PorterData *data;
+
+ data = g_hash_table_lookup (priv->porters, contact);
+
+ if (data != NULL)
+ {
+ g_assert (data->porter == NULL);
+
+ data->porter = wocky_c2s_porter_new (connection, priv->jid);
+ }
+ else
+ {
+ data = g_slice_new0 (PorterData);
+
+ data->self = self;
+ data->contact = contact; /* already will be reffed as the key */
+ data->jid = wocky_contact_dup_jid (contact);
+ data->porter = wocky_c2s_porter_new (connection, priv->jid);
+ data->refcount = 0;
+ data->timeout_id = 0;
+
+ g_hash_table_insert (priv->porters, g_object_ref (contact), data);
+ }
+
+ g_signal_connect (data->porter, "closing", G_CALLBACK (porter_closing_cb),
+ data);
+ g_signal_connect (data->porter, "remote-closed",
+ G_CALLBACK (porter_remote_closed_cb), data);
+
+ register_porter_handlers (self, data->porter, contact);
+ wocky_porter_start (data->porter);
+
+ /* maybe start the timeout */
+ maybe_start_timeout (data);
+
+ return data->porter;
+}
+
+/**
+ * wocky_meta_porter_hold:
+ * @porter: a #WockyMetaPorter
+ * @contact: a #WockyContact
+ *
+ * Increases the hold count of the porter to @contact by
+ * one. This means that if there is a connection open to @contact then
+ * it will not disconnected after a timeout. Note that calling this
+ * function does not mean a connection will be opened. The hold
+ * count on a contact survives across connections.
+ *
+ * To decrement the hold count of the porter to @contact, one
+ * must call wocky_meta_porter_unhold().
+ */
+void
+wocky_meta_porter_hold (WockyMetaPorter *self,
+ WockyContact *contact)
+{
+ WockyMetaPorterPrivate *priv = self->priv;
+ PorterData *data;
+
+ g_return_if_fail (WOCKY_IS_META_PORTER (self));
+
+ data = g_hash_table_lookup (priv->porters, contact);
+
+ if (data == NULL)
+ {
+ data = g_slice_new0 (PorterData);
+ data->self = self;
+ data->contact = contact;
+ data->jid = wocky_contact_dup_jid (contact);
+ data->porter = NULL;
+ data->refcount = 0;
+ data->timeout_id = 0;
+
+ g_hash_table_insert (priv->porters, g_object_ref (contact), data);
+ }
+
+ DEBUG ("Porter to '%s' refcount %u --> %u", data->jid,
+ data->refcount, data->refcount + 1);
+
+ data->refcount++;
+
+ if (data->timeout_id > 0)
+ {
+ g_source_remove (data->timeout_id);
+ data->timeout_id = 0;
+ }
+}
+
+/**
+ * wocky_meta_porter_unhold:
+ * @porter: a #WockyMetaPorter
+ * @contact: a #WockyContact
+ *
+ * Decreases the hold count of the porter to @contact by
+ * one. This means that if there is a connection open to @contact and
+ * the hold count is zero, a connection timeout will be
+ * started.
+ */
+void
+wocky_meta_porter_unhold (WockyMetaPorter *self,
+ WockyContact *contact)
+{
+ WockyMetaPorterPrivate *priv;
+ PorterData *data;
+
+ g_return_if_fail (WOCKY_IS_META_PORTER (self));
+
+ priv = self->priv;
+
+ data = g_hash_table_lookup (priv->porters, contact);
+
+ if (data == NULL)
+ return;
+
+ DEBUG ("Porter to '%s' refcount %u --> %u", data->jid,
+ data->refcount, data->refcount - 1);
+
+ data->refcount--;
+
+ maybe_start_timeout (data);
+}
+
+static void
+wocky_meta_porter_init (WockyMetaPorter *self)
+{
+ WockyMetaPorterPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ WOCKY_TYPE_META_PORTER, WockyMetaPorterPrivate);
+
+ self->priv = priv;
+}
+
+static void
+new_connection_connect_cb (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ WockyLLConnector *connector = WOCKY_LL_CONNECTOR (source);
+ WockyXmppConnection *connection;
+ GError *error = NULL;
+ WockyMetaPorter *self = user_data;
+ WockyMetaPorterPrivate *priv = self->priv;
+ GList *contacts, *l;
+ WockyLLContact *contact = NULL;
+ gchar *from;
+
+ connection = wocky_ll_connector_finish (connector, result,
+ &from, &error);
+
+ if (connection == NULL)
+ {
+ DEBUG ("connection error: %s", error->message);
+ g_clear_error (&error);
+ return;
+ }
+
+ if (from != NULL)
+ {
+ contact = wocky_contact_factory_ensure_ll_contact (priv->contact_factory,
+ from);
+ }
+
+ if (contact == NULL)
+ {
+ GSocketConnection *socket_connection;
+ GSocketAddress *socket_address;
+ GInetAddress *addr;
+
+ /* we didn't get a from attribute in the stream open */
+
+ g_object_get (connection,
+ "stream", &socket_connection,
+ NULL);
+
+ socket_address = g_socket_connection_get_remote_address (
+ socket_connection, NULL);
+
+ addr = g_inet_socket_address_get_address (
+ G_INET_SOCKET_ADDRESS (socket_address));
+
+ contacts = wocky_contact_factory_get_ll_contacts (priv->contact_factory);
+
+ for (l = contacts; l != NULL; l = l->next)
+ {
+ WockyLLContact *c = l->data;
+
+ if (wocky_ll_contact_has_address (c, addr))
+ {
+ contact = g_object_ref (c);
+ break;
+ }
+ }
+
+ g_list_free (contacts);
+ g_object_unref (socket_address);
+ g_object_unref (socket_connection);
+ }
+
+ if (contact != NULL)
+ {
+ create_porter (self, connection, WOCKY_CONTACT (contact));
+ }
+ else
+ {
+ DEBUG ("Failed to find contact for new connection, let it close");
+ }
+
+ g_object_unref (connection);
+}
+
+static gboolean
+_new_connection (GSocketService *service,
+ GSocketConnection *socket,
+ GObject *source_object,
+ gpointer user_data)
+{
+ WockyMetaPorter *self = user_data;
+
+ DEBUG ("new connection!");
+
+ wocky_ll_connector_incoming_async (G_IO_STREAM (socket),
+ NULL, new_connection_connect_cb, self);
+
+ return TRUE;
+}
+
+static void stanza_handler_porter_disposed_cb (gpointer data, GObject *porter);
+
+static void
+free_handler (gpointer data)
+{
+ StanzaHandler *handler = data;
+ GHashTableIter iter;
+ gpointer key, value;
+
+ g_hash_table_iter_init (&iter, handler->porters);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ WockyPorter *porter = key;
+ guint id = GPOINTER_TO_UINT (value);
+
+ wocky_porter_unregister_handler (porter, id);
+
+ g_object_weak_unref (G_OBJECT (porter),
+ stanza_handler_porter_disposed_cb, handler);
+ }
+
+ g_hash_table_destroy (handler->porters);
+ if (handler->contact != NULL)
+ g_object_unref (handler->contact);
+ g_object_unref (handler->stanza);
+ g_slice_free (StanzaHandler, handler);
+}
+
+static void
+loopback_recv_open_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ WockyXmppConnection *connection = WOCKY_XMPP_CONNECTION (source_object);
+ WockyMetaPorter *self = user_data;
+ WockyMetaPorterPrivate *priv = self->priv;
+ WockyLLContact *contact;
+ GError *error = NULL;
+
+ if (!wocky_xmpp_connection_recv_open_finish (connection, result,
+ NULL, NULL, NULL, NULL, NULL, &error))
+ {
+ DEBUG ("Failed to receive stream open from loopback stream: %s", error->message);
+ g_clear_error (&error);
+ g_object_unref (connection);
+ return;
+ }
+
+ contact = wocky_contact_factory_ensure_ll_contact (
+ priv->contact_factory, priv->jid);
+
+ /* the ref, the porter and the connection will all be freed when the
+ * meta porter is freed */
+ create_porter (self, connection, WOCKY_CONTACT (contact));
+ wocky_meta_porter_hold (self, WOCKY_CONTACT (contact));
+
+ g_object_unref (contact);
+ g_object_unref (connection);
+}
+
+static void
+loopback_sent_open_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ WockyXmppConnection *connection = WOCKY_XMPP_CONNECTION (source_object);
+ WockyMetaPorter *self = user_data;
+ GError *error = NULL;
+
+ if (!wocky_xmpp_connection_send_open_finish (connection, result, &error))
+ {
+ DEBUG ("Failed to send stream open to loopback stream: %s", error->message);
+ g_clear_error (&error);
+ g_object_unref (connection);
+ return;
+ }
+
+ wocky_xmpp_connection_recv_open_async (connection, NULL,
+ loopback_recv_open_cb, self);
+}
+
+static void
+create_loopback_porter (WockyMetaPorter *self)
+{
+ WockyMetaPorterPrivate *priv = self->priv;
+ GIOStream *stream;
+ WockyXmppConnection *connection;
+
+ if (priv->jid == NULL)
+ return;
+
+ stream = wocky_loopback_stream_new ();
+ connection = wocky_xmpp_connection_new (stream);
+
+ /* really simple connector */
+ wocky_xmpp_connection_send_open_async (connection, NULL, NULL, NULL,
+ NULL, NULL, NULL, loopback_sent_open_cb, self);
+
+ g_object_unref (stream);
+}
+
+static void
+wocky_meta_porter_constructed (GObject *obj)
+{
+ WockyMetaPorter *self = WOCKY_META_PORTER (obj);
+ WockyMetaPorterPrivate *priv = self->priv;
+
+ priv->listener = g_socket_service_new ();
+ g_signal_connect (priv->listener, "incoming",
+ G_CALLBACK (_new_connection), self);
+
+ priv->next_handler_id = 1;
+
+ priv->connection_factory = wocky_ll_connection_factory_new ();
+
+ priv->porters = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ g_object_unref, porter_data_free);
+
+ priv->handlers = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, free_handler);
+
+ /* Create the loopback porter */
+ if (priv->jid != NULL)
+ create_loopback_porter (self);
+}
+
+static void
+wocky_meta_porter_finalize (GObject *object)
+{
+ WockyMetaPorter *self = WOCKY_META_PORTER (object);
+ WockyMetaPorterPrivate *priv = self->priv;
+
+ g_free (priv->jid);
+ priv->jid = NULL;
+
+ if (G_OBJECT_CLASS (wocky_meta_porter_parent_class)->finalize)
+ G_OBJECT_CLASS (wocky_meta_porter_parent_class)->finalize (object);
+}
+
+static void
+wocky_meta_porter_dispose (GObject *object)
+{
+ WockyMetaPorter *self = WOCKY_META_PORTER (object);
+ WockyMetaPorterPrivate *priv = self->priv;
+
+ g_object_unref (priv->contact_factory);
+ g_object_unref (priv->connection_factory);
+
+ g_socket_service_stop (priv->listener);
+ g_object_unref (priv->listener);
+
+ g_hash_table_destroy (priv->porters);
+ g_hash_table_destroy (priv->handlers);
+
+ if (G_OBJECT_CLASS (wocky_meta_porter_parent_class)->dispose)
+ G_OBJECT_CLASS (wocky_meta_porter_parent_class)->dispose (object);
+}
+
+static void
+wocky_meta_porter_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ WockyMetaPorter *self = WOCKY_META_PORTER (object);
+ WockyMetaPorterPrivate *priv = self->priv;
+
+ switch (property_id)
+ {
+ case PROP_JID:
+ g_value_set_string (value, priv->jid);
+ break;
+ case PROP_CONTACT_FACTORY:
+ g_value_set_object (value, priv->contact_factory);
+ break;
+ case PROP_CONNECTION:
+ /* nothing; just here to implement WockyPorter */
+ g_value_set_object (value, NULL);
+ break;
+ case PROP_RESOURCE:
+ /* nothing; just here to implement WockyPorter */
+ g_value_set_string (value, NULL);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+wocky_meta_porter_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ WockyMetaPorter *self = WOCKY_META_PORTER (object);
+ WockyMetaPorterPrivate *priv = self->priv;
+
+ switch (property_id)
+ {
+ case PROP_JID:
+ priv->jid = g_value_dup_string (value);
+ break;
+ case PROP_CONTACT_FACTORY:
+ priv->contact_factory = g_value_dup_object (value);
+ break;
+ case PROP_CONNECTION:
+ /* nothing; just here to implement WockyPorter */
+ break;
+ case PROP_RESOURCE:
+ /* nothing; just here to implement WockyPorter */
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+wocky_meta_porter_class_init (
+ WockyMetaPorterClass *wocky_meta_porter_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (wocky_meta_porter_class);
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (wocky_meta_porter_class,
+ sizeof (WockyMetaPorterPrivate));
+
+ object_class->dispose = wocky_meta_porter_dispose;
+ object_class->finalize = wocky_meta_porter_finalize;
+ object_class->constructed = wocky_meta_porter_constructed;
+
+ object_class->get_property = wocky_meta_porter_get_property;
+ object_class->set_property = wocky_meta_porter_set_property;
+
+ /**
+ * WockyMetaPorter:contact-factory:
+ *
+ * The #WockyContactFactory object in use by this meta porter.
+ */
+ param_spec = g_param_spec_object ("contact-factory",
+ "Contact factory", "WockyContactFactory object in use",
+ WOCKY_TYPE_CONTACT_FACTORY,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONTACT_FACTORY,
+ param_spec);
+
+ g_object_class_override_property (object_class,
+ PROP_CONNECTION, "connection");
+ g_object_class_override_property (object_class,
+ PROP_JID, "full-jid");
+ g_object_class_override_property (object_class,
+ PROP_JID, "bare-jid");
+ g_object_class_override_property (object_class,
+ PROP_RESOURCE, "resource");
+}
+
+/**
+ * wocky_meta_porter_new:
+ * @jid: the JID of the local user, or %NULL
+ * @contact_factory: a #WockyContactFactory object
+ *
+ * Convenience function to create a new #WockyMetaPorter object. The
+ * JID can be set later by using wocky_meta_porter_set_jid().
+ *
+ * Returns: a new #WockyMetaPorter
+ */
+WockyPorter *
+wocky_meta_porter_new (const gchar *jid,
+ WockyContactFactory *contact_factory)
+{
+ g_return_val_if_fail (WOCKY_IS_CONTACT_FACTORY (contact_factory), NULL);
+
+ return g_object_new (WOCKY_TYPE_META_PORTER,
+ "full-jid", jid,
+ "contact-factory", contact_factory,
+ NULL);
+}
+
+static const gchar *
+wocky_meta_porter_get_jid (WockyPorter *porter)
+{
+ WockyMetaPorter *self;
+
+ g_return_val_if_fail (WOCKY_IS_META_PORTER (porter), NULL);
+
+ self = (WockyMetaPorter *) porter;
+
+ return self->priv->jid;
+}
+
+static const gchar *
+wocky_meta_porter_get_resource (WockyPorter *porter)
+{
+ return NULL;
+}
+
+typedef void (*OpenPorterIfNecessaryFunc) (WockyMetaPorter *self,
+ WockyPorter *porter,
+ GCancellable *cancellable,
+ const GError *error,
+ GSimpleAsyncResult *simple,
+ gpointer user_data);
+
+typedef struct
+{
+ WockyMetaPorter *self;
+ WockyLLContact *contact;
+ OpenPorterIfNecessaryFunc callback;
+ GCancellable *cancellable;
+ GSimpleAsyncResult *simple;
+ gpointer user_data;
+} OpenPorterData;
+
+static void
+made_connection_connect_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ WockyLLConnector *connector = WOCKY_LL_CONNECTOR (source_object);
+ WockyXmppConnection *connection;
+ GError *error = NULL;
+ OpenPorterData *data = user_data;
+ WockyPorter *porter;
+
+ connection = wocky_ll_connector_finish (connector,
+ result, NULL, &error);
+
+ if (connection == NULL)
+ {
+ DEBUG ("failed to connect: %s", error->message);
+ data->callback (data->self, NULL, NULL, error,
+ data->simple, data->user_data);
+ g_clear_error (&error);
+ goto out;
+ }
+
+ DEBUG ("connected");
+
+ porter = create_porter (data->self, connection, WOCKY_CONTACT (data->contact));
+
+ data->callback (data->self, porter, data->cancellable, NULL,
+ data->simple, data->user_data);
+
+ g_object_unref (connection);
+
+out:
+ g_object_unref (data->contact);
+ g_slice_free (OpenPorterData, data);
+}
+
+static void
+make_connection_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ WockyLLConnectionFactory *factory = WOCKY_LL_CONNECTION_FACTORY (source_object);
+ WockyXmppConnection *connection;
+ GError *error = NULL;
+ OpenPorterData *data = user_data;
+ WockyMetaPorterPrivate *priv = data->self->priv;
+ gchar *jid;
+
+ connection = wocky_ll_connection_factory_make_connection_finish (factory, result, &error);
+
+ if (connection == NULL)
+ {
+ DEBUG ("making connection failed: %s", error->message);
+
+ data->callback (data->self, NULL, NULL, error,
+ data->simple, data->user_data);
+
+ g_clear_error (&error);
+
+ g_object_unref (data->contact);
+ g_slice_free (OpenPorterData, data);
+ return;
+ }
+
+ jid = wocky_contact_dup_jid (WOCKY_CONTACT (data->contact));
+
+ wocky_ll_connector_outgoing_async (connection, priv->jid,
+ jid, data->cancellable, made_connection_connect_cb, data);
+
+ g_free (jid);
+}
+
+/* Convenience function to call @callback with a porter and do all the
+ * handling the creating a porter if necessary. */
+static void
+open_porter_if_necessary (WockyMetaPorter *self,
+ WockyLLContact *contact,
+ GCancellable *cancellable,
+ OpenPorterIfNecessaryFunc callback,
+ GSimpleAsyncResult *simple,
+ gpointer user_data)
+{
+ WockyMetaPorterPrivate *priv = self->priv;
+ PorterData *porter_data = g_hash_table_lookup (priv->porters, contact);
+ OpenPorterData *data;
+
+ if (porter_data != NULL && porter_data->porter != NULL)
+ {
+ callback (self, porter_data->porter, cancellable, NULL, simple, user_data);
+ return;
+ }
+
+ data = g_slice_new0 (OpenPorterData);
+ data->self = self;
+ data->contact = g_object_ref (contact);
+ data->callback = callback;
+ data->cancellable = cancellable;
+ data->simple = simple;
+ data->user_data = user_data;
+
+ wocky_ll_connection_factory_make_connection_async (priv->connection_factory,
+ contact, cancellable, make_connection_cb, data);
+}
+
+static void
+meta_porter_send_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple = user_data;
+ GError *error = NULL;
+
+ if (!wocky_porter_send_finish (WOCKY_PORTER (source_object), result, &error))
+ g_simple_async_result_take_error (simple, error);
+
+ g_simple_async_result_complete (simple);
+ g_object_unref (simple);
+}
+
+static void
+meta_porter_send_got_porter_cb (WockyMetaPorter *self,
+ WockyPorter *porter,
+ GCancellable *cancellable,
+ const GError *error,
+ GSimpleAsyncResult *simple,
+ gpointer user_data)
+{
+ WockyStanza *stanza = user_data;
+
+ if (error != NULL)
+ {
+ g_simple_async_result_set_from_error (simple, error);
+ g_simple_async_result_complete (simple);
+ g_object_unref (simple);
+ }
+ else
+ {
+ wocky_porter_send_async (porter, stanza, cancellable,
+ meta_porter_send_cb, simple);
+ }
+
+ g_object_unref (stanza);
+}
+
+static void
+wocky_meta_porter_send_async (WockyPorter *porter,
+ WockyStanza *stanza,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ WockyMetaPorter *self = WOCKY_META_PORTER (porter);
+ WockyMetaPorterPrivate *priv = self->priv;
+ GSimpleAsyncResult *simple;
+ WockyContact *to;
+
+ simple = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
+ wocky_meta_porter_send_async);
+
+ to = wocky_stanza_get_to_contact (stanza);
+
+ g_return_if_fail (WOCKY_IS_LL_CONTACT (to));
+
+ /* stamp on from if there is none */
+ if (wocky_stanza_get_from (stanza) == NULL)
+ {
+ wocky_node_set_attribute (wocky_stanza_get_top_node (stanza),
+ "from", priv->jid);
+ }
+
+ open_porter_if_necessary (self, WOCKY_LL_CONTACT (to), cancellable,
+ meta_porter_send_got_porter_cb, simple, g_object_ref (stanza));
+}
+
+static gboolean
+wocky_meta_porter_send_finish (WockyPorter *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (WOCKY_IS_META_PORTER (self), FALSE);
+
+ wocky_implement_finish_void (self, wocky_meta_porter_send_async);
+}
+
+static guint16
+wocky_meta_porter_listen (WockyMetaPorter *self,
+ GError **error)
+{
+ WockyMetaPorterPrivate *priv = self->priv;
+ guint16 port;
+
+ /* The port 5298 is preferred to remain compatible with old versions of
+ * iChat. Try a few close to it, and if those fail, use a random port. */
+ for (port = 5298; port < 5300; port++)
+ {
+ GError *e = NULL;
+
+ if (g_socket_listener_add_inet_port (G_SOCKET_LISTENER (priv->listener),
+ port, NULL, &e))
+ break;
+
+ if (!g_error_matches (e, G_IO_ERROR,
+ G_IO_ERROR_ADDRESS_IN_USE))
+ {
+ g_propagate_error (error, e);
+ return 0;
+ }
+
+ g_error_free (e);
+ e = NULL;
+ }
+
+ if (port < 5300)
+ return port;
+
+ return g_socket_listener_add_any_inet_port (G_SOCKET_LISTENER (priv->listener),
+ NULL, error);
+}
+
+static void
+wocky_meta_porter_start (WockyPorter *porter)
+{
+ WockyMetaPorter *self = WOCKY_META_PORTER (porter);
+ WockyMetaPorterPrivate *priv = self->priv;
+ GError *error = NULL;
+ guint16 port;
+
+ port = wocky_meta_porter_listen (self, &error);
+
+ if (error != NULL)
+ {
+ DEBUG ("Failed to listen: %s", error->message);
+ g_clear_error (&error);
+ return;
+ }
+
+ DEBUG ("listening on port %u", port);
+
+ g_socket_service_start (G_SOCKET_SERVICE (priv->listener));
+
+ priv->port = port;
+}
+
+static gboolean
+porter_handler_cb (WockyPorter *porter,
+ WockyStanza *stanza,
+ gpointer user_data)
+{
+ StanzaHandler *handler = user_data;
+ WockyMetaPorter *self = handler->self;
+ WockyMetaPorterPrivate *priv = self->priv;
+ WockyLLContact *contact;
+ const gchar *from;
+
+ from = wocky_stanza_get_from (stanza);
+
+ contact = wocky_contact_factory_ensure_ll_contact (
+ priv->contact_factory, from);
+
+ wocky_stanza_set_from_contact (stanza, WOCKY_CONTACT (contact));
+ g_object_unref (contact);
+
+ return handler->callback (WOCKY_PORTER (handler->self),
+ stanza, handler->user_data);
+}
+
+static void
+stanza_handler_porter_disposed_cb (gpointer data,
+ GObject *porter)
+{
+ StanzaHandler *handler = data;
+
+ g_hash_table_remove (handler->porters, porter);
+}
+
+static void
+register_porter_handler (StanzaHandler *handler,
+ WockyPorter *porter)
+{
+ guint id;
+
+ g_assert (g_hash_table_lookup (handler->porters, porter) == NULL);
+
+ if (handler->contact != NULL)
+ {
+ gchar *jid = wocky_contact_dup_jid (handler->contact);
+
+ id = wocky_porter_register_handler_from_by_stanza (porter,
+ handler->type, handler->sub_type, jid,
+ handler->priority, porter_handler_cb, handler,
+ handler->stanza);
+
+ g_free (jid);
+ }
+ else
+ {
+ id = wocky_porter_register_handler_from_anyone_by_stanza (porter,
+ handler->type, handler->sub_type,
+ handler->priority, porter_handler_cb, handler,
+ handler->stanza);
+ }
+
+ g_hash_table_insert (handler->porters, porter, GUINT_TO_POINTER (id));
+
+ g_object_weak_ref (G_OBJECT (porter),
+ stanza_handler_porter_disposed_cb, handler);
+}
+
+static void
+register_porter_handlers (WockyMetaPorter *self,
+ WockyPorter *porter,
+ WockyContact *contact)
+{
+ WockyMetaPorterPrivate *priv = self->priv;
+ GList *handlers, *l;
+
+ handlers = g_hash_table_get_values (priv->handlers);
+
+ for (l = handlers; l != NULL; l = l->next)
+ {
+ StanzaHandler *handler = l->data;
+
+ if (contact == handler->contact || handler->contact == NULL)
+ register_porter_handler (handler, porter);
+ }
+
+ g_list_free (handlers);
+}
+
+static StanzaHandler *
+stanza_handler_new (WockyMetaPorter *self,
+ WockyLLContact *contact,
+ WockyStanzaType type,
+ WockyStanzaSubType sub_type,
+ guint priority,
+ WockyPorterHandlerFunc callback,
+ gpointer user_data,
+ WockyStanza *stanza)
+{
+ StanzaHandler *out = g_slice_new0 (StanzaHandler);
+
+ out->self = self;
+ out->porters = g_hash_table_new (NULL, NULL);
+
+ if (contact != NULL)
+ out->contact = g_object_ref (contact);
+
+ out->type = type;
+ out->sub_type = sub_type;
+ out->priority = priority;
+ out->callback = callback;
+ out->user_data = user_data;
+ out->stanza = g_object_ref (stanza);
+
+ return out;
+}
+
+static guint
+wocky_meta_porter_register_handler_from_by_stanza (WockyPorter *porter,
+ WockyStanzaType type,
+ WockyStanzaSubType sub_type,
+ const gchar *jid,
+ guint priority,
+ WockyPorterHandlerFunc callback,
+ gpointer user_data,
+ WockyStanza *stanza)
+{
+ WockyMetaPorter *self = WOCKY_META_PORTER (porter);
+ WockyMetaPorterPrivate *priv = self->priv;
+ PorterData *porter_data;
+ guint id;
+ StanzaHandler *handler;
+ WockyLLContact *from;
+
+ g_return_val_if_fail (jid != NULL, 0);
+
+ from = wocky_contact_factory_lookup_ll_contact (
+ priv->contact_factory, jid);
+
+ g_return_val_if_fail (WOCKY_IS_LL_CONTACT (from), 0);
+
+ handler = stanza_handler_new (self, from, type, sub_type, priority,
+ callback, user_data, stanza);
+
+ id = priv->next_handler_id++;
+
+ porter_data = g_hash_table_lookup (priv->porters, from);
+ if (porter_data != NULL && porter_data->porter != NULL)
+ register_porter_handler (handler, porter_data->porter);
+
+ g_hash_table_insert (priv->handlers, GUINT_TO_POINTER (id), handler);
+
+ return id;
+}
+
+static guint
+wocky_meta_porter_register_handler_from_anyone_by_stanza (WockyPorter *porter,
+ WockyStanzaType type,
+ WockyStanzaSubType sub_type,
+ guint priority,
+ WockyPorterHandlerFunc callback,
+ gpointer user_data,
+ WockyStanza *stanza)
+{
+ WockyMetaPorter *self = WOCKY_META_PORTER (porter);
+ WockyMetaPorterPrivate *priv = self->priv;
+ PorterData *porter_data;
+ guint id;
+ StanzaHandler *handler;
+ GList *porters, *l;
+
+ handler = stanza_handler_new (self, NULL, type, sub_type, priority,
+ callback, user_data, stanza);
+
+ id = priv->next_handler_id++;
+
+ /* register on all porters */
+ porters = g_hash_table_get_values (priv->porters);
+
+ for (l = porters; l != NULL; l = l->next)
+ {
+ porter_data = l->data;
+
+ if (porter_data->porter != NULL)
+ register_porter_handler (handler, porter_data->porter);
+ }
+
+ g_list_free (porters);
+
+ g_hash_table_insert (priv->handlers, GUINT_TO_POINTER (id), handler);
+
+ return id;
+}
+
+static void
+wocky_meta_porter_unregister_handler (WockyPorter *porter,
+ guint id)
+{
+ WockyMetaPorter *self = WOCKY_META_PORTER (porter);
+ WockyMetaPorterPrivate *priv = self->priv;
+
+ g_hash_table_remove (priv->handlers, GUINT_TO_POINTER (id));
+}
+
+typedef gboolean (* ClosePorterFinishFunc) (WockyPorter *,
+ GAsyncResult *, GError **);
+typedef void (* ClosePorterAsyncFunc) (WockyPorter *,
+ GCancellable *, GAsyncReadyCallback, gpointer);
+
+
+typedef struct
+{
+ GSimpleAsyncResult *simple;
+ guint remaining;
+ gboolean failed;
+ ClosePorterFinishFunc close_finish;
+} ClosePorterData;
+
+static void
+porter_close_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ WockyPorter *porter = WOCKY_PORTER (source_object);
+ GError *error = NULL;
+ ClosePorterData *data = user_data;
+
+
+ if (!data->close_finish (porter, result, &error))
+ {
+ DEBUG ("Failed to close porter: %s", error->message);
+ g_clear_error (&error);
+ data->failed = TRUE;
+ }
+
+ data->remaining--;
+
+ if (data->remaining > 0)
+ return;
+
+ /* all porters have now replied */
+
+ if (data->failed)
+ {
+ GError *err = g_error_new (WOCKY_META_PORTER_ERROR,
+ WOCKY_META_PORTER_ERROR_FAILED_TO_CLOSE,
+ "Failed to close at least one porter");
+
+ g_simple_async_result_take_error (data->simple, err);
+ }
+
+ g_simple_async_result_complete (data->simple);
+
+ g_object_unref (data->simple);
+ g_slice_free (ClosePorterData, data);
+}
+
+static void
+close_all_porters (WockyMetaPorter *self,
+ ClosePorterAsyncFunc close_async_func,
+ ClosePorterFinishFunc close_finish_func,
+ gpointer source_tag,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ WockyMetaPorterPrivate *priv = self->priv;
+ GSimpleAsyncResult *simple;
+ GList *porters, *l;
+ gboolean close_called = FALSE;
+
+ porters = g_hash_table_get_values (priv->porters);
+
+ simple = g_simple_async_result_new (G_OBJECT (self), callback,
+ user_data, source_tag);
+
+ g_signal_emit_by_name (self, "closing");
+
+ if (porters != NULL)
+ {
+ ClosePorterData *data = g_slice_new0 (ClosePorterData);
+ data->close_finish = close_finish_func;
+ data->remaining = 0;
+ data->simple = simple;
+
+ for (l = porters; l != NULL; l = l->next)
+ {
+ PorterData *porter_data = l->data;
+
+ /* NULL if there's a refcount but no porter */
+ if (porter_data->porter == NULL)
+ continue;
+
+ data->remaining++;
+ close_called = TRUE;
+
+ close_async_func (porter_data->porter, cancellable,
+ porter_close_cb, data);
+ }
+
+ /* Actually, none of the PorterData structs had C2S porters */
+ if (!close_called)
+ g_slice_free (ClosePorterData, data);
+ }
+
+ if (!close_called)
+ {
+ /* there were no porters to close anyway */
+ g_simple_async_result_complete (simple);
+ g_object_unref (simple);
+ }
+
+ g_list_free (porters);
+}
+
+static void
+wocky_meta_porter_close_async (WockyPorter *porter,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ WockyMetaPorter *self = WOCKY_META_PORTER (porter);
+
+ close_all_porters (self, wocky_porter_close_async,
+ wocky_porter_close_finish, wocky_meta_porter_close_async,
+ cancellable, callback, user_data);
+}
+
+static gboolean
+wocky_meta_porter_close_finish (WockyPorter *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ wocky_implement_finish_void (self, wocky_meta_porter_close_async);
+}
+
+typedef struct
+{
+ WockyMetaPorter *self; /* already reffed by simple */
+ GSimpleAsyncResult *simple;
+ WockyContact *contact;
+} SendIQData;
+
+static void
+meta_porter_send_iq_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ SendIQData *data = user_data;
+ GSimpleAsyncResult *simple = data->simple;
+ GError *error = NULL;
+ WockyStanza *stanza;
+
+ stanza = wocky_porter_send_iq_finish (WOCKY_PORTER (source_object),
+ result, &error);
+
+ if (stanza == NULL)
+ g_simple_async_result_take_error (simple, error);
+ else
+ g_simple_async_result_set_op_res_gpointer (simple, stanza, g_object_unref);
+
+ g_simple_async_result_complete (simple);
+ g_object_unref (simple);
+
+ wocky_meta_porter_unhold (data->self, data->contact);
+
+ g_object_unref (data->contact);
+ g_slice_free (SendIQData, data);
+}
+
+static void
+meta_porter_send_iq_got_porter_cb (WockyMetaPorter *self,
+ WockyPorter *porter,
+ GCancellable *cancellable,
+ const GError *error,
+ GSimpleAsyncResult *simple,
+ gpointer user_data)
+{
+ WockyStanza *stanza = user_data;
+ WockyContact *contact;
+
+ contact = wocky_stanza_get_to_contact (stanza);
+
+ if (error != NULL)
+ {
+ g_simple_async_result_set_from_error (simple, error);
+ g_simple_async_result_complete (simple);
+
+ g_object_unref (simple);
+ wocky_meta_porter_unhold (self, contact);
+ }
+ else
+ {
+ SendIQData *data = g_slice_new0 (SendIQData);
+ data->self = self;
+ data->simple = simple;
+ data->contact = g_object_ref (contact);
+
+ wocky_porter_send_iq_async (porter, stanza, cancellable,
+ meta_porter_send_iq_cb, data);
+ }
+
+ g_object_unref (stanza);
+}
+
+static void
+wocky_meta_porter_send_iq_async (WockyPorter *porter,
+ WockyStanza *stanza,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ WockyMetaPorter *self = WOCKY_META_PORTER (porter);
+ WockyMetaPorterPrivate *priv = self->priv;
+ GSimpleAsyncResult *simple;
+ WockyContact *to;
+
+ to = wocky_stanza_get_to_contact (stanza);
+
+ g_return_if_fail (WOCKY_IS_LL_CONTACT (to));
+
+ simple = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
+ wocky_meta_porter_send_iq_async);
+
+ wocky_meta_porter_hold (self, to);
+
+ /* stamp on from if there is none */
+ if (wocky_node_get_attribute (wocky_stanza_get_top_node (stanza),
+ "from") == NULL)
+ {
+ wocky_node_set_attribute (wocky_stanza_get_top_node (stanza),
+ "from", priv->jid);
+ }
+
+ open_porter_if_necessary (self, WOCKY_LL_CONTACT (to), cancellable,
+ meta_porter_send_iq_got_porter_cb, simple, g_object_ref (stanza));
+}
+
+static WockyStanza *
+wocky_meta_porter_send_iq_finish (WockyPorter *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ wocky_implement_finish_return_copy_pointer (self, wocky_meta_porter_send_iq_async,
+ g_object_ref);
+}
+
+static void
+wocky_meta_porter_force_close_async (WockyPorter *porter,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ WockyMetaPorter *self = WOCKY_META_PORTER (porter);
+
+ close_all_porters (self, wocky_porter_force_close_async,
+ wocky_porter_force_close_finish, wocky_meta_porter_force_close_async,
+ cancellable, callback, user_data);
+}
+
+static gboolean
+wocky_meta_porter_force_close_finish (WockyPorter *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ wocky_implement_finish_void (self, wocky_meta_porter_force_close_async);
+}
+
+/**
+ * wocky_meta_porter_get_port:
+ * @porter: a #WockyMetaPorter
+ *
+ * Returns the port @porter is listening in on for new incoming XMPP
+ * connections, or 0 if it has not been started yet with
+ * wocky_porter_start().
+ *
+ * Returns: the port @porter is listening in on for new incoming XMPP
+ * connections, or 0 if it has not been started.
+ */
+guint16
+wocky_meta_porter_get_port (WockyMetaPorter *self)
+{
+ g_return_val_if_fail (WOCKY_IS_META_PORTER (self), 0);
+
+ return self->priv->port;
+}
+
+/**
+ * wocky_meta_porter_set_jid:
+ * @porter: a #WockyMetaPorter
+ * @jid: a new JID
+ *
+ * Changes the local JID according to @porter. Note that this function
+ * can only be called once, and only if %NULL was passed to
+ * wocky_meta_porter_new() when creating @porter. Calling it again
+ * will be a no-op.
+ */
+void
+wocky_meta_porter_set_jid (WockyMetaPorter *self,
+ const gchar *jid)
+{
+ WockyMetaPorterPrivate *priv;
+
+ g_return_if_fail (WOCKY_IS_META_PORTER (self));
+
+ priv = self->priv;
+
+ /* You cannot set the meta porter JID again */
+ g_return_if_fail (priv->jid == NULL);
+
+ /* don't try and change existing porter's JIDs */
+
+ priv->jid = g_strdup (jid);
+
+ /* now we can do this */
+ create_loopback_porter (self);
+}
+
+static void
+meta_porter_open_got_porter_cb (WockyMetaPorter *self,
+ WockyPorter *porter,
+ GCancellable *cancellable,
+ const GError *error,
+ GSimpleAsyncResult *simple,
+ gpointer user_data)
+{
+ WockyContact *contact = user_data;
+
+ if (error != NULL)
+ {
+ g_simple_async_result_set_from_error (simple, error);
+ wocky_meta_porter_unhold (self, contact);
+ }
+
+ g_simple_async_result_complete (simple);
+
+ g_object_unref (contact);
+ g_object_unref (simple);
+}
+
+/**
+ * wocky_meta_porter_open_async:
+ * @porter: a #WockyMetaPorter
+ * @contact: the #WockyLLContact
+ * @cancellable: an optional #GCancellable, or %NULL
+ * @callback: a callback to be called
+ * @user_data: data for @callback
+ *
+ * Make an asynchronous request to open a connection to @contact if
+ * one is not already open. The hold count of the porter to
+ * @contact will be incrememented and so after completion
+ * wocky_meta_porter_unhold() should be called on contact to release
+ * the hold.
+ *
+ * When the request is complete, @callback will be called and the user
+ * should call wocky_meta_porter_open_finish() to finish the request.
+ */
+void
+wocky_meta_porter_open_async (WockyMetaPorter *self,
+ WockyLLContact *contact,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple;
+
+ g_return_if_fail (WOCKY_IS_META_PORTER (self));
+ g_return_if_fail (WOCKY_IS_LL_CONTACT (contact));
+ g_return_if_fail (callback != NULL);
+
+ simple = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
+ wocky_meta_porter_open_async);
+
+ wocky_meta_porter_hold (self, WOCKY_CONTACT (contact));
+
+ open_porter_if_necessary (self, contact,
+ cancellable, meta_porter_open_got_porter_cb, simple,
+ g_object_ref (contact));
+}
+
+/**
+ * wocky_meta_porter_open_finish:
+ * @porter: a #WockyMetaPorter
+ * @result: the #GAsyncResult
+ * @error: an optional #GError location to store an error message
+ *
+ * Finishes an asynchronous request to open a connection if one is not
+ * already open. See wocky_meta_porter_open_async() for more details.
+ *
+ * Returns: %TRUE if the operation was a success, otherwise %FALSE
+ */
+gboolean
+wocky_meta_porter_open_finish (WockyMetaPorter *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ wocky_implement_finish_void (self, wocky_meta_porter_open_async);
+}
+
+/**
+ * wocky_meta_porter_borrow_connection:
+ * @porter: a #WockyMetaPorter
+ * @contact: the #WockyContact
+ *
+ * Borrow the #GSocketConnection of the porter to @contact, if one
+ * exists, otherwise %NULL will be returned.
+
+ * Note that the connection returned should be reffed using
+ * g_object_ref() if it needs to be kept. However, it will still be
+ * operated on by the underlying #WockyXmppConnection object so can
+ * close spontaneously unless wocky_meta_porter_hold() is called with
+ * @contact.
+ *
+ * Returns: the #GSocketConnection or %NULL if no connection is open
+ */
+GSocketConnection *
+wocky_meta_porter_borrow_connection (WockyMetaPorter *self,
+ WockyLLContact *contact)
+{
+ WockyMetaPorterPrivate *priv;
+ PorterData *porter_data;
+ GSocketConnection *socket_conn;
+ WockyXmppConnection *xmpp_conn;
+
+ g_return_val_if_fail (WOCKY_IS_META_PORTER (self), NULL);
+ g_return_val_if_fail (WOCKY_IS_LL_CONTACT (contact), NULL);
+
+ priv = self->priv;
+
+ porter_data = g_hash_table_lookup (priv->porters, contact);
+
+ if (porter_data == NULL || porter_data->porter == NULL)
+ return NULL;
+
+ /* splendid, the connection is already open */
+
+ g_object_get (porter_data->porter, "connection", &xmpp_conn, NULL);
+ /* will give it a new ref */
+ g_object_get (xmpp_conn, "base-stream", &socket_conn, NULL);
+
+ /* we take back the ref */
+ g_object_unref (socket_conn);
+ g_object_unref (xmpp_conn);
+
+ /* but this will still be alive */
+ return socket_conn;
+}
+
+static void
+wocky_porter_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ WockyPorterInterface *iface = g_iface;
+
+ iface->get_full_jid = wocky_meta_porter_get_jid;
+ iface->get_bare_jid = wocky_meta_porter_get_jid;
+ /* a dummy implementation to return NULL so if someone calls it on
+ * us it won't assert */
+ iface->get_resource = wocky_meta_porter_get_resource;
+
+ iface->start = wocky_meta_porter_start;
+
+ iface->send_async = wocky_meta_porter_send_async;
+ iface->send_finish = wocky_meta_porter_send_finish;
+
+ iface->register_handler_from_by_stanza =
+ wocky_meta_porter_register_handler_from_by_stanza;
+ iface->register_handler_from_anyone_by_stanza =
+ wocky_meta_porter_register_handler_from_anyone_by_stanza;
+
+ iface->unregister_handler = wocky_meta_porter_unregister_handler;
+
+ iface->close_async = wocky_meta_porter_close_async;
+ iface->close_finish = wocky_meta_porter_close_finish;
+
+ iface->send_iq_async = wocky_meta_porter_send_iq_async;
+ iface->send_iq_finish = wocky_meta_porter_send_iq_finish;
+
+ iface->force_close_async = wocky_meta_porter_force_close_async;
+ iface->force_close_finish = wocky_meta_porter_force_close_finish;
+}