diff options
author | Jonny Lamb <jonny.lamb@collabora.co.uk> | 2011-03-22 15:31:20 +0000 |
---|---|---|
committer | Jonny Lamb <jonny.lamb@collabora.co.uk> | 2011-03-22 15:31:20 +0000 |
commit | 1343f70de56c6917a224b3c2618eaf654a9fc958 (patch) | |
tree | ff9b76732f7e93722736f379d4291308e5bdc511 | |
parent | 916518447065436474ddd0d86462dafa3e28815e (diff) | |
parent | 901aca41915146f450842d7ab41071d5dd0e4f46 (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>
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__ */ |