summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonny Lamb <jonny.lamb@collabora.co.uk>2011-03-22 15:31:20 +0000
committerJonny Lamb <jonny.lamb@collabora.co.uk>2011-03-22 15:31:20 +0000
commit1343f70de56c6917a224b3c2618eaf654a9fc958 (patch)
treeff9b76732f7e93722736f379d4291308e5bdc511
parent916518447065436474ddd0d86462dafa3e28815e (diff)
parent901aca41915146f450842d7ab41071d5dd0e4f46 (diff)
Merge branch 'meta-porter'
Conflicts: docs/reference/wocky-docs.sgml wocky/wocky-debug.h Signed-off-by: Jonny Lamb <jonny.lamb@collabora.co.uk> Reviewed-by: Will Thompson <will.thompson@collabora.co.uk>
-rw-r--r--configure.ac2
-rw-r--r--docs/reference/wocky-docs.sgml1
-rw-r--r--tests/Makefile.am7
-rw-r--r--tests/wocky-loopback-test.c191
-rw-r--r--tests/wocky-pubsub-node-test.c4
-rw-r--r--tests/wocky-pubsub-service-test.c2
-rw-r--r--tests/wocky-roster-test.c2
-rw-r--r--tests/wocky-session-test.c6
-rw-r--r--tests/wocky-test-helper.c4
-rw-r--r--wocky/Makefile.am10
-rw-r--r--wocky/wocky-bare-contact.c9
-rw-r--r--wocky/wocky-contact-factory.c121
-rw-r--r--wocky/wocky-contact-factory.h14
-rw-r--r--wocky/wocky-contact.c11
-rw-r--r--wocky/wocky-contact.h6
-rw-r--r--wocky/wocky-debug.c1
-rw-r--r--wocky/wocky-debug.h1
-rw-r--r--wocky/wocky-ll-connection-factory.c288
-rw-r--r--wocky/wocky-ll-connection-factory.h89
-rw-r--r--wocky/wocky-ll-connector.c518
-rw-r--r--wocky/wocky-ll-connector.h94
-rw-r--r--wocky/wocky-ll-contact.c290
-rw-r--r--wocky/wocky-ll-contact.h81
-rw-r--r--wocky/wocky-loopback-stream.c544
-rw-r--r--wocky/wocky-loopback-stream.h67
-rw-r--r--wocky/wocky-meta-porter.c1696
-rw-r--r--wocky/wocky-meta-porter.h97
-rw-r--r--wocky/wocky-resource-contact.c12
-rw-r--r--wocky/wocky-session.c44
-rw-r--r--wocky/wocky-session.h6
-rw-r--r--wocky/wocky-stanza.c122
-rw-r--r--wocky/wocky-stanza.h13
-rw-r--r--wocky/wocky-types.h1
-rw-r--r--wocky/wocky-uninstalled.pc.in2
-rw-r--r--wocky/wocky-utils.h12
35 files changed, 4337 insertions, 31 deletions
diff --git a/configure.ac b/configure.ac
index b38eb36..c70b6ea 100644
--- a/configure.ac
+++ b/configure.ac
@@ -109,7 +109,7 @@ AC_C_BIGENDIAN
dnl Check for Glib
PKG_CHECK_MODULES(GLIB, [glib-2.0 >= 2.16, gobject-2.0 >= 2.16, gthread-2.0 >=
-2.4, gio-2.0 >= 2.21])
+2.4, gio-2.0 >= 2.28])
AC_SUBST(GLIB_CFLAGS)
AC_SUBST(GLIB_LIBS)
diff --git a/docs/reference/wocky-docs.sgml b/docs/reference/wocky-docs.sgml
index f7e2ebf..5314379 100644
--- a/docs/reference/wocky-docs.sgml
+++ b/docs/reference/wocky-docs.sgml
@@ -31,6 +31,7 @@
<xi:include href="xml/wocky-jabber-auth.xml"/>
<xi:include href="xml/wocky-jabber-auth-digest.xml"/>
<xi:include href="xml/wocky-jabber-auth-password.xml"/>
+ <xi:include href="xml/wocky-meta-porter.xml"/>
<xi:include href="xml/wocky-muc-enumtypes.xml"/>
<xi:include href="xml/wocky-muc.xml"/>
<xi:include href="xml/wocky-namespaces.xml"/>
diff --git a/tests/Makefile.am b/tests/Makefile.am
index dde392f..f2ff173 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -42,6 +42,7 @@ TEST_PROGS = wocky-xmpp-reader-test \
wocky-xmpp-readwrite-test \
wocky-xmpp-connection-test \
wocky-porter-test \
+ wocky-loopback-test \
wocky-xmpp-node-test \
wocky-node-tree-test \
wocky-stanza-test \
@@ -130,6 +131,12 @@ wocky_porter_test_SOURCES = \
wocky-test-helper.c wocky-test-helper.h \
wocky-test-stream.c wocky-test-stream.h
+wocky_loopback_test_DEPENDENCIES = $(LIBWOCKY)
+wocky_loopback_test_SOURCES = \
+ wocky-test-helper.c wocky-test-helper.h \
+ wocky-test-stream.c wocky-test-stream.h \
+ wocky-loopback-test.c
+
wocky_xmpp_node_test_DEPENDENCIES = $(LIBWOCKY)
wocky_xmpp_node_test_SOURCES = \
wocky-test-helper.c wocky-test-helper.h \
diff --git a/tests/wocky-loopback-test.c b/tests/wocky-loopback-test.c
new file mode 100644
index 0000000..712fc3e
--- /dev/null
+++ b/tests/wocky-loopback-test.c
@@ -0,0 +1,191 @@
+#include <wocky/wocky-c2s-porter.h>
+#include <wocky/wocky-loopback-stream.h>
+
+#include "wocky-test-helper.h"
+
+/* I'm not happy about this, but the existing test stuff really relies
+ * on having multiple streams */
+typedef struct
+{
+ test_data_t data;
+ GIOStream *stream;
+ WockyXmppConnection *conn;
+ WockySession *session;
+ WockyPorter *porter;
+} loopback_test_t;
+
+static gboolean
+test_timeout (gpointer data)
+{
+ g_test_message ("Timeout reached :(");
+ g_assert_not_reached ();
+
+ return FALSE;
+}
+
+static loopback_test_t *
+setup (void)
+{
+ loopback_test_t *test = g_slice_new0 (loopback_test_t);
+
+ test->data.loop = g_main_loop_new (NULL, FALSE);
+ test->data.cancellable = g_cancellable_new ();
+ test->data.timeout_id = g_timeout_add_seconds (10, test_timeout, NULL);
+ test->data.expected_stanzas = g_queue_new ();
+
+ test->stream = wocky_loopback_stream_new ();
+
+ test->conn = wocky_xmpp_connection_new (test->stream);
+
+ test->session = wocky_session_new_with_connection (test->conn,
+ "example.com");
+
+ test->porter = wocky_session_get_porter (test->session);
+
+ return test;
+}
+
+static void
+send_received_open_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ WockyXmppConnection *conn = WOCKY_XMPP_CONNECTION (source);
+ loopback_test_t *d = (loopback_test_t *) user_data;
+
+ g_assert (wocky_xmpp_connection_recv_open_finish (conn, res,
+ NULL, NULL, NULL, NULL, NULL, NULL));
+
+ wocky_session_start (d->session);
+
+ d->data.outstanding--;
+ g_main_loop_quit (d->data.loop);
+}
+
+static void
+send_open_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ WockyXmppConnection *conn = WOCKY_XMPP_CONNECTION (source);
+
+ g_assert (wocky_xmpp_connection_send_open_finish (conn,
+ res, NULL));
+
+ wocky_xmpp_connection_recv_open_async (conn,
+ NULL, send_received_open_cb, user_data);
+}
+
+static void
+start_test (loopback_test_t *test)
+{
+ wocky_xmpp_connection_send_open_async (test->conn,
+ NULL, NULL, NULL, NULL, NULL, NULL, send_open_cb, test);
+
+ test->data.outstanding++;
+
+ test_wait_pending (&(test->data));
+}
+
+static void
+close_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ loopback_test_t *test = user_data;
+
+ g_assert (wocky_porter_close_finish (WOCKY_PORTER (source),
+ res, NULL));
+
+ test->data.outstanding--;
+ g_main_loop_quit (test->data.loop);
+
+ g_main_loop_unref (test->data.loop);
+
+ g_object_unref (test->session);
+ g_object_unref (test->conn);
+ g_object_unref (test->data.cancellable);
+ g_source_remove (test->data.timeout_id);
+
+ g_assert (g_queue_get_length (test->data.expected_stanzas) == 0);
+ g_queue_free (test->data.expected_stanzas);
+
+ g_slice_free (loopback_test_t, test);
+}
+
+static void
+cleanup (loopback_test_t *test)
+{
+ wocky_porter_close_async (test->porter, NULL, close_cb, test);
+
+ test->data.outstanding++;
+ test_wait_pending (&(test->data));
+}
+
+static void
+send_stanza_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ test_data_t *data = (test_data_t *) user_data;
+
+ g_assert (wocky_porter_send_finish (
+ WOCKY_PORTER (source), res, NULL));
+
+ data->outstanding--;
+ g_main_loop_quit (data->loop);
+}
+
+/* receive testing */
+static gboolean
+test_receive_stanza_received_cb (WockyPorter *porter,
+ WockyStanza *stanza,
+ gpointer user_data)
+{
+ test_data_t *test = (test_data_t *) user_data;
+ test_expected_stanza_received (test, stanza);
+ return TRUE;
+}
+
+static void
+test_receive (void)
+{
+ loopback_test_t *test = setup ();
+ WockyStanza *s;
+
+ start_test (test);
+
+ wocky_porter_register_handler_from_anyone (test->porter,
+ WOCKY_STANZA_TYPE_MESSAGE, WOCKY_STANZA_SUB_TYPE_NONE, 0,
+ test_receive_stanza_received_cb, test, NULL);
+
+ /* Send a stanza */
+ s = wocky_stanza_build (WOCKY_STANZA_TYPE_MESSAGE,
+ WOCKY_STANZA_SUB_TYPE_CHAT, "juliet@example.com", "romeo@example.net",
+ NULL);
+
+ wocky_porter_send_async (test->porter, s, NULL,
+ send_stanza_cb, test);
+ g_queue_push_tail (test->data.expected_stanzas, s);
+ /* We are waiting for the stanza to be sent and received on the other
+ * side */
+ test->data.outstanding += 2;
+
+ test_wait_pending (&(test->data));
+
+ cleanup (test);
+}
+
+int
+main (int argc, char **argv)
+{
+ int result;
+
+ test_init (argc, argv);
+
+ g_test_add_func ("/loopback-porter/receive", test_receive);
+
+ result = g_test_run ();
+ test_deinit ();
+ return result;
+}
diff --git a/tests/wocky-pubsub-node-test.c b/tests/wocky-pubsub-node-test.c
index 945028c..fb53370 100644
--- a/tests/wocky-pubsub-node-test.c
+++ b/tests/wocky-pubsub-node-test.c
@@ -25,7 +25,7 @@ test_instantiation (void)
stream = g_object_new (WOCKY_TYPE_TEST_STREAM, NULL);
connection = wocky_xmpp_connection_new (stream->stream0);
- session = wocky_session_new (connection, "example.com");
+ session = wocky_session_new_with_connection (connection, "example.com");
pubsub = wocky_pubsub_service_new (session, "pubsub.localhost");
g_assert (pubsub != NULL);
@@ -56,7 +56,7 @@ test_make_publish_stanza (void)
stream = g_object_new (WOCKY_TYPE_TEST_STREAM, NULL);
connection = wocky_xmpp_connection_new (stream->stream0);
- session = wocky_session_new (connection, "example.com");
+ session = wocky_session_new_with_connection (connection, "example.com");
pubsub = wocky_pubsub_service_new (session, "pubsub.localhost");
node = wocky_pubsub_service_ensure_node (pubsub, "track1");
diff --git a/tests/wocky-pubsub-service-test.c b/tests/wocky-pubsub-service-test.c
index 7f4e9bf..2ea4df4 100644
--- a/tests/wocky-pubsub-service-test.c
+++ b/tests/wocky-pubsub-service-test.c
@@ -24,7 +24,7 @@ create_session (void)
stream = g_object_new (WOCKY_TYPE_TEST_STREAM, NULL);
connection = wocky_xmpp_connection_new (stream->stream0);
- session = wocky_session_new (connection, "example.com");
+ session = wocky_session_new_with_connection (connection, "example.com");
g_object_unref (connection);
g_object_unref (stream);
diff --git a/tests/wocky-roster-test.c b/tests/wocky-roster-test.c
index a8fd45d..49a0efc 100644
--- a/tests/wocky-roster-test.c
+++ b/tests/wocky-roster-test.c
@@ -26,7 +26,7 @@ test_instantiation (void)
stream = g_object_new (WOCKY_TYPE_TEST_STREAM, NULL);
connection = wocky_xmpp_connection_new (stream->stream0);
- session = wocky_session_new (connection, "example.com");
+ session = wocky_session_new_with_connection (connection, "example.com");
roster = wocky_roster_new (session);
diff --git a/tests/wocky-session-test.c b/tests/wocky-session-test.c
index f6846b7..8cca2a5 100644
--- a/tests/wocky-session-test.c
+++ b/tests/wocky-session-test.c
@@ -19,7 +19,7 @@ test_instantiation (void)
stream = g_object_new (WOCKY_TYPE_TEST_STREAM, NULL);
connection = wocky_xmpp_connection_new (stream->stream0);
- session = wocky_session_new (connection, "example.com");
+ session = wocky_session_new_with_connection (connection, "example.com");
g_assert (session != NULL);
g_assert (WOCKY_IS_SESSION (session));
@@ -35,7 +35,7 @@ test_get_porter (void)
WockySession *session;
WockyPorter *porter;
- session = wocky_session_new (test->in, "example.com");
+ session = wocky_session_new_with_connection (test->in, "example.com");
porter = wocky_session_get_porter (session);
g_assert (WOCKY_IS_PORTER (porter));
@@ -51,7 +51,7 @@ test_get_contact_factory (void)
WockySession *session;
WockyContactFactory *factory;
- session = wocky_session_new (test->in, "example.com");
+ session = wocky_session_new_with_connection (test->in, "example.com");
factory = wocky_session_get_contact_factory (session);
g_assert (WOCKY_IS_CONTACT_FACTORY (factory));
diff --git a/tests/wocky-test-helper.c b/tests/wocky-test-helper.c
index ee5e754..3bf7496 100644
--- a/tests/wocky-test-helper.c
+++ b/tests/wocky-test-helper.c
@@ -30,8 +30,8 @@ setup_test_full (guint timeout,
data->in = wocky_xmpp_connection_new (data->stream->stream0);
data->out = wocky_xmpp_connection_new (data->stream->stream1);
- data->session_in = wocky_session_new (data->in, in_jid);
- data->session_out = wocky_session_new (data->out, out_jid);
+ data->session_in = wocky_session_new_with_connection (data->in, in_jid);
+ data->session_out = wocky_session_new_with_connection (data->out, out_jid);
data->sched_in = wocky_session_get_porter (data->session_in);
data->sched_out = wocky_session_get_porter (data->session_out);
diff --git a/wocky/Makefile.am b/wocky/Makefile.am
index a7e39ce..d5f9299 100644
--- a/wocky/Makefile.am
+++ b/wocky/Makefile.am
@@ -51,6 +51,7 @@ handwritten_headers = \
wocky-bare-contact.h \
wocky-c2s-porter.h \
wocky-caps-cache.h \
+ wocky-ll-connection-factory.h \
wocky-connector.h \
wocky-contact.h \
wocky-contact-factory.h \
@@ -60,6 +61,10 @@ handwritten_headers = \
wocky-jabber-auth.h \
wocky-jabber-auth-digest.h \
wocky-jabber-auth-password.h \
+ wocky-ll-connector.h \
+ wocky-ll-contact.h \
+ wocky-loopback-stream.h \
+ wocky-meta-porter.h \
wocky-muc.h \
wocky-namespaces.h \
wocky-node.h \
@@ -99,6 +104,7 @@ handwritten_sources = \
wocky-bare-contact.c \
wocky-c2s-porter.c \
wocky-caps-cache.c \
+ wocky-ll-connection-factory.c \
wocky-connector.c \
wocky-contact.c \
wocky-contact-factory.c \
@@ -108,6 +114,10 @@ handwritten_sources = \
wocky-jabber-auth.c \
wocky-jabber-auth-digest.c \
wocky-jabber-auth-password.c \
+ wocky-ll-connector.c \
+ wocky-ll-contact.c \
+ wocky-loopback-stream.c \
+ wocky-meta-porter.c \
wocky-muc.c \
wocky-node.c \
wocky-node-tree.c \
diff --git a/wocky/wocky-bare-contact.c b/wocky/wocky-bare-contact.c
index 479d952..aecce9a 100644
--- a/wocky/wocky-bare-contact.c
+++ b/wocky/wocky-bare-contact.c
@@ -215,10 +215,17 @@ wocky_bare_contact_finalize (GObject *object)
G_OBJECT_CLASS (wocky_bare_contact_parent_class)->finalize (object);
}
+static gchar *
+bare_contact_dup_jid (WockyContact *contact)
+{
+ return g_strdup (wocky_bare_contact_get_jid (WOCKY_BARE_CONTACT (contact)));
+}
+
static void
wocky_bare_contact_class_init (WockyBareContactClass *wocky_bare_contact_class)
{
GObjectClass *object_class = G_OBJECT_CLASS (wocky_bare_contact_class);
+ WockyContactClass *contact_class = WOCKY_CONTACT_CLASS (wocky_bare_contact_class);
GParamSpec *spec;
g_type_class_add_private (wocky_bare_contact_class,
@@ -230,6 +237,8 @@ wocky_bare_contact_class_init (WockyBareContactClass *wocky_bare_contact_class)
object_class->dispose = wocky_bare_contact_dispose;
object_class->finalize = wocky_bare_contact_finalize;
+ contact_class->dup_jid = bare_contact_dup_jid;
+
/**
* WockyBareContact:jid:
*
diff --git a/wocky/wocky-contact-factory.c b/wocky/wocky-contact-factory.c
index 390977f..b307344 100644
--- a/wocky/wocky-contact-factory.c
+++ b/wocky/wocky-contact-factory.c
@@ -65,6 +65,7 @@ enum
{
BARE_CONTACT_ADDED,
RESOURCE_CONTACT_ADDED,
+ LL_CONTACT_ADDED,
LAST_SIGNAL,
};
@@ -77,6 +78,8 @@ struct _WockyContactFactoryPrivate
GHashTable *bare_contacts;
/* full JID (gchar *) => weak reffed (WockyResourceContact *) */
GHashTable *resource_contacts;
+ /* JID (gchar *) => weak reffed (WockyLLContact *) */
+ GHashTable *ll_contacts;
gboolean dispose_has_run;
};
@@ -94,6 +97,8 @@ wocky_contact_factory_init (WockyContactFactory *self)
NULL);
priv->resource_contacts = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, NULL);
+ priv->ll_contacts = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, NULL);
}
static void
@@ -137,8 +142,9 @@ remove_contact (gpointer key,
return value == contact;
}
-/* Called when a WockyBareContact or WockyResourceContact has been disposed so
- * we can remove it from his hash table. */
+/* Called when a WockyBareContact, WockyResourceContact or
+ * WockyLLContact has been disposed so we can remove it from his hash
+ * table. */
static void
contact_disposed_cb (gpointer user_data,
GObject *contact)
@@ -175,6 +181,13 @@ wocky_contact_factory_dispose (GObject *object)
priv->resource_contacts);
}
+ g_hash_table_iter_init (&iter, priv->ll_contacts);
+ while (g_hash_table_iter_next (&iter, NULL, &contact))
+ {
+ g_object_weak_unref (G_OBJECT (contact), contact_disposed_cb,
+ priv->ll_contacts);
+ }
+
if (G_OBJECT_CLASS (wocky_contact_factory_parent_class)->dispose)
G_OBJECT_CLASS (wocky_contact_factory_parent_class)->dispose (object);
}
@@ -187,6 +200,7 @@ wocky_contact_factory_finalize (GObject *object)
g_hash_table_destroy (priv->bare_contacts);
g_hash_table_destroy (priv->resource_contacts);
+ g_hash_table_destroy (priv->ll_contacts);
G_OBJECT_CLASS (wocky_contact_factory_parent_class)->finalize (object);
}
@@ -217,6 +231,12 @@ wocky_contact_factory_class_init (
G_SIGNAL_RUN_LAST, 0, NULL, NULL,
_wocky_signals_marshal_VOID__OBJECT,
G_TYPE_NONE, 1, G_TYPE_OBJECT);
+
+ signals[LL_CONTACT_ADDED] = g_signal_new ("ll-contact-added",
+ G_OBJECT_CLASS_TYPE (wocky_contact_factory_class),
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+ _wocky_signals_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1, G_TYPE_OBJECT);
}
/**
@@ -357,3 +377,100 @@ wocky_contact_factory_lookup_resource_contact (WockyContactFactory *self,
return g_hash_table_lookup (priv->resource_contacts, full_jid);
}
+
+/**
+ * wocky_contact_factory_ensure_ll_contact:
+ * @factory: a #WockyContactFactory instance
+ * @jid: the JID of a contact
+ *
+ * Returns an instance of #WockyLLContact for @jid.
+ * The factory cache is used, but if the contact is not found in the cache,
+ * a new #WockyLLContact is created and cached for future use.
+ *
+ * Returns: a new reference to a #WockyLLContact instance, which the
+ * caller is expected to release with g_object_unref() after use.
+ */
+WockyLLContact *
+wocky_contact_factory_ensure_ll_contact (WockyContactFactory *self,
+ const gchar *jid)
+{
+ WockyContactFactoryPrivate *priv = self->priv;
+ WockyLLContact *contact;
+
+ contact = g_hash_table_lookup (priv->ll_contacts, jid);
+ if (contact != NULL)
+ return g_object_ref (contact);
+
+ contact = wocky_ll_contact_new (jid);
+
+ g_object_weak_ref (G_OBJECT (contact), contact_disposed_cb,
+ priv->ll_contacts);
+ g_hash_table_insert (priv->ll_contacts, g_strdup (jid), contact);
+
+ g_signal_emit (self, signals[LL_CONTACT_ADDED], 0, contact);
+
+ return contact;
+}
+
+/**
+ * wocky_contact_factory_lookup_ll_contact:
+ * @factory: a #WockyContactFactory instance
+ * @jid: the JID of a contact
+ *
+ * Looks up if there's a #WockyLLContact for @jid in the cache, and
+ * returns it if it's found.
+ *
+ * Returns: a borrowed #WockyLLContact instance (which the caller should
+ * reference with g_object_ref() if it will be kept), or %NULL if the
+ * contact is not found.
+ */
+WockyLLContact *
+wocky_contact_factory_lookup_ll_contact (WockyContactFactory *self,
+ const gchar *jid)
+{
+ WockyContactFactoryPrivate *priv = self->priv;
+
+ return g_hash_table_lookup (priv->ll_contacts, jid);
+}
+
+/**
+ * wocky_contact_factory_add_ll_contact:
+ * @factory: a #WockyContactFactory instance
+ * @contact: a #WockyLLContact
+ *
+ * Adds @contact to the contact factory.
+ */
+void
+wocky_contact_factory_add_ll_contact (WockyContactFactory *self,
+ WockyLLContact *contact)
+{
+ WockyContactFactoryPrivate *priv = self->priv;
+
+ if (g_hash_table_lookup (priv->ll_contacts,
+ wocky_contact_dup_jid (WOCKY_CONTACT (contact)))
+ == contact)
+ return;
+
+ g_object_weak_ref (G_OBJECT (contact), contact_disposed_cb,
+ priv->ll_contacts);
+ g_hash_table_insert (priv->ll_contacts,
+ g_strdup (wocky_contact_dup_jid (WOCKY_CONTACT (contact))),
+ contact);
+
+ g_signal_emit (self, signals[LL_CONTACT_ADDED], 0, contact);
+}
+
+/**
+ * wocky_contact_factory_get_ll_contacts:
+ * @factory: a #WockyContactFactory instance
+ *
+ * <!-- -->
+ *
+ * Returns: a newly allocated #GList of #WockyLLContact<!-- -->s which
+ * should be freed using g_list_free().
+ */
+GList *
+wocky_contact_factory_get_ll_contacts (WockyContactFactory *self)
+{
+ return g_hash_table_get_values (self->priv->ll_contacts);
+}
diff --git a/wocky/wocky-contact-factory.h b/wocky/wocky-contact-factory.h
index 84f00e3..be3c39a 100644
--- a/wocky/wocky-contact-factory.h
+++ b/wocky/wocky-contact-factory.h
@@ -25,6 +25,7 @@
#include "wocky-bare-contact.h"
#include "wocky-resource-contact.h"
+#include "wocky-ll-contact.h"
G_BEGIN_DECLS
@@ -85,6 +86,19 @@ WockyResourceContact * wocky_contact_factory_lookup_resource_contact (
WockyContactFactory *factory,
const gchar *full_jid);
+WockyLLContact * wocky_contact_factory_ensure_ll_contact (
+ WockyContactFactory *factory,
+ const gchar *jid);
+
+WockyLLContact * wocky_contact_factory_lookup_ll_contact (
+ WockyContactFactory *factory,
+ const gchar *jid);
+
+void wocky_contact_factory_add_ll_contact (WockyContactFactory *factory,
+ WockyLLContact *contact);
+
+GList * wocky_contact_factory_get_ll_contacts (WockyContactFactory *factory);
+
G_END_DECLS
#endif /* #ifndef __WOCKY_CONTACT_FACTORY_H__*/
diff --git a/wocky/wocky-contact.c b/wocky/wocky-contact.c
index 4df18f0..84a6da6 100644
--- a/wocky/wocky-contact.c
+++ b/wocky/wocky-contact.c
@@ -156,3 +156,14 @@ wocky_contact_class_init (WockyContactClass *wocky_contact_class)
object_class->dispose = wocky_contact_dispose;
object_class->finalize = wocky_contact_finalize;
}
+
+gchar *
+wocky_contact_dup_jid (WockyContact *self)
+{
+ WockyContactClass *cls = WOCKY_CONTACT_GET_CLASS (self);
+
+ if (cls->dup_jid != NULL)
+ return cls->dup_jid (self);
+ else
+ return NULL;
+}
diff --git a/wocky/wocky-contact.h b/wocky/wocky-contact.h
index 89fd51a..763c8b4 100644
--- a/wocky/wocky-contact.h
+++ b/wocky/wocky-contact.h
@@ -35,9 +35,13 @@ typedef struct _WockyContact WockyContact;
typedef struct _WockyContactClass WockyContactClass;
typedef struct _WockyContactPrivate WockyContactPrivate;
+typedef gchar * (*WockyContactDupJidImpl) (WockyContact *self);
+
struct _WockyContactClass {
/*<private>*/
GObjectClass parent_class;
+
+ WockyContactDupJidImpl dup_jid;
};
struct _WockyContact {
@@ -65,6 +69,8 @@ GType wocky_contact_get_type (void);
(G_TYPE_INSTANCE_GET_CLASS ((obj), WOCKY_TYPE_CONTACT, \
WockyContactClass))
+gchar * wocky_contact_dup_jid (WockyContact *self) G_GNUC_WARN_UNUSED_RESULT;
+
G_END_DECLS
#endif /* #ifndef __WOCKY_CONTACT_H__*/
diff --git a/wocky/wocky-debug.c b/wocky/wocky-debug.c
index e233f83..d9f4f8f 100644
--- a/wocky/wocky-debug.c
+++ b/wocky/wocky-debug.c
@@ -32,6 +32,7 @@ static GDebugKey keys[] = {
{ "ping", DEBUG_PING },
{ "heartbeat", DEBUG_HEARTBEAT },
{ "presence", DEBUG_PRESENCE },
+ { "connection-factory",DEBUG_CONNECTION_FACTORY},
{ 0, },
};
diff --git a/wocky/wocky-debug.h b/wocky/wocky-debug.h
index 3051370..6a5c105 100644
--- a/wocky/wocky-debug.h
+++ b/wocky/wocky-debug.h
@@ -35,6 +35,7 @@ typedef enum
DEBUG_PING = 1 << 17,
DEBUG_HEARTBEAT = 1 << 18,
DEBUG_PRESENCE = 1 << 19,
+ DEBUG_CONNECTION_FACTORY= 1 << 20,
} WockyDebugFlags;
#define DEBUG_XMPP (DEBUG_XMPP_READER | DEBUG_XMPP_WRITER)
diff --git a/wocky/wocky-ll-connection-factory.c b/wocky/wocky-ll-connection-factory.c
new file mode 100644
index 0000000..87799ba
--- /dev/null
+++ b/wocky/wocky-ll-connection-factory.c
@@ -0,0 +1,288 @@
+/*
+ * wocky-ll-connection-factory.c - Source for WockyLLConnectionFactory
+ * 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 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
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "wocky-ll-connection-factory.h"
+
+#include "wocky-utils.h"
+
+#define DEBUG_FLAG DEBUG_CONNECTION_FACTORY
+#include "wocky-debug.h"
+
+G_DEFINE_TYPE (WockyLLConnectionFactory, wocky_ll_connection_factory, G_TYPE_OBJECT)
+
+/* private structure */
+struct _WockyLLConnectionFactoryPrivate
+{
+ GSocketClient *client;
+};
+
+GQuark
+wocky_ll_connection_factory_error_quark (void)
+{
+ static GQuark quark = 0;
+
+ if (!quark)
+ quark = g_quark_from_static_string (
+ "wocky_ll_connection_factory_error");
+
+ return quark;
+}
+
+static void
+wocky_ll_connection_factory_init (WockyLLConnectionFactory *self)
+{
+ WockyLLConnectionFactoryPrivate *priv;
+
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, WOCKY_TYPE_LL_CONNECTION_FACTORY,
+ WockyLLConnectionFactoryPrivate);
+ priv = self->priv;
+
+ priv->client = g_socket_client_new ();
+}
+
+static void
+wocky_ll_connection_factory_dispose (GObject *object)
+{
+ WockyLLConnectionFactory *self = WOCKY_LL_CONNECTION_FACTORY (object);
+
+ g_object_unref (self->priv->client);
+
+ if (G_OBJECT_CLASS (wocky_ll_connection_factory_parent_class)->dispose)
+ G_OBJECT_CLASS (wocky_ll_connection_factory_parent_class)->dispose (object);
+}
+
+static void
+wocky_ll_connection_factory_class_init (
+ WockyLLConnectionFactoryClass *wocky_ll_connection_factory_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (wocky_ll_connection_factory_class);
+
+ object_class->dispose = wocky_ll_connection_factory_dispose;
+
+ g_type_class_add_private (wocky_ll_connection_factory_class,
+ sizeof (WockyLLConnectionFactoryPrivate));
+}
+
+/**
+ * wocky_ll_connection_factory_new:
+ *
+ * Convenience function to create a new #WockyLLConnectionFactory object.
+ *
+ * Returns: a newly created instance of #WockyLLConnectionFactory
+ */
+WockyLLConnectionFactory *
+wocky_ll_connection_factory_new (void)
+{
+ return g_object_new (WOCKY_TYPE_LL_CONNECTION_FACTORY,
+ NULL);
+}
+
+typedef struct
+{
+ /* the simple async result will hold a ref to this */
+ WockyLLConnectionFactory *self;
+
+ GSimpleAsyncResult *simple;
+ GCancellable *cancellable;
+
+ GQueue *addresses;
+} NewConnectionData;
+
+static void
+free_new_connection_data (NewConnectionData *data)
+{
+ g_queue_foreach (data->addresses, (GFunc) g_object_unref, NULL);
+ g_queue_free (data->addresses);
+
+ if (data->cancellable != NULL)
+ g_object_unref (data->cancellable);
+
+ g_object_unref (data->simple);
+ g_slice_free (NewConnectionData, data);
+}
+
+static void process_one_address (NewConnectionData *data);
+
+static void
+connect_to_host_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GSocketClient *client = G_SOCKET_CLIENT (source_object);
+ NewConnectionData *data = user_data;
+ GSocketConnection *conn;
+ GError *error = NULL;
+ WockyXmppConnection *connection;
+
+ conn = g_socket_client_connect_to_host_finish (client, result, &error);
+
+ if (conn == NULL)
+ {
+ DEBUG ("failed to connect: %s", error->message);
+ g_clear_error (&error);
+
+ /* shame, well let's move on */
+ process_one_address (data);
+ return;
+ }
+
+ connection = wocky_xmpp_connection_new (G_IO_STREAM (conn));
+
+ DEBUG ("made connection");
+
+ g_simple_async_result_set_op_res_gpointer (data->simple, connection, NULL);
+ g_simple_async_result_complete (data->simple);
+ free_new_connection_data (data);
+}
+
+static void
+process_one_address (NewConnectionData *data)
+{
+ GInetSocketAddress *addr;
+ gchar *host;
+
+ if (g_cancellable_is_cancelled (data->cancellable))
+ {
+ GError *error = g_error_new (G_IO_ERROR,
+ G_IO_ERROR_CANCELLED, "Operation cancelled");
+ g_simple_async_result_take_error (data->simple, error);
+ g_simple_async_result_complete (data->simple);
+ free_new_connection_data (data);
+ return;
+ }
+
+ addr = g_queue_pop_head (data->addresses);
+
+ /* check we haven't gotten to the end of the list */
+ if (addr == NULL)
+ {
+ GError *error = g_error_new (WOCKY_LL_CONNECTION_FACTORY_ERROR,
+ WOCKY_LL_CONNECTION_FACTORY_ERROR_NO_CONTACT_ADDRESS_CAN_BE_CONNECTED_TO,
+ "Failed to connect to any of the contact's addresses");
+ g_simple_async_result_take_error (data->simple, error);
+ g_simple_async_result_complete (data->simple);
+ free_new_connection_data (data);
+ return;
+ }
+
+ host = g_inet_address_to_string (g_inet_socket_address_get_address (addr));
+
+ DEBUG ("connecting to %s (port %" G_GUINT16_FORMAT ")", host,
+ g_inet_socket_address_get_port (addr));
+
+ g_socket_client_connect_to_host_async (data->self->priv->client,
+ host, g_inet_socket_address_get_port (addr),
+ data->cancellable, connect_to_host_cb, data);
+
+ g_free (host);
+
+ g_object_unref (addr);
+}
+
+static void
+add_to_queue (gpointer data,
+ gpointer user_data)
+{
+ GQueue *queue = user_data;
+
+ g_queue_push_tail (queue, data);
+}
+
+/**
+ * wocky_ll_connection_factory_make_connection_async:
+ * @factory: a #WockyLLConnectionFactory
+ * @contact: the #WockyLLStanza to connect to
+ * @cancellable: optional #GCancellable object, %NULL to ignore
+ * @callback: callback to call when the request is satisfied
+ * @user_data: the data to pass to callback function
+ *
+ * Request a connection to @contact. When the connection has been
+ * made, @callback will be called and the caller can then call
+ * #wocky_ll_connection_factorymake_connection_finish to get the
+ * connection.
+ */
+void
+wocky_ll_connection_factory_make_connection_async (
+ WockyLLConnectionFactory *self,
+ WockyLLContact *contact,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ NewConnectionData *data;
+ GList *addr;
+
+ g_return_if_fail (WOCKY_IS_LL_CONNECTION_FACTORY (self));
+ g_return_if_fail (WOCKY_IS_LL_CONTACT (contact));
+ g_return_if_fail (callback != NULL);
+
+ data = g_slice_new0 (NewConnectionData);
+ data->self = self;
+
+ if (cancellable != NULL)
+ data->cancellable = g_object_ref (cancellable);
+
+ data->simple = g_simple_async_result_new (G_OBJECT (self), callback,
+ user_data, wocky_ll_connection_factory_make_connection_async);
+
+ data->addresses = g_queue_new ();
+
+ addr = wocky_ll_contact_get_addresses (contact);
+ g_list_foreach (addr, add_to_queue, data->addresses);
+ g_list_free (addr);
+
+ if (data->addresses == NULL)
+ {
+ GError *error = g_error_new (WOCKY_LL_CONNECTION_FACTORY_ERROR,
+ WOCKY_LL_CONNECTION_FACTORY_ERROR_NO_CONTACT_ADDRESSES,
+ "No addresses available for contact");
+ g_simple_async_result_take_error (data->simple, error);
+ g_simple_async_result_complete (data->simple);
+ free_new_connection_data (data);
+ return;
+ }
+
+ /* go go go */
+ process_one_address (data);
+}
+
+/**
+ * wocky_ll_connection_factory_make_connection_finish:
+ * @factory: a #WockyLLConnectionFactory
+ * @result: a #GAsyncResult
+ * @error: a #GError location to store the error occuring, or %NULL to ignore
+ *
+ * Gets the connection that's been created.
+ *
+ * Returns: the new #WockyXmppConnection on success, %NULL on error
+ */
+WockyXmppConnection *
+wocky_ll_connection_factory_make_connection_finish (
+ WockyLLConnectionFactory *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ wocky_implement_finish_return_pointer (self,
+ wocky_ll_connection_factory_make_connection_async);
+}
+
diff --git a/wocky/wocky-ll-connection-factory.h b/wocky/wocky-ll-connection-factory.h
new file mode 100644
index 0000000..f4437cc
--- /dev/null
+++ b/wocky/wocky-ll-connection-factory.h
@@ -0,0 +1,89 @@
+/*
+ * wocky-ll-connection-factory.h - Header for WockyLLConnectionFactory
+ * 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 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 __WOCKY_LL_CONNECTION_FACTORY_H__
+#define __WOCKY_LL_CONNECTION_FACTORY_H__
+
+#include <glib-object.h>
+
+#include <gio/gio.h>
+
+#include "wocky-xmpp-connection.h"
+#include "wocky-ll-contact.h"
+
+G_BEGIN_DECLS
+
+typedef struct _WockyLLConnectionFactory WockyLLConnectionFactory;
+typedef struct _WockyLLConnectionFactoryClass WockyLLConnectionFactoryClass;
+typedef struct _WockyLLConnectionFactoryPrivate WockyLLConnectionFactoryPrivate;
+
+typedef enum
+{
+ WOCKY_LL_CONNECTION_FACTORY_ERROR_NO_CONTACT_ADDRESSES,
+ WOCKY_LL_CONNECTION_FACTORY_ERROR_NO_CONTACT_ADDRESS_CAN_BE_CONNECTED_TO, /* omg so long! */
+} WockyLLConnectionFactoryError;
+
+GQuark wocky_ll_connection_factory_error_quark (void);
+
+#define WOCKY_LL_CONNECTION_FACTORY_ERROR (wocky_ll_connection_factory_error_quark ())
+
+struct _WockyLLConnectionFactoryClass {
+ GObjectClass parent_class;
+};
+
+struct _WockyLLConnectionFactory {
+ GObject parent;
+ WockyLLConnectionFactoryPrivate *priv;
+};
+
+GType wocky_ll_connection_factory_get_type (void);
+
+#define WOCKY_TYPE_LL_CONNECTION_FACTORY \
+ (wocky_ll_connection_factory_get_type ())
+#define WOCKY_LL_CONNECTION_FACTORY(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), WOCKY_TYPE_LL_CONNECTION_FACTORY, \
+ WockyLLConnectionFactory))
+#define WOCKY_LL_CONNECTION_FACTORY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), WOCKY_TYPE_LL_CONNECTION_FACTORY, \
+ WockyLLConnectionFactoryClass))
+#define WOCKY_IS_LL_CONNECTION_FACTORY(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), WOCKY_TYPE_LL_CONNECTION_FACTORY))
+#define WOCKY_IS_LL_CONNECTION_FACTORY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), WOCKY_TYPE_LL_CONNECTION_FACTORY))
+#define WOCKY_LL_CONNECTION_FACTORY_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), WOCKY_TYPE_LL_CONNECTION_FACTORY, \
+ WockyLLConnectionFactoryClass))
+
+WockyLLConnectionFactory * wocky_ll_connection_factory_new (void);
+
+void wocky_ll_connection_factory_make_connection_async (
+ WockyLLConnectionFactory *factory,
+ WockyLLContact *contact,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+WockyXmppConnection * wocky_ll_connection_factory_make_connection_finish (
+ WockyLLConnectionFactory *factory,
+ GAsyncResult *result,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* #ifndef __WOCKY_LL_CONNECTION_FACTORY_H__*/
diff --git a/wocky/wocky-ll-connector.c b/wocky/wocky-ll-connector.c
new file mode 100644
index 0000000..569b3a2
--- /dev/null
+++ b/wocky/wocky-ll-connector.c
@@ -0,0 +1,518 @@
+/*
+ * wocky-ll-connector.c - Source for WockyLLConnector
+ * 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 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
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gio/gio.h>
+
+#include "wocky-ll-connector.h"
+
+#include "wocky-utils.h"
+#include "wocky-namespaces.h"
+
+#define DEBUG_FLAG DEBUG_CONNECTOR
+#include "wocky-debug.h"
+
+static void initable_iface_init (gpointer, gpointer);
+
+G_DEFINE_TYPE_WITH_CODE (WockyLLConnector, wocky_ll_connector, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, initable_iface_init))
+
+enum
+{
+ PROP_STREAM = 1,
+ PROP_CONNECTION,
+ PROP_LOCAL_JID,
+ PROP_REMOTE_JID,
+ PROP_INCOMING,
+};
+
+/* private structure */
+struct _WockyLLConnectorPrivate
+{
+ GIOStream *stream;
+ WockyXmppConnection *connection;
+ gchar *local_jid;
+ gchar *remote_jid;
+ gboolean incoming;
+
+ gchar *from;
+
+ GSimpleAsyncResult *simple;
+ GCancellable *cancellable;
+};
+
+GQuark
+wocky_ll_connector_error_quark (void)
+{
+ static GQuark quark = 0;
+
+ if (!quark)
+ quark = g_quark_from_static_string (
+ "wocky_ll_connector_error");
+
+ return quark;
+}
+
+static void
+wocky_ll_connector_init (WockyLLConnector *self)
+{
+ WockyLLConnectorPrivate *priv;
+
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, WOCKY_TYPE_LL_CONNECTOR,
+ WockyLLConnectorPrivate);
+ priv = self->priv;
+}
+
+static void
+wocky_ll_connector_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ WockyLLConnector *connector = WOCKY_LL_CONNECTOR (object);
+ WockyLLConnectorPrivate *priv = connector->priv;
+
+ switch (property_id)
+ {
+ case PROP_STREAM:
+ priv->stream = g_value_get_object (value);
+ break;
+ case PROP_CONNECTION:
+ priv->connection = g_value_get_object (value);
+ break;
+ case PROP_LOCAL_JID:
+ priv->local_jid = g_value_dup_string (value);
+ break;
+ case PROP_REMOTE_JID:
+ priv->remote_jid = g_value_dup_string (value);
+ break;
+ case PROP_INCOMING:
+ priv->incoming = g_value_get_boolean (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+wocky_ll_connector_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ WockyLLConnector *connector = WOCKY_LL_CONNECTOR (object);
+ WockyLLConnectorPrivate *priv = connector->priv;
+
+ switch (property_id)
+ {
+ case PROP_STREAM:
+ g_value_set_object (value, priv->stream);
+ break;
+ case PROP_CONNECTION:
+ g_value_set_object (value, priv->connection);
+ break;
+ case PROP_LOCAL_JID:
+ g_value_set_string (value, priv->local_jid);
+ break;
+ case PROP_REMOTE_JID:
+ g_value_set_string (value, priv->remote_jid);
+ break;
+ case PROP_INCOMING:
+ g_value_set_boolean (value, priv->incoming);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+wocky_ll_connector_dispose (GObject *object)
+{
+ WockyLLConnector *self = WOCKY_LL_CONNECTOR (object);
+ WockyLLConnectorPrivate *priv = self->priv;
+
+ DEBUG ("dispose called");
+
+ g_object_unref (priv->connection);
+ priv->connection = NULL;
+
+ g_free (priv->local_jid);
+ priv->local_jid = NULL;
+
+ g_free (priv->remote_jid);
+ priv->remote_jid = NULL;
+
+ g_free (priv->from);
+ priv->from = NULL;
+
+ g_object_unref (priv->simple);
+ priv->simple = NULL;
+
+ g_object_unref (priv->cancellable);
+ priv->cancellable = NULL;
+
+ if (G_OBJECT_CLASS (wocky_ll_connector_parent_class)->dispose)
+ G_OBJECT_CLASS (wocky_ll_connector_parent_class)->dispose (object);
+}
+
+static void
+wocky_ll_connector_constructed (GObject *object)
+{
+ WockyLLConnector *self = WOCKY_LL_CONNECTOR (object);
+ WockyLLConnectorPrivate *priv = self->priv;
+
+ if (G_OBJECT_CLASS (wocky_ll_connector_parent_class)->constructed)
+ G_OBJECT_CLASS (wocky_ll_connector_parent_class)->constructed (object);
+
+ if (priv->connection == NULL)
+ priv->connection = wocky_xmpp_connection_new (priv->stream);
+}
+static void
+wocky_ll_connector_class_init (
+ WockyLLConnectorClass *wocky_ll_connector_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (wocky_ll_connector_class);
+ GParamSpec *spec;
+
+ object_class->get_property = wocky_ll_connector_get_property;
+ object_class->set_property = wocky_ll_connector_set_property;
+ object_class->dispose = wocky_ll_connector_dispose;
+ object_class->constructed = wocky_ll_connector_constructed;
+
+ spec = g_param_spec_object ("stream", "XMPP stream",
+ "The XMPP stream", G_TYPE_IO_STREAM,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_STREAM, spec);
+
+ spec = g_param_spec_object ("connection", "XMPP connection",
+ "The XMPP connection", WOCKY_TYPE_XMPP_CONNECTION,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONNECTION, spec);
+
+ spec = g_param_spec_string ("local-jid", "User's JID",
+ "Local user's XMPP JID",
+ "",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_LOCAL_JID, spec);
+
+ spec = g_param_spec_string ("remote-jid", "Contact's JID",
+ "Remote contact's XMPP JID",
+ "",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_REMOTE_JID, spec);
+
+ spec = g_param_spec_boolean ("incoming", "Incoming",
+ "Whether the connection is incoming",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INCOMING, spec);
+
+ g_type_class_add_private (wocky_ll_connector_class,
+ sizeof (WockyLLConnectorPrivate));
+}
+
+/**
+ * wocky_ll_connector_incoming_async:
+ * @stream: a #GIOStream
+ * @cancellable: an optional #GCancellable, or %NULL
+ * @callback: a function to call when the operation is complete
+ * @user_data: data to pass to @callback
+ *
+ * Start an asychronous connect operation with an incoming link-local
+ * connection by negotiating the stream open stanzas and sending
+ * stream features.
+ *
+ * The ownership of @stream is taken by the connector.
+ *
+ * When the operation is complete, @callback will be called and it
+ * should call wocky_ll_connector_finish().
+ */
+void
+wocky_ll_connector_incoming_async (
+ GIOStream *stream,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_async_initable_new_async (WOCKY_TYPE_LL_CONNECTOR,
+ G_PRIORITY_DEFAULT, cancellable, callback, user_data,
+ "stream", stream,
+ "incoming", TRUE,
+ NULL);
+}
+
+/**
+ * wocky_ll_connector_outgoing_async:
+ * @connection: a #WockyXmppConnection
+ * @local_jid: the JID of the local user
+ * @remote_jid: the JID of the remote contact
+ * @cancellable: an optional #GCancellable, or %NULL
+ * @callback: a function to call when the operation is complete
+ * @user_data: data to pass to @callback
+ *
+ * Start an asychronous connect operation with an outgoing link-local
+ * connection by negotiating the stream open stanzas and sending
+ * stream features.
+ *
+ * The ownership of @connection is taken by the connector.
+ *
+ * When the operation is complete, @callback will be called and it
+ * should call wocky_ll_connector_finish().
+ */
+void
+wocky_ll_connector_outgoing_async (
+ WockyXmppConnection *connection,
+ const gchar *local_jid,
+ const gchar *remote_jid,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_async_initable_new_async (WOCKY_TYPE_LL_CONNECTOR,
+ G_PRIORITY_DEFAULT, cancellable, callback, user_data,
+ "connection", connection,
+ "local-jid", local_jid,
+ "remote-jid", remote_jid,
+ "incoming", FALSE,
+ NULL);
+}
+
+static void
+features_sent_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ WockyXmppConnection *connection = WOCKY_XMPP_CONNECTION (source_object);
+ WockyLLConnector *self = user_data;
+ WockyLLConnectorPrivate *priv = self->priv;
+ GError *error = NULL;
+
+ if (!wocky_xmpp_connection_send_stanza_finish (connection, result, &error))
+ {
+ GError *err = g_error_new (WOCKY_LL_CONNECTOR_ERROR,
+ WOCKY_LL_CONNECTOR_ERROR_FAILED_TO_SEND_STANZA,
+ "Failed to send stream features: %s", error->message);
+ g_clear_error (&error);
+
+ DEBUG ("%s", err->message);
+
+ g_simple_async_result_take_error (priv->simple, err);
+ }
+
+ g_simple_async_result_complete (priv->simple);
+ g_object_unref (self);
+}
+
+static void send_open_cb (GObject *source_object,
+ GAsyncResult *result, gpointer user_data);
+
+static void
+recv_open_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ WockyXmppConnection *connection = WOCKY_XMPP_CONNECTION (source_object);
+ GError *error = NULL;
+ WockyLLConnector *self = user_data;
+ WockyLLConnectorPrivate *priv = self->priv;
+ gchar *from = NULL;
+
+ if (!wocky_xmpp_connection_recv_open_finish (connection, result,
+ NULL, &from, NULL, NULL, NULL, &error))
+ {
+ GError *err = g_error_new (WOCKY_LL_CONNECTOR_ERROR,
+ WOCKY_LL_CONNECTOR_ERROR_FAILED_TO_RECEIVE_STANZA,
+ "Failed to receive stream open: %s", error->message);
+ g_clear_error (&error);
+
+ DEBUG ("%s", err->message);
+ g_simple_async_result_take_error (priv->simple, err);
+ g_simple_async_result_complete (priv->simple);
+ g_object_unref (self);
+ return;
+ }
+
+ if (!priv->incoming)
+ {
+ WockyStanza *features;
+
+ DEBUG ("connected, sending stream features but not "
+ "expecting anything back");
+
+ features = wocky_stanza_new ("features", WOCKY_XMPP_NS_STREAM);
+ wocky_xmpp_connection_send_stanza_async (connection,
+ features, NULL, features_sent_cb, self);
+ g_object_unref (features);
+ }
+ else
+ {
+ DEBUG ("stream opened from %s, sending open back",
+ from != NULL ? from : "<no from attribute>");
+
+ wocky_xmpp_connection_send_open_async (connection, from,
+ priv->local_jid, "1.0", NULL, NULL, priv->cancellable,
+ send_open_cb, self);
+ }
+
+ priv->from = from;
+}
+
+static void
+send_open_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ WockyXmppConnection *connection = WOCKY_XMPP_CONNECTION (source_object);
+ GError *error = NULL;
+ WockyLLConnector *self = user_data;
+ WockyLLConnectorPrivate *priv = self->priv;
+
+ if (!wocky_xmpp_connection_send_open_finish (connection, result, &error))
+ {
+ GError *err = g_error_new (WOCKY_LL_CONNECTOR_ERROR,
+ WOCKY_LL_CONNECTOR_ERROR_FAILED_TO_SEND_STANZA,
+ "Failed to send stream open: %s", error->message);
+ g_clear_error (&error);
+
+ DEBUG ("%s", err->message);
+
+ g_simple_async_result_take_error (priv->simple, err);
+ g_simple_async_result_complete (priv->simple);
+ g_object_unref (self);
+ return;
+ }
+
+ if (!priv->incoming)
+ {
+ DEBUG ("successfully sent stream open, now waiting for other side to too");
+
+ wocky_xmpp_connection_recv_open_async (connection, priv->cancellable,
+ recv_open_cb, self);
+ }
+ else
+ {
+ WockyStanza *features;
+
+ DEBUG ("connected, sending stream features but not "
+ "expecting anything back");
+
+ features = wocky_stanza_new ("features", WOCKY_XMPP_NS_STREAM);
+ wocky_xmpp_connection_send_stanza_async (connection,
+ features, NULL, features_sent_cb, self);
+ g_object_unref (features);
+ }
+}
+
+/**
+ * wocky_ll_connector_finish:
+ * @connector: a #WockyLLConnector
+ * @result: a #GAsyncResult
+ * @from: a location to store the remote user's JID, or %NULL
+ * @error: a location to save errors to, or %NULL to ignore
+ *
+ * Gets the result of the asynchronous connect request.
+ *
+ * Returns: the connected #WockyXmppConnection which should be freed
+ * using g_object_unref(), or %NULL on error
+ */
+WockyXmppConnection *
+wocky_ll_connector_finish (WockyLLConnector *self,
+ GAsyncResult *result,
+ gchar **from,
+ GError **error)
+{
+ WockyLLConnectorPrivate *priv = self->priv;
+
+ if (g_async_initable_new_finish (G_ASYNC_INITABLE (self),
+ result, error) == NULL)
+ return NULL;
+
+ if (from != NULL)
+ *from = g_strdup (priv->from);
+
+ return g_object_ref (priv->connection);
+}
+
+static void
+wocky_ll_connector_init_async (GAsyncInitable *initable,
+ int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ WockyLLConnector *self = WOCKY_LL_CONNECTOR (initable);
+ WockyLLConnectorPrivate *priv = self->priv;
+
+ g_return_if_fail (priv->simple == NULL);
+
+ priv->simple = g_simple_async_result_new (G_OBJECT (self),
+ callback, user_data, wocky_ll_connector_init_async);
+
+ if (cancellable != NULL)
+ priv->cancellable = g_object_ref (cancellable);
+
+ if (priv->incoming)
+ {
+ /* we need to wait for stream open first */
+ wocky_xmpp_connection_recv_open_async (priv->connection,
+ priv->cancellable, recv_open_cb, self);
+ }
+ else
+ {
+ /* we need to send stream open first */
+ wocky_xmpp_connection_send_open_async (priv->connection,
+ priv->remote_jid, priv->local_jid, "1.0", NULL, NULL, priv->cancellable,
+ send_open_cb, self);
+ }
+}
+
+static gboolean
+wocky_ll_connector_init_finish (GAsyncInitable *initable,
+ GAsyncResult *result,
+ GError **error)
+{
+ WockyLLConnector *self = WOCKY_LL_CONNECTOR (initable);
+ GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result);
+ WockyLLConnectorPrivate *priv = self->priv;
+
+ g_return_val_if_fail (priv->simple == simple, FALSE);
+
+ if (g_simple_async_result_propagate_error (simple, error))
+ return FALSE;
+
+ g_return_val_if_fail (g_simple_async_result_is_valid (result,
+ G_OBJECT (self), wocky_ll_connector_init_async), FALSE);
+
+ return TRUE;
+}
+
+static void
+initable_iface_init (gpointer g_iface,
+ gpointer data)
+{
+ GAsyncInitableIface *iface = g_iface;
+
+ iface->init_async = wocky_ll_connector_init_async;
+ iface->init_finish = wocky_ll_connector_init_finish;
+}
diff --git a/wocky/wocky-ll-connector.h b/wocky/wocky-ll-connector.h
new file mode 100644
index 0000000..b9a6b7f
--- /dev/null
+++ b/wocky/wocky-ll-connector.h
@@ -0,0 +1,94 @@
+/*
+ * wocky-ll-connector.h - Header for WockyLLConnector
+ * 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 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 __WOCKY_LL_CONNECTOR_H__
+#define __WOCKY_LL_CONNECTOR_H__
+
+#include <glib-object.h>
+
+#include <gio/gio.h>
+
+#include "wocky-xmpp-connection.h"
+
+G_BEGIN_DECLS
+
+typedef struct _WockyLLConnector WockyLLConnector;
+typedef struct _WockyLLConnectorClass WockyLLConnectorClass;
+typedef struct _WockyLLConnectorPrivate WockyLLConnectorPrivate;
+
+typedef enum
+{
+ WOCKY_LL_CONNECTOR_ERROR_FAILED_TO_SEND_STANZA,
+ WOCKY_LL_CONNECTOR_ERROR_FAILED_TO_RECEIVE_STANZA,
+} WockyLLConnectorError;
+
+GQuark wocky_ll_connector_error_quark (void);
+
+#define WOCKY_LL_CONNECTOR_ERROR (wocky_ll_connector_error_quark ())
+
+struct _WockyLLConnectorClass {
+ GObjectClass parent_class;
+};
+
+struct _WockyLLConnector {
+ GObject parent;
+ WockyLLConnectorPrivate *priv;
+};
+
+GType wocky_ll_connector_get_type (void);
+
+#define WOCKY_TYPE_LL_CONNECTOR \
+ (wocky_ll_connector_get_type ())
+#define WOCKY_LL_CONNECTOR(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), WOCKY_TYPE_LL_CONNECTOR, \
+ WockyLLConnector))
+#define WOCKY_LL_CONNECTOR_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), WOCKY_TYPE_LL_CONNECTOR, \
+ WockyLLConnectorClass))
+#define WOCKY_IS_LL_CONNECTOR(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), WOCKY_TYPE_LL_CONNECTOR))
+#define WOCKY_IS_LL_CONNECTOR_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), WOCKY_TYPE_LL_CONNECTOR))
+#define WOCKY_LL_CONNECTOR_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), WOCKY_TYPE_LL_CONNECTOR, \
+ WockyLLConnectorClass))
+
+void wocky_ll_connector_incoming_async (
+ GIOStream *stream,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+void wocky_ll_connector_outgoing_async (
+ WockyXmppConnection *connection,
+ const gchar *local_jid,
+ const gchar *remote_jid,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+WockyXmppConnection * wocky_ll_connector_finish (
+ WockyLLConnector *connector,
+ GAsyncResult *result,
+ gchar **from,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* #ifndef __WOCKY_LL_CONNECTOR_H__*/
diff --git a/wocky/wocky-ll-contact.c b/wocky/wocky-ll-contact.c
new file mode 100644
index 0000000..9cae218
--- /dev/null
+++ b/wocky/wocky-ll-contact.c
@@ -0,0 +1,290 @@
+/*
+ * wocky-ll-contact.c - Source for WockyLLContact
+ * 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 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: wocky-ll-contact
+ * @title: WockyLLContact
+ * @short_description: Wrapper around a link-local contact.
+ * @include: wocky/wocky-ll-contact.h
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "wocky-ll-contact.h"
+
+#include <gio/gio.h>
+
+#include "wocky-utils.h"
+
+G_DEFINE_TYPE (WockyLLContact, wocky_ll_contact, WOCKY_TYPE_CONTACT)
+
+/* properties */
+enum
+{
+ PROP_JID = 1,
+};
+
+/* signal enum */
+enum
+{
+ LAST_SIGNAL,
+};
+
+/* private structure */
+struct _WockyLLContactPrivate
+{
+ gboolean dispose_has_run;
+
+ gchar *jid;
+};
+
+static void
+wocky_ll_contact_init (WockyLLContact *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ WOCKY_TYPE_LL_CONTACT, WockyLLContactPrivate);
+}
+
+static void
+wocky_ll_contact_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ WockyLLContact *self = WOCKY_LL_CONTACT (object);
+ WockyLLContactPrivate *priv = self->priv;
+
+ switch (property_id)
+ {
+ case PROP_JID:
+ priv->jid = g_value_dup_string (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+wocky_ll_contact_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ WockyLLContact *self = WOCKY_LL_CONTACT (object);
+ WockyLLContactPrivate *priv = self->priv;
+
+ switch (property_id)
+ {
+ case PROP_JID:
+ g_value_set_string (value, priv->jid);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+wocky_ll_contact_constructed (GObject *object)
+{
+ WockyLLContact *self = WOCKY_LL_CONTACT (object);
+
+ g_assert (self->priv->jid != NULL);
+}
+
+static void
+wocky_ll_contact_finalize (GObject *object)
+{
+ WockyLLContact *self = WOCKY_LL_CONTACT (object);
+ WockyLLContactPrivate *priv = self->priv;
+
+ if (priv->jid != NULL)
+ g_free (priv->jid);
+
+ G_OBJECT_CLASS (wocky_ll_contact_parent_class)->finalize (object);
+}
+
+static gchar *
+ll_contact_dup_jid (WockyContact *contact)
+{
+ return g_strdup (wocky_ll_contact_get_jid (WOCKY_LL_CONTACT (contact)));
+}
+
+static void
+wocky_ll_contact_class_init (WockyLLContactClass *wocky_ll_contact_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (wocky_ll_contact_class);
+ WockyContactClass *contact_class = WOCKY_CONTACT_CLASS (wocky_ll_contact_class);
+ GParamSpec *spec;
+
+ g_type_class_add_private (wocky_ll_contact_class,
+ sizeof (WockyLLContactPrivate));
+
+ object_class->constructed = wocky_ll_contact_constructed;
+ object_class->set_property = wocky_ll_contact_set_property;
+ object_class->get_property = wocky_ll_contact_get_property;
+ object_class->finalize = wocky_ll_contact_finalize;
+
+ contact_class->dup_jid = ll_contact_dup_jid;
+
+ /**
+ * WockyLLContact:jid:
+ *
+ * The contact's link-local JID.
+ */
+ spec = g_param_spec_string ("jid", "Contact JID",
+ "Contact JID",
+ "",
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_JID, spec);
+}
+
+/**
+ * wocky_ll_contact_new:
+ * @jid: the JID of the contact to create
+ *
+ * Creates a new #WockyLLContact for a given JID.
+ *
+ * Returns: a newly constructed #WockyLLContact
+ */
+
+WockyLLContact *
+wocky_ll_contact_new (const gchar *jid)
+{
+ return g_object_new (WOCKY_TYPE_LL_CONTACT,
+ "jid", jid,
+ NULL);
+}
+
+/**
+ * wocky_ll_contact_get_jid:
+ * @contact: a #WockyLLContact instance
+ *
+ * Returns the JID of the contact wrapped by @contact.
+ *
+ * Returns: @contact's JID.
+ */
+const gchar *
+wocky_ll_contact_get_jid (WockyLLContact *contact)
+{
+ WockyLLContactPrivate *priv;
+
+ g_return_val_if_fail (WOCKY_IS_LL_CONTACT (contact), NULL);
+
+ priv = contact->priv;
+
+ return priv->jid;
+}
+
+/**
+ * wocky_ll_contact_equal:
+ * @a: a #WockyLLContact instance
+ * @b: a #WockyLLContact instance to compare with @a
+ *
+ * Compares whether two #WockyLLContact instances refer to the same
+ * link-local contact.
+ *
+ * Returns: #TRUE if the two contacts match.
+ */
+gboolean
+wocky_ll_contact_equal (WockyLLContact *a,
+ WockyLLContact *b)
+{
+ if (a == NULL || b == NULL)
+ return FALSE;
+
+ if (wocky_strdiff (wocky_ll_contact_get_jid (a),
+ wocky_ll_contact_get_jid (b)))
+ return FALSE;
+
+ return TRUE;
+}
+
+/**
+ * wocky_ll_contact_get_addresses:
+ * @self: a #WockyLLContact
+ *
+ * Returns a #GList of #GInetSocketAddress<!-- -->es which are
+ * advertised by the contact @self as addresses to connect on. Note
+ * that the #GInetSocketAddresses should be unreffed by calling
+ * g_object_unref() on each list member and the list freed using
+ * g_list_free() when the caller is finished.
+ *
+ * Returns: (element-type GInetSocketAddress) (transfer full): a new
+ * #GList of #GInetSocketAddress<!-- -->es.
+ */
+GList *
+wocky_ll_contact_get_addresses (WockyLLContact *self)
+{
+ WockyLLContactClass *cls;
+
+ g_return_val_if_fail (WOCKY_IS_LL_CONTACT (self), NULL);
+
+ cls = WOCKY_LL_CONTACT_GET_CLASS (self);
+
+ if (cls->get_addresses != NULL)
+ return cls->get_addresses (self);
+
+ return NULL;
+}
+
+/**
+ * wocky_ll_contact_has_address:
+ * @self: a #WockyLLContact
+ * @address: a #GInetAddress
+ *
+ * Checks whether @address relates to the contact @self.
+ *
+ * Returns: %TRUE if @address relates to the contact @self, otherwise
+ * %FALSE
+ */
+gboolean
+wocky_ll_contact_has_address (WockyLLContact *self,
+ GInetAddress *address)
+{
+ gchar *s = g_inet_address_to_string (address);
+ gboolean ret = FALSE;
+ GList *l, *addresses = wocky_ll_contact_get_addresses (self);
+
+ for (l = addresses; l != NULL; l = l->next)
+ {
+ GInetAddress *a = g_inet_socket_address_get_address (
+ G_INET_SOCKET_ADDRESS (l->data));
+ gchar *tmp = g_inet_address_to_string (a);
+
+ if (!wocky_strdiff (tmp, s))
+ ret = TRUE;
+
+ g_free (tmp);
+
+ if (ret)
+ break;
+ }
+
+ g_list_foreach (addresses, (GFunc) g_object_unref, NULL);
+ g_list_free (addresses);
+ g_free (s);
+
+ return ret;
+}
diff --git a/wocky/wocky-ll-contact.h b/wocky/wocky-ll-contact.h
new file mode 100644
index 0000000..13ae1d9
--- /dev/null
+++ b/wocky/wocky-ll-contact.h
@@ -0,0 +1,81 @@
+/*
+ * wocky-ll-contact.h - Header for WockyLLContact
+ * 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 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 __WOCKY_LL_CONTACT_H__
+#define __WOCKY_LL_CONTACT_H__
+
+#include <glib-object.h>
+
+#include <gio/gio.h>
+
+#include "wocky-types.h"
+#include "wocky-contact.h"
+
+G_BEGIN_DECLS
+
+typedef struct _WockyLLContactClass WockyLLContactClass;
+typedef struct _WockyLLContactPrivate WockyLLContactPrivate;
+
+typedef GList * (*WockyLLContactGetAddressesImpl) (WockyLLContact *);
+
+struct _WockyLLContactClass {
+ WockyContactClass parent_class;
+
+ WockyLLContactGetAddressesImpl get_addresses;
+};
+
+struct _WockyLLContact {
+ WockyContact parent;
+
+ WockyLLContactPrivate *priv;
+};
+
+GType wocky_ll_contact_get_type (void);
+
+#define WOCKY_TYPE_LL_CONTACT \
+ (wocky_ll_contact_get_type ())
+#define WOCKY_LL_CONTACT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), WOCKY_TYPE_LL_CONTACT, \
+ WockyLLContact))
+#define WOCKY_LL_CONTACT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), WOCKY_TYPE_LL_CONTACT, \
+ WockyLLContactClass))
+#define WOCKY_IS_LL_CONTACT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), WOCKY_TYPE_LL_CONTACT))
+#define WOCKY_IS_LL_CONTACT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), WOCKY_TYPE_LL_CONTACT))
+#define WOCKY_LL_CONTACT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), WOCKY_TYPE_LL_CONTACT, \
+ WockyLLContactClass))
+
+WockyLLContact * wocky_ll_contact_new (const gchar *jid);
+
+const gchar *wocky_ll_contact_get_jid (WockyLLContact *contact);
+
+gboolean wocky_ll_contact_equal (WockyLLContact *a,
+ WockyLLContact *b);
+
+GList * wocky_ll_contact_get_addresses (WockyLLContact *self);
+
+gboolean wocky_ll_contact_has_address (WockyLLContact *self,
+ GInetAddress *address);
+
+G_END_DECLS
+
+#endif /* #ifndef __WOCKY_LL_CONTACT_H__*/
diff --git a/wocky/wocky-loopback-stream.c b/wocky/wocky-loopback-stream.c
new file mode 100644
index 0000000..64bc6b2
--- /dev/null
+++ b/wocky/wocky-loopback-stream.c
@@ -0,0 +1,544 @@
+/*
+ * wocky-loopback-stream.c - Source for WockyLoopbackStream
+ * Copyright (C) 2009-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 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 "wocky-loopback-stream.h"
+
+enum {
+ PROP_IO_INPUT_STREAM = 1,
+ PROP_IO_OUTPUT_STREAM
+};
+
+static GType wocky_loopback_input_stream_get_type (void);
+static GType wocky_loopback_output_stream_get_type (void);
+
+struct _WockyLoopbackStreamPrivate
+{
+ GInputStream *input;
+ GOutputStream *output;
+};
+
+typedef struct
+{
+ GOutputStream parent;
+ GAsyncQueue *queue;
+ GError *write_error /* no, this is not a coding style violation */;
+ gboolean dispose_has_run;
+} WockyLoopbackOutputStream;
+
+typedef struct
+{
+ GOutputStreamClass parent_class;
+} WockyLoopbackOutputStreamClass;
+
+typedef struct
+{
+ GInputStream parent;
+ GAsyncQueue *queue;
+ guint offset;
+ GArray *out_array;
+ GSimpleAsyncResult *read_result;
+ GCancellable *read_cancellable;
+ gulong read_cancellable_sig_id;
+ void *buffer;
+ gsize count;
+ GError *read_error /* no, this is not a coding style violation */;
+ gboolean dispose_has_run;
+} WockyLoopbackInputStream;
+
+typedef struct
+{
+ GOutputStreamClass parent_class;
+} WockyLoopbackInputStreamClass;
+
+
+G_DEFINE_TYPE (WockyLoopbackStream, wocky_loopback_stream,
+ G_TYPE_IO_STREAM);
+G_DEFINE_TYPE (WockyLoopbackInputStream, wocky_loopback_input_stream,
+ G_TYPE_INPUT_STREAM);
+G_DEFINE_TYPE (WockyLoopbackOutputStream, wocky_loopback_output_stream,
+ G_TYPE_OUTPUT_STREAM);
+
+#define WOCKY_TYPE_LOOPBACK_INPUT_STREAM (wocky_loopback_input_stream_get_type ())
+#define WOCKY_TYPE_LOOPBACK_OUTPUT_STREAM (wocky_loopback_output_stream_get_type ())
+
+#define WOCKY_LOOPBACK_INPUT_STREAM(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), \
+ WOCKY_TYPE_LOOPBACK_INPUT_STREAM, \
+ WockyLoopbackInputStream))
+
+#define WOCKY_LOOPBACK_OUTPUT_STREAM(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), \
+ WOCKY_TYPE_LOOPBACK_OUTPUT_STREAM, \
+ WockyLoopbackOutputStream))
+
+static gboolean wocky_loopback_input_stream_try_read (WockyLoopbackInputStream *self);
+
+static void
+output_data_written_cb (GOutputStream *output,
+ WockyLoopbackInputStream *input_stream)
+{
+ wocky_loopback_input_stream_try_read (input_stream);
+}
+
+/* connection */
+static void
+wocky_loopback_stream_init (WockyLoopbackStream *self)
+{
+ WockyLoopbackStreamPrivate *priv;
+
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, WOCKY_TYPE_LOOPBACK_STREAM,
+ WockyLoopbackStreamPrivate);
+ priv = self->priv;
+
+ priv->output = g_object_new (WOCKY_TYPE_LOOPBACK_OUTPUT_STREAM, NULL);
+
+ priv->input = g_object_new (WOCKY_TYPE_LOOPBACK_INPUT_STREAM, NULL);
+ WOCKY_LOOPBACK_INPUT_STREAM (priv->input)->queue =
+ g_async_queue_ref (
+ WOCKY_LOOPBACK_OUTPUT_STREAM (priv->output)->queue);
+
+ g_signal_connect (priv->output, "data-written",
+ G_CALLBACK (output_data_written_cb), priv->input);
+}
+
+static void
+wocky_loopback_stream_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ WockyLoopbackStream *self = WOCKY_LOOPBACK_STREAM (object);
+ WockyLoopbackStreamPrivate *priv = self->priv;
+
+ switch (property_id)
+ {
+ case PROP_IO_INPUT_STREAM:
+ g_value_set_object (value, priv->input);
+ break;
+ case PROP_IO_OUTPUT_STREAM:
+ g_value_set_object (value, priv->output);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+wocky_loopback_stream_dispose (GObject *object)
+{
+ WockyLoopbackStream *self = WOCKY_LOOPBACK_STREAM (object);
+ WockyLoopbackStreamPrivate *priv = self->priv;
+
+ if (G_OBJECT_CLASS (wocky_loopback_stream_parent_class)->dispose)
+ G_OBJECT_CLASS (wocky_loopback_stream_parent_class)->dispose (object);
+
+ g_object_unref (priv->input);
+ g_object_unref (priv->output);
+}
+
+static GInputStream *
+wocky_loopback_stream_get_input_stream (GIOStream *stream)
+{
+ return WOCKY_LOOPBACK_STREAM (stream)->priv->input;
+}
+
+static GOutputStream *
+wocky_loopback_stream_get_output_stream (GIOStream *stream)
+{
+ return WOCKY_LOOPBACK_STREAM (stream)->priv->output;
+}
+
+static void
+wocky_loopback_stream_class_init (
+ WockyLoopbackStreamClass *wocky_loopback_stream_class)
+{
+ GObjectClass *obj_class = G_OBJECT_CLASS (wocky_loopback_stream_class);
+ GIOStreamClass *stream_class = G_IO_STREAM_CLASS (
+ wocky_loopback_stream_class);
+
+ g_type_class_add_private (wocky_loopback_stream_class,
+ sizeof (WockyLoopbackStreamPrivate));
+
+ obj_class->dispose = wocky_loopback_stream_dispose;
+ obj_class->get_property = wocky_loopback_stream_get_property;
+
+ stream_class->get_input_stream = wocky_loopback_stream_get_input_stream;
+ stream_class->get_output_stream = wocky_loopback_stream_get_output_stream;
+
+ g_object_class_install_property (obj_class, PROP_IO_INPUT_STREAM,
+ g_param_spec_object ("input-stream", "Input stream",
+ "the input stream",
+ G_TYPE_INPUT_STREAM,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (obj_class, PROP_IO_OUTPUT_STREAM,
+ g_param_spec_object ("output-stream", "Output stream", "the output stream",
+ G_TYPE_OUTPUT_STREAM,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+}
+
+GIOStream *
+wocky_loopback_stream_new (void)
+{
+ return g_object_new (WOCKY_TYPE_LOOPBACK_STREAM, NULL);
+}
+
+/* Input stream */
+static gssize
+wocky_loopback_input_stream_read (GInputStream *stream,
+ void *buffer,
+ gsize count,
+ GCancellable *cancellable,
+ GError **error)
+{
+ WockyLoopbackInputStream *self = WOCKY_LOOPBACK_INPUT_STREAM (stream);
+ gsize written = 0;
+
+ if (self->out_array == NULL)
+ {
+ g_assert (self->offset == 0);
+ self->out_array = g_async_queue_pop (self->queue);
+ }
+
+ do
+ {
+ gsize towrite;
+
+ if (self->offset == 0)
+ {
+ towrite = MIN (count - written, MAX (self->out_array->len/2, 1));
+ }
+ else
+ {
+ towrite = MIN (count - written, self->out_array->len - self->offset);
+ }
+
+ memcpy ((guchar *) buffer + written,
+ self->out_array->data + self->offset,
+ towrite);
+
+ self->offset += towrite;
+ written += towrite;
+
+ if (self->offset == self->out_array->len)
+ {
+ g_array_free (self->out_array, TRUE);
+ self->out_array = g_async_queue_try_pop (self->queue);
+ self->offset = 0;
+ }
+ else
+ {
+ break;
+ }
+ }
+ while (written < count && self->out_array != NULL);
+
+ return written;
+}
+
+static void
+read_async_complete (WockyLoopbackInputStream *self)
+{
+ GSimpleAsyncResult *r = self->read_result;
+
+ if (self->read_cancellable != NULL)
+ {
+ g_signal_handler_disconnect (self->read_cancellable,
+ self->read_cancellable_sig_id);
+ g_object_unref (self->read_cancellable);
+ self->read_cancellable = NULL;
+ }
+
+ self->read_result = NULL;
+
+ g_simple_async_result_complete_in_idle (r);
+ g_object_unref (r);
+}
+
+static void
+read_cancelled_cb (GCancellable *cancellable,
+ WockyLoopbackInputStream *self)
+{
+ g_simple_async_result_set_error (self->read_result,
+ G_IO_ERROR, G_IO_ERROR_CANCELLED, "Reading cancelled");
+
+ self->buffer = NULL;
+ read_async_complete (self);
+}
+
+static void
+wocky_loopback_input_stream_read_async (GInputStream *stream,
+ void *buffer,
+ gsize count,
+ int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ WockyLoopbackInputStream *self = WOCKY_LOOPBACK_INPUT_STREAM (stream);
+
+ g_assert (self->buffer == NULL);
+ g_assert (self->read_result == NULL);
+ g_assert (self->read_cancellable == NULL);
+
+ self->buffer = buffer;
+ self->count = count;
+
+ self->read_result = g_simple_async_result_new (G_OBJECT (stream),
+ callback, user_data, wocky_loopback_input_stream_read_async);
+
+ if (self->read_error != NULL)
+ {
+ g_simple_async_result_set_from_error (self->read_result,
+ self->read_error);
+
+ g_error_free (self->read_error);
+ self->read_error = NULL;
+ read_async_complete (self);
+ return;
+ }
+
+ if (cancellable != NULL)
+ {
+ self->read_cancellable = g_object_ref (cancellable);
+ self->read_cancellable_sig_id = g_signal_connect (cancellable,
+ "cancelled", G_CALLBACK (read_cancelled_cb), self);
+ }
+
+ wocky_loopback_input_stream_try_read (self);
+}
+
+static gssize
+wocky_loopback_input_stream_read_finish (GInputStream *stream,
+ GAsyncResult *result,
+ GError **error)
+{
+ WockyLoopbackInputStream *self = WOCKY_LOOPBACK_INPUT_STREAM (stream);
+ gssize len = -1;
+
+ if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result),
+ error))
+ goto out;
+
+ g_return_val_if_fail (g_simple_async_result_is_valid (result,
+ G_OBJECT (self), wocky_loopback_input_stream_read_async), -1);
+
+ len = wocky_loopback_input_stream_read (stream, self->buffer, self->count, NULL,
+ error);
+
+ out:
+ self->buffer = NULL;
+
+ return len;
+}
+
+static gboolean
+wocky_loopback_input_stream_try_read (WockyLoopbackInputStream *self)
+{
+ if (self->read_result == NULL)
+ /* No pending read operation */
+ return FALSE;
+
+ if (self->out_array == NULL
+ && g_async_queue_length (self->queue) == 0)
+ return FALSE;
+
+ read_async_complete (self);
+ return TRUE;
+}
+
+static void
+wocky_loopback_input_stream_init (WockyLoopbackInputStream *self)
+{
+}
+
+static void
+wocky_loopback_input_stream_dispose (GObject *object)
+{
+ WockyLoopbackInputStream *self = WOCKY_LOOPBACK_INPUT_STREAM (object);
+
+ if (self->dispose_has_run)
+ return;
+
+ self->dispose_has_run = TRUE;
+
+ if (self->out_array != NULL)
+ g_array_free (self->out_array, TRUE);
+ self->out_array = NULL;
+
+ if (self->queue != NULL)
+ g_async_queue_unref (self->queue);
+ self->queue = NULL;
+
+ g_warn_if_fail (self->read_result == NULL);
+ g_warn_if_fail (self->read_cancellable == NULL);
+
+ /* release any references held by the object here */
+ if (G_OBJECT_CLASS (wocky_loopback_input_stream_parent_class)->dispose)
+ G_OBJECT_CLASS (wocky_loopback_input_stream_parent_class)->dispose (object);
+}
+
+static void
+wocky_loopback_input_stream_class_init (
+ WockyLoopbackInputStreamClass *wocky_loopback_input_stream_class)
+{
+ GObjectClass *obj_class = G_OBJECT_CLASS (wocky_loopback_input_stream_class);
+ GInputStreamClass *stream_class =
+ G_INPUT_STREAM_CLASS (wocky_loopback_input_stream_class);
+
+ obj_class->dispose = wocky_loopback_input_stream_dispose;
+ stream_class->read_fn = wocky_loopback_input_stream_read;
+ stream_class->read_async = wocky_loopback_input_stream_read_async;
+ stream_class->read_finish = wocky_loopback_input_stream_read_finish;
+}
+
+/* Output stream */
+enum
+{
+ OUTPUT_DATA_WRITTEN,
+ LAST_SIGNAL
+};
+
+static guint output_signals[LAST_SIGNAL] = {0};
+
+static gssize
+wocky_loopback_output_stream_write (GOutputStream *stream,
+ const void *buffer,
+ gsize count,
+ GCancellable *cancellable,
+ GError **error)
+{
+ WockyLoopbackOutputStream *self = WOCKY_LOOPBACK_OUTPUT_STREAM (stream);
+ GArray *data;
+
+ data = g_array_sized_new (FALSE, FALSE, sizeof (guint8), count);
+
+ g_array_insert_vals (data, 0, buffer, count);
+
+ g_async_queue_push (self->queue, data);
+ g_signal_emit (self, output_signals[OUTPUT_DATA_WRITTEN], 0);
+
+ return count;
+}
+
+static void
+wocky_loopback_output_stream_write_async (GOutputStream *stream,
+ const void *buffer,
+ gsize count,
+ int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple;
+ GError *error = NULL;
+ gssize result;
+
+ result = wocky_loopback_output_stream_write (stream, buffer, count, cancellable,
+ &error);
+
+ simple = g_simple_async_result_new (G_OBJECT (stream), callback, user_data,
+ wocky_loopback_output_stream_write_async);
+
+ if (result == -1)
+ {
+ g_simple_async_result_set_from_error (simple, error);
+ g_error_free (error);
+ }
+ else
+ {
+ g_simple_async_result_set_op_res_gssize (simple, result);
+ }
+
+ g_simple_async_result_complete_in_idle (simple);
+ g_object_unref (simple);
+}
+
+static gssize
+wocky_loopback_output_stream_write_finish (GOutputStream *stream,
+ GAsyncResult *result,
+ GError **error)
+{
+ if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result),
+ error))
+ return -1;
+
+ g_return_val_if_fail (g_simple_async_result_is_valid (result,
+ G_OBJECT (stream), wocky_loopback_output_stream_write_async), -1);
+
+ return g_simple_async_result_get_op_res_gssize (
+ G_SIMPLE_ASYNC_RESULT (result));
+}
+
+static void
+wocky_loopback_output_stream_dispose (GObject *object)
+{
+ WockyLoopbackOutputStream *self = WOCKY_LOOPBACK_OUTPUT_STREAM (object);
+
+ if (self->dispose_has_run)
+ return;
+
+ self->dispose_has_run = TRUE;
+
+ g_async_queue_push (self->queue,
+ g_array_sized_new (FALSE, FALSE, sizeof (guint8), 0));
+ g_async_queue_unref (self->queue);
+
+ /* release any references held by the object here */
+ if (G_OBJECT_CLASS (wocky_loopback_output_stream_parent_class)->dispose)
+ G_OBJECT_CLASS (wocky_loopback_output_stream_parent_class)->dispose (object);
+}
+
+static void
+queue_destroyed (gpointer data)
+{
+ g_array_free ((GArray *) data, TRUE);
+}
+
+static void
+wocky_loopback_output_stream_init (WockyLoopbackOutputStream *self)
+{
+ self->queue = g_async_queue_new_full (queue_destroyed);
+}
+
+static void
+wocky_loopback_output_stream_class_init (
+ WockyLoopbackOutputStreamClass *wocky_loopback_output_stream_class)
+{
+ GObjectClass *obj_class = G_OBJECT_CLASS (wocky_loopback_output_stream_class);
+ GOutputStreamClass *stream_class =
+ G_OUTPUT_STREAM_CLASS (wocky_loopback_output_stream_class);
+
+ obj_class->dispose = wocky_loopback_output_stream_dispose;
+
+ stream_class->write_fn = wocky_loopback_output_stream_write;
+ stream_class->write_async = wocky_loopback_output_stream_write_async;
+ stream_class->write_finish = wocky_loopback_output_stream_write_finish;
+
+ output_signals[OUTPUT_DATA_WRITTEN] = g_signal_new ("data-written",
+ G_OBJECT_CLASS_TYPE(wocky_loopback_output_stream_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
diff --git a/wocky/wocky-loopback-stream.h b/wocky/wocky-loopback-stream.h
new file mode 100644
index 0000000..b31063e
--- /dev/null
+++ b/wocky/wocky-loopback-stream.h
@@ -0,0 +1,67 @@
+/*
+ * wocky-loopback-stream.h - Header for WockyLoopbackStream
+ * Copyright (C) 2009-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 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 __WOCKY_LOOPBACK_STREAM_H__
+#define __WOCKY_LOOPBACK_STREAM_H__
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+typedef struct _WockyLoopbackStream WockyLoopbackStream;
+typedef struct _WockyLoopbackStreamClass WockyLoopbackStreamClass;
+typedef struct _WockyLoopbackStreamPrivate WockyLoopbackStreamPrivate;
+
+struct _WockyLoopbackStreamClass
+{
+ GIOStreamClass parent_class;
+};
+
+struct _WockyLoopbackStream
+{
+ GIOStream parent;
+
+ WockyLoopbackStreamPrivate *priv;
+};
+
+GType wocky_loopback_stream_get_type (void);
+
+/* TYPE MACROS */
+#define WOCKY_TYPE_LOOPBACK_STREAM \
+ (wocky_loopback_stream_get_type ())
+#define WOCKY_LOOPBACK_STREAM(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), WOCKY_TYPE_LOOPBACK_STREAM, \
+ WockyLoopbackStream))
+#define WOCKY_LOOPBACK_STREAM_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), WOCKY_TYPE_LOOPBACK_STREAM, \
+ WockyLoopbackStreamClass))
+#define WOCKY_IS_LOOPBACK_STREAM(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), WOCKY_TYPE_LOOPBACK_STREAM))
+#define WOCKY_IS_LOOPBACK_STREAM_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), WOCKY_TYPE_LOOPBACK_STREAM))
+#define WOCKY_LOOPBACK_STREAM_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), WOCKY_TYPE_LOOPBACK_STREAM, \
+ WockyLoopbackStreamClass))
+
+GIOStream * wocky_loopback_stream_new (void);
+
+G_END_DECLS
+
+#endif /* #ifndef __WOCKY_LOOPBACK_STREAM_H__*/
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;
+}
diff --git a/wocky/wocky-meta-porter.h b/wocky/wocky-meta-porter.h
new file mode 100644
index 0000000..35f4200
--- /dev/null
+++ b/wocky/wocky-meta-porter.h
@@ -0,0 +1,97 @@
+/*
+ * wocky-meta-porter.h - Header 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
+ */
+
+#ifndef __WOCKY_META_PORTER_H__
+#define __WOCKY_META_PORTER_H__
+
+#include <glib-object.h>
+
+#include <gio/gio.h>
+
+#include "wocky-contact-factory.h"
+#include "wocky-porter.h"
+
+G_BEGIN_DECLS
+
+typedef struct _WockyMetaPorter WockyMetaPorter;
+typedef struct _WockyMetaPorterClass WockyMetaPorterClass;
+typedef struct _WockyMetaPorterPrivate WockyMetaPorterPrivate;
+
+typedef enum
+{
+ WOCKY_META_PORTER_ERROR_NO_CONTACT_ADDRESS,
+ WOCKY_META_PORTER_ERROR_FAILED_TO_CLOSE,
+} WockyMetaPorterError;
+
+GQuark wocky_meta_porter_error_quark (void);
+
+#define WOCKY_META_PORTER_ERROR (wocky_meta_porter_error_quark ())
+
+struct _WockyMetaPorterClass
+{
+ GObjectClass parent_class;
+};
+
+struct _WockyMetaPorter
+{
+ GObject parent;
+
+ WockyMetaPorterPrivate *priv;
+};
+
+GType wocky_meta_porter_get_type (void);
+
+/* TYPE MACROS */
+#define WOCKY_TYPE_META_PORTER \
+ (wocky_meta_porter_get_type ())
+#define WOCKY_META_PORTER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), WOCKY_TYPE_META_PORTER, \
+ WockyMetaPorter))
+#define WOCKY_META_PORTER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), WOCKY_TYPE_META_PORTER, \
+ WockyMetaPorterClass))
+#define WOCKY_IS_META_PORTER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), WOCKY_TYPE_META_PORTER))
+#define WOCKY_IS_META_PORTER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), WOCKY_TYPE_META_PORTER))
+#define WOCKY_META_PORTER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), WOCKY_TYPE_META_PORTER, \
+ WockyMetaPorterClass))
+
+WockyPorter * wocky_meta_porter_new (const gchar *jid,
+ WockyContactFactory *contact_factory);
+
+guint16 wocky_meta_porter_get_port (WockyMetaPorter *porter);
+
+void wocky_meta_porter_hold (WockyMetaPorter *porter, WockyContact *contact);
+void wocky_meta_porter_unhold (WockyMetaPorter *porter, WockyContact *contact);
+
+void wocky_meta_porter_set_jid (WockyMetaPorter *porter, const gchar *jid);
+
+void wocky_meta_porter_open_async (WockyMetaPorter *porter,
+ WockyLLContact *contact, GCancellable *cancellable,
+ GAsyncReadyCallback callback, gpointer user_data);
+
+gboolean wocky_meta_porter_open_finish (WockyMetaPorter *porter,
+ GAsyncResult *result, GError **error);
+
+GSocketConnection * wocky_meta_porter_borrow_connection (WockyMetaPorter *porter,
+ WockyLLContact *contact);
+
+#endif /* #ifndef __WOCKY_META_PORTER_H__*/
diff --git a/wocky/wocky-resource-contact.c b/wocky/wocky-resource-contact.c
index 76a9465..44b1c57 100644
--- a/wocky/wocky-resource-contact.c
+++ b/wocky/wocky-resource-contact.c
@@ -167,11 +167,21 @@ wocky_resource_contact_finalize (GObject *object)
G_OBJECT_CLASS (wocky_resource_contact_parent_class)->finalize (object);
}
+static gchar *
+wocky_resource_contact_dup_jid (WockyContact *contact)
+{
+ WockyResourceContact *self = WOCKY_RESOURCE_CONTACT (contact);
+ const gchar *bare = wocky_bare_contact_get_jid (self->priv->bare_contact);
+
+ return g_strdup_printf ("%s/%s", bare, self->priv->resource);
+}
+
static void
wocky_resource_contact_class_init (
WockyResourceContactClass *wocky_resource_contact_class)
{
GObjectClass *object_class = G_OBJECT_CLASS (wocky_resource_contact_class);
+ WockyContactClass *contact_class = WOCKY_CONTACT_CLASS (wocky_resource_contact_class);
GParamSpec *spec;
g_type_class_add_private (wocky_resource_contact_class,
@@ -183,6 +193,8 @@ wocky_resource_contact_class_init (
object_class->dispose = wocky_resource_contact_dispose;
object_class->finalize = wocky_resource_contact_finalize;
+ contact_class->dup_jid = wocky_resource_contact_dup_jid;
+
/**
* WockyResourceContact:resource:
*
diff --git a/wocky/wocky-session.c b/wocky/wocky-session.c
index 6e99e6e..a5357e6 100644
--- a/wocky/wocky-session.c
+++ b/wocky/wocky-session.c
@@ -43,6 +43,7 @@
#include "wocky-signals-marshal.h"
#include "wocky-utils.h"
#include "wocky-c2s-porter.h"
+#include "wocky-meta-porter.h"
G_DEFINE_TYPE (WockySession, wocky_session, G_TYPE_OBJECT)
@@ -149,9 +150,10 @@ wocky_session_constructed (GObject *object)
WockySession *self = WOCKY_SESSION (object);
WockySessionPrivate *priv = self->priv;
- g_assert (priv->connection != NULL);
-
- priv->porter = wocky_c2s_porter_new (priv->connection, priv->full_jid);
+ if (priv->connection != NULL)
+ priv->porter = wocky_c2s_porter_new (priv->connection, priv->full_jid);
+ else
+ priv->porter = wocky_meta_porter_new (priv->full_jid, priv->contact_factory);
}
static void
@@ -165,7 +167,12 @@ wocky_session_dispose (GObject *object)
priv->dispose_has_run = TRUE;
- g_object_unref (priv->connection);
+ if (priv->connection != NULL)
+ {
+ g_object_unref (priv->connection);
+ priv->connection = NULL;
+ }
+
g_object_unref (priv->porter);
g_object_unref (priv->contact_factory);
@@ -226,15 +233,26 @@ wocky_session_class_init (WockySessionClass *wocky_session_class)
}
WockySession *
-wocky_session_new (WockyXmppConnection *conn,
+wocky_session_new_with_connection (WockyXmppConnection *conn,
const gchar *full_jid)
{
+ g_return_val_if_fail (WOCKY_IS_XMPP_CONNECTION (conn), NULL);
+ g_return_val_if_fail (full_jid != NULL, NULL);
+
return g_object_new (WOCKY_TYPE_SESSION,
"connection", conn,
"full-jid", full_jid,
NULL);
}
+WockySession *
+wocky_session_new_ll (const gchar *full_jid)
+{
+ return g_object_new (WOCKY_TYPE_SESSION,
+ "full-jid", full_jid,
+ NULL);
+}
+
void wocky_session_start (WockySession *self)
{
WockySessionPrivate *priv = self->priv;
@@ -257,3 +275,19 @@ wocky_session_get_contact_factory (WockySession *self)
return priv->contact_factory;
}
+
+void
+wocky_session_set_jid (WockySession *self,
+ const gchar *jid)
+{
+ WockySessionPrivate *priv = self->priv;
+
+ g_free (priv->full_jid);
+ priv->full_jid = g_strdup (jid);
+
+ if (WOCKY_IS_META_PORTER (priv->porter))
+ {
+ wocky_meta_porter_set_jid (WOCKY_META_PORTER (priv->porter),
+ priv->full_jid);
+ }
+}
diff --git a/wocky/wocky-session.h b/wocky/wocky-session.h
index 79dc80c..e3a534a 100644
--- a/wocky/wocky-session.h
+++ b/wocky/wocky-session.h
@@ -69,7 +69,9 @@ GType wocky_session_get_type (void);
(G_TYPE_INSTANCE_GET_CLASS ((obj), WOCKY_TYPE_SESSION, \
WockySessionClass))
-WockySession * wocky_session_new (WockyXmppConnection *conn,
+WockySession * wocky_session_new_ll (const gchar *full_jid);
+
+WockySession * wocky_session_new_with_connection (WockyXmppConnection *conn,
const gchar *full_jid);
void wocky_session_start (WockySession *session);
@@ -78,6 +80,8 @@ WockyPorter * wocky_session_get_porter (WockySession *session);
WockyContactFactory * wocky_session_get_contact_factory (WockySession *session);
+void wocky_session_set_jid (WockySession *session, const gchar *jid);
+
G_END_DECLS
#endif /* #ifndef __WOCKY_SESSION_H__*/
diff --git a/wocky/wocky-stanza.c b/wocky/wocky-stanza.c
index 2e88594..ac0ca08 100644
--- a/wocky/wocky-stanza.c
+++ b/wocky/wocky-stanza.c
@@ -33,6 +33,9 @@ G_DEFINE_TYPE(WockyStanza, wocky_stanza, WOCKY_TYPE_NODE_TREE)
/* private structure */
struct _WockyStanzaPrivate
{
+ WockyContact *from_contact;
+ WockyContact *to_contact;
+
gboolean dispose_has_run;
};
@@ -122,6 +125,9 @@ wocky_stanza_init (WockyStanza *self)
{
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, WOCKY_TYPE_STANZA,
WockyStanzaPrivate);
+
+ self->priv->from_contact = NULL;
+ self->priv->to_contact = NULL;
}
static void wocky_stanza_dispose (GObject *object);
@@ -136,10 +142,9 @@ wocky_stanza_class_init (WockyStanzaClass *wocky_stanza_class)
object_class->dispose = wocky_stanza_dispose;
object_class->finalize = wocky_stanza_finalize;
-
}
-void
+static void
wocky_stanza_dispose (GObject *object)
{
WockyStanza *self = WOCKY_STANZA (object);
@@ -155,14 +160,29 @@ wocky_stanza_dispose (GObject *object)
G_OBJECT_CLASS (wocky_stanza_parent_class)->dispose (object);
}
-void
+static void
wocky_stanza_finalize (GObject *object)
{
+ WockyStanza *self = WOCKY_STANZA (object);
+
+ if (self->priv->from_contact != NULL)
+ {
+ g_object_unref (self->priv->from_contact);
+ self->priv->from_contact = NULL;
+ }
+
+ if (self->priv->to_contact != NULL)
+ {
+ g_object_unref (self->priv->to_contact);
+ self->priv->to_contact = NULL;
+ }
+
G_OBJECT_CLASS (wocky_stanza_parent_class)->finalize (object);
}
WockyStanza *
-wocky_stanza_new (const gchar *name, const gchar *ns)
+wocky_stanza_new (const gchar *name,
+ const gchar *ns)
{
WockyStanza *result;
@@ -208,7 +228,7 @@ get_sub_type_name (WockyStanzaSubType sub_type)
static gboolean
check_sub_type (WockyStanzaType type,
- WockyStanzaSubType sub_type)
+ WockyStanzaSubType sub_type)
{
WockyStanzaType expected_type;
@@ -233,7 +253,7 @@ check_sub_type (WockyStanzaType type,
static WockyStanza *
wocky_stanza_new_with_sub_type (WockyStanzaType type,
- WockyStanzaSubType sub_type)
+ WockyStanzaSubType sub_type)
{
WockyStanza *stanza = NULL;
const gchar *sub_type_name;
@@ -309,10 +329,10 @@ wocky_stanza_new_with_sub_type (WockyStanzaType type,
*/
WockyStanza *
wocky_stanza_build (WockyStanzaType type,
- WockyStanzaSubType sub_type,
- const gchar *from,
- const gchar *to,
- ...)
+ WockyStanzaSubType sub_type,
+ const gchar *from,
+ const gchar *to,
+ ...)
{
WockyStanza *stanza;
@@ -326,6 +346,32 @@ wocky_stanza_build (WockyStanzaType type,
}
WockyStanza *
+wocky_stanza_build_to_contact (WockyStanzaType type,
+ WockyStanzaSubType sub_type,
+ const gchar *from,
+ WockyContact *to,
+ ...)
+
+{
+ WockyStanza *stanza;
+ va_list ap;
+ gchar *to_jid = NULL;
+
+ if (to != NULL)
+ to_jid = wocky_contact_dup_jid (to);
+
+ va_start (ap, to);
+ stanza = wocky_stanza_build_va (type, sub_type, from, to_jid, ap);
+ va_end (ap);
+
+ g_free (to_jid);
+
+ stanza->priv->to_contact = g_object_ref (to);
+
+ return stanza;
+}
+
+WockyStanza *
wocky_stanza_build_va (WockyStanzaType type,
WockyStanzaSubType sub_type,
const gchar *from,
@@ -398,8 +444,8 @@ get_sub_type_from_name (const gchar *name)
void
wocky_stanza_get_type_info (WockyStanza *stanza,
- WockyStanzaType *type,
- WockyStanzaSubType *sub_type)
+ WockyStanzaType *type,
+ WockyStanzaSubType *sub_type)
{
g_return_if_fail (stanza != NULL);
g_assert (wocky_stanza_get_top_node (stanza) != NULL);
@@ -422,6 +468,7 @@ create_iq_reply (WockyStanza *iq,
WockyNode *node;
WockyStanzaSubType sub_type;
const gchar *from, *to, *id;
+ WockyContact *contact;
g_return_val_if_fail (iq != NULL, NULL);
@@ -442,6 +489,11 @@ create_iq_reply (WockyStanza *iq,
sub_type_reply, to, from, ap);
wocky_node_set_attribute (wocky_stanza_get_top_node (reply), "id", id);
+
+ contact = wocky_stanza_get_from_contact (iq);
+ if (contact != NULL)
+ wocky_stanza_set_to_contact (reply, contact);
+
return reply;
}
@@ -677,3 +729,49 @@ wocky_stanza_get_to (WockyStanza *self)
return wocky_node_get_attribute (wocky_stanza_get_top_node (self), "to");
}
+
+WockyContact *
+wocky_stanza_get_to_contact (WockyStanza *self)
+{
+ g_return_val_if_fail (self != NULL, NULL);
+ g_return_val_if_fail (WOCKY_IS_STANZA (self), NULL);
+
+ return self->priv->to_contact;
+}
+
+WockyContact *
+wocky_stanza_get_from_contact (WockyStanza *self)
+{
+ g_return_val_if_fail (self != NULL, NULL);
+ g_return_val_if_fail (WOCKY_IS_STANZA (self), NULL);
+
+ return self->priv->from_contact;
+}
+
+void
+wocky_stanza_set_to_contact (WockyStanza *self,
+ WockyContact *contact)
+{
+ g_return_if_fail (self != NULL);
+ g_return_if_fail (WOCKY_IS_STANZA (self));
+ g_return_if_fail (WOCKY_IS_CONTACT (contact));
+
+ if (self->priv->to_contact != NULL)
+ g_object_unref (self->priv->to_contact);
+
+ self->priv->to_contact = g_object_ref (contact);
+}
+
+void
+wocky_stanza_set_from_contact (WockyStanza *self,
+ WockyContact *contact)
+{
+ g_return_if_fail (self != NULL);
+ g_return_if_fail (WOCKY_IS_STANZA (self));
+ g_return_if_fail (WOCKY_IS_CONTACT (contact));
+
+ if (self->priv->from_contact != NULL)
+ g_object_unref (self->priv->from_contact);
+
+ self->priv->from_contact = g_object_ref (contact);
+}
diff --git a/wocky/wocky-stanza.h b/wocky/wocky-stanza.h
index 8aaff92..b6ec8eb 100644
--- a/wocky/wocky-stanza.h
+++ b/wocky/wocky-stanza.h
@@ -26,6 +26,7 @@
#include <glib-object.h>
#include "wocky-node-tree.h"
#include "wocky-xmpp-error.h"
+#include "wocky-contact.h"
G_BEGIN_DECLS
@@ -158,6 +159,10 @@ WockyStanza * wocky_stanza_build (WockyStanzaType type,
WockyStanzaSubType sub_type, const gchar *from, const gchar *to,
...) G_GNUC_NULL_TERMINATED;
+WockyStanza * wocky_stanza_build_to_contact (WockyStanzaType type,
+ WockyStanzaSubType sub_type, const gchar *from,
+ WockyContact *to, ...) G_GNUC_NULL_TERMINATED;
+
void wocky_stanza_get_type_info (WockyStanza *stanza,
WockyStanzaType *type, WockyStanzaSubType *sub_type);
@@ -191,6 +196,14 @@ gboolean wocky_stanza_extract_errors (WockyStanza *stanza,
gboolean wocky_stanza_extract_stream_error (WockyStanza *stanza,
GError **stream_error);
+WockyContact * wocky_stanza_get_to_contact (WockyStanza *self);
+WockyContact * wocky_stanza_get_from_contact (WockyStanza *self);
+
+void wocky_stanza_set_to_contact (WockyStanza *self,
+ WockyContact *contact);
+void wocky_stanza_set_from_contact (WockyStanza *self,
+ WockyContact *contact);
+
G_END_DECLS
#endif /* #ifndef __WOCKY_STANZA_H__*/
diff --git a/wocky/wocky-types.h b/wocky/wocky-types.h
index 5e42000..8837268 100644
--- a/wocky/wocky-types.h
+++ b/wocky/wocky-types.h
@@ -25,6 +25,7 @@
G_BEGIN_DECLS
typedef struct _WockyBareContact WockyBareContact;
+typedef struct _WockyLLContact WockyLLContact;
typedef struct _WockyNodeTree WockyNodeTree;
typedef struct _WockyResourceContact WockyResourceContact;
typedef struct _WockySession WockySession;
diff --git a/wocky/wocky-uninstalled.pc.in b/wocky/wocky-uninstalled.pc.in
index 4864e30..001f6e2 100644
--- a/wocky/wocky-uninstalled.pc.in
+++ b/wocky/wocky-uninstalled.pc.in
@@ -7,6 +7,6 @@ Name: Wocky (uninstalled copy)
Description: XMPP library
Version: @VERSION@
Requires: pkg-config >= 0.21
-Requires.private: glib-2.0 >= 2.16, gobject-2.0 >= 2.16, gio-2.0
+Requires.private: glib-2.0 >= 2.16, gobject-2.0 >= 2.16, gio-2.0 >= 2.28
Libs: ${abs_top_builddir}/wocky/libwocky.la
Cflags: -I${abs_top_srcdir} -I${abs_top_builddir} -I${abs_top_builddir}/wocky
diff --git a/wocky/wocky-utils.h b/wocky/wocky-utils.h
index d1ab0fa..cc143be 100644
--- a/wocky/wocky-utils.h
+++ b/wocky/wocky-utils.h
@@ -129,6 +129,18 @@ void wocky_g_string_free (GString *str);
return NULL; \
} G_STMT_END
+#define wocky_implement_finish_return_pointer(source, tag) \
+ G_STMT_START { \
+ GSimpleAsyncResult *_simple; \
+ _simple = (GSimpleAsyncResult *) result; \
+ if (g_simple_async_result_propagate_error (_simple, error)) \
+ return NULL; \
+ g_return_val_if_fail (g_simple_async_result_is_valid (result, \
+ G_OBJECT (source), tag), \
+ NULL); \
+ return g_simple_async_result_get_op_res_gpointer (_simple); \
+ } G_STMT_END
+
G_END_DECLS
#endif /* #ifndef __WOCKY_UTILS_H__ */