diff options
author | Will Thompson <will.thompson@collabora.co.uk> | 2012-01-26 13:09:47 +0000 |
---|---|---|
committer | Will Thompson <will.thompson@collabora.co.uk> | 2012-01-26 13:09:47 +0000 |
commit | e142bebe3e112d715af2b4784b24730eb9c310a0 (patch) | |
tree | 594393ed618bf82b4600f61c624de58ad3340ca1 /src | |
parent | a2a603bdfb12bc9aa80726bf1f822cc22c76917e (diff) | |
parent | 035a6b0a6e8ce1787abf2583bb918436282026fe (diff) |
Merge branch 'master' into BYE-BYE-LOUDMOUTH
Conflicts:
src/connection.c
src/connection.h
src/ft-manager.c
src/jingle-content.c
src/jingle-factory.c
src/jingle-session.c
src/message-util.c
src/muc-channel.c
src/muc-channel.h
Diffstat (limited to 'src')
85 files changed, 5396 insertions, 2717 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 66a873c11..b3d140e5e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -13,6 +13,8 @@ libexec_PROGRAMS=telepathy-gabble noinst_PROGRAMS = write-mgr-file libgabble_convenience_la_SOURCES = \ + addressing-util.h \ + addressing-util.c \ auth-manager.h \ auth-manager.c \ base64.h \ @@ -44,13 +46,15 @@ libgabble_convenience_la_SOURCES = \ call-stream.h \ call-stream.c \ $(top_srcdir)/gabble/capabilities-set.h \ - capabilities.h \ + $(top_srcdir)/gabble/capabilities.h \ capabilities.c \ $(top_srcdir)/gabble/caps-hash.h \ caps-hash.h \ caps-hash.c \ - caps-channel-manager.h \ + $(top_srcdir)/gabble/caps-channel-manager.h \ caps-channel-manager.c \ + conn-addressing.h \ + conn-addressing.c \ conn-aliasing.h \ conn-aliasing.c \ conn-avatars.h \ @@ -84,14 +88,10 @@ libgabble_convenience_la_SOURCES = \ disco.c \ error.c \ error.h \ - ft-channel.c \ - ft-channel.h \ - ft-manager.c \ - ft-manager.h \ gabble.c \ gabble.h \ - gtalk-file-collection.h \ - gtalk-file-collection.c \ + google-relay.c \ + google-relay.h \ im-channel.h \ im-channel.c \ im-factory.h \ @@ -150,6 +150,8 @@ libgabble_convenience_la_SOURCES = \ request-pipeline.c \ roster.h \ roster.c \ + room-config.h \ + room-config.c \ roomlist-channel.h \ roomlist-channel.c \ roomlist-manager.h \ @@ -183,8 +185,28 @@ libgabble_convenience_la_SOURCES = \ vcard-manager.h \ vcard-manager.c +if ENABLE_FILE_TRANSFER +libgabble_convenience_la_SOURCES += \ + ft-channel.c \ + ft-channel.h \ + ft-manager.c \ + ft-manager.h \ + gtalk-file-collection.h \ + gtalk-file-collection.c +else +EXTRA_DIST += \ + ft-channel.c \ + ft-channel.h \ + ft-manager.c \ + ft-manager.h \ + gtalk-file-collection.h \ + gtalk-file-collection.c +endif + enumtype_sources = \ + $(top_srcdir)/src/connection.h \ $(top_srcdir)/src/jingle-factory.h \ + $(top_srcdir)/src/room-config.h \ $(top_srcdir)/src/presence.h libgabble_convenience_la_LIBADD = \ @@ -218,14 +240,19 @@ telepathy_gabble_LDFLAGS = -export-dynamic noinst_LTLIBRARIES = libgabble-convenience.la AM_CFLAGS = $(ERROR_CFLAGS) -I$(top_srcdir) -I$(top_builddir) \ + @TP_YELL_CFLAGS@ \ @DBUS_CFLAGS@ @GLIB_CFLAGS@ @WOCKY_CFLAGS@ \ - @HANDLE_LEAK_DEBUG_CFLAGS@ @TP_GLIB_CFLAGS@ \ + @TP_GLIB_CFLAGS@ \ @SOUP_CFLAGS@ @NICE_CFLAGS@ @GMODULE_CFLAGS@ \ - @TP_YELL_CFLAGS@ \ -I $(top_srcdir)/lib -I $(top_builddir)/lib \ -DG_LOG_DOMAIN=\"gabble\" \ -DPLUGIN_DIR=\"$(libdir)/telepathy/gabble-0\" +# following flag is requied to make getnameinfo work +if WINDOWS + AM_CFLAGS += -D_WIN32_WINNT=0x0501 +endif + ALL_LIBS = @DBUS_LIBS@ @GLIB_LIBS@ @WOCKY_LIBS@ @TP_GLIB_LIBS@ \ @SOUP_LIBS@ @NICE_LIBS@ @GMODULE_LIBS@ @@ -271,3 +298,14 @@ gabble-enumtypes.c: $(enumtype_sources) Makefile.in --vprod " { @VALUENAME@, \"@VALUENAME@\", \"@valuenick@\" }," \ --vtail " { 0, NULL, NULL }\n };\n etype = g_@type@_register_static (\"@EnumName@\", values);\n }\n return etype;\n}\n" \ $(enumtype_sources) > $@ + +Android.mk: Makefile.am $(BUILT_SOURCES) + androgenizer -:PROJECT telepathy-gabble -:SHARED telepathy-gabble -:TAGS eng debug \ + -:REL_TOP $(top_srcdir) -:ABS_TOP $(abs_top_srcdir) \ + -:SOURCES $(libgabble_convenience_la_SOURCES) \ + $(nodist_libgabble_convenience_la_SOURCES) main.c \ + -:CFLAGS $(DEFS) $(CFLAGS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CFLAGS) -DBUILD_AS_ANDROID_SERVICE \ + -:CPPFLAGS $(CPPFLAGS) $(AM_CPPFLAGS) $(telepathy_gabble_LDFLAGS) \ + -:LDFLAGS $(telepathy_gabble_LDADD) $(libgabble_convenience_la_LIBADD) \ + > $@ diff --git a/src/addressing-util.c b/src/addressing-util.c new file mode 100644 index 000000000..1ea089854 --- /dev/null +++ b/src/addressing-util.c @@ -0,0 +1,521 @@ +/* + * addressing-util.c - Source for Gabble addressing utility functions + * Copyright (C) 2010 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 "config.h" +#include "addressing-util.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <wocky/wocky-utils.h> + +#include "connection.h" +#include "util.h" + +static const gchar *addressable_vcard_fields[] = {"x-jabber", "x-facebook-id", NULL}; +static const gchar *addressable_uri_schemes[] = {"xmpp", NULL}; + + +const gchar * const * +gabble_get_addressable_uri_schemes () +{ + return addressable_uri_schemes; +} + +const gchar * const * +gabble_get_addressable_vcard_fields () +{ + return addressable_vcard_fields; +} + +gchar * +gabble_normalize_contact_uri (const gchar *uri, + GError **error) +{ + gchar *scheme = NULL; + gchar *normalized_jid = NULL; + gchar *normalized_uri = NULL; + + g_return_val_if_fail (uri != NULL, NULL); + + normalized_jid = gabble_uri_to_jid (uri, error); + if (normalized_jid == NULL) + { + goto OUT; + } + + scheme = g_uri_parse_scheme (uri); + + normalized_uri = gabble_jid_to_uri (scheme, normalized_jid, error); + +OUT: + g_free (scheme); + g_free (normalized_jid); + + return normalized_uri; +} + +gchar * +gabble_uri_to_jid (const gchar *uri, + GError **error) +{ + gchar *scheme; + gchar *normalized_jid = NULL; + + g_return_val_if_fail (uri != NULL, NULL); + + scheme = g_uri_parse_scheme (uri); + + if (scheme == NULL) + { + g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, + "'%s' is not a valid URI", uri); + goto OUT; + } + else if (g_ascii_strcasecmp (scheme, "xmpp") == 0) + { + gchar *node = NULL; + gchar *domain = NULL; + gchar *resource = NULL; + + if (!gabble_parse_xmpp_uri (uri, &node, &domain, &resource, error)) + goto OUT; + + normalized_jid = gabble_encode_jid (node, domain, resource); + + g_free (node); + g_free (domain); + g_free (resource); + } + else + { + g_set_error (error, TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED, + "'%s' URI scheme is not supported by this protocol", + scheme); + goto OUT; + } + +OUT: + g_free (scheme); + + return normalized_jid; +} + +gchar * +gabble_jid_to_uri (const gchar *scheme, + const gchar *jid, + GError **error) +{ + gchar *normalized_uri = NULL; + gchar *node = NULL; + gchar *domain = NULL; + gchar *resource = NULL; + gchar *escaped_node = NULL; + gchar *escaped_domain = NULL; + gchar *escaped_resource = NULL; + gchar *escaped_jid = NULL; + gchar *normalized_scheme = NULL; + + g_return_val_if_fail (scheme != NULL, NULL); + + if (!wocky_decode_jid (jid, &node, &domain, &resource)) + { + g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, + "'%s' is not a valid JID", jid); + return NULL; + } + + /* convert from "foo?" to "foo%3F" */ + if (node) + escaped_node = g_uri_escape_string (node, NULL, TRUE); + + g_assert (domain != NULL); + escaped_domain = g_uri_escape_string (domain, NULL, TRUE); + + if (resource) + escaped_resource = g_uri_escape_string (resource, NULL, TRUE); + + escaped_jid = gabble_encode_jid (escaped_node, escaped_domain, escaped_resource); + + normalized_scheme = g_ascii_strdown (scheme, -1); + + normalized_uri = g_strdup_printf ("%s:%s", normalized_scheme, escaped_jid); + + g_free (node); + g_free (domain); + g_free (resource); + g_free (escaped_node); + g_free (escaped_domain); + g_free (escaped_resource); + g_free (escaped_jid); + g_free (normalized_scheme); + + return normalized_uri; +} + +TpHandle +gabble_ensure_handle_from_uri (TpHandleRepoIface *repo, + const gchar *uri, + GError **error) +{ + TpHandle handle; + + gchar *jid = gabble_uri_to_jid (uri, error); + + if (jid == NULL) + return 0; + + handle = tp_handle_ensure (repo, jid, NULL, error); + + g_free (jid); + + return handle; +} + +gchar * +gabble_normalize_vcard_address (const gchar *vcard_field, + const gchar *vcard_address, + GError **error) +{ + gchar *normalized_jid = NULL; + gchar *normalized_address = NULL; + + g_return_val_if_fail (vcard_field != NULL, NULL); + g_return_val_if_fail (vcard_address != NULL, NULL); + + normalized_jid = gabble_vcard_address_to_jid (vcard_field, vcard_address, error); + if (normalized_jid == NULL) + { + goto OUT; + } + + normalized_address = gabble_jid_to_vcard_address (vcard_field, normalized_jid, error); + +OUT: + g_free (normalized_jid); + + return normalized_address; +} + +gchar * +gabble_vcard_address_to_jid (const gchar *vcard_field, + const gchar *vcard_address, + GError **error) +{ + gchar *normalized_jid = NULL; + + g_return_val_if_fail (vcard_field != NULL, NULL); + g_return_val_if_fail (vcard_address != NULL, NULL); + + if (g_ascii_strcasecmp (vcard_field, "x-jabber") == 0) + { + GError *gabble_error = NULL; + + normalized_jid = gabble_normalize_contact (NULL, + vcard_address, GUINT_TO_POINTER (GABBLE_JID_GLOBAL), + &gabble_error); + + if (gabble_error != NULL) + { + g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, + "'%s' is an invalid address: %s", vcard_address, + gabble_error->message); + g_error_free (gabble_error); + } + } + else if (g_ascii_strcasecmp (vcard_field, "x-facebook-id") == 0) + { + const gchar *s; + + s = vcard_address; + while (*s && (g_ascii_isdigit (*s))) + s++; + if (G_UNLIKELY (*s != '\0')) + { + g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, + "'%s' is an invalid facebook chat address", vcard_address); + goto OUT; + } + + normalized_jid = g_strdup_printf ("-%s@chat.facebook.com", vcard_address); + } + else + { + g_set_error (error, TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED, + "'%s' vCard field is not supported by this protocol", vcard_field); + } + +OUT: + return normalized_jid; +} + +gchar * +gabble_jid_to_vcard_address (const gchar *vcard_field, + const gchar *jid, + GError **error) +{ + gchar *normalized_address = NULL; + + g_return_val_if_fail (vcard_field != NULL, NULL); + g_return_val_if_fail (jid != NULL, NULL); + + if (g_ascii_strcasecmp (vcard_field, "x-jabber") == 0) + { + GError *gabble_error = NULL; + + normalized_address = gabble_normalize_contact (NULL, + jid, GUINT_TO_POINTER (GABBLE_JID_GLOBAL), + &gabble_error); + + if (gabble_error != NULL) + { + g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, + "'%s' is an invalid address: %s", jid, + gabble_error->message); + g_error_free (gabble_error); + } + } + else if (g_ascii_strcasecmp (vcard_field, "x-facebook-id") == 0) + { + gchar *address = g_ascii_strdown (jid, -1); + + if (address[0] == '-' && + g_str_has_suffix (address, "@chat.facebook.com")) + { + const gchar *at = strchr (address, '@'); + const gchar *start_of_number = address + 1; + const gchar *s; + + g_assert (at != NULL); + + normalized_address = g_strndup (start_of_number, (int) (at - start_of_number)); + + s = normalized_address; + while (*s && (g_ascii_isdigit (*s))) + s++; + if (G_UNLIKELY (*s != '\0')) + { + g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, + "'%s' is an invalid facebook chat address", jid); + } + } + else + { + g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, + "'%s' is an invalid facebook chat address", jid); + } + + g_free (address); + } + else + { + g_set_error (error, TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED, + "'%s' vCard field is not supported by this protocol", vcard_field); + } + + return normalized_address; +} + +TpHandle +gabble_ensure_handle_from_vcard_address (TpHandleRepoIface *repo, + const gchar *vcard_field, + const gchar *vcard_address, + GError **error) +{ + gchar *normalized_jid; + TpHandle handle; + + normalized_jid = gabble_vcard_address_to_jid (vcard_field, vcard_address, error); + if (normalized_jid == NULL) + return 0; + + handle = tp_handle_ensure (repo, normalized_jid, NULL, error); + + g_free (normalized_jid); + + return handle; +} + +gchar ** +gabble_uris_for_handle (TpHandleRepoIface *contact_repo, + TpHandle contact) +{ + GPtrArray *uris = g_ptr_array_new (); + + for (const gchar * const *scheme = addressable_uri_schemes; *scheme != NULL; scheme++) + { + gchar *uri = gabble_uri_for_handle (contact_repo, *scheme, contact); + + if (uri != NULL) + { + g_ptr_array_add (uris, uri); + } + } + + g_ptr_array_add (uris, NULL); + return (gchar **) g_ptr_array_free (uris, FALSE); +} + +GHashTable * +gabble_vcard_addresses_for_handle (TpHandleRepoIface *contact_repo, + TpHandle contact) +{ + GHashTable *addresses = g_hash_table_new_full (g_str_hash, g_str_equal, + NULL, (GDestroyNotify) g_free); + + for (const gchar * const *field = addressable_vcard_fields; *field != NULL; field++) + { + gchar *vcard_address = gabble_vcard_address_for_handle (contact_repo, *field, contact); + + if (vcard_address != NULL) + { + g_hash_table_insert (addresses, (gpointer) *field, vcard_address); + } + } + + return addresses; +} + +gchar * +gabble_vcard_address_for_handle (TpHandleRepoIface *contact_repo, + const gchar *vcard_field, + TpHandle contact) +{ + const gchar *identifier = tp_handle_inspect (contact_repo, contact); + return gabble_jid_to_vcard_address (vcard_field, identifier, NULL); +} + +gchar * +gabble_uri_for_handle (TpHandleRepoIface *contact_repo, + const gchar *scheme, + TpHandle contact) +{ + const gchar *identifier = tp_handle_inspect (contact_repo, contact); + return gabble_jid_to_uri (scheme, identifier, NULL); +} + +gboolean +gabble_parse_xmpp_uri (const gchar *uri, + gchar **node, + gchar **domain, + gchar **resource, + GError **error) +{ + gboolean ret = FALSE; + gchar *scheme; + const gchar *jid; + gchar *tmp_node = NULL; + gchar *tmp_domain = NULL; + gchar *tmp_resource = NULL; + gchar *unescaped_node = NULL; + gchar *unescaped_domain = NULL; + gchar *unescaped_resource = NULL; + gchar *unescaped_jid = NULL; + gchar *normalized_jid = NULL; + GError *gabble_error = NULL; + + g_return_val_if_fail (uri != NULL, FALSE); + g_return_val_if_fail (domain != NULL, FALSE); + + scheme = g_uri_parse_scheme (uri); + + if (scheme == NULL) + { + g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, + "'%s' is not a valid URI", uri); + goto OUT; + } + + jid = uri + strlen (scheme) + 1; /* Strip the scheme */ + + if (!wocky_decode_jid (jid, &tmp_node, &tmp_domain, &tmp_resource)) + { + g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, + "'%s' is not a valid XMPP URI", uri); + goto OUT; + } + + /* convert from "foo%3F" to "foo?" */ + if (tmp_node) + { + unescaped_node = g_uri_unescape_string (tmp_node, NULL); + if (unescaped_node == NULL) + { + g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, + "'%s' is not a valid XMPP URI", uri); + goto OUT; + } + } + + g_assert (tmp_domain); + unescaped_domain = g_uri_unescape_string (tmp_domain, NULL); + if (unescaped_domain == NULL) + { + g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, + "'%s' is not a valid XMPP URI", uri); + goto OUT; + } + + if (tmp_resource) + { + unescaped_resource = g_uri_unescape_string (tmp_resource, NULL); + if (unescaped_resource == NULL) + { + g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, + "'%s' is not a valid XMPP URI", uri); + goto OUT; + } + } + + unescaped_jid = gabble_encode_jid (unescaped_node, unescaped_domain, unescaped_resource); + + normalized_jid = gabble_normalize_contact (NULL, unescaped_jid, + GUINT_TO_POINTER (GABBLE_JID_GLOBAL), &gabble_error); + + if (gabble_error != NULL) + { + g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, + "'%s' is not a valid XMPP URI: %s", uri, + gabble_error->message); + g_error_free (gabble_error); + goto OUT; + } + + if (!wocky_decode_jid (normalized_jid, node, domain, resource)) + { + g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, + "'%s' is not a valid XMPP URI", uri); + goto OUT; + } + + ret = TRUE; + +OUT: + g_free (scheme); + g_free (tmp_node); + g_free (tmp_domain); + g_free (tmp_resource); + g_free (unescaped_node); + g_free (unescaped_domain); + g_free (unescaped_resource); + g_free (unescaped_jid); + g_free (normalized_jid); + return ret; +} diff --git a/src/addressing-util.h b/src/addressing-util.h new file mode 100644 index 000000000..9835e0d70 --- /dev/null +++ b/src/addressing-util.h @@ -0,0 +1,76 @@ +/* + * addressing-util.c - Headers for Gabble addressing utility functions + * Copyright (C) 2010 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 __GABBLE_UTIL_ADDRESSING_H__ +#define __GABBLE_UTIL_ADDRESSING_H__ + +#include <telepathy-glib/handle-repo-dynamic.h> + +const gchar * const * gabble_get_addressable_uri_schemes (void); + +const gchar * const * gabble_get_addressable_vcard_fields (void); + +gchar * gabble_normalize_contact_uri (const gchar *uri, + GError **error); +gchar * gabble_uri_to_jid (const gchar *uri, + GError **error); +gchar * gabble_jid_to_uri (const gchar *scheme, + const gchar *jid, + GError **error); + +TpHandle gabble_ensure_handle_from_uri (TpHandleRepoIface *repo, + const gchar *uri, + GError **error); + +gchar * gabble_normalize_vcard_address (const gchar *vcard_field, + const gchar *vcard_address, + GError **error); +gchar * gabble_vcard_address_to_jid (const gchar *vcard_field, + const gchar *vcard_address, + GError **error); +gchar * gabble_jid_to_vcard_address (const gchar *vcard_field, + const gchar *jid, + GError **error); + +TpHandle gabble_ensure_handle_from_vcard_address (TpHandleRepoIface *repo, + const gchar *vcard_field, + const gchar *vcard_address, + GError **error); + +gchar **gabble_uris_for_handle (TpHandleRepoIface *contact_repo, + TpHandle contact); + +GHashTable *gabble_vcard_addresses_for_handle (TpHandleRepoIface *contact_repo, + TpHandle contact); + +gchar *gabble_uri_for_handle (TpHandleRepoIface *contact_repo, + const gchar *uri_scheme, + TpHandle contact); + +gchar *gabble_vcard_address_for_handle (TpHandleRepoIface *contact_repo, + const gchar *vcard_field, + TpHandle contact); + +gboolean gabble_parse_xmpp_uri (const gchar *uri, + gchar **node, + gchar **domain, + gchar **resource, + GError **error); + +#endif /* __GABBLE_UTIL_ADDRESSING_H__ */ diff --git a/src/auth-manager.c b/src/auth-manager.c index a94248542..5089e9aed 100644 --- a/src/auth-manager.c +++ b/src/auth-manager.c @@ -28,7 +28,7 @@ #define DEBUG_FLAG GABBLE_DEBUG_AUTH -#include "caps-channel-manager.h" +#include "gabble/caps-channel-manager.h" #include "server-sasl-channel.h" #include "connection.h" #include "debug.h" @@ -325,7 +325,10 @@ gabble_auth_manager_start_auth_async (WockyAuthRegistry *registry, g_ptr_array_add (mech_array, iter->data); } - g_ptr_array_add (mech_array, X_TELEPATHY_PASSWORD); + if (wocky_auth_registry_supports_one_of (registry, mechanisms, + allow_plain)) + g_ptr_array_add (mech_array, X_TELEPATHY_PASSWORD); + g_ptr_array_add (mech_array, NULL); /* we'll use these if we fall back to the base class to use diff --git a/src/bytestream-factory.c b/src/bytestream-factory.c index 64696f79b..57d8509a0 100644 --- a/src/bytestream-factory.c +++ b/src/bytestream-factory.c @@ -26,6 +26,7 @@ #include <dbus/dbus-glib.h> #include <dbus/dbus-glib-lowlevel.h> #include <loudmouth/loudmouth.h> +#include <wocky/wocky-utils.h> #include <telepathy-glib/interfaces.h> #define DEBUG_FLAG GABBLE_DEBUG_BYTESTREAM @@ -679,16 +680,16 @@ gabble_bytestream_factory_dispose (GObject *object) priv->iq_socks5_cb, LM_MESSAGE_TYPE_IQ); lm_message_handler_unref (priv->iq_socks5_cb); - g_hash_table_destroy (priv->ibb_bytestreams); + g_hash_table_unref (priv->ibb_bytestreams); priv->ibb_bytestreams = NULL; - g_hash_table_destroy (priv->muc_bytestreams); + g_hash_table_unref (priv->muc_bytestreams); priv->muc_bytestreams = NULL; - g_hash_table_destroy (priv->socks5_bytestreams); + g_hash_table_unref (priv->socks5_bytestreams); priv->socks5_bytestreams = NULL; - g_hash_table_destroy (priv->multiple_bytestreams); + g_hash_table_unref (priv->multiple_bytestreams); priv->multiple_bytestreams = NULL; proxies = g_slist_concat (priv->socks5_proxies, @@ -1150,7 +1151,7 @@ bytestream_factory_iq_si_cb (LmMessageHandler *handler, { /* jid is not a muc jid so we need contact's resource */ - if (!gabble_decode_jid (from, NULL, NULL, &peer_resource)) + if (!wocky_decode_jid (from, NULL, NULL, &peer_resource)) { DEBUG ("Got an SI IQ response from a bad JID. Ignoring."); goto out; @@ -1272,11 +1273,13 @@ bytestream_factory_iq_si_cb (LmMessageHandler *handler, si_tube_received (self, msg, si, bytestream, peer_handle, room_handle, stream_id); } +#ifdef ENABLE_FILE_TRANSFER else if (!tp_strdiff (profile, NS_FILE_TRANSFER)) { gabble_ft_manager_handle_si_request (priv->conn->ft_manager, bytestream, peer_handle, stream_id, msg); } +#endif else { GError e = { WOCKY_SI_ERROR, WOCKY_SI_ERROR_BAD_PROFILE, "" }; @@ -2081,7 +2084,7 @@ streaminit_reply_cb (GabbleConnection *conn, { /* jid is not a muc jid so we need contact's resource */ - if (!gabble_decode_jid (from, NULL, NULL, &peer_resource)) + if (!wocky_decode_jid (from, NULL, NULL, &peer_resource)) { DEBUG ("Got an SI request with a bad JID"); goto END; diff --git a/src/bytestream-ibb.c b/src/bytestream-ibb.c index 52868f03c..c4ac85afd 100644 --- a/src/bytestream-ibb.c +++ b/src/bytestream-ibb.c @@ -166,7 +166,7 @@ gabble_bytestream_ibb_finalize (GObject *object) if (priv->write_buffer != NULL) g_string_free (priv->write_buffer, TRUE); - g_hash_table_destroy (priv->sent_stanzas_not_acked); + g_hash_table_unref (priv->sent_stanzas_not_acked); G_OBJECT_CLASS (gabble_bytestream_ibb_parent_class)->finalize (object); } diff --git a/src/bytestream-muc.c b/src/bytestream-muc.c index 4cab448a3..359158bb9 100644 --- a/src/bytestream-muc.c +++ b/src/bytestream-muc.c @@ -129,7 +129,7 @@ gabble_bytestream_muc_finalize (GObject *object) if (priv->buffers != NULL) { - g_hash_table_destroy (priv->buffers); + g_hash_table_unref (priv->buffers); priv->buffers = NULL; } @@ -516,7 +516,7 @@ gabble_bytestream_muc_receive (GabbleBytestreamMuc *self, } else { - DEBUG ("Append data to buffer of %s (%zu bytes)", from, str->len); + DEBUG ("Append data to buffer of %s (%" G_GSIZE_FORMAT " bytes)", from, str->len); g_string_append_len (buffer, str->str, str->len); } @@ -542,7 +542,7 @@ gabble_bytestream_muc_receive (GabbleBytestreamMuc *self, if (fully_received) { - DEBUG ("fully received %zu bytes of data", str->len); + DEBUG ("fully received %" G_GSIZE_FORMAT " bytes of data", str->len); g_signal_emit_by_name (G_OBJECT (self), "data-received", sender, str); g_string_free (str, TRUE); } diff --git a/src/bytestream-socks5.c b/src/bytestream-socks5.c index d9d45db94..f306ecf70 100644 --- a/src/bytestream-socks5.c +++ b/src/bytestream-socks5.c @@ -1639,7 +1639,7 @@ get_local_interfaces_ips (void) GSList *ips = NULL; /* FIXME: add IPv6 addresses */ - if ((sockfd = socket (AF_INET, SOCK_DGRAM, IPPROTO_IP)) == INVALID_SOCKET) + if ((sockfd = socket (AF_INET, SOCK_DGRAM, IPPROTO_IP)) == (int) INVALID_SOCKET) { DEBUG ("Cannot open socket to retrieve interface list"); return NULL; @@ -1659,7 +1659,7 @@ get_local_interfaces_ips (void) } ret = WSAIoctl (sockfd, SIO_GET_INTERFACE_LIST, NULL, 0, iflist, - size, &bytes, NULL, NULL); + size, (LPDWORD) &bytes, NULL, NULL); error = WSAGetLastError (); if (ret == SOCKET_ERROR && error != WSAEFAULT) diff --git a/src/call-muc-channel.c b/src/call-muc-channel.c index 33392e301..f07088f21 100644 --- a/src/call-muc-channel.c +++ b/src/call-muc-channel.c @@ -296,9 +296,7 @@ static void call_muc_do_update (GabbleCallMucChannel *self) { GabbleCallMucChannelPrivate *priv = self->priv; -#ifdef ENABLE_DEBUG MucCallState old = priv->state; -#endif switch (priv->state) { @@ -757,7 +755,7 @@ call_muc_channel_got_participant_presence (GabbleCallMucChannel *self, static void call_muc_channel_presence_cb (WockyMuc *wmuc, WockyStanza *stanza, - GHashTable *code, + guint codes, WockyMucMember *who, gpointer user_data) { @@ -769,7 +767,7 @@ call_muc_channel_presence_cb (WockyMuc *wmuc, static void call_muc_channel_left_cb (GObject *source, WockyStanza *stanza, - GHashTable *code, + guint codes, WockyMucMember *member, const gchar *actor_jid, const gchar *why, @@ -820,7 +818,7 @@ call_muc_channel_update_all_members (GabbleCallMucChannel *self) static void call_muc_channel_joined_cb (WockyMuc *muc, WockyStanza *stanza, - GHashTable *code, + guint codes, gpointer user_data) { GabbleCallMucChannel *self = GABBLE_CALL_MUC_CHANNEL (user_data); @@ -845,7 +843,7 @@ call_muc_channel_pre_presence_cb (WockyMuc *wmuc, static void call_muc_channel_own_presence_cb (WockyMuc *wmuc, WockyStanza *stanza, - GHashTable *code, + guint codes, gpointer user_data) { GabbleCallMucChannel *self = GABBLE_CALL_MUC_CHANNEL (user_data); diff --git a/src/call-stream.c b/src/call-stream.c index 1e0bc0053..4c51ed8ea 100644 --- a/src/call-stream.c +++ b/src/call-stream.c @@ -176,9 +176,17 @@ static void google_relay_session_cb (GPtrArray *relays, gpointer user_data) { - TpyBaseMediaCallStream *stream = TPY_BASE_MEDIA_CALL_STREAM (user_data); + TpWeakRef *weak_ref = user_data; + TpyBaseMediaCallStream *stream = TPY_BASE_MEDIA_CALL_STREAM ( + tp_weak_ref_dup_object (weak_ref)); - tpy_base_media_call_stream_set_relay_info (stream, relays); + if (stream != NULL) + { + tpy_base_media_call_stream_set_relay_info (stream, relays); + g_object_unref (stream); + } + + tp_weak_ref_destroy (weak_ref); } static void @@ -343,13 +351,13 @@ gabble_call_stream_constructed (GObject *obj) * We ask for enough relays for 2 components (RTP and RTCP) since we * don't yet know whether there will be RTCP. */ gabble_jingle_factory_create_google_relay_session (conn->jingle_factory, - 2, google_relay_session_cb, obj); + 2, google_relay_session_cb, tp_weak_ref_new (self, NULL, NULL)); } else { GPtrArray *relays = g_ptr_array_new (); tpy_base_media_call_stream_set_relay_info (media_base, relays); - g_ptr_array_free (relays, TRUE); + g_ptr_array_unref (relays); } stun_servers = get_stun_servers (self); diff --git a/src/capabilities.c b/src/capabilities.c index b3ef11e87..a29b78468 100644 --- a/src/capabilities.c +++ b/src/capabilities.c @@ -19,7 +19,7 @@ */ #include "config.h" -#include "capabilities.h" +#include "gabble/capabilities.h" #include <stdlib.h> #include <string.h> @@ -61,7 +61,10 @@ static const Feature self_advertised_features[] = { FEATURE_FIXED, NS_BYTESTREAMS }, { FEATURE_FIXED, NS_VERSION }, +#ifdef ENABLE_FILE_TRANSFER { FEATURE_OPTIONAL, NS_FILE_TRANSFER }, + { FEATURE_OPTIONAL, NS_TP_FT_METADATA }, +#endif { FEATURE_OPTIONAL, NS_GOOGLE_TRANSPORT_P2P }, { FEATURE_OPTIONAL, NS_JINGLE_TRANSPORT_ICEUDP }, diff --git a/src/capabilities.h b/src/capabilities.h deleted file mode 100644 index 811f11375..000000000 --- a/src/capabilities.h +++ /dev/null @@ -1,73 +0,0 @@ -/* - * capabilities.h - Connection.Interface.Capabilities constants and utilities - * Copyright (C) 2005 Collabora Ltd. - * Copyright (C) 2005 Nokia Corporation - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#ifndef __GABBLE_CAPABILITIES__H__ -#define __GABBLE_CAPABILITIES__H__ - -#include <glib-object.h> - -#include "gabble/capabilities-set.h" - -/* Pseudo-capabilities for buggy or strange implementations, represented as - * strings starting with a character not allowed in XML (the ASCII beep :-) */ -#define QUIRK_PREFIX_CHAR '\x07' -#define QUIRK_PREFIX "\x07" -/* Gabble 0.7.x with 16 <= x < 29 omits @creator on <content/> */ -#define QUIRK_OMITS_CONTENT_CREATORS "\x07omits-content-creators" -/* The Google Webmail client doesn't support some features */ -#define QUIRK_GOOGLE_WEBMAIL_CLIENT "\x07google-webmail-client" - -/* Some useful capability sets for Jingle etc. */ -const GabbleCapabilitySet *gabble_capabilities_get_legacy (void); -const GabbleCapabilitySet *gabble_capabilities_get_any_audio (void); -const GabbleCapabilitySet *gabble_capabilities_get_any_video (void); -const GabbleCapabilitySet *gabble_capabilities_get_any_audio_video (void); -const GabbleCapabilitySet *gabble_capabilities_get_any_google_av (void); -const GabbleCapabilitySet *gabble_capabilities_get_any_jingle_av (void); -const GabbleCapabilitySet *gabble_capabilities_get_any_transport (void); -const GabbleCapabilitySet *gabble_capabilities_get_geoloc_notify (void); -const GabbleCapabilitySet *gabble_capabilities_get_olpc_notify (void); - -/* XEP-0115 version 1.3: - * - * "The names of the feature bundles MUST NOT be used for semantic purposes: - * they are merely opaque identifiers" - * - * However, some old Jabber clients (e.g. Gabble 0.2) and various Google - * clients require the bundle names "voice-v1" and "video-v1". We keep these - * names for compatibility. - */ -#define BUNDLE_SHARE_V1 "share-v1" -#define BUNDLE_VOICE_V1 "voice-v1" -#define BUNDLE_VIDEO_V1 "video-v1" -#define BUNDLE_PMUC_V1 "pmuc-v1" - -const GabbleCapabilitySet *gabble_capabilities_get_bundle_share_v1 (void); -const GabbleCapabilitySet *gabble_capabilities_get_bundle_voice_v1 (void); -const GabbleCapabilitySet *gabble_capabilities_get_bundle_video_v1 (void); - -/* Return the capabilities we always have */ -const GabbleCapabilitySet *gabble_capabilities_get_fixed_caps (void); - -void gabble_capabilities_init (gpointer conn); -void gabble_capabilities_finalize (gpointer conn); - -#endif /* __GABBLE_CAPABILITIES__H__ */ - diff --git a/src/caps-channel-manager.c b/src/caps-channel-manager.c index bc3d2ea98..dea13a652 100644 --- a/src/caps-channel-manager.c +++ b/src/caps-channel-manager.c @@ -21,7 +21,7 @@ */ #include "config.h" -#include "caps-channel-manager.h" +#include "gabble/caps-channel-manager.h" #include <telepathy-glib/dbus.h> #include <telepathy-glib/channel-manager.h> @@ -71,7 +71,7 @@ gabble_caps_channel_manager_get_contact_capabilities ( { method (caps_manager, handle, caps, arr); } - /* ... else assume there is not caps for this kind of channels */ + /* ... else assume there are no caps for this kind of channel */ } /** @@ -82,6 +82,7 @@ gabble_caps_channel_manager_get_contact_capabilities ( * GHashTable with string keys and GValue values * @cap_tokens: the handler capability tokens supported by the client * @cap_set: a set into which to merge additional XMPP capabilities + * @data_forms: a #GPtrArray of #WockyDataForm objects * * Convert the capabilities of a Telepathy client into XMPP capabilities to be * advertised. @@ -95,7 +96,8 @@ gabble_caps_channel_manager_represent_client ( const gchar *client_name, const GPtrArray *filters, const gchar * const *cap_tokens, - GabbleCapabilitySet *cap_set) + GabbleCapabilitySet *cap_set, + GPtrArray *data_forms) { GabbleCapsChannelManagerInterface *iface = GABBLE_CAPS_CHANNEL_MANAGER_GET_INTERFACE (caps_manager); @@ -103,6 +105,6 @@ gabble_caps_channel_manager_represent_client ( if (method != NULL) { - method (caps_manager, client_name, filters, cap_tokens, cap_set); + method (caps_manager, client_name, filters, cap_tokens, cap_set, data_forms); } } diff --git a/src/caps-channel-manager.h b/src/caps-channel-manager.h deleted file mode 100644 index 05947b757..000000000 --- a/src/caps-channel-manager.h +++ /dev/null @@ -1,105 +0,0 @@ -/* - * caps-channel-manager.h - interface holding capabilities functions for - * channel managers - * - * Copyright (C) 2008 Collabora Ltd. - * Copyright (C) 2008 Nokia Corporation - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#ifndef GABBLE_CAPS_CHANNEL_MANAGER_H -#define GABBLE_CAPS_CHANNEL_MANAGER_H - -#include <glib-object.h> -#include <telepathy-glib/exportable-channel.h> -#include <telepathy-glib/handle.h> - -#include "capabilities.h" - -G_BEGIN_DECLS - -#define GABBLE_TYPE_CAPS_CHANNEL_MANAGER \ - (gabble_caps_channel_manager_get_type ()) - -#define GABBLE_CAPS_CHANNEL_MANAGER(obj) \ - (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ - GABBLE_TYPE_CAPS_CHANNEL_MANAGER, GabbleCapsChannelManager)) - -#define GABBLE_IS_CAPS_CHANNEL_MANAGER(obj) \ - (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \ - GABBLE_TYPE_CAPS_CHANNEL_MANAGER)) - -#define GABBLE_CAPS_CHANNEL_MANAGER_GET_INTERFACE(obj) \ - (G_TYPE_INSTANCE_GET_INTERFACE ((obj), \ - GABBLE_TYPE_CAPS_CHANNEL_MANAGER, GabbleCapsChannelManagerInterface)) - -typedef struct _GabbleCapsChannelManager GabbleCapsChannelManager; -typedef struct _GabbleCapsChannelManagerInterface GabbleCapsChannelManagerInterface; - -/* virtual methods */ - -typedef void (*GabbleCapsChannelManagerGetContactCapsFunc) ( - GabbleCapsChannelManager *manager, - TpHandle handle, - const GabbleCapabilitySet *caps, - GPtrArray *arr); - -typedef void (*GabbleCapsChannelManagerResetCapsFunc) ( - GabbleCapsChannelManager *manager); - -typedef void (*GabbleCapsChannelManagerAddCapFunc) ( - GabbleCapsChannelManager *manager, - GHashTable *cap, - GabbleCapabilitySet *cap_set); - -typedef void (*GabbleCapsChannelManagerRepresentClientFunc) ( - GabbleCapsChannelManager *manager, - const gchar *client_name, - const GPtrArray *filters, - const gchar * const *cap_tokens, - GabbleCapabilitySet *cap_set); - -void gabble_caps_channel_manager_reset_capabilities ( - GabbleCapsChannelManager *caps_manager); - -void gabble_caps_channel_manager_get_contact_capabilities ( - GabbleCapsChannelManager *caps_manager, - TpHandle handle, - const GabbleCapabilitySet *caps, - GPtrArray *arr); - -void gabble_caps_channel_manager_represent_client ( - GabbleCapsChannelManager *caps_manager, - const gchar *client_name, - const GPtrArray *filters, - const gchar * const *cap_tokens, - GabbleCapabilitySet *cap_set); - -struct _GabbleCapsChannelManagerInterface { - GTypeInterface parent; - - GabbleCapsChannelManagerResetCapsFunc reset_caps; - GabbleCapsChannelManagerGetContactCapsFunc get_contact_caps; - GabbleCapsChannelManagerRepresentClientFunc represent_client; - - gpointer priv; -}; - -GType gabble_caps_channel_manager_get_type (void); - -G_END_DECLS - -#endif diff --git a/src/caps-hash.c b/src/caps-hash.c index bf8a2a085..bd143eecd 100644 --- a/src/caps-hash.c +++ b/src/caps-hash.c @@ -35,7 +35,7 @@ #define DEBUG_FLAG GABBLE_DEBUG_PRESENCE #include "base64.h" -#include "capabilities.h" +#include "gabble/capabilities.h" #include "debug.h" #include "namespaces.h" #include "presence-cache.h" @@ -61,6 +61,7 @@ caps_hash_compute_from_self_presence (GabbleConnection *self) const GabbleCapabilitySet *cap_set; GPtrArray *features = g_ptr_array_new (); GPtrArray *identities = wocky_disco_identity_array_new (); + GPtrArray *data_forms; gchar *str; /* XEP-0030 requires at least 1 identity. We don't need more. */ @@ -71,9 +72,11 @@ caps_hash_compute_from_self_presence (GabbleConnection *self) cap_set = gabble_presence_peek_caps (presence); gabble_capability_set_foreach (cap_set, ptr_array_add_str, features); - str = wocky_caps_hash_compute_from_lists (features, identities, NULL); + data_forms = gabble_presence_peek_data_forms (presence); - g_ptr_array_free (features, TRUE); + str = wocky_caps_hash_compute_from_lists (features, identities, data_forms); + + g_ptr_array_unref (features); wocky_disco_identity_array_free (identities); return str; @@ -85,8 +88,8 @@ caps_hash_compute_from_self_presence (GabbleConnection *self) * Returns: the hash. The called must free the returned hash with g_free(). */ gchar * -gabble_caps_hash_compute (const GabbleCapabilitySet *cap_set, - const GPtrArray *identities) +gabble_caps_hash_compute_full (const GabbleCapabilitySet *cap_set, + const GPtrArray *identities, GPtrArray *data_forms) { GPtrArray *features = g_ptr_array_new (); GPtrArray *identities_copy = ((identities == NULL) ? @@ -96,10 +99,23 @@ gabble_caps_hash_compute (const GabbleCapabilitySet *cap_set, gabble_capability_set_foreach (cap_set, ptr_array_add_str, features); - str = wocky_caps_hash_compute_from_lists (features, identities_copy, NULL); + str = wocky_caps_hash_compute_from_lists (features, identities_copy, + data_forms); - g_ptr_array_free (features, TRUE); + g_ptr_array_unref (features); wocky_disco_identity_array_free (identities_copy); return str; } + +/** + * Compute the hash as defined by the XEP-0115 from a received GabbleCapabilitySet + * + * Returns: the hash. The called must free the returned hash with g_free(). + */ +gchar * +gabble_caps_hash_compute (const GabbleCapabilitySet *cap_set, + const GPtrArray *identities) +{ + return gabble_caps_hash_compute_full (cap_set, identities, NULL); +} diff --git a/src/conn-addressing.c b/src/conn-addressing.c new file mode 100644 index 000000000..6842ae008 --- /dev/null +++ b/src/conn-addressing.c @@ -0,0 +1,169 @@ +/* + * conn-addressing.h - Header for Gabble connection code handling addressing. + * Copyright (C) 2010 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 "conn-addressing.h" + +#include <dbus/dbus-glib-lowlevel.h> + +#include <telepathy-glib/gtypes.h> +#include <telepathy-glib/interfaces.h> + +#include "extensions/extensions.h" + +#include "addressing-util.h" +#include "namespaces.h" +#include "util.h" + +static const char *assumed_interfaces[] = { + TP_IFACE_CONNECTION, + GABBLE_IFACE_CONNECTION_INTERFACE_ADDRESSING, + NULL + }; + + +static void +_fill_contact_attributes (TpHandleRepoIface *contact_repo, + TpHandle contact, + GHashTable *attributes_hash) +{ + gchar **uris = gabble_uris_for_handle (contact_repo, contact); + GHashTable *addresses = gabble_vcard_addresses_for_handle (contact_repo, contact); + + tp_contacts_mixin_set_contact_attribute (attributes_hash, + contact, GABBLE_IFACE_CONNECTION_INTERFACE_ADDRESSING"/uris", + tp_g_value_slice_new_take_boxed (G_TYPE_STRV, uris)); + + tp_contacts_mixin_set_contact_attribute (attributes_hash, + contact, GABBLE_IFACE_CONNECTION_INTERFACE_ADDRESSING"/addresses", + tp_g_value_slice_new_take_boxed (TP_HASH_TYPE_STRING_STRING_MAP, addresses)); +} + +static void +conn_addressing_fill_contact_attributes (GObject *obj, + const GArray *contacts, + GHashTable *attributes_hash) +{ + guint i; + TpHandleRepoIface *contact_repo = tp_base_connection_get_handles ( + (TpBaseConnection *) obj, TP_HANDLE_TYPE_CONTACT); + + for (i = 0; i < contacts->len; i++) + { + TpHandle contact = g_array_index (contacts, TpHandle, i); + _fill_contact_attributes (contact_repo, contact, attributes_hash); + } +} + +static void +conn_addressing_get_contacts_by_uri (GabbleSvcConnectionInterfaceAddressing *iface, + const gchar **uris, + const gchar **interfaces, + DBusGMethodInvocation *context) +{ + const gchar **uri; + TpHandleRepoIface *contact_repo = tp_base_connection_get_handles ( + (TpBaseConnection *) iface, TP_HANDLE_TYPE_CONTACT); + GHashTable *attributes; + GHashTable *requested = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + GArray *handles = g_array_sized_new (TRUE, TRUE, sizeof (TpHandle), + g_strv_length ((gchar **) uris)); + gchar *sender = dbus_g_method_get_sender (context); + + for (uri = uris; *uri != NULL; uri++) + { + TpHandle h = gabble_ensure_handle_from_uri (contact_repo, *uri, NULL); + + if (h == 0) + continue; + + g_hash_table_insert (requested, g_strdup (*uri), GUINT_TO_POINTER (h)); + g_array_append_val (handles, h); + } + + attributes = tp_contacts_mixin_get_contact_attributes (G_OBJECT (iface), handles, + interfaces, assumed_interfaces, sender); + + gabble_svc_connection_interface_addressing_return_from_get_contacts_by_uri ( + context, requested, attributes); + + tp_handles_unref (contact_repo, handles); + g_hash_table_unref (requested); + g_hash_table_unref (attributes); + g_free (sender); +} + +static void +conn_addressing_get_contacts_by_vcard_field (GabbleSvcConnectionInterfaceAddressing *iface, + const gchar *field, + const gchar **addresses, + const gchar **interfaces, + DBusGMethodInvocation *context) +{ + const gchar **address; + TpHandleRepoIface *contact_repo = tp_base_connection_get_handles ( + (TpBaseConnection *) iface, TP_HANDLE_TYPE_CONTACT); + GHashTable *attributes; + GHashTable *requested = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + GArray *handles = g_array_sized_new (TRUE, TRUE, sizeof (TpHandle), + g_strv_length ((gchar **) addresses)); + gchar *sender = dbus_g_method_get_sender (context); + + for (address = addresses; *address != NULL; address++) + { + TpHandle h = gabble_ensure_handle_from_vcard_address (contact_repo, field, + *address, NULL); + + if (h == 0) + continue; + + g_hash_table_insert (requested, g_strdup (*address), GUINT_TO_POINTER (h)); + g_array_append_val (handles, h); + } + + attributes = tp_contacts_mixin_get_contact_attributes (G_OBJECT (iface), handles, + interfaces, assumed_interfaces, sender); + + gabble_svc_connection_interface_addressing_return_from_get_contacts_by_vcard_field ( + context, requested, attributes); + + tp_handles_unref (contact_repo, handles); + g_hash_table_unref (requested); + g_hash_table_unref (attributes); + g_free (sender); +} + +void +conn_addressing_init (GabbleConnection *self) { + tp_contacts_mixin_add_contact_attributes_iface (G_OBJECT (self), + GABBLE_IFACE_CONNECTION_INTERFACE_ADDRESSING, + conn_addressing_fill_contact_attributes); +} + +void +conn_addressing_iface_init (gpointer g_iface, + gpointer iface_data) +{ +#define IMPLEMENT(x) \ + gabble_svc_connection_interface_addressing_implement_##x (\ + g_iface, conn_addressing_##x) + + IMPLEMENT (get_contacts_by_uri); + IMPLEMENT (get_contacts_by_vcard_field); +#undef IMPLEMENT +} diff --git a/src/conn-addressing.h b/src/conn-addressing.h new file mode 100644 index 000000000..2dbe08756 --- /dev/null +++ b/src/conn-addressing.h @@ -0,0 +1,34 @@ +/* + * conn-addressing.h - Header for Gabble connection code handling addressing. + * Copyright (C) 2010 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 GABBLE_CONN_ADDRESSING_H +#define GABBLE_CONN_ADDRESSING_H + +#include <glib.h> + +#include "connection.h" + +G_BEGIN_DECLS + +void conn_addressing_iface_init (gpointer g_iface, gpointer iface_data); +void conn_addressing_init (GabbleConnection *self); + +G_END_DECLS + +#endif /* GABBLE_CONN_ADDRESSING_H */ diff --git a/src/conn-aliasing.c b/src/conn-aliasing.c index 08ca7cd76..af39c51bc 100644 --- a/src/conn-aliasing.c +++ b/src/conn-aliasing.c @@ -21,6 +21,7 @@ #include "config.h" #include "conn-aliasing.h" +#include <wocky/wocky-utils.h> #include <telepathy-glib/contacts-mixin.h> #include <telepathy-glib/gtypes.h> #include <telepathy-glib/interfaces.h> @@ -42,6 +43,11 @@ static void gabble_conn_aliasing_pep_nick_reply_handler ( GabbleConnection *conn, LmMessage *msg, TpHandle handle); static GQuark gabble_conn_aliasing_pep_alias_quark (void); +static GabbleConnectionAliasSource _gabble_connection_get_cached_remote_alias ( + GabbleConnection *, TpHandle, gchar **); +static void maybe_request_vcard (GabbleConnection *self, TpHandle handle, + GabbleConnectionAliasSource source); + /* distinct from any strdup()d pointer - used for negative caching */ static const gchar *NO_ALIAS = ""; @@ -137,7 +143,7 @@ aliases_request_free (AliasesRequest *request) (TpBaseConnection *) request->conn, TP_HANDLE_TYPE_CONTACT); tp_handles_unref (contact_handles, request->contacts); - g_array_free (request->contacts, TRUE); + g_array_unref (request->contacts); g_free (request->vcard_requests); g_free (request->pep_requests); g_strfreev (request->aliases); @@ -473,14 +479,6 @@ gabble_connection_request_aliases (TpSvcConnectionInterfaceAliasing *iface, aliases_request_free (request); } - -struct _i_hate_g_hash_table_foreach -{ - GabbleConnection *conn; - GError **error; - gboolean retval; -}; - static LmHandlerResult nick_publish_msg_reply_cb (GabbleConnection *conn, LmMessage *sent_msg, @@ -503,85 +501,104 @@ nick_publish_msg_reply_cb (GabbleConnection *conn, return LM_HANDLER_RESULT_REMOVE_MESSAGE; } -static void -setaliases_foreach (gpointer key, gpointer value, gpointer user_data) +static gboolean +set_one_alias ( + GabbleConnection *conn, + TpHandle handle, + gchar *alias, + GError **error) { - struct _i_hate_g_hash_table_foreach *data = - (struct _i_hate_g_hash_table_foreach *) user_data; - TpHandle handle = GPOINTER_TO_UINT (key); - gchar *alias = (gchar *) value; - GError *error = NULL; - TpBaseConnection *base = (TpBaseConnection *) data->conn; + TpBaseConnection *base = (TpBaseConnection *) conn; TpHandleRepoIface *contact_handles = tp_base_connection_get_handles (base, TP_HANDLE_TYPE_CONTACT); + gboolean ret = TRUE; g_assert (base->status == TP_CONNECTION_STATUS_CONNECTED); - if (!tp_handle_is_valid (contact_handles, handle, &error)) + if (tp_str_empty (alias)) + alias = NULL; + + if (!tp_handle_is_valid (contact_handles, handle, error)) { - data->retval = FALSE; + ret = FALSE; } else if (base->self_handle == handle) { /* only alter the roster if we're already there, e.g. because someone * added us with another client */ - if (gabble_roster_handle_has_entry (data->conn->roster, handle) - && !gabble_roster_handle_set_name (data->conn->roster, handle, - alias, data->error)) + if (gabble_roster_handle_has_entry (conn->roster, handle) + && !gabble_roster_handle_set_name (conn->roster, handle, + alias, error)) { - data->retval = FALSE; + ret = FALSE; } } - else if (!gabble_roster_handle_set_name (data->conn->roster, handle, alias, - data->error)) + else { - data->retval = FALSE; + gchar *remote_alias = NULL; + GabbleConnectionAliasSource source = GABBLE_CONNECTION_ALIAS_FROM_ROSTER; + + if (alias == NULL) + { + source = _gabble_connection_get_cached_remote_alias (conn, handle, + &remote_alias); + alias = remote_alias; + } + + ret = gabble_roster_handle_set_name (conn->roster, handle, alias, error); + g_free (remote_alias); + + /* If we don't have a cached remote alias for this contact, try to ask + * for one. (Maybe we haven't seen a PEP update or fetched their vCard in + * this session?) + */ + maybe_request_vcard (conn, handle, source); } if (base->self_handle == handle) { - GList *edits = NULL; + GabbleVCardManagerEditInfo *edit; + GQueue edits = G_QUEUE_INIT; /* User has called SetAliases on themselves - patch their vCard. * FIXME: because SetAliases is currently synchronous, we ignore errors * here, and just let the request happen in the background. */ - if (data->conn->features & GABBLE_CONNECTION_FEATURES_PEP) + if (conn->features & GABBLE_CONNECTION_FEATURES_PEP) { /* Publish nick using PEP */ LmMessage *msg; WockyNode *item; - msg = wocky_pep_service_make_publish_stanza (data->conn->pep_nick, - &item); - wocky_node_add_child_with_content_ns (item, "nick", - alias, NS_NICK); + msg = wocky_pep_service_make_publish_stanza (conn->pep_nick, &item); + /* Does the right thing if alias == NULL. */ + wocky_node_add_child_with_content_ns (item, "nick", alias, NS_NICK); - _gabble_connection_send_with_reply (data->conn, msg, + _gabble_connection_send_with_reply (conn, msg, nick_publish_msg_reply_cb, NULL, NULL, NULL); lm_message_unref (msg); } - edits = g_list_append (edits, gabble_vcard_manager_edit_info_new ( - NULL, alias, GABBLE_VCARD_EDIT_SET_ALIAS, NULL)); - gabble_vcard_manager_edit (data->conn->vcard_manager, 0, NULL, - NULL, G_OBJECT (data->conn), edits); - } - - if (NULL != error) - { - if (NULL == *(data->error)) - { - *(data->error) = error; - } + if (alias == NULL) + /* Deliberately not doing the fall-back-to-FN-on-GTalk dance because + * clearing your FN is more serious. + */ + edit = gabble_vcard_manager_edit_info_new ("NICKNAME", NULL, + GABBLE_VCARD_EDIT_DELETE, NULL); else - { - g_error_free (error); - } + edit = gabble_vcard_manager_edit_info_new (NULL, alias, + GABBLE_VCARD_EDIT_SET_ALIAS, NULL); + + g_queue_push_head (&edits, edit); + /* Yes, gabble_vcard_manager_edit steals the list you pass it. */ + gabble_vcard_manager_edit (conn->vcard_manager, 0, NULL, + NULL, G_OBJECT (conn), edits.head); } + + return ret; } /** @@ -597,27 +614,32 @@ gabble_connection_set_aliases (TpSvcConnectionInterfaceAliasing *iface, { GabbleConnection *self = GABBLE_CONNECTION (iface); TpBaseConnection *base = (TpBaseConnection *) self; - GError *error = NULL; - struct _i_hate_g_hash_table_foreach data = { NULL, NULL, TRUE }; + GHashTableIter iter; + gpointer key, value; + gboolean retval = TRUE; + GError *first_error = NULL; g_assert (GABBLE_IS_CONNECTION (self)); TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context); - data.conn = self; - data.error = &error; - - g_hash_table_foreach (aliases, setaliases_foreach, &data); + g_hash_table_iter_init (&iter, aliases); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + if (!set_one_alias (self, GPOINTER_TO_UINT (key), value, + (first_error == NULL ? &first_error : NULL))) + retval = FALSE; + } - if (data.retval) + if (retval) { tp_svc_connection_interface_aliasing_return_from_set_aliases ( context); } else { - dbus_g_method_return_error (context, error); - g_error_free (error); + dbus_g_method_return_error (context, first_error); + g_error_free (first_error); } } @@ -766,17 +788,32 @@ gabble_conn_aliasing_pep_nick_reply_handler (GabbleConnection *conn, } } - void gabble_conn_aliasing_nickname_updated (GObject *object, TpHandle handle, gpointer user_data) { + GArray *handles; + + handles = g_array_sized_new (FALSE, FALSE, sizeof (TpHandle), 1); + g_array_append_val (handles, handle); + + gabble_conn_aliasing_nicknames_updated (object, handles, user_data); + + g_array_unref (handles); +} + +void +gabble_conn_aliasing_nicknames_updated (GObject *object, + GArray *handles, + gpointer user_data) +{ GabbleConnection *conn = GABBLE_CONNECTION (user_data); - GabbleConnectionAliasSource signal_source, current_source; - gchar *alias = NULL; + GabbleConnectionAliasSource signal_source; GPtrArray *aliases; - GValue entry = { 0, }; + guint i; + + g_return_if_fail (handles->len > 0); if (object == user_data) { @@ -801,47 +838,58 @@ gabble_conn_aliasing_nickname_updated (GObject *object, return; } - current_source = _gabble_connection_get_cached_alias (conn, handle, &alias); - - g_assert (current_source != GABBLE_CONNECTION_ALIAS_NONE); + aliases = g_ptr_array_sized_new (handles->len); - /* if the active alias for this handle is already known and from - * a higher priority, this signal is not interesting so we do - * nothing */ - if (signal_source < current_source) + for (i = 0; i < handles->len; i++) { - DEBUG ("ignoring boring alias change for handle %u, signal from %u " - "but source %u has alias \"%s\"", handle, signal_source, - current_source, alias); - goto OUT; - } + TpHandle handle = g_array_index (handles, TpHandle, i); + GabbleConnectionAliasSource current_source; + gchar *alias = NULL; + GValue entry = { 0, }; + + current_source = _gabble_connection_get_cached_alias (conn, handle, + &alias); + g_assert (current_source != GABBLE_CONNECTION_ALIAS_NONE); + + /* if the active alias for this handle is already known and from + * a higher priority, this signal is not interesting so we do + * nothing */ + if (signal_source < current_source) + { + DEBUG ("ignoring boring alias change for handle %u, signal from %u " + "but source %u has alias \"%s\"", handle, signal_source, + current_source, alias); + g_free (alias); + continue; + } - g_value_init (&entry, TP_STRUCT_TYPE_ALIAS_PAIR); - g_value_take_boxed (&entry, dbus_g_type_specialized_construct - (TP_STRUCT_TYPE_ALIAS_PAIR)); + g_value_init (&entry, TP_STRUCT_TYPE_ALIAS_PAIR); + g_value_take_boxed (&entry, + dbus_g_type_specialized_construct (TP_STRUCT_TYPE_ALIAS_PAIR)); - dbus_g_type_struct_set (&entry, - 0, handle, - 1, alias, - G_MAXUINT); + dbus_g_type_struct_set (&entry, + 0, handle, + 1, alias, + G_MAXUINT); - aliases = g_ptr_array_sized_new (1); - g_ptr_array_add (aliases, g_value_get_boxed (&entry)); + g_ptr_array_add (aliases, g_value_get_boxed (&entry)); + /* Check whether the roster has an entry for the handle and if so, set + * the roster alias so the vCard isn't fetched on every connect. */ + if (signal_source < GABBLE_CONNECTION_ALIAS_FROM_ROSTER && + gabble_roster_handle_has_entry (conn->roster, handle)) + gabble_roster_handle_set_name (conn->roster, handle, alias, NULL); - tp_svc_connection_interface_aliasing_emit_aliases_changed (conn, aliases); + g_free (alias); + } - g_value_unset (&entry); - g_ptr_array_free (aliases, TRUE); + if (aliases->len > 0) + tp_svc_connection_interface_aliasing_emit_aliases_changed (conn, aliases); - /* Check whether the roster has an entry for the handle and if so, set the - * roster alias so the vCard isn't fetched on every connect. */ - if (signal_source < GABBLE_CONNECTION_ALIAS_FROM_ROSTER && - gabble_roster_handle_has_entry (conn->roster, handle)) - gabble_roster_handle_set_name (conn->roster, handle, alias, NULL); + for (i = 0; i < aliases->len; i++) + g_boxed_free (TP_STRUCT_TYPE_ALIAS_PAIR, g_ptr_array_index (aliases, i)); -OUT: - g_free (alias); + g_ptr_array_unref (aliases); } static void @@ -862,29 +910,18 @@ maybe_set (gchar **target, *target = g_strdup (source); } -GabbleConnectionAliasSource -_gabble_connection_get_cached_alias (GabbleConnection *conn, - TpHandle handle, - gchar **alias) +static GabbleConnectionAliasSource +get_cached_remote_alias ( + GabbleConnection *conn, + TpHandleRepoIface *contact_handles, + TpHandle handle, + const gchar *jid, + gchar **alias) { TpBaseConnection *base = (TpBaseConnection *) conn; - TpHandleRepoIface *contact_handles = tp_base_connection_get_handles (base, - TP_HANDLE_TYPE_CONTACT); GabblePresence *pres; - const gchar *tmp, *jid; - gchar *resource = NULL; - - g_return_val_if_fail (NULL != conn, GABBLE_CONNECTION_ALIAS_NONE); - g_return_val_if_fail (GABBLE_IS_CONNECTION (conn), GABBLE_CONNECTION_ALIAS_NONE); - g_return_val_if_fail (tp_handle_is_valid (contact_handles, handle, NULL), - GABBLE_CONNECTION_ALIAS_NONE); - - tmp = gabble_roster_handle_get_name (conn->roster, handle); - if (NULL != tmp) - { - maybe_set (alias, tmp); - return GABBLE_CONNECTION_ALIAS_FROM_ROSTER; - } + const gchar *tmp; + gchar *resource; tmp = tp_handle_get_qdata (contact_handles, handle, gabble_conn_aliasing_pep_alias_quark ()); @@ -918,12 +955,8 @@ _gabble_connection_get_cached_alias (GabbleConnection *conn, } } - jid = tp_handle_inspect (contact_handles, handle); - g_assert (NULL != jid); - - /* MUC handles have the nickname in the resource */ - if (gabble_decode_jid (jid, NULL, NULL, &resource) && + if (wocky_decode_jid (jid, NULL, NULL, &resource) && NULL != resource) { set_or_clear (alias, resource); @@ -942,9 +975,106 @@ _gabble_connection_get_cached_alias (GabbleConnection *conn, } } - /* otherwise just take their jid */ + maybe_set (alias, NULL); + return GABBLE_CONNECTION_ALIAS_NONE; +} + +/* + * _gabble_connection_get_cached_alias: + * @conn: a connection + * @handle: a handle + * @alias: (allow-none): location at which to store @handle's alias. If + * provided, it will always be set to a non-NULL, non-empty string, + * which the caller must free. + * + * Gets the best possible alias for @handle, falling back to their JID if + * necessary. + * + * Returns: the source of the alias. + */ +GabbleConnectionAliasSource +_gabble_connection_get_cached_alias (GabbleConnection *conn, + TpHandle handle, + gchar **alias) +{ + TpBaseConnection *base = (TpBaseConnection *) conn; + TpHandleRepoIface *contact_handles = tp_base_connection_get_handles (base, + TP_HANDLE_TYPE_CONTACT); + const gchar *tmp, *jid; + gboolean roster_alias_was_jid = FALSE; + GabbleConnectionAliasSource source; + + g_return_val_if_fail (NULL != conn, GABBLE_CONNECTION_ALIAS_NONE); + g_return_val_if_fail (GABBLE_IS_CONNECTION (conn), GABBLE_CONNECTION_ALIAS_NONE); + g_return_val_if_fail (tp_handle_is_valid (contact_handles, handle, NULL), + GABBLE_CONNECTION_ALIAS_NONE); + + jid = tp_handle_inspect (contact_handles, handle); + g_assert (NULL != jid); + + tmp = gabble_roster_handle_get_name (conn->roster, handle); + if (!tp_strdiff (tmp, jid)) + { + /* Normally, we prefer whatever we've cached on the roster, to avoid + * wasting bandwidth checking for aliases by repeatedly fetching the + * vCard, and (more importantly) to prefer anything the local user set + * over what the contact says their name is. + * + * However, if the alias stored on the roster is just the contact's JID, + * we check for better aliases that we happen to have received from other + * sources (maybe a PEP nick update, or a vCard we've fetched for the + * avatar, or whatever). If we can't find anything better, we'll use the + * JID, and still say that it came from the roster: this means we don't + * defeat negative caching for contacts who genuinely don't have an + * alias. + */ + roster_alias_was_jid = TRUE; + } + else if (!tp_str_empty (tmp)) + { + maybe_set (alias, tmp); + return GABBLE_CONNECTION_ALIAS_FROM_ROSTER; + } + + source = get_cached_remote_alias (conn, contact_handles, handle, jid, alias); + if (source != GABBLE_CONNECTION_ALIAS_NONE) + return source; + + /* otherwise just take their jid, which may have been specified on the roster + * as the contact's alias. */ maybe_set (alias, jid); - return GABBLE_CONNECTION_ALIAS_FROM_JID; + return roster_alias_was_jid ? GABBLE_CONNECTION_ALIAS_FROM_ROSTER + : GABBLE_CONNECTION_ALIAS_FROM_JID; +} + +/* + * _gabble_connection_get_cached_remote_alias: + * @conn: a connection + * @handle: a handle + * @alias: (allow-none): location at which to store @handle's alias. If + * provided, it may be set to %NULL (if @handle has no cached remote + * alias) or a non-empty string which the caller must free. + * + * Gets the best cached alias for @handle as provided by them (such as via PEP + * Nicknames, in their vCard, etc), not considering anything the local user has + * specified on their roster. + * + * Returns: the source of the alias, or GABBLE_CONNECTION_ALIAS_NONE if we have + * no cached remote alias for @handle + */ +static GabbleConnectionAliasSource +_gabble_connection_get_cached_remote_alias ( + GabbleConnection *conn, + TpHandle handle, + gchar **alias) +{ + TpBaseConnection *base = (TpBaseConnection *) conn; + TpHandleRepoIface *contact_handles = tp_base_connection_get_handles (base, + TP_HANDLE_TYPE_CONTACT); + const gchar *jid = tp_handle_inspect (contact_handles, handle); + + g_assert (NULL != jid); + return get_cached_remote_alias (conn, contact_handles, handle, jid, alias); } static void @@ -1054,7 +1184,7 @@ gabble_connection_get_aliases (TpSvcConnectionInterfaceAliasing *iface, tp_svc_connection_interface_aliasing_return_from_get_aliases (context, result); - g_hash_table_destroy (result); + g_hash_table_unref (result); } diff --git a/src/conn-aliasing.h b/src/conn-aliasing.h index 8716bb78b..353e3238e 100644 --- a/src/conn-aliasing.h +++ b/src/conn-aliasing.h @@ -33,6 +33,9 @@ void conn_aliasing_iface_init (gpointer g_iface, gpointer iface_data); void gabble_conn_aliasing_nickname_updated (GObject *object, TpHandle handle, gpointer user_data); +void gabble_conn_aliasing_nicknames_updated (GObject *object, + GArray *handles, gpointer user_data); + GabbleConnectionAliasSource _gabble_connection_get_cached_alias ( GabbleConnection *, TpHandle, gchar **); diff --git a/src/conn-avatars.c b/src/conn-avatars.c index aabe8ed43..9ec2e941f 100644 --- a/src/conn-avatars.c +++ b/src/conn-avatars.c @@ -303,7 +303,7 @@ _got_self_avatar_for_get_known_avatar_tokens (GObject *obj, tp_svc_connection_interface_avatars_return_from_get_known_avatar_tokens ( context->invocation, context->ret); - g_hash_table_destroy (context->ret); + g_hash_table_unref (context->ret); g_slice_free (GetKnownAvatarTokensContext, context); } @@ -403,7 +403,7 @@ gabble_connection_get_known_avatar_tokens (TpSvcConnectionInterfaceAvatars *ifac tp_svc_connection_interface_avatars_return_from_get_known_avatar_tokens ( invocation, ret); - g_hash_table_destroy (ret); + g_hash_table_unref (ret); } @@ -569,7 +569,7 @@ _request_avatar_cb (GabbleVCardManager *self, g_array_append_vals (arr, avatar->str, avatar->len); tp_svc_connection_interface_avatars_return_from_request_avatar ( context, arr, mime_type); - g_array_free (arr, TRUE); + g_array_unref (arr); out: if (avatar != NULL) @@ -640,7 +640,7 @@ emit_avatar_retrieved (TpSvcConnectionInterfaceAvatars *iface, g_array_append_vals (arr, avatar_str->str, avatar_str->len); tp_svc_connection_interface_avatars_emit_avatar_retrieved (iface, contact, sha1, arr, mime_type); - g_array_free (arr, TRUE); + g_array_unref (arr); g_free (sha1); g_string_free (avatar_str, TRUE); } diff --git a/src/conn-contact-info.c b/src/conn-contact-info.c index d67aa0ee4..aaaafc7e2 100644 --- a/src/conn-contact-info.c +++ b/src/conn-contact-info.c @@ -57,7 +57,10 @@ typedef enum { /* in Telepathy, one multi-line value; in XMPP, a sequence of <LINE>s */ FIELD_LABEL, /* same as FIELD_STRUCTURED except the last element may repeat n times */ - FIELD_ORG + FIELD_ORG, + + /* a field we intentionally ignore */ + FIELD_IGNORED } FieldBehaviour; typedef struct { @@ -128,7 +131,8 @@ static VCardField known_fields[] = { /* Things we don't handle: */ - /* PHOTO: we treat it as the avatar instead */ + /* PHOTO is handled by the Avatar code */ + { "PHOTO", NULL, FIELD_IGNORED }, /* KEY: is Base64 (perhaps? hard to tell from the XEP) */ /* LOGO: can be base64 or a URL */ @@ -238,7 +242,7 @@ _create_contact_field_extended (GPtrArray *contact_info, /* The strings in both arrays are borrowed, so we just need to free the * arrays themselves. */ - g_ptr_array_free (field_params, TRUE); + g_ptr_array_unref (field_params); g_free (field_values); } @@ -331,7 +335,7 @@ _parse_vcard (WockyNode *vcard_node, _insert_contact_field (contact_info, "org", NULL, (const gchar * const *) field_values->pdata); - g_ptr_array_free (field_values, TRUE); + g_ptr_array_unref (field_values); } break; @@ -371,6 +375,9 @@ _parse_vcard (WockyNode *vcard_node, } break; + case FIELD_IGNORED: + break; + default: g_assert_not_reached (); } @@ -971,6 +978,9 @@ conn_contact_info_build_supported_fields (GabbleConnection *conn, guint i; TpContactInfoFieldFlags tp_flags = field->tp_flags; + if (field->behaviour == FIELD_IGNORED) + continue; + /* Shorthand to avoid having to put it in the struct initialization: * on XMPP, there is no field that supports arbitrary type-parameters. * Setting Parameters_Mandatory eliminates the special case that an diff --git a/src/conn-mail-notif.c b/src/conn-mail-notif.c index cd7aa1548..9bd5373c1 100644 --- a/src/conn-mail-notif.c +++ b/src/conn-mail-notif.c @@ -73,6 +73,7 @@ struct _GabbleConnectionMailNotificationPrivate guint poll_timeout_id; guint poll_count; GList *inbox_url_requests; /* list of DBusGMethodInvocation */ + gboolean should_set_google_settings; }; @@ -125,7 +126,7 @@ return_from_request_inbox_url (GabbleConnection *conn) if (error == NULL) { g_value_array_free (result); - g_ptr_array_free (empty_array, TRUE); + g_ptr_array_unref (empty_array); } else { @@ -214,7 +215,7 @@ gabble_mail_notification_request_mail_url ( context, result); g_value_array_free (result); - g_ptr_array_free (empty_array, TRUE); + g_ptr_array_unref (empty_array); g_free (url); } else @@ -460,10 +461,28 @@ store_unread_mails (GabbleConnection *conn, conn, priv->unread_count, collector.mails_added, (const char **)mails_removed->pdata); - g_ptr_array_free (collector.mails_added, TRUE); - g_ptr_array_free (mails_removed, TRUE); + g_ptr_array_unref (collector.mails_added); + g_ptr_array_unref (mails_removed); } +static void +set_settings_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GError *error = NULL; + WockyPorter *porter = WOCKY_PORTER (source_object); + WockyStanza *reply = wocky_porter_send_iq_finish (porter, res, &error); + + if (reply == NULL || + wocky_stanza_extract_errors (reply, NULL, &error, NULL, NULL)) + { + DEBUG ("Failed to set google user settings: %s", error->message); + g_error_free (error); + } + + tp_clear_object (&reply); +} static void query_unread_mails_cb (GObject *source_object, @@ -580,6 +599,40 @@ new_mail_handler (WockyPorter *porter, return TRUE; } +/* Make sure google knows we want mail notifications. According to + * Google clients should set 'mailnotifications' to true when needed + * but never to false, for compatibility reasons: + * https://code.google.com/apis/talk/jep_extensions/usersettings.html#3 */ +static void +ensure_google_settings (GabbleConnection *self) +{ + TpBaseConnection *base_conn = TP_BASE_CONNECTION (self); + WockyStanza *query; + WockyPorter *porter; + + if (!self->mail_priv->should_set_google_settings) + return; + + if (base_conn->status != TP_CONNECTION_STATUS_CONNECTED) + return; + + porter = wocky_session_get_porter (self->session); + query = wocky_stanza_build (WOCKY_STANZA_TYPE_IQ, + WOCKY_STANZA_SUB_TYPE_SET, NULL, NULL, + '@', "id", "user-setting-3", + '(', "usersetting", + ':', NS_GOOGLE_SETTING, + '(', "mailnotifications", + '@', "value", "true", + ')', + ')', + NULL); + wocky_porter_send_iq_async (porter, query, NULL, + set_settings_cb, self); + self->mail_priv->should_set_google_settings = FALSE; + + g_object_unref (query); +} static void connection_status_changed (GabbleConnection *conn, @@ -603,10 +656,14 @@ connection_status_changed (GabbleConnection *conn, ')', NULL); + if (conn->features & GABBLE_CONNECTION_FEATURES_GOOGLE_SETTING) + conn->mail_priv->should_set_google_settings = TRUE; + if (conn->mail_priv->interested) { DEBUG ("Someone is already interested in MailNotification"); update_unread_mails (conn); + ensure_google_settings (conn); } } } @@ -621,6 +678,7 @@ mail_clients_interested_cb (GabbleConnection *self, self->mail_priv->interested = TRUE; update_unread_mails (self); + ensure_google_settings (self); } /* called on transition from 1 to 0 interested clients */ @@ -769,7 +827,7 @@ conn_mail_notif_properties_getter (GObject *object, { GPtrArray *mails = get_unread_mails (conn); g_value_set_boxed (value, mails); - g_ptr_array_free (mails, TRUE); + g_ptr_array_unref (mails); } else if (name == prop_quarks[PROP_MAIL_ADDRESS]) { diff --git a/src/conn-olpc.c b/src/conn-olpc.c index 9ab0ddfbb..c47fc7221 100644 --- a/src/conn-olpc.c +++ b/src/conn-olpc.c @@ -263,7 +263,7 @@ get_properties_reply_cb (GObject *source, gabble_svc_olpc_buddy_info_return_from_get_properties (ctx->context, properties); - g_hash_table_destroy (properties); + g_hash_table_unref (properties); out: pubsub_query_ctx_free (ctx); @@ -382,7 +382,7 @@ invitees_quark (void) return q; } -void +static void gabble_connection_connected_olpc (GabbleConnection *conn) { GHashTable *preload = g_object_steal_qdata ((GObject *) conn, @@ -391,7 +391,7 @@ gabble_connection_connected_olpc (GabbleConnection *conn) if (preload != NULL) { transmit_properties (conn, preload, NULL); - g_hash_table_destroy (preload); + g_hash_table_unref (preload); } } @@ -427,7 +427,7 @@ olpc_buddy_info_set_properties (GabbleSvcOLPCBuddyInfo *iface, preload = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) tp_g_value_slice_free); g_object_set_qdata_full ((GObject *) conn, preload_quark, preload, - (GDestroyNotify) g_hash_table_destroy); + (GDestroyNotify) g_hash_table_unref); } tp_g_hash_table_update (preload, properties, @@ -469,7 +469,7 @@ olpc_buddy_props_pep_node_changed (WockyPepService *pep, properties = lm_message_node_extract_properties (node, "property"); gabble_svc_olpc_buddy_info_emit_properties_changed (conn, handle, properties); - g_hash_table_destroy (properties); + g_hash_table_unref (properties); out: tp_handle_unref (contact_repo, handle); } @@ -722,7 +722,7 @@ free_activities (GPtrArray *activities) for (i = 0; i < activities->len; i++) g_boxed_free (GABBLE_STRUCT_TYPE_ACTIVITY, activities->pdata[i]); - g_ptr_array_free (activities, TRUE); + g_ptr_array_unref (activities); } static void @@ -1941,7 +1941,7 @@ olpc_activity_properties_get_properties (GabbleSvcOLPCActivityProperties *iface, properties); if (not_prop) - g_hash_table_destroy (properties); + g_hash_table_unref (properties); } struct _i_hate_g_hash_table_foreach @@ -2072,7 +2072,7 @@ update_activity_properties (GabbleConnection *conn, if (g_hash_table_size (new_properties) == 0) { - g_hash_table_destroy (new_properties); + g_hash_table_unref (new_properties); return; } @@ -2176,6 +2176,8 @@ connection_status_changed_cb (GabbleConnection *conn, DEBUG ("Failed to send PEP activity props reset in response to " "initial connection"); } + + gabble_connection_connected_olpc (conn); } } @@ -2868,7 +2870,7 @@ connection_presence_do_update (GabblePresenceCache *cache, gabble_svc_olpc_buddy_info_emit_activities_changed (conn, handle, empty); - g_ptr_array_free (empty, TRUE); + g_ptr_array_unref (empty); } } @@ -2968,20 +2970,20 @@ unref_activities_in_each_set (TpHandle handle, void conn_olpc_activity_properties_dispose (GabbleConnection *self) { - g_hash_table_destroy (self->olpc_current_act); + g_hash_table_unref (self->olpc_current_act); self->olpc_current_act = NULL; g_hash_table_foreach (self->olpc_pep_activities, (GHFunc) unref_activities_in_each_set, self); - g_hash_table_destroy (self->olpc_pep_activities); + g_hash_table_unref (self->olpc_pep_activities); self->olpc_pep_activities = NULL; g_hash_table_foreach (self->olpc_invited_activities, (GHFunc) unref_activities_in_each_set, self); - g_hash_table_destroy (self->olpc_invited_activities); + g_hash_table_unref (self->olpc_invited_activities); self->olpc_invited_activities = NULL; - g_hash_table_destroy (self->olpc_activities_info); + g_hash_table_unref (self->olpc_activities_info); self->olpc_activities_info = NULL; } @@ -2996,7 +2998,7 @@ find_activity_by_id (GabbleConnection *self, while (g_hash_table_iter_next (&iter, &key, &value)) { GabbleOlpcActivity *activity = GABBLE_OLPC_ACTIVITY (value); - if (strcmp (activity->id, activity_id) == 0) + if (!tp_strdiff (activity->id, activity_id)) return activity; } diff --git a/src/conn-olpc.h b/src/conn-olpc.h index 43be890bc..11958c40e 100644 --- a/src/conn-olpc.h +++ b/src/conn-olpc.h @@ -27,8 +27,6 @@ void olpc_buddy_info_iface_init (gpointer g_iface, gpointer iface_data); -void gabble_connection_connected_olpc (GabbleConnection *conn); - void olpc_activity_properties_iface_init (gpointer g_iface, gpointer iface_data); diff --git a/src/conn-power-saving.c b/src/conn-power-saving.c index 7c47396c0..e1f25ad95 100644 --- a/src/conn-power-saving.c +++ b/src/conn-power-saving.c @@ -139,13 +139,12 @@ conn_power_saving_set_power_saving ( DEBUG ("%sabling presence queueing", enable ? "en" : "dis"); - /* Of course, the Google Talk server doesn't advertise support for - * google:queue. So we use google:roster. We still support the hypothetically - * advertised google:queue just in case google starts using it, or another - * server implementation adopts it. google:queue is described here: - * http://mail.jabber.org/pipermail/summit/2010-February/000528.html */ - if (self->features & (GABBLE_CONNECTION_FEATURES_GOOGLE_QUEUE | - GABBLE_CONNECTION_FEATURES_GOOGLE_ROSTER)) + /* google:queue is loosely described here: + * <http://mail.jabber.org/pipermail/summit/2010-February/000528.html>. Since + * April 2011, it is advertised as a stream feature by the Google Talk + * server; the development version of M-Link, and possibly other servers, + * also implement the protocol and advertise this stream feature. */ + if (self->features & GABBLE_CONNECTION_FEATURES_GOOGLE_QUEUE) { ToggleQueueingContext *queueing_context; queueing_context = g_slice_new0 (ToggleQueueingContext); diff --git a/src/conn-presence.c b/src/conn-presence.c index 0072b09cf..22283b4dc 100644 --- a/src/conn-presence.c +++ b/src/conn-presence.c @@ -83,6 +83,9 @@ struct _GabbleConnectionPresencePrivate { /* The shared status IQ handler */ guint iq_shared_status_cb; + + /* The previous presence when using shared status */ + GabblePresenceId previous_shared_status; }; static const TpPresenceStatusOptionalArgumentSpec gabble_status_arguments[] = { @@ -223,7 +226,7 @@ construct_contact_statuses_cb (GObject *obj, } contact_status = tp_presence_status_new (status, parameters); - g_hash_table_destroy (parameters); + g_hash_table_unref (parameters); g_hash_table_insert (contact_statuses, GUINT_TO_POINTER (handle), contact_status); @@ -251,7 +254,7 @@ conn_presence_emit_presence_update ( contact_statuses = construct_contact_statuses_cb ((GObject *) self, contact_handles, NULL); tp_presence_mixin_emit_presence_update ((GObject *) self, contact_statuses); - g_hash_table_destroy (contact_statuses); + g_hash_table_unref (contact_statuses); } @@ -270,7 +273,7 @@ emit_presences_changed_for_self (GabbleConnection *self) g_array_insert_val (handles, 0, base->self_handle); conn_presence_emit_presence_update (self, handles); - g_array_free (handles, TRUE); + g_array_unref (handles); } static WockyStanza * @@ -317,6 +320,12 @@ build_shared_status_stanza (GabbleConnection *self) return iq; } +static gboolean +is_presence_away (GabblePresenceId status) +{ + return status == GABBLE_PRESENCE_AWAY || status == GABBLE_PRESENCE_XA; +} + static void set_shared_status_cb (GObject *source_object, GAsyncResult *res, @@ -324,18 +333,41 @@ set_shared_status_cb (GObject *source_object, { GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT (user_data); GabbleConnection *self = GABBLE_CONNECTION (source_object); + GabbleConnectionPresencePrivate *priv = self->presence_priv; + GabblePresence *presence = self->self_presence; GError *error = NULL; - if (!conn_util_send_iq_finish (self, res, NULL, &error) || - !conn_presence_signal_own_presence (self, NULL, &error)) + if (!conn_util_send_iq_finish (self, res, NULL, &error)) { g_simple_async_result_set_error (result, CONN_PRESENCE_ERROR, CONN_PRESENCE_ERROR_SET_SHARED_STATUS, "error setting Google shared status: %s", error->message); } + else + { + gabble_muc_factory_broadcast_presence (self->muc_factory); - g_simple_async_result_complete (result); - g_object_unref (result); + if (is_presence_away (priv->previous_shared_status)) + { + /* To use away and xa we need to send a <presence/> to the server, + * but then GTalk also expects us to leave the status using + * <presence/> too. */ + conn_presence_signal_own_presence (self, NULL, &error); + } + else if (priv->previous_shared_status == GABBLE_PRESENCE_HIDDEN && + is_presence_away (presence->status)) + { + /* We sent the shared status change to leave the invisibility, so + * now we can actually go to away / xa. */ + conn_presence_signal_own_presence (self, NULL, &error); + emit_presences_changed_for_self (self); + } + + priv->previous_shared_status = presence->status; + } + + g_simple_async_result_complete (result); + g_object_unref (result); if (error != NULL) g_error_free (error); @@ -349,7 +381,7 @@ insert_presence_to_shared_statuses (GabbleConnection *self) const gchar *show = presence->status == GABBLE_PRESENCE_DND ? "dnd" : "default"; gchar **statuses = g_hash_table_lookup (priv->shared_statuses, show); - if (presence->status_message == NULL) + if (presence->status_message == NULL || is_presence_away (presence->status)) return; if (statuses == NULL) @@ -380,23 +412,52 @@ set_shared_status (GabbleConnection *self, { GabbleConnectionPresencePrivate *priv = self->presence_priv; GabblePresence *presence = self->self_presence; - WockyStanza *iq; g_object_ref (result); - DEBUG ("shared status invisibility is %savailable", - priv->shared_status_compat ? "" : "un"); + /* Away is treated like idleness in GTalk; it's per connection and not + * global. To set the presence as away we use the traditional <presence/>, + * but, if we were invisible, we need to first leave invisibility. */ + if (!is_presence_away (presence->status) || + priv->previous_shared_status == GABBLE_PRESENCE_HIDDEN) + { + WockyStanza *iq; - if (presence->status == GABBLE_PRESENCE_HIDDEN && !priv->shared_status_compat) - presence->status = GABBLE_PRESENCE_DND; + DEBUG ("shared status invisibility is %savailable", + priv->shared_status_compat ? "" : "un"); - insert_presence_to_shared_statuses (self); + if (presence->status == GABBLE_PRESENCE_HIDDEN && !priv->shared_status_compat) + presence->status = GABBLE_PRESENCE_DND; - iq = build_shared_status_stanza (self); + insert_presence_to_shared_statuses (self); - conn_util_send_iq_async (self, iq, NULL, set_shared_status_cb, result); + iq = build_shared_status_stanza (self); - g_object_unref (iq); + conn_util_send_iq_async (self, iq, NULL, set_shared_status_cb, result); + + g_object_unref (iq); + } + else + { + gboolean retval; + GError *error = NULL; + + DEBUG ("not updating shared status as it's not supported for away"); + + retval = conn_presence_signal_own_presence (self, NULL, &error); + if (!retval) + { + g_simple_async_result_set_from_error (result, error); + g_error_free (error); + } + + emit_presences_changed_for_self (self); + + priv->previous_shared_status = presence->status; + + g_simple_async_result_complete_in_idle (result); + g_object_unref (result); + } } static void @@ -758,7 +819,7 @@ store_shared_statuses (GabbleConnection *self, NULL); if (priv->shared_statuses != NULL) - g_hash_table_destroy (priv->shared_statuses); + g_hash_table_unref (priv->shared_statuses); priv->shared_statuses = g_hash_table_new_full ( g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_strfreev); @@ -801,8 +862,17 @@ store_shared_statuses (GabbleConnection *self, } } + /* - status-min-ver == 0 means that at least one resource doesn't support + * Google shared status, so we fallback to "dnd". + * - status-min-ver == 1 means that all the resources support shared + * status, but at least one doesn't support invisibility; we have to fall + * fall back to "dnd". + * - status-miv-ver == 2 means that all the resources support shared status + * with invisibility. + * - any other value means that the other resources will have to fall back + * to version 2 for us. */ priv->shared_status_compat = - g_strcmp0 (min_version, GOOGLE_SHARED_STATUS_VERSION) == 0; + (g_strcmp0 (min_version, "0") != 0 && g_strcmp0 (min_version, "1") != 0); if (invisible) { @@ -821,12 +891,23 @@ store_shared_statuses (GabbleConnection *self, presence_id = GABBLE_PRESENCE_AVAILABLE; } - /* If we are connected, use the new shared status. If not, override with local */ - if (base->status == TP_CONNECTION_STATUS_CONNECTED) - rv = gabble_presence_update (self->self_presence, resource, presence_id, - status_message, prio, NULL, time (NULL)); + if (base->status != TP_CONNECTION_STATUS_CONNECTED) + { + /* Not connected, override with the local status. */ + rv = TRUE; + } + else if (is_presence_away (self->self_presence->status)) + { + /* Away presence is not overridden with remote presence because it's + * per connection. */ + rv = FALSE; + } else - rv = TRUE; + { + /* Update with the remote presence */ + rv = gabble_presence_update (self->self_presence, resource, presence_id, + status_message, prio, NULL, time (NULL)); + } g_free (resource); @@ -998,6 +1079,10 @@ get_shared_status_async (GabbleConnection *self, conn_util_send_iq_async (self, iq, NULL, get_shared_status_cb, result); + /* We cannot use the chat status with GTalk's shared status. */ + if (self->self_presence->status == GABBLE_PRESENCE_CHAT) + self->self_presence->status = GABBLE_PRESENCE_AVAILABLE; + g_object_unref (iq); } @@ -1354,6 +1439,38 @@ privacy_lists_loaded_cb (GObject *source_object, } static void +shared_status_toggle_initial_presence_visibility_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + GabbleConnection *self = GABBLE_CONNECTION (source_object); + GSimpleAsyncResult *external_result = G_SIMPLE_ASYNC_RESULT (user_data); + GError *error = NULL; + + if (!toggle_presence_visibility_finish (self, result, &error)) + { + g_simple_async_result_set_from_error (external_result, error); + g_clear_error (&error); + } + else if (self->self_presence->status != GABBLE_PRESENCE_AWAY && + self->self_presence->status != GABBLE_PRESENCE_XA) + { + /* With shared status we send the normal <presence/> only with away and + * extended away, but for initial status we need to send <presence/> as + * it also contains the caps. */ + if (!conn_presence_signal_own_presence (self, NULL, &error)) + { + g_simple_async_result_set_from_error (external_result, error); + g_error_free (error); + } + } + + g_simple_async_result_complete_in_idle (external_result); + + g_object_unref (external_result); +} + +static void shared_status_setup_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) @@ -1383,7 +1500,8 @@ shared_status_setup_cb (GObject *source_object, g_error_free (error); } - get_existing_privacy_lists_async (self, privacy_lists_loaded_cb, user_data); + toggle_presence_visibility_async (self, + shared_status_toggle_initial_presence_visibility_cb, user_data); } void @@ -1780,6 +1898,12 @@ status_available_cb (GObject *obj, guint status) TpConnectionPresenceType presence_type = gabble_statuses[status].presence_type; + if (base->status != TP_CONNECTION_STATUS_CONNECTED) + { + /* we just don't know yet */ + return TRUE; + } + /* This relies on the fact the first entries in the statuses table * are from gabble_base_statuses. If index to the statuses table is outside * the gabble_base_statuses table, the status is provided by a plugin. */ @@ -1789,36 +1913,35 @@ status_available_cb (GObject *obj, guint status) * lists, so any extra status should be backed by one. If it's not * (or if privacy lists are not supported by the server at all) * by the time we're connected, it's not available. */ - - if (base->status == TP_CONNECTION_STATUS_CONNECTED) + if (priv->privacy_statuses != NULL && + g_hash_table_lookup (priv->privacy_statuses, + gabble_statuses[status].name)) { - if (priv->privacy_statuses != NULL && - g_hash_table_lookup (priv->privacy_statuses, - gabble_statuses[status].name)) - { - return TRUE; - } - else - { - return FALSE; - } + return TRUE; } else { - /* we just don't know yet */ - return TRUE; + return FALSE; } } - /* If we've gone online and found that the server doesn't support invisible, - * reject it. - */ - if (base->status == TP_CONNECTION_STATUS_CONNECTED && - presence_type == TP_CONNECTION_PRESENCE_TYPE_HIDDEN && + if (presence_type == TP_CONNECTION_PRESENCE_TYPE_HIDDEN && priv->invisibility_method == INVISIBILITY_METHOD_NONE) - return FALSE; + { + /* If we've gone online and found that the server doesn't support + * invisible, reject it. */ + return FALSE; + } + else if (status == GABBLE_PRESENCE_CHAT && + priv->shared_statuses != NULL) + { + /* We cannot use the chat status with GTalk's shared status. */ + return FALSE; + } else - return TRUE; + { + return TRUE; + } } GabblePresenceId @@ -1853,6 +1976,7 @@ void conn_presence_init (GabbleConnection *conn) { conn->presence_priv = g_slice_new0 (GabbleConnectionPresencePrivate); + conn->presence_priv->previous_shared_status = GABBLE_PRESENCE_UNKNOWN; g_signal_connect (conn->presence_cache, "presences-updated", G_CALLBACK (connection_presences_updated_cb), conn); @@ -1897,22 +2021,17 @@ conn_presence_finalize (GabbleConnection *conn) g_free (priv->invisible_list_name); if (priv->privacy_statuses != NULL) - g_hash_table_destroy (priv->privacy_statuses); + g_hash_table_unref (priv->privacy_statuses); if (priv->shared_statuses != NULL) - g_hash_table_destroy (priv->shared_statuses); + g_hash_table_unref (priv->shared_statuses); if (priv->iq_list_push_cb != NULL) lm_message_handler_unref (priv->iq_list_push_cb); - tp_presence_mixin_finalize ((GObject *) conn); -} - + g_slice_free (GabbleConnectionPresencePrivate, priv); -void -conn_presence_iface_init (gpointer g_iface, gpointer iface_data) -{ - tp_presence_mixin_iface_init (g_iface, iface_data); + tp_presence_mixin_finalize ((GObject *) conn); } static void diff --git a/src/connection.c b/src/connection.c index c676b828b..312d33952 100644 --- a/src/connection.c +++ b/src/connection.c @@ -18,14 +18,12 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#include "config.h" #include "connection.h" #include "gabble.h" +#include <stdio.h> #include <string.h> -#define DBUS_API_SUBJECT_TO_CHANGE - #include <dbus/dbus-glib.h> #include <dbus/dbus-glib-lowlevel.h> #include <glib-object.h> @@ -34,7 +32,9 @@ #include <wocky/wocky-disco-identity.h> #include <wocky/wocky-tls-handler.h> #include <wocky/wocky-ping.h> +#include <wocky/wocky-utils.h> #include <wocky/wocky-xmpp-error.h> +#include <wocky/wocky-data-form.h> #include <telepathy-glib/channel-manager.h> #include <telepathy-glib/dbus.h> #include <telepathy-glib/enums.h> @@ -50,8 +50,8 @@ #include <gabble/error.h> #include "bytestream-factory.h" -#include "capabilities.h" -#include "caps-channel-manager.h" +#include "gabble/capabilities.h" +#include "gabble/caps-channel-manager.h" #include "caps-hash.h" #include "auth-manager.h" #include "conn-aliasing.h" @@ -86,6 +86,7 @@ #include "util.h" #include "vcard-manager.h" #include "conn-util.h" +#include "conn-addressing.h" static guint disco_reply_timeout = 5; @@ -117,10 +118,10 @@ G_DEFINE_TYPE_WITH_CODE(GabbleConnection, tp_base_contact_list_mixin_list_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACT_GROUPS, tp_base_contact_list_mixin_groups_iface_init); + G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACT_BLOCKING, + tp_base_contact_list_mixin_blocking_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_SIMPLE_PRESENCE, tp_presence_mixin_simple_presence_iface_init); - G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_PRESENCE, - conn_presence_iface_init); G_IMPLEMENT_INTERFACE (GABBLE_TYPE_SVC_CONNECTION_INTERFACE_GABBLE_DECLOAK, conn_decloak_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_LOCATION, @@ -140,6 +141,8 @@ G_DEFINE_TYPE_WITH_CODE(GabbleConnection, conn_client_types_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_POWER_SAVING, conn_power_saving_iface_init); + G_IMPLEMENT_INTERFACE (GABBLE_TYPE_SVC_CONNECTION_INTERFACE_ADDRESSING, + conn_addressing_iface_init); ) /* properties */ @@ -237,6 +240,10 @@ struct _GabbleConnectionPrivate /* serial number of current advertised caps */ guint caps_serial; + /* Last activity time for XEP-0012 purposes, where "activity" is defined to + * mean "sending a message". + */ + time_t last_activity_time; /* capabilities from various sources: */ /* subscriptions on behalf of the Connection, like PEP "+notify" @@ -256,6 +263,10 @@ struct _GabbleConnectionPrivate /* the union of the above */ GabbleCapabilitySet *all_caps; + /* data forms provided via UpdateCapabilities() + * gchar * (client name) => GPtrArray<owned WockyDataForm> */ + GHashTable *client_data_forms; + /* auth manager */ GabbleAuthManager *auth_manager; @@ -309,8 +320,8 @@ _gabble_connection_create_channel_managers (TpBaseConnection *conn) GPtrArray *tmp; self->roster = gabble_roster_new (self); - g_signal_connect (self->roster, "nickname-update", G_CALLBACK - (gabble_conn_aliasing_nickname_updated), self); + g_signal_connect (self->roster, "nicknames-update", G_CALLBACK + (gabble_conn_aliasing_nicknames_updated), self); g_ptr_array_add (channel_managers, self->roster); self->priv->im_factory = g_object_new (GABBLE_TYPE_IM_FACTORY, @@ -354,8 +365,10 @@ _gabble_connection_create_channel_managers (TpBaseConnection *conn) "connection", self, NULL)); +#ifdef ENABLE_FILE_TRANSFER self->ft_manager = gabble_ft_manager_new (self); g_ptr_array_add (channel_managers, self->ft_manager); +#endif /* plugin channel managers */ loader = gabble_plugin_loader_dup (); @@ -363,7 +376,7 @@ _gabble_connection_create_channel_managers (TpBaseConnection *conn) g_object_unref (loader); g_ptr_array_foreach (tmp, add_to_array, channel_managers); - g_ptr_array_free (tmp, TRUE); + g_ptr_array_unref (tmp); return channel_managers; } @@ -412,6 +425,7 @@ gabble_connection_constructor (GType type, conn_sidecars_init (self); conn_mail_notif_init (self); conn_client_types_init (self); + conn_addressing_init (self); tp_contacts_mixin_add_contact_attributes_iface (G_OBJECT (self), TP_IFACE_CONNECTION_INTERFACE_CAPABILITIES, @@ -442,6 +456,9 @@ gabble_connection_constructor (GType type, priv->client_caps = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) gabble_capability_set_free); + priv->client_data_forms = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, (GDestroyNotify) g_ptr_array_unref); + /* Historically, the optional Jingle transports were in our initial * presence, but could be removed by AdvertiseCapabilities(). Emulate * that here for now. */ @@ -519,6 +536,7 @@ gabble_connection_init (GabbleConnection *self) self->lmconn = lm_connection_new (); priv->caps_serial = 1; + priv->last_activity_time = time (NULL); priv->port = 5222; gabble_capabilities_init (self); @@ -568,7 +586,11 @@ gabble_connection_get_property (GObject *object, g_value_set_string (value, priv->resource); break; case PROP_PRIORITY: +#if GLIB_CHECK_VERSION (2, 31, 0) + g_value_set_schar (value, priv->priority); +#else g_value_set_char (value, priv->priority); +#endif break; case PROP_HTTPS_PROXY_SERVER: g_value_set_string (value, priv->https_proxy_server); @@ -690,7 +712,11 @@ gabble_connection_set_property (GObject *object, } break; case PROP_PRIORITY: +#if GLIB_CHECK_VERSION (2, 31, 0) + priv->priority = g_value_get_schar (value); +#else priv->priority = g_value_get_char (value); +#endif break; case PROP_HTTPS_PROXY_SERVER: g_free (priv->https_proxy_server); @@ -801,14 +827,6 @@ _gabble_connection_create_handle_repos (TpBaseConnection *conn, conn); } -static void -base_connected_cb (TpBaseConnection *base_conn) -{ - GabbleConnection *conn = GABBLE_CONNECTION (base_conn); - - gabble_connection_connected_olpc (conn); -} - #define TWICE(x) (x), (x) static const gchar *implemented_interfaces[] = { @@ -834,6 +852,7 @@ static const gchar *implemented_interfaces[] = { GABBLE_IFACE_CONNECTION_INTERFACE_GABBLE_DECLOAK, GABBLE_IFACE_CONNECTION_FUTURE, TP_IFACE_CONNECTION_INTERFACE_CLIENT_TYPES, + GABBLE_IFACE_CONNECTION_INTERFACE_ADDRESSING, NULL }; static const gchar **interfaces_always_present = implemented_interfaces + 3; @@ -926,7 +945,6 @@ gabble_connection_class_init (GabbleConnectionClass *gabble_connection_class) parent_class->create_channel_factories = NULL; parent_class->create_channel_managers = _gabble_connection_create_channel_managers; - parent_class->connected = base_connected_cb; parent_class->shut_down = connection_shut_down; parent_class->start_connecting = _gabble_connection_connect; parent_class->interfaces_always_present = interfaces_always_present; @@ -1140,7 +1158,7 @@ gabble_connection_class_init (GabbleConnectionClass *gabble_connection_class) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property ( - object_class, PROP_DECLOAK_AUTOMATICALLY, + object_class, PROP_POWER_SAVING, g_param_spec_boolean ( "power-saving", "Power saving active?", "Queue remote presence updates server-side for less network chatter", @@ -1207,8 +1225,8 @@ gabble_connection_dispose (GObject *object) conn_olpc_activity_properties_dispose (self); - g_hash_table_destroy (self->avatar_requests); - g_hash_table_destroy (self->vcard_requests); + g_hash_table_unref (self->avatar_requests); + g_hash_table_unref (self->vcard_requests); conn_presence_dispose (self); @@ -1221,13 +1239,15 @@ gabble_connection_dispose (GObject *object) priv->porter = NULL; tp_clear_pointer (&self->lmconn, lm_connection_unref); - g_hash_table_destroy (priv->client_caps); + g_hash_table_unref (priv->client_caps); gabble_capability_set_free (priv->all_caps); gabble_capability_set_free (priv->notify_caps); gabble_capability_set_free (priv->legacy_caps); gabble_capability_set_free (priv->sidecar_caps); gabble_capability_set_free (priv->bonus_caps); + g_hash_table_unref (priv->client_data_forms); + if (priv->disconnect_timer != 0) { g_source_remove (priv->disconnect_timer); @@ -1309,7 +1329,7 @@ _gabble_connection_set_properties_from_account (GabbleConnection *conn, username = server = resource = NULL; result = TRUE; - if (!gabble_decode_jid (account, &username, &server, &resource)) + if (!wocky_decode_jid (account, &username, &server, &resource)) { g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, "unable to extract JID from account name"); @@ -1368,6 +1388,14 @@ WockyPorter *gabble_connection_dup_porter (GabbleConnection *conn) return NULL; } +WockySession * +gabble_connection_get_session (GabbleConnection *connection) +{ + g_return_val_if_fail (GABBLE_IS_CONNECTION (connection), NULL); + + return connection->session; +} + /** * _gabble_connection_send * @@ -1389,6 +1417,18 @@ _gabble_connection_send (GabbleConnection *conn, LmMessage *msg, GError **error) return TRUE; } +void +gabble_connection_update_last_use (GabbleConnection *conn) +{ + conn->priv->last_activity_time = time (NULL); +} + +static gdouble +gabble_connection_get_last_use (GabbleConnection *conn) +{ + return difftime (time (NULL), conn->priv->last_activity_time); +} + typedef struct { GabbleConnectionMsgReplyFunc reply_func; @@ -1837,7 +1877,10 @@ connector_connected (GabbleConnection *self, self->session = wocky_session_new_with_connection (conn, jid); priv->porter = wocky_session_get_porter (self->session); - priv->pinger = wocky_ping_new (priv->porter, priv->keepalive_interval); + + g_assert (WOCKY_IS_C2S_PORTER (priv->porter)); + priv->pinger = wocky_ping_new (WOCKY_C2S_PORTER (priv->porter), + priv->keepalive_interval); g_signal_connect (priv->porter, "remote-closed", G_CALLBACK (remote_closed_cb), self); @@ -1980,6 +2023,41 @@ connector_register_cb (GObject *source, g_free (jid); } +static gboolean +connection_iq_last_cb ( + WockyPorter *porter, + WockyStanza *stanza, + gpointer user_data) +{ + GabbleConnection *self = GABBLE_CONNECTION (user_data); + const gchar *from = wocky_stanza_get_from (stanza); + /* Aside from 21 being an appropriate number, 2 ^ 64 is 20 digits long. */ + char seconds[21]; + + /* Check if the peer, if any, is authorized to receive our presence. */ + if (from != NULL) + { + TpHandleRepoIface *contact_repo = tp_base_connection_get_handles ( + (TpBaseConnection *) self, TP_HANDLE_TYPE_CONTACT); + TpHandle handle = tp_handle_lookup (contact_repo, from, NULL, NULL); + + /* If there's no handle for them, they're certainly not on the roster. */ + if (handle == 0 || + !gabble_roster_handle_gets_presence_from_us (self->roster, handle)) + { + wocky_porter_send_iq_error (porter, stanza, + WOCKY_XMPP_ERROR_FORBIDDEN, NULL); + return TRUE; + } + } + + sprintf (seconds, "%.0f", gabble_connection_get_last_use (self)); + wocky_porter_acknowledge_iq (porter, stanza, + '(', "query", ':', NS_LAST, '@', "seconds", seconds, ')', + NULL); + return TRUE; +} + static void connect_iq_callbacks (GabbleConnection *conn) { @@ -1997,6 +2075,12 @@ connect_iq_callbacks (GabbleConnection *conn) iq_version_cb, conn, '(', "query", ':', NS_VERSION, ')', NULL); + wocky_porter_register_handler_from_anyone (priv->porter, + WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_GET, + WOCKY_PORTER_HANDLER_PRIORITY_NORMAL, + connection_iq_last_cb, conn, + '(', "query", ':', NS_LAST, ')', NULL); + /* FIXME: the porter should do this for us. */ wocky_porter_register_handler_from_anyone (priv->porter, WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_NONE, @@ -2253,7 +2337,8 @@ gabble_connection_fill_in_caps (GabbleConnection *self, /* Ensure this set of capabilities is in the cache. */ gabble_presence_cache_add_own_caps (self->presence_cache, caps_hash, - gabble_presence_peek_caps (presence), NULL); + gabble_presence_peek_caps (presence), NULL, + gabble_presence_peek_data_forms (presence)); /* XEP-0115 deprecates 'ext' feature bundles. But we still need * BUNDLE_VOICE_V1 it for backward-compatibility with Gabble 0.2 */ @@ -2270,8 +2355,10 @@ gabble_connection_fill_in_caps (GabbleConnection *self, if (voice_v1) g_string_append (ext, " " BUNDLE_VOICE_V1); - if (video_v1) + if (video_v1) { g_string_append (ext, " " BUNDLE_VIDEO_V1); + g_string_append (ext, " " BUNDLE_CAMERA_V1); + } wocky_node_set_attribute (node, "ext", ext->str); g_string_free (ext, TRUE); @@ -2349,10 +2436,13 @@ gabble_connection_refresh_capabilities (GabbleConnection *self, GHashTableIter iter; gpointer k, v; GabbleCapabilitySet *save_set; + GPtrArray *data_forms; save_set = self->priv->all_caps; self->priv->all_caps = gabble_capability_set_new (); + data_forms = g_ptr_array_new (); + gabble_capability_set_update (self->priv->all_caps, gabble_capabilities_get_fixed_caps ()); gabble_capability_set_update (self->priv->all_caps, self->priv->notify_caps); @@ -2360,6 +2450,7 @@ gabble_connection_refresh_capabilities (GabbleConnection *self, gabble_capability_set_update (self->priv->all_caps, self->priv->sidecar_caps); gabble_capability_set_update (self->priv->all_caps, self->priv->bonus_caps); + /* first, normal caps */ g_hash_table_iter_init (&iter, self->priv->client_caps); while (g_hash_table_iter_next (&iter, &k, &v)) @@ -2375,13 +2466,22 @@ gabble_connection_refresh_capabilities (GabbleConnection *self, gabble_capability_set_update (self->priv->all_caps, v); } + /* now data forms */ + g_hash_table_iter_init (&iter, self->priv->client_data_forms); + + /* just borrow the ref, data_forms doesn't have a free func */ + while (g_hash_table_iter_next (&iter, &k, &v)) + tp_g_ptr_array_extend (data_forms, v); + if (self->self_presence != NULL) gabble_presence_set_capabilities (self->self_presence, - self->priv->resource, self->priv->all_caps, self->priv->caps_serial++); + self->priv->resource, self->priv->all_caps, data_forms, + self->priv->caps_serial++); if (gabble_capability_set_equals (self->priv->all_caps, save_set)) { gabble_capability_set_free (save_set); + g_ptr_array_unref (data_forms); DEBUG ("nothing to do"); return FALSE; } @@ -2390,6 +2490,7 @@ gabble_connection_refresh_capabilities (GabbleConnection *self, if (base->status != TP_CONNECTION_STATUS_CONNECTED) { gabble_capability_set_free (save_set); + g_ptr_array_unref (data_forms); DEBUG ("not emitting self-presence stanza: not connected yet"); return FALSE; } @@ -2397,6 +2498,7 @@ gabble_connection_refresh_capabilities (GabbleConnection *self, if (!conn_presence_signal_own_presence (self, NULL, &error)) { gabble_capability_set_free (save_set); + g_ptr_array_unref (data_forms); DEBUG ("error sending presence: %s", error->message); g_error_free (error); return FALSE; @@ -2407,6 +2509,8 @@ gabble_connection_refresh_capabilities (GabbleConnection *self, else *old_out = save_set; + g_ptr_array_unref (data_forms); + return TRUE; } @@ -2468,6 +2572,7 @@ iq_disco_cb (WockyPorter *porter, const GabbleCapabilityInfo *info = NULL; const GabbleCapabilitySet *features = NULL; const GPtrArray *identities = NULL; + const GPtrArray *data_forms = NULL; /* query's existence is checked by WockyPorter before this function is called */ query = wocky_node_get_child (wocky_stanza_get_top_node (stanza), "query"); @@ -2497,19 +2602,25 @@ iq_disco_cb (WockyPorter *porter, wocky_node_set_attribute (result_query, "node", node); if (node == NULL) - features = gabble_presence_peek_caps (self->self_presence); - /* If node is not NULL, it can be either a caps bundle as defined in the - * legacy XEP-0115 version 1.3 or an hash as defined in XEP-0115 version - * 1.5. Let's see if it's a verification string we've told the cache about. - */ + { + features = gabble_presence_peek_caps (self->self_presence); + data_forms = gabble_presence_peek_data_forms (self->self_presence); + } else - info = gabble_presence_cache_peek_own_caps (self->presence_cache, - suffix); + { + /* If node is not NULL, it can be either a caps bundle as defined in the + * legacy XEP-0115 version 1.3 or an hash as defined in XEP-0115 version + * 1.5. Let's see if it's a verification string we've told the cache about. + */ + info = gabble_presence_cache_peek_own_caps (self->presence_cache, + suffix); + } if (info) { features = info->cap_set; identities = info->identities; + data_forms = info->data_forms; } if (identities && identities->len != 0) @@ -2548,6 +2659,18 @@ iq_disco_cb (WockyPorter *porter, features = gabble_capabilities_get_bundle_video_v1 (); } + if (data_forms != NULL) + { + guint i; + + for (i = 0; i < data_forms->len; i++) + { + WockyDataForm *form = g_ptr_array_index (data_forms, i); + + wocky_data_form_add_to_node (form, result_query); + } + } + if (features == NULL && tp_strdiff (suffix, BUNDLE_PMUC_V1)) { wocky_porter_send_iq_error (porter, stanza, @@ -2633,6 +2756,13 @@ set_status_to_connected (GabbleConnection *conn) { TpBaseConnection *base = (TpBaseConnection *) conn; + if (base->status == TP_CONNECTION_STATUS_DISCONNECTED) + { + /* We already failed to connect, but at the time an async thing was + * still pending, and now it has finished. Do nothing special. */ + return; + } + if (conn->features & GABBLE_CONNECTION_FEATURES_PEP) { const gchar *ifaces[] = { GABBLE_IFACE_OLPC_BUDDY_INFO, @@ -2650,6 +2780,14 @@ set_status_to_connected (GabbleConnection *conn) tp_base_connection_add_interfaces ((TpBaseConnection *) conn, ifaces); } + if (tp_base_contact_list_can_block (gabble_connection_get_contact_list (conn))) + { + const gchar *ifaces[] = + { TP_IFACE_CONNECTION_INTERFACE_CONTACT_BLOCKING, NULL }; + + tp_base_connection_add_interfaces ((TpBaseConnection *) conn, ifaces); + } + /* go go gadget on-line */ tp_base_connection_change_status (base, TP_CONNECTION_STATUS_CONNECTED, TP_CONNECTION_STATUS_REASON_REQUESTED); @@ -2737,6 +2875,8 @@ connection_disco_cb (GabbleDisco *disco, conn->features |= GABBLE_CONNECTION_FEATURES_GOOGLE_SHARED_STATUS; else if (0 == strcmp (var, NS_GOOGLE_QUEUE)) conn->features |= GABBLE_CONNECTION_FEATURES_GOOGLE_QUEUE; + else if (0 == strcmp (var, NS_GOOGLE_SETTING)) + conn->features |= GABBLE_CONNECTION_FEATURES_GOOGLE_SETTING; } } @@ -2897,7 +3037,7 @@ _emit_capabilities_changed (GabbleConnection *conn, g_boxed_free (TP_STRUCT_TYPE_CAPABILITY_CHANGE, g_ptr_array_index (caps_arr, i)); } - g_ptr_array_free (caps_arr, TRUE); + g_ptr_array_unref (caps_arr); /* o.f.T.C.ContactCapabilities */ caps_arr = gabble_connection_build_contact_caps (conn, handle, new_set); @@ -2909,14 +3049,24 @@ _emit_capabilities_changed (GabbleConnection *conn, tp_svc_connection_interface_contact_capabilities_emit_contact_capabilities_changed ( conn, hash); - g_hash_table_destroy (hash); + g_hash_table_unref (hash); } -/** +static const GabbleCapabilitySet * +empty_caps_set (void) +{ + static GabbleCapabilitySet *empty = NULL; + + if (G_UNLIKELY (empty == NULL)) + empty = gabble_capability_set_new (); + + return empty; +} + +/* * gabble_connection_get_handle_contact_capabilities: * - * Returns: a set of channel classes representing @handle's capabilities, or - * %NULL if unknown. + * Returns: an array of channel classes representing @handle's capabilities */ static GPtrArray * gabble_connection_get_handle_contact_capabilities ( @@ -2926,7 +3076,6 @@ gabble_connection_get_handle_contact_capabilities ( TpBaseConnection *base_conn = TP_BASE_CONNECTION (self); GabblePresence *p; const GabbleCapabilitySet *caps; - GPtrArray *arr; if (handle == base_conn->self_handle) p = self->self_presence; @@ -2934,20 +3083,11 @@ gabble_connection_get_handle_contact_capabilities ( p = gabble_presence_cache_get (self->presence_cache, handle); if (p == NULL) - { - DEBUG ("don't know %u's presence; assuming text chat caps.", handle); - - arr = g_ptr_array_new (); - gabble_caps_channel_manager_get_contact_capabilities ( - GABBLE_CAPS_CHANNEL_MANAGER (self->priv->im_factory), - handle, NULL, arr); - - return arr; - } + caps = empty_caps_set (); + else + caps = gabble_presence_peek_caps (p); - caps = gabble_presence_peek_caps (p); - arr = gabble_connection_build_contact_caps (self, handle, caps); - return arr; + return gabble_connection_build_contact_caps (self, handle, caps); } static void @@ -3078,7 +3218,112 @@ gabble_connection_advertise_capabilities (TpSvcConnectionInterfaceCapabilities * context, ret); g_ptr_array_foreach (ret, (GFunc) g_value_array_free, NULL); - g_ptr_array_free (ret, TRUE); + g_ptr_array_unref (ret); +} + +static const gchar * +get_form_type (WockyDataForm *form) +{ + WockyDataFormField *field; + + field = g_hash_table_lookup (form->fields, + "FORM_TYPE"); + g_assert (field != NULL); + + return field->raw_value_contents[0]; +} + +static const gchar * +check_form_is_unique (GabbleConnection *self, + const gchar *form_type) +{ + GHashTableIter iter; + gpointer key, value; + + g_hash_table_iter_init (&iter, self->priv->client_data_forms); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + const gchar *manager = key; + GPtrArray *data_forms = value; + guint i; + + if (data_forms == NULL || data_forms->len == 0) + continue; + + for (i = 0; i < data_forms->len; i++) + { + WockyDataForm *form = g_ptr_array_index (data_forms, i); + + if (!tp_strdiff (get_form_type (form), form_type)) + return manager; + } + } + + return NULL; +} + +static gboolean +check_data_form_in_list (GPtrArray *array, + const gchar *form_type) +{ + guint i; + + for (i = 0; i < array->len; i++) + { + WockyDataForm *form = g_ptr_array_index (array, i); + + if (!tp_strdiff (get_form_type (form), form_type)) + return TRUE; + } + + return FALSE; +} + +static gboolean +check_data_form_is_valid (GabbleConnection *self, + WockyDataForm *form, + GPtrArray *existing_forms) +{ + WockyDataFormField *field; + const gchar *form_type, *other_client; + + /* We want rid of forms with no FORM_TYPE quickly. */ + field = g_hash_table_lookup (form->fields, "FORM_TYPE"); + + if (field == NULL || tp_str_empty (field->raw_value_contents[0])) + { + WARNING ("data form with no FORM_TYPE field; ignoring"); + return FALSE; + } + + form_type = field->raw_value_contents[0]; + + /* We'll get warnings (potentially bad) if two clients cause a + * channel manager to create two data forms with the same FORM_TYPE, + * or if multiple channel managers create two data forms with the + * same FORM_TYPE, for the same client. This is probably not a + * problem in practice given hardly anyone uses data forms in entity + * capabilities anyway. */ + + /* We don't want the same data form from another caps channel + * manager for this client either */ + if (check_data_form_in_list (existing_forms, form_type)) + { + WARNING ("duplicate data form '%s' from another channel " + "manager; ignoring", form_type); + return FALSE; + } + + /* And lastly we don't want a form we're already advertising. */ + other_client = check_form_is_unique (self, form_type); + if (other_client != NULL) + { + WARNING ("Data form '%s' already provided by client " + "%s; ignoring", form_type, other_client); + return FALSE; + } + + return TRUE; } /** @@ -3096,13 +3341,11 @@ gabble_connection_update_capabilities ( { GabbleConnection *self = GABBLE_CONNECTION (iface); TpBaseConnection *base = (TpBaseConnection *) self; - GabbleCapabilitySet *old_caps; + GabbleCapabilitySet *old_caps = NULL; TpChannelManagerIter iter; TpChannelManager *manager; guint i; - old_caps = gabble_capability_set_copy (self->priv->all_caps); - /* Now that someone has told us our *actual* capabilities, we can stop * advertising spurious caps in initial presence */ gabble_capability_set_clear (self->priv->bonus_caps); @@ -3127,10 +3370,12 @@ gabble_connection_update_capabilities ( const GPtrArray *filters = g_value_get_boxed (va->values + 1); const gchar * const * cap_tokens = g_value_get_boxed (va->values + 2); GabbleCapabilitySet *cap_set; + GPtrArray *data_forms; g_hash_table_remove (self->priv->client_caps, client_name); + g_hash_table_remove (self->priv->client_data_forms, client_name); - if ((cap_tokens == NULL || cap_tokens[0] != NULL) && + if ((cap_tokens == NULL || cap_tokens[0] == NULL) && filters->len == 0) { /* no capabilities */ @@ -3139,6 +3384,8 @@ gabble_connection_update_capabilities ( } cap_set = gabble_capability_set_new (); + data_forms = g_ptr_array_new_with_free_func ( + (GDestroyNotify) g_object_unref); tp_base_connection_channel_manager_iter_init (&iter, base); @@ -3146,18 +3393,30 @@ gabble_connection_update_capabilities ( { if (GABBLE_IS_CAPS_CHANNEL_MANAGER (manager)) { + GPtrArray *forms = g_ptr_array_new_with_free_func ( + (GDestroyNotify) g_object_unref); + guint j; + + /* First, represent the client... */ gabble_caps_channel_manager_represent_client ( GABBLE_CAPS_CHANNEL_MANAGER (manager), client_name, filters, - cap_tokens, cap_set); + cap_tokens, cap_set, forms); + + /* Now check the forms... */ + for (j = 0; j < forms->len; j++) + { + WockyDataForm *form = g_ptr_array_index (forms, j); + + if (check_data_form_is_valid (self, form, data_forms)) + g_ptr_array_add (data_forms, g_object_ref (form)); + } + + g_ptr_array_unref (forms); } } - if (gabble_capability_set_size (cap_set) == 0) - { - DEBUG ("client %s has no interesting capabilities", client_name); - gabble_capability_set_free (cap_set); - } - else + /* first deal with normal caps */ + if (gabble_capability_set_size (cap_set) > 0) { if (DEBUGGING) { @@ -3170,6 +3429,33 @@ gabble_connection_update_capabilities ( g_hash_table_insert (self->priv->client_caps, g_strdup (client_name), cap_set); } + else + { + DEBUG ("client %s has no interesting capabilities", client_name); + gabble_capability_set_free (cap_set); + } + + /* now data forms */ + if (data_forms->len > 0) + { + guint j; + + /* now print out what forms we have here */ + DEBUG ("client %s contributes %u data form%s:", client_name, + data_forms->len, + data_forms->len > 1 ? "s" : ""); + + for (j = 0; j < data_forms->len; j++) + DEBUG (" - %s", get_form_type (g_ptr_array_index (data_forms, j))); + + g_hash_table_insert (self->priv->client_data_forms, + g_strdup (client_name), data_forms); + } + else + { + DEBUG ("client %s has no interesting data forms", client_name); + g_ptr_array_unref (data_forms); + } } if (gabble_connection_refresh_capabilities (self, &old_caps)) @@ -3300,7 +3586,7 @@ conn_capabilities_fill_contact_attributes (GObject *obj, } if (array != NULL) - g_ptr_array_free (array, TRUE); + g_ptr_array_unref (array); } static void @@ -3313,21 +3599,14 @@ conn_contact_capabilities_fill_contact_attributes (GObject *obj, for (i = 0; i < contacts->len; i++) { TpHandle handle = g_array_index (contacts, TpHandle, i); - GPtrArray *array; - - array = gabble_connection_get_handle_contact_capabilities (self, handle); - - if (array != NULL) - { - GValue *val = tp_g_value_slice_new ( - TP_ARRAY_TYPE_REQUESTABLE_CHANNEL_CLASS_LIST); + GValue *val = tp_g_value_slice_new_take_boxed ( + TP_ARRAY_TYPE_REQUESTABLE_CHANNEL_CLASS_LIST, + gabble_connection_get_handle_contact_capabilities (self, handle)); - g_value_take_boxed (val, array); - tp_contacts_mixin_set_contact_attribute (attributes_hash, - handle, - TP_IFACE_CONNECTION_INTERFACE_CONTACT_CAPABILITIES"/capabilities", - val); - } + tp_contacts_mixin_set_contact_attribute (attributes_hash, + handle, + TP_IFACE_CONNECTION_INTERFACE_CONTACT_CAPABILITIES"/capabilities", + val); } } @@ -3376,7 +3655,7 @@ gabble_connection_get_capabilities (TpSvcConnectionInterfaceCapabilities *iface, g_value_array_free (g_ptr_array_index (ret, i)); } - g_ptr_array_free (ret, TRUE); + g_ptr_array_unref (ret); } /** @@ -3414,19 +3693,17 @@ gabble_connection_get_contact_capabilities ( for (i = 0; i < handles->len; i++) { - GPtrArray *arr; TpHandle handle = g_array_index (handles, TpHandle, i); + GPtrArray *arr; arr = gabble_connection_get_handle_contact_capabilities (self, handle); - - if (arr != NULL) - g_hash_table_insert (ret, GUINT_TO_POINTER (handle), arr); + g_hash_table_insert (ret, GUINT_TO_POINTER (handle), arr); } tp_svc_connection_interface_contact_capabilities_return_from_get_contact_capabilities (context, ret); - g_hash_table_destroy (ret); + g_hash_table_unref (ret); } @@ -3500,7 +3777,7 @@ gabble_connection_send_presence (GabbleConnection *conn, lm_message_node_add_own_nick ( wocky_stanza_get_top_node (message), conn); - if (!CHECK_STR_EMPTY(status)) + if (!tp_str_empty (status)) wocky_node_add_child_with_content ( wocky_stanza_get_top_node (message), "status", status); @@ -3590,9 +3867,10 @@ gabble_connection_update_sidecar_capabilities (GabbleConnection *self, /* identities is actually a WockyDiscoIdentityArray */ gchar * -gabble_connection_add_sidecar_own_caps (GabbleConnection *self, +gabble_connection_add_sidecar_own_caps_full (GabbleConnection *self, const GabbleCapabilitySet *cap_set, - const GPtrArray *identities) + const GPtrArray *identities, + GPtrArray *data_forms) { GPtrArray *identities_copy = ((identities == NULL) ? wocky_disco_identity_array_new () : @@ -3605,12 +3883,107 @@ gabble_connection_add_sidecar_own_caps (GabbleConnection *self, wocky_disco_identity_new ("client", CLIENT_TYPE, NULL, PACKAGE_STRING)); - ver = gabble_caps_hash_compute (cap_set, identities_copy); + ver = gabble_caps_hash_compute_full (cap_set, identities_copy, data_forms); gabble_presence_cache_add_own_caps (self->presence_cache, ver, - cap_set, identities_copy); + cap_set, identities_copy, data_forms); wocky_disco_identity_array_free (identities_copy); return ver; } + +gchar * +gabble_connection_add_sidecar_own_caps (GabbleConnection *self, + const GabbleCapabilitySet *cap_set, + const GPtrArray *identities) +{ + return gabble_connection_add_sidecar_own_caps_full (self, cap_set, + identities, NULL); +} + +const gchar * +gabble_connection_get_jid_for_caps (GabbleConnection *conn, + WockyXep0115Capabilities *caps) +{ + TpHandle handle; + TpBaseConnection *base; + TpHandleRepoIface *contact_handles; + + g_return_val_if_fail (GABBLE_IS_CONNECTION (conn), NULL); + g_return_val_if_fail (GABBLE_IS_PRESENCE (caps), NULL); + + base = (TpBaseConnection *) conn; + + if ((GabblePresence *) caps == conn->self_presence) + { + handle = tp_base_connection_get_self_handle (base); + } + else + { + handle = gabble_presence_cache_get_handle (conn->presence_cache, + (GabblePresence *) caps); + } + + contact_handles = tp_base_connection_get_handles (base, + TP_HANDLE_TYPE_CONTACT); + + return tp_handle_inspect (contact_handles, handle); +} + +const gchar * +gabble_connection_pick_best_resource_for_caps (GabbleConnection *connection, + const gchar *jid, + GabbleCapabilitySetPredicate predicate, + gconstpointer user_data) +{ + TpBaseConnection *base; + TpHandleRepoIface *contact_handles; + TpHandle handle; + GabblePresence *presence; + + g_return_val_if_fail (GABBLE_IS_CONNECTION (connection), NULL); + g_return_val_if_fail (!tp_str_empty (jid), NULL); + + base = (TpBaseConnection *) connection; + contact_handles = tp_base_connection_get_handles (base, + TP_HANDLE_TYPE_CONTACT); + + handle = tp_handle_ensure (contact_handles, jid, + NULL, NULL); + + if (handle == 0) + return NULL; + + presence = gabble_presence_cache_get (connection->presence_cache, + handle); + + if (presence == NULL) + return NULL; + + return gabble_presence_pick_resource_by_caps (presence, 0, + predicate, user_data); +} + +TpBaseContactList * +gabble_connection_get_contact_list (GabbleConnection *connection) +{ + g_return_val_if_fail (GABBLE_IS_CONNECTION (connection), NULL); + + return (TpBaseContactList *) connection->roster; +} + +WockyXep0115Capabilities * +gabble_connection_get_caps (GabbleConnection *connection, + TpHandle handle) +{ + GabblePresence *presence; + + g_return_val_if_fail (GABBLE_IS_CONNECTION (connection), NULL); + g_return_val_if_fail (handle > 0, NULL); + + presence = gabble_presence_cache_get (connection->presence_cache, + handle); + + return (WockyXep0115Capabilities *) presence; +} diff --git a/src/connection.h b/src/connection.h index ba34a257f..77fb1e24a 100644 --- a/src/connection.h +++ b/src/connection.h @@ -21,6 +21,8 @@ #ifndef __GABBLE_CONNECTION_H__ #define __GABBLE_CONNECTION_H__ +#include "config.h" + #include <dbus/dbus-glib.h> #include <glib-object.h> #include <loudmouth/loudmouth.h> @@ -34,8 +36,10 @@ #include <wocky/wocky-pep-service.h> #include "gabble/connection.h" -#include "capabilities.h" +#include "gabble/capabilities.h" +#ifdef ENABLE_FILE_TRANSFER #include "ft-manager.h" +#endif #include "jingle-factory.h" #include "muc-factory.h" #include "types.h" @@ -51,21 +55,23 @@ G_BEGIN_DECLS /* order must match array of statuses in conn-presence.c */ /* in increasing order of presence */ +/*< prefix=GABBLE_PRESENCE >*/ typedef enum { GABBLE_PRESENCE_OFFLINE = 0, GABBLE_PRESENCE_UNKNOWN, GABBLE_PRESENCE_ERROR, - GABBLE_PRESENCE_LAST_UNAVAILABLE = GABBLE_PRESENCE_ERROR, + GABBLE_PRESENCE_LAST_UNAVAILABLE = GABBLE_PRESENCE_ERROR, /*< skip >*/ GABBLE_PRESENCE_HIDDEN, GABBLE_PRESENCE_XA, GABBLE_PRESENCE_AWAY, GABBLE_PRESENCE_DND, GABBLE_PRESENCE_AVAILABLE, GABBLE_PRESENCE_CHAT, - NUM_GABBLE_PRESENCES + NUM_GABBLE_PRESENCES /*< skip >*/ } GabblePresenceId; +/*< flags >*/ typedef enum { GABBLE_CONNECTION_FEATURES_NONE = 0, @@ -78,6 +84,7 @@ typedef enum GABBLE_CONNECTION_FEATURES_INVISIBLE = 1 << 6, GABBLE_CONNECTION_FEATURES_GOOGLE_SHARED_STATUS = 1 << 7, GABBLE_CONNECTION_FEATURES_GOOGLE_QUEUE = 1 << 8, + GABBLE_CONNECTION_FEATURES_GOOGLE_SETTING = 1 << 9, } GabbleConnectionFeatures; typedef struct _GabbleConnectionPrivate GabbleConnectionPrivate; @@ -163,8 +170,10 @@ struct _GabbleConnection { /* jingle factory */ GabbleJingleFactory *jingle_factory; +#ifdef ENABLE_FILE_TRANSFER /* file transfer manager */ GabbleFtManager *ft_manager; +#endif /* PEP */ WockyPepService *pep_nick; @@ -200,8 +209,6 @@ typedef enum { GABBLE_CONNECTION_ALIAS_FROM_ROSTER } GabbleConnectionAliasSource; -gchar *gabble_connection_get_full_jid (GabbleConnection *conn); - WockyPorter *gabble_connection_dup_porter (GabbleConnection *conn); gboolean _gabble_connection_set_properties_from_account ( @@ -213,6 +220,7 @@ gboolean _gabble_connection_send_with_reply (GabbleConnection *conn, gpointer user_data, GError **error); void _gabble_connection_acknowledge_set_iq (GabbleConnection *conn, LmMessage *iq); +void gabble_connection_update_last_use (GabbleConnection *conn); const char *_gabble_connection_find_conference_server (GabbleConnection *); gchar *gabble_connection_get_canonical_room_name (GabbleConnection *conn, diff --git a/src/debug.c b/src/debug.c index bb19422b6..24d2b1808 100644 --- a/src/debug.c +++ b/src/debug.c @@ -114,7 +114,7 @@ gabble_debug_free (void) if (flag_to_domains == NULL) return; - g_hash_table_destroy (flag_to_domains); + g_hash_table_unref (flag_to_domains); flag_to_domains = NULL; } diff --git a/src/debug.h b/src/debug.h index f51ad8a55..191a51e1c 100644 --- a/src/debug.h +++ b/src/debug.h @@ -4,6 +4,8 @@ #include "config.h" #include <glib.h> +#include <wocky/wocky-stanza.h> +#include <loudmouth/loudmouth.h> G_BEGIN_DECLS @@ -89,10 +91,30 @@ G_END_DECLS } G_STMT_END #else /* !defined (ENABLE_DEBUG) */ -# define DEBUG(format, ...) G_STMT_START { } G_STMT_END +static inline void +DEBUG ( + const gchar *format, + ...) +{ +} + # define DEBUGGING 0 -# define STANZA_DEBUG(st, s) G_STMT_START { } G_STMT_END -# define NODE_DEBUG(n, s) G_STMT_START { } G_STMT_END + +static inline void +STANZA_DEBUG ( + WockyStanza *stanza, + const gchar *format, + ...) +{ +} + +static inline void +NODE_DEBUG ( + LmMessageNode *node, + const gchar *format, + ...) +{ +} #endif /* !defined (ENABLE_DEBUG) */ #endif /* DEBUG_FLAG */ diff --git a/src/disco.c b/src/disco.c index fce4509a3..bee1c3e1b 100644 --- a/src/disco.c +++ b/src/disco.c @@ -26,8 +26,6 @@ #include <string.h> -#define DBUS_API_SUBJECT_TO_CHANGE - #include <dbus/dbus-glib.h> #include <dbus/dbus-glib-lowlevel.h> #include <telepathy-glib/dbus.h> @@ -239,7 +237,7 @@ gabble_disco_dispose (GObject *object) g_free ((char *) item->name); g_free ((char *) item->category); g_free ((char *) item->type); - g_hash_table_destroy (item->features); + g_hash_table_unref (item->features); g_free (item); } @@ -657,7 +655,7 @@ item_info_cb (GabbleDisco *disco, item.features = keys; pipeline->callback (pipeline, &item, pipeline->user_data); - g_hash_table_destroy (keys); + g_hash_table_unref (keys); done: gabble_disco_fill_pipeline (disco, pipeline); @@ -842,8 +840,8 @@ gabble_disco_pipeline_destroy (gpointer self) gabble_disco_cancel_request (pipeline->disco, request); } - g_hash_table_destroy (pipeline->remaining_items); - g_ptr_array_free (pipeline->disco_pipeline, TRUE); + g_hash_table_unref (pipeline->remaining_items); + g_ptr_array_unref (pipeline->disco_pipeline); g_free (pipeline); } diff --git a/src/ft-channel.c b/src/ft-channel.c index fb55fad73..878dc8fba 100644 --- a/src/ft-channel.c +++ b/src/ft-channel.c @@ -80,6 +80,8 @@ G_DEFINE_TYPE_WITH_CODE (GabbleFileTransferChannel, gabble_file_transfer_channel file_transfer_iface_init); G_IMPLEMENT_INTERFACE (GABBLE_TYPE_SVC_CHANNEL_TYPE_FILETRANSFER_FUTURE, NULL); + G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_FILE_TRANSFER_METADATA, + NULL); ); #define GABBLE_UNDEFINED_FILE_SIZE G_MAXUINT64 @@ -122,7 +124,14 @@ enum PROP_CONNECTION, PROP_BYTESTREAM, + + /* Chan.Type.FileTransfer.FUTURE */ PROP_GTALK_FILE_COLLECTION, + + /* Chan.Iface.FileTransfer.Metadata */ + PROP_SERVICE_NAME, + PROP_METADATA, + LAST_PROPERTY }; @@ -160,6 +169,8 @@ struct _GabbleFileTransferChannelPrivate { guint64 date; gchar *file_collection; gchar *uri; + gchar *service_name; + GHashTable *metadata; gboolean channel_opened; }; @@ -317,6 +328,8 @@ gabble_file_transfer_channel_get_property (GObject *object, TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER, "TransferredBytes", TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER, "InitialOffset", GABBLE_IFACE_CHANNEL_TYPE_FILETRANSFER_FUTURE, "FileCollection", + TP_IFACE_CHANNEL_INTERFACE_FILE_TRANSFER_METADATA, "ServiceName", + TP_IFACE_CHANNEL_INTERFACE_FILE_TRANSFER_METADATA, "Metadata", NULL); /* URI is immutable only for outgoing transfers */ @@ -335,6 +348,24 @@ gabble_file_transfer_channel_get_property (GObject *object, case PROP_GTALK_FILE_COLLECTION: g_value_set_object (value, self->priv->gtalk_file_collection); break; + case PROP_SERVICE_NAME: + g_value_set_string (value, self->priv->service_name); + break; + case PROP_METADATA: + { + /* We're fine with priv->metadata being NULL but dbus-glib + * doesn't like iterating NULL as if it was a hash table. */ + if (self->priv->metadata == NULL) + { + g_value_take_boxed (value, + g_hash_table_new (g_str_hash, g_str_equal)); + } + else + { + g_value_set_boxed (value, self->priv->metadata); + } + } + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -361,13 +392,10 @@ gabble_file_transfer_channel_set_property (GObject *object, case PROP_CONNECTION: self->priv->connection = g_value_get_object (value); break; + /* these properties are writable in the interface, but not actually + * meaningfully changeable on this channel, so we do nothing */ case PROP_HANDLE_TYPE: - g_assert (g_value_get_uint (value) == 0 - || g_value_get_uint (value) == TP_HANDLE_TYPE_CONTACT); - break; case PROP_CHANNEL_TYPE: - /* these properties are writable in the interface, but not actually - * meaningfully changeable on this channel, so we do nothing */ break; case PROP_STATE: gabble_file_transfer_channel_set_state ( @@ -426,6 +454,12 @@ gabble_file_transfer_channel_set_property (GObject *object, set_gtalk_file_collection (self, GTALK_FILE_COLLECTION (g_value_get_object (value))); break; + case PROP_SERVICE_NAME: + self->priv->service_name = g_value_dup_string (value); + break; + case PROP_METADATA: + self->priv->metadata = g_value_dup_boxed (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -435,7 +469,7 @@ gabble_file_transfer_channel_set_property (GObject *object, static void free_array (GArray *array) { - g_array_free (array, TRUE); + g_array_unref (array); } static void @@ -650,6 +684,12 @@ gabble_file_transfer_channel_class_init ( { NULL } }; + static TpDBusPropertiesMixinPropImpl file_metadata_props[] = { + { "ServiceName", "service-name", NULL }, + { "Metadata", "metadata", NULL }, + { NULL } + }; + static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = { { TP_IFACE_CHANNEL, tp_dbus_properties_mixin_getter_gobject_properties, @@ -666,6 +706,11 @@ gabble_file_transfer_channel_class_init ( NULL, file_future_props }, + { TP_IFACE_CHANNEL_INTERFACE_FILE_TRANSFER_METADATA, + tp_dbus_properties_mixin_getter_gobject_properties, + NULL, + file_metadata_props + }, { NULL } }; @@ -894,6 +939,22 @@ gabble_file_transfer_channel_class_init ( g_object_class_install_property (object_class, PROP_URI, param_spec); + param_spec = g_param_spec_string ("service-name", + "ServiceName", + "The Metadata.ServiceName property of this channel", + "", + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_SERVICE_NAME, + param_spec); + + param_spec = g_param_spec_boxed ("metadata", + "Metadata", + "The Metadata.Metadata property of this channel", + TP_HASH_TYPE_METADATA, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_METADATA, + param_spec); + gabble_file_transfer_channel_class->dbus_props_class.interfaces = prop_interfaces; tp_dbus_properties_mixin_class_init (object_class, @@ -967,9 +1028,12 @@ gabble_file_transfer_channel_finalize (GObject *object) g_free (self->priv->content_type); g_free (self->priv->content_hash); g_free (self->priv->description); - g_hash_table_destroy (self->priv->available_socket_types); + g_hash_table_unref (self->priv->available_socket_types); g_free (self->priv->file_collection); g_free (self->priv->uri); + g_free (self->priv->service_name); + if (self->priv->metadata != NULL) + g_hash_table_unref (self->priv->metadata); G_OBJECT_CLASS (gabble_file_transfer_channel_parent_class)->finalize (object); } @@ -1298,6 +1362,83 @@ bytestream_negotiate_cb (GabbleBytestreamIface *bytestream, } +static void +add_metadata_forms (GabbleFileTransferChannel *self, + WockyNode *file) +{ + if (!tp_str_empty (self->priv->service_name)) + { + WockyStanza *tmp = wocky_stanza_build (WOCKY_STANZA_TYPE_IQ, + WOCKY_STANZA_SUB_TYPE_RESULT, NULL, NULL, + '(', "x", + ':', NS_X_DATA, + '@', "type", "result", + '(', "field", + '@', "var", "FORM_TYPE", + '@', "type", "hidden", + '(', "value", + '$', NS_TP_FT_METADATA_SERVICE, + ')', + ')', + '(', "field", + '@', "var", "ServiceName", + '(', "value", + '$', self->priv->service_name, + ')', + ')', + ')', + NULL); + WockyNode *x = wocky_node_get_first_child (wocky_stanza_get_top_node (tmp)); + WockyNodeTree *tree = wocky_node_tree_new_from_node (x); + + wocky_node_add_node_tree (file, tree); + g_object_unref (tree); + g_object_unref (tmp); + } + + if (self->priv->metadata != NULL + && g_hash_table_size (self->priv->metadata) > 0) + { + WockyStanza *tmp = wocky_stanza_build (WOCKY_STANZA_TYPE_IQ, + WOCKY_STANZA_SUB_TYPE_RESULT, NULL, NULL, + '(', "x", + ':', NS_X_DATA, + '@', "type", "result", + '(', "field", + '@', "var", "FORM_TYPE", + '@', "type", "hidden", + '(', "value", + '$', NS_TP_FT_METADATA, + ')', + ')', + ')', + NULL); + WockyNode *x = wocky_node_get_first_child (wocky_stanza_get_top_node (tmp)); + WockyNodeTree *tree; + GHashTableIter iter; + gpointer key, val; + + g_hash_table_iter_init (&iter, self->priv->metadata); + while (g_hash_table_iter_next (&iter, &key, &val)) + { + const gchar * const *values = val; + + WockyNode *field = wocky_node_add_child (x, "field"); + wocky_node_set_attribute (field, "var", key); + + for (; values != NULL && *values != NULL; values++) + { + wocky_node_add_child_with_content (field, "value", *values); + } + } + + tree = wocky_node_tree_new_from_node (x); + wocky_node_add_node_tree (file, tree); + g_object_unref (tree); + g_object_unref (tmp); + } +} + static gboolean offer_bytestream (GabbleFileTransferChannel *self, const gchar *jid, const gchar *resource, GError **error) @@ -1338,6 +1479,8 @@ offer_bytestream (GabbleFileTransferChannel *self, const gchar *jid, "mime-type", self->priv->content_type, NULL); + add_metadata_forms (self, file_node); + if (self->priv->content_hash != NULL) wocky_node_set_attribute (file_node, "hash", self->priv->content_hash); @@ -1350,7 +1493,11 @@ offer_bytestream (GabbleFileTransferChannel *self, const gchar *jid, t = (time_t) self->priv->date; tm = gmtime (&t); +#ifdef G_OS_WIN32 + strftime (date_str, sizeof (date_str), "%Y-%m-%dT%H:%M:%SZ", tm); +#else strftime (date_str, sizeof (date_str), "%FT%H:%M:%SZ", tm); +#endif wocky_node_set_attribute (file_node, "date", date_str); } @@ -1474,7 +1621,7 @@ gabble_file_transfer_channel_offer_file (GabbleFileTransferChannel *self, gboolean jingle_share = FALSE; const gchar *si_resource = NULL; const gchar *share_resource = NULL; - g_assert (!CHECK_STR_EMPTY (self->priv->filename)); + g_assert (!tp_str_empty (self->priv->filename)); g_assert (self->priv->size != GABBLE_UNDEFINED_FILE_SIZE); g_return_val_if_fail (self->priv->bytestream == NULL, FALSE); g_return_val_if_fail (self->priv->gtalk_file_collection == NULL, FALSE); @@ -1491,6 +1638,21 @@ gabble_file_transfer_channel_offer_file (GabbleFileTransferChannel *self, return FALSE; } + if (self->priv->service_name != NULL || self->priv->metadata != NULL) + { + if (!gabble_presence_has_cap (presence, NS_TP_FT_METADATA)) + { + DEBUG ("trying to use Metadata properties on a contact " + "who doesn't support it"); + g_set_error (error, TP_ERRORS, TP_ERROR_NOT_CAPABLE, + "The specified contact does not support the " + "Metadata extension; you should ensure both ServiceName and " + "Metadata properties are not present in the channel " + "request"); + return FALSE; + } + } + contact_repo = tp_base_connection_get_handles ( (TpBaseConnection *) self->priv->connection, TP_HANDLE_TYPE_CONTACT); room_repo = tp_base_connection_get_handles ( @@ -2166,7 +2328,7 @@ setup_local_socket (GabbleFileTransferChannel *self, DEBUG ("local socket %s", path); g_free (path); - g_array_free (array, TRUE); + g_array_unref (array); } else #endif @@ -2252,7 +2414,9 @@ gabble_file_transfer_channel_new (GabbleConnection *conn, GabbleBytestreamIface *bytestream, GTalkFileCollection *gtalk_file_collection, const gchar *file_collection, - const gchar *uri) + const gchar *uri, + const gchar *service_name, + const GHashTable *metadata) { return g_object_new (GABBLE_TYPE_FILE_TRANSFER_CHANNEL, @@ -2273,5 +2437,7 @@ gabble_file_transfer_channel_new (GabbleConnection *conn, "bytestream", bytestream, "gtalk-file-collection", gtalk_file_collection, "uri", uri, + "service-name", service_name, + "metadata", metadata, NULL); } diff --git a/src/ft-channel.h b/src/ft-channel.h index 3465b79a6..e015e17f6 100644 --- a/src/ft-channel.h +++ b/src/ft-channel.h @@ -72,7 +72,7 @@ gabble_file_transfer_channel_new (GabbleConnection *conn, const gchar *description, guint64 date, guint64 initial_offset, gboolean resume_supported, GabbleBytestreamIface *bytestream, GTalkFileCollection *gtalk_fc, const gchar *file_collection, - const gchar *uri); + const gchar *uri, const gchar *service_name, const GHashTable *metadata); gboolean gabble_file_transfer_channel_offer_file ( GabbleFileTransferChannel *self, GError **error); diff --git a/src/ft-manager.c b/src/ft-manager.c index 0f1ab3e09..6e8aefc87 100644 --- a/src/ft-manager.c +++ b/src/ft-manager.c @@ -30,15 +30,16 @@ #include "jingle-session.h" #include "jingle-share.h" -#include "caps-channel-manager.h" +#include "gabble/caps-channel-manager.h" #include "connection.h" #include "ft-manager.h" +#include "ft-channel.h" #include "gabble-signals-marshal.h" #include "namespaces.h" #include "presence-cache.h" #include "util.h" -#include "ft-channel.h" +#include <wocky/wocky-data-form.h> #include <telepathy-glib/base-connection.h> #include <telepathy-glib/channel-factory-iface.h> @@ -307,7 +308,7 @@ gabble_ft_manager_channels_created (GabbleFtManager *self, GList *channels) tp_channel_manager_emit_new_channels (self, new_channels); - g_hash_table_destroy (new_channels); + g_hash_table_unref (new_channels); } static void @@ -381,7 +382,7 @@ new_jingle_session_cb (GabbleJingleFactory *jf, channel = gabble_file_transfer_channel_new (self->priv->connection, sess->peer, sess->peer, TP_FILE_TRANSFER_STATE_PENDING, NULL, filename, entry->size, TP_FILE_HASH_TYPE_NONE, NULL, - NULL, 0, 0, FALSE, NULL, gtalk_fc, token, NULL); + NULL, 0, 0, FALSE, NULL, gtalk_fc, token, NULL, NULL, NULL); g_free (filename); gtalk_file_collection_add_channel (gtalk_fc, channel); @@ -442,9 +443,10 @@ gabble_ft_manager_handle_request (TpChannelManager *manager, tp_base_connection_get_handles (base_connection, TP_HANDLE_TYPE_CONTACT); TpHandle handle; const gchar *content_type, *filename, *content_hash, *description; - const gchar *file_uri; + const gchar *file_uri, *service_name; guint64 size, date, initial_offset; TpFileHashType content_hash_type; + const GHashTable *metadata; GError *error = NULL; gboolean valid; @@ -549,13 +551,28 @@ gabble_ft_manager_handle_request (TpChannelManager *manager, file_uri = tp_asv_get_string (request_properties, TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_URI); + service_name = tp_asv_get_string (request_properties, + TP_PROP_CHANNEL_INTERFACE_FILE_TRANSFER_METADATA_SERVICE_NAME); + + metadata = tp_asv_get_boxed (request_properties, + TP_PROP_CHANNEL_INTERFACE_FILE_TRANSFER_METADATA_METADATA, + TP_HASH_TYPE_METADATA); + + if (metadata != NULL && g_hash_table_lookup ((GHashTable *) metadata, "FORM_TYPE")) + { + g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, + "Metadata cannot contain an item with key 'FORM_TYPE'"); + goto error; + } + DEBUG ("Requested outgoing channel with contact: %s", tp_handle_inspect (contact_repo, handle)); chan = gabble_file_transfer_channel_new (self->priv->connection, handle, base_connection->self_handle, TP_FILE_TRANSFER_STATE_PENDING, content_type, filename, size, content_hash_type, content_hash, - description, date, initial_offset, TRUE, NULL, NULL, NULL, file_uri); + description, date, initial_offset, TRUE, NULL, NULL, NULL, file_uri, + service_name, metadata); if (!gabble_file_transfer_channel_offer_file (chan, &error)) { @@ -582,20 +599,38 @@ static const gchar * const file_transfer_channel_fixed_properties[] = { NULL }; + /* ContentHashType has to be first so we can easily skip it when needed */ +#define STANDARD_PROPERTIES \ + TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_CONTENT_HASH_TYPE, \ + TP_PROP_CHANNEL_TARGET_HANDLE, \ + TP_PROP_CHANNEL_TARGET_ID, \ + TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_CONTENT_TYPE, \ + TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_FILENAME, \ + TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_SIZE, \ + TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_CONTENT_HASH, \ + TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_DESCRIPTION, \ + TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_DATE, \ + TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_URI + static const gchar * const file_transfer_channel_allowed_properties[] = { - /* ContentHashType has to be first so we can easily skip it when needed */ - TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_CONTENT_HASH_TYPE, - TP_PROP_CHANNEL_TARGET_HANDLE, - TP_PROP_CHANNEL_TARGET_ID, - TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_CONTENT_TYPE, - TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_FILENAME, - TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_SIZE, - TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_CONTENT_HASH, - TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_DESCRIPTION, - TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_DATE, - TP_PROP_CHANNEL_TYPE_FILE_TRANSFER_URI, - NULL + STANDARD_PROPERTIES, + NULL +}; + +static const gchar * const file_transfer_channel_allowed_properties_with_metadata_prop[] = +{ + STANDARD_PROPERTIES, + TP_PROP_CHANNEL_INTERFACE_FILE_TRANSFER_METADATA_METADATA, + NULL +}; + +static const gchar * const file_transfer_channel_allowed_properties_with_both_metadata_props[] = +{ + STANDARD_PROPERTIES, + TP_PROP_CHANNEL_INTERFACE_FILE_TRANSFER_METADATA_SERVICE_NAME, + TP_PROP_CHANNEL_INTERFACE_FILE_TRANSFER_METADATA_METADATA, + NULL }; static void @@ -615,7 +650,7 @@ gabble_ft_manager_type_foreach_channel_class (GType type, g_hash_table_insert (table, TP_IFACE_CHANNEL ".TargetHandleType", tp_g_value_slice_new_uint (TP_HANDLE_TYPE_CONTACT)); - func (type, table, file_transfer_channel_allowed_properties, + func (type, table, file_transfer_channel_allowed_properties_with_both_metadata_props, user_data); /* MD5 HashType class */ @@ -624,10 +659,10 @@ gabble_ft_manager_type_foreach_channel_class (GType type, tp_g_value_slice_new_uint (TP_FILE_HASH_TYPE_MD5)); /* skip ContentHashType in allowed properties */ - func (type, table, file_transfer_channel_allowed_properties + 1, + func (type, table, file_transfer_channel_allowed_properties_with_both_metadata_props + 1, user_data); - g_hash_table_destroy (table); + g_hash_table_unref (table); } static WockyNode * @@ -658,6 +693,111 @@ hyvaa_vappua ( #undef die_if_null } +static WockyDataForm * +find_data_form (WockyNode *file, + const gchar *form_type) +{ + WockyNodeIter iter; + WockyNode *x; + + wocky_node_iter_init (&iter, file, "x", NS_X_DATA); + while (wocky_node_iter_next (&iter, &x)) + { + GError *error = NULL; + WockyDataForm *form = wocky_data_form_new_from_node (x, &error); + WockyDataFormField *field; + + if (form == NULL) + { + DEBUG ("Failed to parse data form: %s", error->message); + g_clear_error (&error); + continue; + } + + field = g_hash_table_lookup (form->fields, "FORM_TYPE"); + + if (field == NULL) + { + DEBUG ("Data form doesn't have FORM_TYPE field!"); + g_object_unref (form); + continue; + } + + /* found it! */ + if (!tp_strdiff (field->raw_value_contents[0], form_type)) + return form; + + g_object_unref (form); + } + + return NULL; +} + +static gchar * +extract_service_name (WockyNode *file) +{ + WockyDataForm *form = find_data_form (file, NS_TP_FT_METADATA_SERVICE); + WockyDataFormField *field; + gchar *service_name = NULL; + + if (form == NULL) + return NULL; + + field = g_hash_table_lookup (form->fields, "ServiceName"); + + if (field == NULL) + { + DEBUG ("ServiceName propery not present in data form; odd..."); + goto out; + } + + if (field->raw_value_contents == NULL + || field->raw_value_contents[0] == NULL) + { + DEBUG ("ServiceName property doesn't have a real value; odd..."); + } + else + { + service_name = g_strdup (field->raw_value_contents[0]); + } + +out: + g_object_unref (form); + return service_name; +} + +static GHashTable * +extract_metadata (WockyNode *file) +{ + WockyDataForm *form = find_data_form (file, NS_TP_FT_METADATA); + GHashTable *metadata; + GHashTableIter iter; + gpointer key, value; + + if (form == NULL) + return NULL; + + metadata = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, (GDestroyNotify) g_strfreev); + + g_hash_table_iter_init (&iter, form->fields); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + const gchar *var = key; + WockyDataFormField *field = value; + + if (!tp_strdiff (var, "FORM_TYPE")) + continue; + + g_hash_table_insert (metadata, + g_strdup (var), + g_strdupv (field->raw_value_contents)); + } + + g_object_unref (form); + return metadata; +} + void gabble_ft_manager_handle_si_request (GabbleFtManager *self, GabbleBytestreamIface *bytestream, TpHandle handle, @@ -667,6 +807,8 @@ void gabble_ft_manager_handle_si_request (GabbleFtManager *self, WockyNode *si_node, *file_node, *desc_node; const gchar *filename, *size_str, *content_type, *content_hash, *description; const gchar *date_str; + gchar *service_name; + GHashTable *metadata; guint64 size; guint64 date = 0; TpFileHashType content_hash_type; @@ -719,12 +861,21 @@ void gabble_ft_manager_handle_si_request (GabbleFtManager *self, resume_supported = (wocky_node_get_child (file_node, "range") != NULL); + /* metadata */ + service_name = extract_service_name (file_node); + metadata = extract_metadata (file_node); + chan = gabble_file_transfer_channel_new (self->priv->connection, handle, handle, TP_FILE_TRANSFER_STATE_PENDING, content_type, filename, size, content_hash_type, content_hash, - description, date, 0, resume_supported, bytestream, NULL, NULL, NULL); + description, date, 0, resume_supported, bytestream, NULL, NULL, NULL, + service_name, metadata); gabble_ft_manager_channel_created (self, chan, NULL); + + g_free (service_name); + if (metadata != NULL) + g_hash_table_unref (metadata); } static void @@ -770,12 +921,16 @@ gabble_ft_manager_get_tmp_dir (GabbleFtManager *self) #endif static void -add_file_transfer_channel_class (GPtrArray *arr) +add_file_transfer_channel_class (GPtrArray *arr, + gboolean include_metadata_properties, + const gchar *service_name_str) { GValue monster = {0, }; GHashTable *fixed_properties; GValue *channel_type_value; GValue *target_handle_type_value; + GValue *service_name_value; + const gchar * const *allowed_properties; g_value_init (&monster, TP_STRUCT_TYPE_REQUESTABLE_CHANNEL_CLASS); g_value_take_boxed (&monster, @@ -785,28 +940,60 @@ add_file_transfer_channel_class (GPtrArray *arr) fixed_properties = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify) tp_g_value_slice_free); - channel_type_value = tp_g_value_slice_new (G_TYPE_STRING); - g_value_set_static_string (channel_type_value, + channel_type_value = tp_g_value_slice_new_static_string ( TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER); g_hash_table_insert (fixed_properties, TP_IFACE_CHANNEL ".ChannelType", channel_type_value); - target_handle_type_value = tp_g_value_slice_new (G_TYPE_UINT); - g_value_set_uint (target_handle_type_value, TP_HANDLE_TYPE_CONTACT); + target_handle_type_value = tp_g_value_slice_new_uint (TP_HANDLE_TYPE_CONTACT); g_hash_table_insert (fixed_properties, TP_IFACE_CHANNEL ".TargetHandleType", target_handle_type_value); + if (service_name_str != NULL) + { + service_name_value = tp_g_value_slice_new_string (service_name_str); + g_hash_table_insert (fixed_properties, + TP_PROP_CHANNEL_INTERFACE_FILE_TRANSFER_METADATA_SERVICE_NAME, + service_name_value); + } + + if (include_metadata_properties) + { + if (service_name_str == NULL) + allowed_properties = file_transfer_channel_allowed_properties_with_both_metadata_props; + else + allowed_properties = file_transfer_channel_allowed_properties_with_metadata_prop; + } + else + { + allowed_properties = file_transfer_channel_allowed_properties; + } + dbus_g_type_struct_set (&monster, 0, fixed_properties, - 1, file_transfer_channel_allowed_properties, + 1, allowed_properties, G_MAXUINT); - g_hash_table_destroy (fixed_properties); + g_hash_table_unref (fixed_properties); g_ptr_array_add (arr, g_value_get_boxed (&monster)); } static void +get_contact_caps_foreach (gpointer data, + gpointer user_data) +{ + const gchar *ns = data; + GPtrArray *arr = user_data; + + if (!g_str_has_prefix (ns, NS_TP_FT_METADATA "#")) + return; + + add_file_transfer_channel_class (arr, TRUE, + ns + strlen (NS_TP_FT_METADATA "#")); +} + +static void gabble_ft_manager_get_contact_caps ( GabbleCapsChannelManager *manager G_GNUC_UNUSED, TpHandle handle G_GNUC_UNUSED, @@ -815,7 +1002,12 @@ gabble_ft_manager_get_contact_caps ( { if (gabble_capability_set_has (caps, NS_FILE_TRANSFER) || gabble_capability_set_has (caps, NS_GOOGLE_FEAT_SHARE)) - add_file_transfer_channel_class (arr); + { + add_file_transfer_channel_class (arr, + gabble_capability_set_has (caps, NS_TP_FT_METADATA), NULL); + } + + gabble_capability_set_foreach (caps, get_contact_caps_foreach, arr); } static void @@ -824,13 +1016,16 @@ gabble_ft_manager_represent_client ( const gchar *client_name, const GPtrArray *filters, const gchar * const *cap_tokens G_GNUC_UNUSED, - GabbleCapabilitySet *cap_set) + GabbleCapabilitySet *cap_set, + GPtrArray *data_forms G_GNUC_UNUSED) { guint i; for (i = 0; i < filters->len; i++) { GHashTable *channel_class = g_ptr_array_index (filters, i); + const gchar *service_name; + gchar *ns; if (tp_strdiff (tp_asv_get_string (channel_class, TP_IFACE_CHANNEL ".ChannelType"), @@ -845,9 +1040,28 @@ gabble_ft_manager_represent_client ( DEBUG ("client %s supports file transfer", client_name); gabble_capability_set_add (cap_set, NS_FILE_TRANSFER); gabble_capability_set_add (cap_set, NS_GOOGLE_FEAT_SHARE); - /* there's no point in looking at the subsequent filters if we've - * already added the FT capability */ - break; + gabble_capability_set_add (cap_set, NS_TP_FT_METADATA); + + /* now look at service names */ + + /* capabilities mean being able to RECEIVE said kinds of + * FTs. hence, skip Requested=true (locally initiated) channel + * classes */ + if (tp_asv_get_boolean (channel_class, + TP_PROP_CHANNEL_REQUESTED, FALSE)) + continue; + + service_name = tp_asv_get_string (channel_class, + TP_PROP_CHANNEL_INTERFACE_FILE_TRANSFER_METADATA_SERVICE_NAME); + + if (service_name == NULL) + continue; + + ns = g_strconcat (NS_TP_FT_METADATA "#", service_name, NULL); + + DEBUG ("%s: adding capability %s", client_name, ns); + gabble_capability_set_add (cap_set, ns); + g_free (ns); } } diff --git a/src/gabble.c b/src/gabble.c index 0165e789c..fef4454e7 100644 --- a/src/gabble.c +++ b/src/gabble.c @@ -109,10 +109,12 @@ log_handler (const gchar *log_domain, void gabble_init (void) { +#if !GLIB_CHECK_VERSION (2, 31, 0) /* libsoup uses glib in multiple threads and don't have this, so we have to * enable it manually here */ if (!g_thread_supported ()) g_thread_init (NULL); +#endif g_type_init (); wocky_init (); diff --git a/src/google-relay.c b/src/google-relay.c new file mode 100644 index 000000000..d78f37e00 --- /dev/null +++ b/src/google-relay.c @@ -0,0 +1,341 @@ +/* + * google-relay.c - Support for Google relays for Jingle + * + * Copyright (C) 2006-2008 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 "config.h" +#include "google-relay.h" + +#include <string.h> + +#ifdef ENABLE_GOOGLE_RELAY +#include <libsoup/soup.h> +#endif + +#include <telepathy-glib/util.h> + +#define DEBUG_FLAG GABBLE_DEBUG_MEDIA + +#ifdef G_OS_WIN32 +#undef ERROR +#endif + +#include "debug.h" + +#define RELAY_HTTP_TIMEOUT 5 + +struct _GabbleGoogleRelayResolver { +#ifdef ENABLE_GOOGLE_RELAY + SoupSession *soup; +#else + GObject *soup; +#endif +}; + +typedef struct +{ + GPtrArray *relays; + guint component; + guint requests_to_do; + GabbleJingleFactoryRelaySessionCb callback; + gpointer user_data; +} RelaySessionData; + +static RelaySessionData * +relay_session_data_new (guint requests_to_do, + GabbleJingleFactoryRelaySessionCb callback, + gpointer user_data) +{ + RelaySessionData *rsd = g_slice_new0 (RelaySessionData); + + rsd->relays = g_ptr_array_sized_new (requests_to_do); + rsd->component = 1; + rsd->requests_to_do = requests_to_do; + rsd->callback = callback; + rsd->user_data = user_data; + + return rsd; +} + +/* This is a GSourceFunc */ +static gboolean +relay_session_data_call (gpointer p) +{ + RelaySessionData *rsd = p; + + g_assert (rsd->callback != NULL); + + rsd->callback (rsd->relays, rsd->user_data); + + return FALSE; +} + +/* This is a GDestroyNotify */ +static void +relay_session_data_destroy (gpointer p) +{ + RelaySessionData *rsd = p; + + g_ptr_array_foreach (rsd->relays, (GFunc) g_hash_table_unref, NULL); + g_ptr_array_unref (rsd->relays); + + g_slice_free (RelaySessionData, rsd); +} + +#ifdef ENABLE_GOOGLE_RELAY + +static void +translate_relay_info (GPtrArray *relays, + const gchar *relay_ip, + const gchar *username, + const gchar *password, + const gchar *static_type, + const gchar *port_string, + guint component) +{ + GHashTable *asv; + guint64 portll; + guint port; + + if (port_string == NULL) + { + DEBUG ("no relay port for %s found", static_type); + return; + } + + portll = g_ascii_strtoull (port_string, NULL, 10); + + if (portll == 0 || portll > G_MAXUINT16) + { + DEBUG ("failed to parse relay port '%s' for %s", port_string, + static_type); + return; + } + port = (guint) portll; + + DEBUG ("type=%s ip=%s port=%u username=%s password=%s component=%u", + static_type, relay_ip, port, username, password, component); + /* keys are static, values are slice-allocated */ + asv = g_hash_table_new_full (g_str_hash, g_str_equal, + NULL, (GDestroyNotify) tp_g_value_slice_free); + g_hash_table_insert (asv, "ip", + tp_g_value_slice_new_string (relay_ip)); + g_hash_table_insert (asv, "type", + tp_g_value_slice_new_static_string (static_type)); + g_hash_table_insert (asv, "port", + tp_g_value_slice_new_uint (port)); + g_hash_table_insert (asv, "username", + tp_g_value_slice_new_string (username)); + g_hash_table_insert (asv, "password", + tp_g_value_slice_new_string (password)); + g_hash_table_insert (asv, "component", + tp_g_value_slice_new_uint (component)); + + g_ptr_array_add (relays, asv); +} + +static void +on_http_response (SoupSession *soup, + SoupMessage *msg, + gpointer user_data) +{ + RelaySessionData *rsd = user_data; + + if (msg->status_code != 200) + { + DEBUG ("Google session creation failed, relaying not used: %d %s", + msg->status_code, msg->reason_phrase); + } + else + { + /* parse a=b lines into GHashTable + * (key, value both borrowed from items of the strv 'lines') */ + GHashTable *map = g_hash_table_new (g_str_hash, g_str_equal); + gchar **lines; + guint i; + const gchar *relay_ip; + const gchar *relay_udp_port; + const gchar *relay_tcp_port; + const gchar *relay_ssltcp_port; + const gchar *username; + const gchar *password; + gchar *escaped_str; + + escaped_str = g_strescape (msg->response_body->data, "\r\n"); + DEBUG ("Response from Google:\n====\n%s\n====", escaped_str); + g_free (escaped_str); + + lines = g_strsplit (msg->response_body->data, "\n", 0); + + if (lines != NULL) + { + for (i = 0; lines[i] != NULL; i++) + { + gchar *delim = strchr (lines[i], '='); + size_t len; + + if (delim == NULL || delim == lines[i]) + { + /* ignore empty keys or lines without '=' */ + continue; + } + + len = strlen (lines[i]); + + if (lines[i][len - 1] == '\r') + { + lines[i][len - 1] = '\0'; + } + + *delim = '\0'; + g_hash_table_insert (map, lines[i], delim + 1); + } + } + + relay_ip = g_hash_table_lookup (map, "relay.ip"); + relay_udp_port = g_hash_table_lookup (map, "relay.udp_port"); + relay_tcp_port = g_hash_table_lookup (map, "relay.tcp_port"); + relay_ssltcp_port = g_hash_table_lookup (map, "relay.ssltcp_port"); + username = g_hash_table_lookup (map, "username"); + password = g_hash_table_lookup (map, "password"); + + if (relay_ip == NULL) + { + DEBUG ("No relay.ip found"); + } + else if (username == NULL) + { + DEBUG ("No username found"); + } + else if (password == NULL) + { + DEBUG ("No password found"); + } + else + { + translate_relay_info (rsd->relays, relay_ip, username, password, + "udp", relay_udp_port, rsd->component); + translate_relay_info (rsd->relays, relay_ip, username, password, + "tcp", relay_tcp_port, rsd->component); + translate_relay_info (rsd->relays, relay_ip, username, password, + "tls", relay_ssltcp_port, rsd->component); + } + + g_strfreev (lines); + g_hash_table_unref (map); + } + + rsd->component++; + + if ((--rsd->requests_to_do) == 0) + { + relay_session_data_call (rsd); + relay_session_data_destroy (rsd); + } +} + +#endif /* ENABLE_GOOGLE_RELAY */ + +GabbleGoogleRelayResolver * +gabble_google_relay_resolver_new (void) +{ + GabbleGoogleRelayResolver *resolver = + g_slice_new0 (GabbleGoogleRelayResolver); + +#ifdef ENABLE_GOOGLE_RELAY + + resolver->soup = soup_session_async_new (); + + /* If we don't get answer in a few seconds, relay won't do + * us much help anyways. */ + g_object_set (resolver->soup, "timeout", RELAY_HTTP_TIMEOUT, NULL); + +#endif + + return resolver; +} + +void +gabble_google_relay_resolver_destroy (GabbleGoogleRelayResolver *self) +{ + tp_clear_object (&self->soup); + + g_slice_free (GabbleGoogleRelayResolver, self); +} + +void +gabble_google_relay_resolver_resolve (GabbleGoogleRelayResolver *self, + guint components, + const gchar *server, + guint16 port, + const gchar *token, + GabbleJingleFactoryRelaySessionCb callback, + gpointer user_data) +{ + RelaySessionData *rsd = + relay_session_data_new (components, callback, user_data); + +#ifdef ENABLE_GOOGLE_RELAY + + gchar *url; + guint i; + + if (server == NULL) + { + DEBUG ("No relay server provided, not creating google relay session"); + g_idle_add_full (G_PRIORITY_DEFAULT, relay_session_data_call, rsd, + relay_session_data_destroy); + return; + } + + if (token == NULL) + { + DEBUG ("No relay token provided, not creating google relay session"); + g_idle_add_full (G_PRIORITY_DEFAULT, relay_session_data_call, rsd, + relay_session_data_destroy); + return; + } + + url = g_strdup_printf ("http://%s:%u/create_session", server, (guint) port); + + for (i = 0; i < components; i++) + { + SoupMessage *msg = soup_message_new ("GET", url); + + DEBUG ("Trying to create a new relay session on %s", url); + + /* libjingle sets both headers, so shall we */ + soup_message_headers_append (msg->request_headers, + "X-Talk-Google-Relay-Auth", token); + soup_message_headers_append (msg->request_headers, + "X-Google-Relay-Auth", token); + + soup_session_queue_message (self->soup, msg, on_http_response, rsd); + } + + g_free (url); + +#else /* !ENABLE_GOOGLE_RELAY */ + + DEBUG ("Google relay service is not supported"); + + g_idle_add_full (G_PRIORITY_DEFAULT, relay_session_data_call, rsd, + relay_session_data_destroy); + +#endif +} diff --git a/src/google-relay.h b/src/google-relay.h new file mode 100644 index 000000000..6eb180e35 --- /dev/null +++ b/src/google-relay.h @@ -0,0 +1,45 @@ +/* + * google-relay.h - Header for GabbleGoogleRelaySession + * + * Copyright (C) 2006-2008 Collabora Ltd. + * Copyright (C) 2011 Nokia Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __GABBLE_GOOGLE_RELAY_H__ +#define __GABBLE_GOOGLE_RELAY_H__ + +#include <glib.h> + +#include "jingle-factory.h" + +G_BEGIN_DECLS + +typedef struct _GabbleGoogleRelayResolver GabbleGoogleRelayResolver; + +GabbleGoogleRelayResolver * gabble_google_relay_resolver_new (void); +void gabble_google_relay_resolver_destroy (GabbleGoogleRelayResolver *self); +void gabble_google_relay_resolver_resolve (GabbleGoogleRelayResolver *self, + guint requests_to_do, + const gchar *server, + guint16 port, + const gchar *token, + GabbleJingleFactoryRelaySessionCb callback, + gpointer user_data); + +G_END_DECLS + +#endif /* __GABBLE_GOOGLE_RELAY_H__ */ diff --git a/src/gtalk-file-collection.c b/src/gtalk-file-collection.c index 2bdaf5fa5..ffc40d580 100644 --- a/src/gtalk-file-collection.c +++ b/src/gtalk-file-collection.c @@ -256,9 +256,9 @@ gtalk_file_collection_dispose (GObject *object) set_current_channel (self, NULL); - tp_clear_pointer (&self->priv->channels_reading, g_hash_table_destroy); - tp_clear_pointer (&self->priv->channels_usable, g_hash_table_destroy); - tp_clear_pointer (&self->priv->share_channels, g_hash_table_destroy); + tp_clear_pointer (&self->priv->channels_reading, g_hash_table_unref); + tp_clear_pointer (&self->priv->channels_usable, g_hash_table_unref); + tp_clear_pointer (&self->priv->share_channels, g_hash_table_unref); for (i = self->priv->channels; i; i = i->next) { diff --git a/src/im-channel.c b/src/im-channel.c index c1b52a5da..dba3aedba 100644 --- a/src/im-channel.c +++ b/src/im-channel.c @@ -95,6 +95,12 @@ struct _GabbleIMChannelPrivate gboolean dispose_has_run; }; +typedef struct { + GabbleIMChannel *channel; + TpMessage *message; + gchar *token; +} _GabbleIMSendMessageCtx; + static void gabble_im_channel_init (GabbleIMChannel *self) { @@ -170,6 +176,13 @@ gabble_im_channel_fill_immutable_properties (TpBaseChannel *chan, NULL); } +static gchar * +gabble_im_channel_get_object_path_suffix (TpBaseChannel *chan) +{ + return g_strdup_printf ("ImChannel%u", + tp_base_channel_get_target_handle (chan)); +} + static void gabble_im_channel_class_init (GabbleIMChannelClass *gabble_im_channel_class) { @@ -190,6 +203,7 @@ gabble_im_channel_class_init (GabbleIMChannelClass *gabble_im_channel_class) base_class->close = gabble_im_channel_close; base_class->fill_immutable_properties = gabble_im_channel_fill_immutable_properties; + base_class->get_object_path_suffix = gabble_im_channel_get_object_path_suffix; tp_message_mixin_init_dbus_properties (object_class); } @@ -292,6 +306,33 @@ gabble_im_channel_finalize (GObject *object) G_OBJECT_CLASS (gabble_im_channel_parent_class)->finalize (object); } +static void +_gabble_im_channel_message_sent_cb (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + WockyPorter *porter = WOCKY_PORTER (source); + GError *error = NULL; + _GabbleIMSendMessageCtx *context = user_data; + GabbleIMChannel *chan = context->channel; + TpMessage *message = context->message; + + if (wocky_porter_send_finish (porter, res, &error)) + { + tp_message_mixin_sent ((GObject *) chan, message, 0, + context->token, NULL); + } + else + { + tp_message_mixin_sent ((GObject *) chan, context->message, + 0, NULL, error); + } + + g_object_unref (context->channel); + g_object_unref (context->message); + g_free (context->token); + g_slice_free (_GabbleIMSendMessageCtx, context); +} static void _gabble_im_channel_send_message (GObject *object, @@ -300,8 +341,14 @@ _gabble_im_channel_send_message (GObject *object, { GabbleIMChannel *self = GABBLE_IM_CHANNEL (object); TpBaseChannel *base = (TpBaseChannel *) self; + GabbleConnection *gabble_conn; GabbleIMChannelPrivate *priv; gint state = -1; + WockyStanza *stanza = NULL; + gchar *id = NULL; + GError *error = NULL; + WockyPorter *porter; + _GabbleIMSendMessageCtx *context; g_assert (GABBLE_IS_IM_CHANNEL (self)); priv = self->priv; @@ -314,127 +361,185 @@ _gabble_im_channel_send_message (GObject *object, /* We don't support providing successful delivery reports. */ flags = 0; + gabble_conn = + GABBLE_CONNECTION (tp_base_channel_get_connection (base)); + + stanza = gabble_message_util_build_stanza (message, + gabble_conn, 0, state, priv->peer_jid, + priv->send_nick, &id, &error); + + + if (stanza != NULL) + { + porter = gabble_connection_dup_porter (gabble_conn); + context = g_slice_new0 (_GabbleIMSendMessageCtx); + context->channel = g_object_ref (base); + context->message = g_object_ref (message); + context->token = id; + wocky_porter_send_async (porter, stanza, NULL, + _gabble_im_channel_message_sent_cb, context); + g_object_unref (porter); + g_object_unref (stanza); + } + else + { + tp_message_mixin_sent (object, message, flags, NULL, error); + g_error_free (error); + } - gabble_message_util_send_message (object, - GABBLE_CONNECTION (tp_base_channel_get_connection (base)), - message, flags, 0, state, priv->peer_jid, priv->send_nick); if (priv->send_nick) priv->send_nick = FALSE; } -/** - * _gabble_im_channel_receive +static TpMessage * +build_message ( + GabbleIMChannel *self, + TpChannelTextMessageType type, + time_t timestamp, + const char *text) +{ + TpBaseChannel *base_chan = (TpBaseChannel *) self; + TpBaseConnection *base_conn = tp_base_channel_get_connection (base_chan); + TpMessage *msg = tp_cm_message_new (base_conn, 2); + + if (type != TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL) + tp_message_set_uint32 (msg, 0, "message-type", type); + + if (timestamp != 0) + tp_message_set_int64 (msg, 0, "message-sent", timestamp); + + /* Body */ + tp_message_set_string (msg, 1, "content-type", "text/plain"); + tp_message_set_string (msg, 1, "content", text); + + return msg; +} + +/* + * _gabble_im_channel_receive: + * @chan: a channel + * @type: the message type + * @from: the full JID we received the message from + * @timestamp: the time at which the message was sent (not the time it was + * received) + * @id: the id='' attribute from the <message/> stanza, if any + * @text: the plaintext body of the message + * @send_error: the reason why sending @text to @sender failed, or + * GABBLE_TEXT_CHANNEL_SEND_NO_ERROR if this call is not to report + * a failure to send. + * @delivery_status: if @send_error is GABBLE_TEXT_CHANNEL_SEND_NO_ERROR, + * ignored; else the delivery status to attach to the report. + * @state: a #TpChannelChatState, or -1 if there was no chat state in the + * message. * + * Shoves an incoming message into @chan, possibly updating the chat state at + * the same time; or maybe this is a delivery report? Who knows! It's a magical + * adventure. */ void _gabble_im_channel_receive (GabbleIMChannel *chan, TpChannelTextMessageType type, - TpHandle sender, const char *from, time_t timestamp, const gchar *id, const char *text, - TpChannelTextSendError send_error, - TpDeliveryStatus delivery_status, gint state) { GabbleIMChannelPrivate *priv; TpBaseChannel *base_chan; - TpBaseConnection *base_conn; + TpHandle peer; TpMessage *msg; - gchar *tmp; g_assert (GABBLE_IS_IM_CHANNEL (chan)); priv = chan->priv; base_chan = (TpBaseChannel *) chan; - base_conn = tp_base_channel_get_connection (base_chan); + peer = tp_base_channel_get_target_handle (base_chan); - if (send_error == GABBLE_TEXT_CHANNEL_SEND_NO_ERROR) + /* update peer's full JID if it's changed */ + if (tp_strdiff (from, priv->peer_jid)) { - /* update peer's full JID if it's changed */ - if (tp_strdiff (from, priv->peer_jid)) - { - g_free (priv->peer_jid); - priv->peer_jid = g_strdup (from); - } - - if (state == -1) - { - priv->chat_states_supported = CHAT_STATES_NOT_SUPPORTED; - } - else - { - _gabble_im_channel_state_receive (chan, state); - } + g_free (priv->peer_jid); + priv->peer_jid = g_strdup (from); } - else - { - /* strip off the resource (if any), since we just failed to send to it */ - char *slash = strchr (priv->peer_jid, '/'); - if (slash != NULL) - *slash = '\0'; + if (state == -1) + priv->chat_states_supported = CHAT_STATES_NOT_SUPPORTED; + else + _gabble_im_channel_state_receive (chan, state); - priv->chat_states_supported = CHAT_STATES_UNKNOWN; - } + msg = build_message (chan, type, timestamp, text); + tp_cm_message_set_sender (msg, peer); + tp_message_set_int64 (msg, 0, "message-received", time (NULL)); - msg = tp_cm_message_new (base_conn, 2); + if (id != NULL) + tp_message_set_string (msg, 0, "message-token", id); - /* Header */ - if (type != TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL) - tp_message_set_uint32 (msg, 0, "message-type", type); + tp_message_mixin_take_received (G_OBJECT (chan), msg); +} - if (timestamp != 0) - tp_message_set_int64 (msg, 0, "message-sent", timestamp); +void +_gabble_im_channel_report_delivery ( + GabbleIMChannel *self, + TpChannelTextMessageType type, + time_t timestamp, + const gchar *id, + const char *text, + TpChannelTextSendError send_error, + TpDeliveryStatus delivery_status) +{ + GabbleIMChannelPrivate *priv; + TpBaseChannel *base_chan = (TpBaseChannel *) self; + TpBaseConnection *base_conn; + TpHandle peer; + TpMessage *msg, *delivery_report; + gchar *tmp; - /* Body */ - tp_message_set_string (msg, 1, "content-type", "text/plain"); - tp_message_set_string (msg, 1, "content", text); + g_return_if_fail (GABBLE_IS_IM_CHANNEL (self)); + priv = self->priv; + peer = tp_base_channel_get_target_handle (base_chan); + base_conn = tp_base_channel_get_connection (base_chan); - if (send_error == GABBLE_TEXT_CHANNEL_SEND_NO_ERROR) + if (send_error != GABBLE_TEXT_CHANNEL_SEND_NO_ERROR) { - tp_cm_message_set_sender (msg, sender); - tp_message_set_int64 (msg, 0, "message-received", time (NULL)); + /* strip off the resource (if any), since we just failed to send to it */ + char *slash = strchr (priv->peer_jid, '/'); - if (id != NULL) - tp_message_set_string (msg, 0, "message-token", id); + if (slash != NULL) + *slash = '\0'; - tp_message_mixin_take_received (G_OBJECT (chan), msg); + priv->chat_states_supported = CHAT_STATES_UNKNOWN; } - else - { - TpMessage *delivery_report = tp_cm_message_new (base_conn, 1); - tp_message_set_uint32 (delivery_report, 0, "message-type", - TP_CHANNEL_TEXT_MESSAGE_TYPE_DELIVERY_REPORT); - tp_cm_message_set_sender (delivery_report, sender); - tp_message_set_int64 (delivery_report, 0, "message-received", - time (NULL)); + msg = build_message (self, type, timestamp, text); + delivery_report = tp_cm_message_new (base_conn, 1); + tp_message_set_uint32 (delivery_report, 0, "message-type", + TP_CHANNEL_TEXT_MESSAGE_TYPE_DELIVERY_REPORT); + tp_cm_message_set_sender (delivery_report, peer); + tp_message_set_int64 (delivery_report, 0, "message-received", + time (NULL)); - tmp = gabble_generate_id (); - tp_message_set_string (delivery_report, 0, "message-token", tmp); - g_free (tmp); + tmp = gabble_generate_id (); + tp_message_set_string (delivery_report, 0, "message-token", tmp); + g_free (tmp); - tp_message_set_uint32 (delivery_report, 0, "delivery-status", - delivery_status); - tp_message_set_uint32 (delivery_report, 0, "delivery-error", send_error); + tp_message_set_uint32 (delivery_report, 0, "delivery-status", + delivery_status); + tp_message_set_uint32 (delivery_report, 0, "delivery-error", send_error); - if (id != NULL) - tp_message_set_string (delivery_report, 0, "delivery-token", id); + if (id != NULL) + tp_message_set_string (delivery_report, 0, "delivery-token", id); - /* We're getting a send error, so the original sender of the echoed - * message must be us! */ - tp_cm_message_set_sender (msg, base_conn->self_handle); + /* This is a delivery report, so the original sender of the echoed message + * must be us! */ + tp_cm_message_set_sender (msg, base_conn->self_handle); - /* Since this is a send error, we can trust the id on the message. */ - if (id != NULL) - tp_message_set_string (msg, 0, "message-token", id); + /* Since this is a delivery report, we can trust the id on the message. */ + if (id != NULL) + tp_message_set_string (msg, 0, "message-token", id); - tp_cm_message_take_message (delivery_report, 0, "delivery-echo", msg); - - tp_message_mixin_take_received (G_OBJECT (chan), delivery_report); - } + tp_cm_message_take_message (delivery_report, 0, "delivery-echo", msg); + tp_message_mixin_take_received (G_OBJECT (self), delivery_report); } /** diff --git a/src/im-channel.h b/src/im-channel.h index 17d6271b0..fdbae19bf 100644 --- a/src/im-channel.h +++ b/src/im-channel.h @@ -66,17 +66,23 @@ GType gabble_im_channel_get_type (void); void _gabble_im_channel_receive (GabbleIMChannel *chan, TpChannelTextMessageType type, - TpHandle sender, const char *from, time_t timestamp, const char *id, const char *text, - TpChannelTextSendError send_error, - TpDeliveryStatus delivery_status, gint state); void _gabble_im_channel_state_receive (GabbleIMChannel *chan, TpChannelChatState state); +void _gabble_im_channel_report_delivery ( + GabbleIMChannel *self, + TpChannelTextMessageType type, + time_t timestamp, + const gchar *id, + const char *text, + TpChannelTextSendError send_error, + TpDeliveryStatus delivery_status); + G_END_DECLS #endif /* #ifndef __GABBLE_IM_CHANNEL_H__*/ diff --git a/src/im-factory.c b/src/im-factory.c index 08f19f6c4..6932d2201 100644 --- a/src/im-factory.c +++ b/src/im-factory.c @@ -20,8 +20,6 @@ #include "config.h" #include "im-factory.h" -#define DBUS_API_SUBJECT_TO_CHANGE - #include <string.h> #include <dbus/dbus-glib.h> @@ -31,17 +29,19 @@ #include <telepathy-glib/dbus.h> #include <telepathy-glib/gtypes.h> #include <telepathy-glib/interfaces.h> +#include <wocky/wocky-c2s-porter.h> #define DEBUG_FLAG GABBLE_DEBUG_IM #include "extensions/extensions.h" -#include "caps-channel-manager.h" +#include "gabble/caps-channel-manager.h" #include "connection.h" #include "debug.h" #include "disco.h" #include "im-channel.h" #include "message-util.h" +#include "namespaces.h" static void channel_manager_iface_init (gpointer, gpointer); static void caps_channel_manager_iface_init (gpointer, gpointer); @@ -70,11 +70,6 @@ struct _GabbleImFactoryPrivate gboolean dispose_has_run; }; -#define GABBLE_IM_FACTORY_GET_PRIVATE(o) \ - (G_TYPE_INSTANCE_GET_PRIVATE ((o), GABBLE_TYPE_IM_FACTORY,\ - GabbleImFactoryPrivate)) - - static void gabble_im_factory_init (GabbleImFactory *self) { @@ -93,20 +88,24 @@ gabble_im_factory_init (GabbleImFactory *self) static void connection_status_changed_cb (GabbleConnection *conn, guint status, guint reason, GabbleImFactory *self); +static void porter_available_cb ( + GabbleConnection *conn, + WockyPorter *porter, + gpointer user_data); - -static GObject * -gabble_im_factory_constructor (GType type, guint n_props, - GObjectConstructParam *props) +static void +gabble_im_factory_constructed (GObject *obj) { - GObject *obj = G_OBJECT_CLASS (gabble_im_factory_parent_class)-> - constructor (type, n_props, props); GabbleImFactory *self = GABBLE_IM_FACTORY (obj); + GObjectClass *parent_class = gabble_im_factory_parent_class; + + if (parent_class->constructed != NULL) + parent_class->constructed (obj); self->priv->status_changed_id = g_signal_connect (self->priv->conn, "status-changed", (GCallback) connection_status_changed_cb, obj); - - return obj; + tp_g_signal_connect_object (self->priv->conn, + "porter-available", (GCallback) porter_available_cb, obj, 0); } @@ -117,7 +116,7 @@ static void gabble_im_factory_dispose (GObject *object) { GabbleImFactory *fac = GABBLE_IM_FACTORY (object); - GabbleImFactoryPrivate *priv = GABBLE_IM_FACTORY_GET_PRIVATE (fac); + GabbleImFactoryPrivate *priv = fac->priv; if (priv->dispose_has_run) return; @@ -139,7 +138,7 @@ gabble_im_factory_get_property (GObject *object, GParamSpec *pspec) { GabbleImFactory *fac = GABBLE_IM_FACTORY (object); - GabbleImFactoryPrivate *priv = GABBLE_IM_FACTORY_GET_PRIVATE (fac); + GabbleImFactoryPrivate *priv = fac->priv; switch (property_id) { case PROP_CONNECTION: @@ -158,7 +157,7 @@ gabble_im_factory_set_property (GObject *object, GParamSpec *pspec) { GabbleImFactory *fac = GABBLE_IM_FACTORY (object); - GabbleImFactoryPrivate *priv = GABBLE_IM_FACTORY_GET_PRIVATE (fac); + GabbleImFactoryPrivate *priv = fac->priv; switch (property_id) { case PROP_CONNECTION: @@ -179,7 +178,7 @@ gabble_im_factory_class_init (GabbleImFactoryClass *gabble_im_factory_class) g_type_class_add_private (gabble_im_factory_class, sizeof (GabbleImFactoryPrivate)); - object_class->constructor = gabble_im_factory_constructor; + object_class->constructed = gabble_im_factory_constructed; object_class->dispose = gabble_im_factory_dispose; object_class->get_property = gabble_im_factory_get_property; @@ -193,11 +192,10 @@ gabble_im_factory_class_init (GabbleImFactoryClass *gabble_im_factory_class) } -static GabbleIMChannel *new_im_channel (GabbleImFactory *fac, - TpHandle handle, TpHandle initiator, gpointer request_token); - -static void im_channel_closed_cb (GabbleIMChannel *chan, gpointer user_data); - +static GabbleIMChannel *get_channel_for_incoming_message ( + GabbleImFactory *self, + const gchar *jid, + gboolean create_if_missing); /** * im_factory_message_cb: @@ -211,18 +209,14 @@ im_factory_message_cb (LmMessageHandler *handler, gpointer user_data) { GabbleImFactory *fac = GABBLE_IM_FACTORY (user_data); - GabbleImFactoryPrivate *priv = GABBLE_IM_FACTORY_GET_PRIVATE (fac); - TpBaseConnection *conn = (TpBaseConnection *) priv->conn; - TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (conn, - TP_HANDLE_TYPE_CONTACT); const gchar *from, *body, *id; time_t stamp; TpChannelTextMessageType msgtype; - TpHandle handle; GabbleIMChannel *chan; gint state; TpChannelTextSendError send_error; TpDeliveryStatus delivery_status; + gboolean create_if_missing; if (!gabble_message_util_parse_incoming_message (message, &from, &stamp, &msgtype, &id, &body, &state, &send_error, &delivery_status)) @@ -233,62 +227,46 @@ im_factory_message_cb (LmMessageHandler *handler, return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; } - handle = tp_handle_ensure (contact_repo, from, NULL, NULL); - if (handle == 0) + /* We don't want to open up a channel for the sole purpose of reporting a + * send error, nor if this is just a chat state notification. + */ + create_if_missing = + (send_error == GABBLE_TEXT_CHANNEL_SEND_NO_ERROR) && + (body != NULL); + chan = get_channel_for_incoming_message (fac, from, create_if_missing); + if (chan == NULL) { - STANZA_DEBUG (message, "ignoring message node from malformed jid"); + if (create_if_missing) + STANZA_DEBUG (message, "ignoring message from non-contact JID"); + else + DEBUG ("ignoring message error or chat state notification from '%s': " + "no existing channel", from); + return LM_HANDLER_RESULT_REMOVE_MESSAGE; } - chan = g_hash_table_lookup (priv->channels, GUINT_TO_POINTER (handle)); - - if (chan == NULL) + if (send_error != GABBLE_TEXT_CHANNEL_SEND_NO_ERROR) { - if (send_error != GABBLE_TEXT_CHANNEL_SEND_NO_ERROR) - { - DEBUG ("ignoring message error; no sending channel"); - tp_handle_unref (contact_repo, handle); - return LM_HANDLER_RESULT_REMOVE_MESSAGE; - } - if (body == NULL) { - /* don't create a new channel if all we have is a chat state */ - DEBUG ("ignoring message without body; no existing channel"); - tp_handle_unref (contact_repo, handle); + DEBUG ("ignoring error sending chat state to %s", from); return LM_HANDLER_RESULT_REMOVE_MESSAGE; } - DEBUG ("found no IM channel, creating one"); + DEBUG ("got error sending to %s, msgtype %u, body:\n%s", + from, msgtype, body); - chan = new_im_channel (fac, handle, handle, NULL); + _gabble_im_channel_report_delivery (chan, msgtype, stamp, id, body, + send_error, delivery_status); } - - g_assert (chan != NULL); - - /* now the channel is referencing the handle, so if we unref it, that's - * not a problem */ - tp_handle_unref (contact_repo, handle); - - if (send_error != GABBLE_TEXT_CHANNEL_SEND_NO_ERROR) + else if (body != NULL) { - if (body == NULL) - { - DEBUG ("ignoring error sending chat state to %s (handle %u)", from, - handle); - return LM_HANDLER_RESULT_REMOVE_MESSAGE; - } - - DEBUG ("got error sending to %s (handle %u), msgtype %u, body:\n%s", - from, handle, msgtype, body); + _gabble_im_channel_receive (chan, msgtype, from, stamp, id, body, state); + } + else if (state != -1) + { + _gabble_im_channel_state_receive (chan, (TpChannelChatState) state); } - - if (body != NULL) - _gabble_im_channel_receive (chan, msgtype, handle, from, stamp, id, body, - send_error, delivery_status, state); - else if (state != -1 && send_error == GABBLE_TEXT_CHANNEL_SEND_NO_ERROR) - _gabble_im_channel_state_receive (chan, (TpChannelChatState) state); - return LM_HANDLER_RESULT_REMOVE_MESSAGE; } @@ -305,7 +283,7 @@ static void im_channel_closed_cb (GabbleIMChannel *chan, gpointer user_data) { GabbleImFactory *self = GABBLE_IM_FACTORY (user_data); - GabbleImFactoryPrivate *priv = GABBLE_IM_FACTORY_GET_PRIVATE (self); + GabbleImFactoryPrivate *priv = self->priv; TpHandle contact_handle; gboolean really_destroyed; @@ -338,40 +316,43 @@ im_channel_closed_cb (GabbleIMChannel *chan, gpointer user_data) } } -/** - * new_im_channel +/* + * new_im_channel: + * @fac: the factory + * @handle: a contact handle, for whom a channel must not yet exist + * @request_token: if the channel is being created in response to a channel + * request, the associated request token; otherwise, NULL. + * + * Creates a new 1-1 text channel to a contact. Must only be called when no 1-1 + * text channel is already open to that contact. + * + * Returns: (transfer none): a freshly-constructed channel */ static GabbleIMChannel * new_im_channel (GabbleImFactory *fac, TpHandle handle, - TpHandle initiator, gpointer request_token) { - GabbleImFactoryPrivate *priv; - TpBaseConnection *conn; + GabbleImFactoryPrivate *priv = fac->priv; + TpBaseConnection *conn = (TpBaseConnection *) priv->conn; GabbleIMChannel *chan; - char *object_path; GSList *request_tokens; + TpHandle initiator; - g_return_val_if_fail (GABBLE_IS_IM_FACTORY (fac), NULL); g_return_val_if_fail (handle != 0, NULL); - g_return_val_if_fail (initiator != 0, NULL); - priv = GABBLE_IM_FACTORY_GET_PRIVATE (fac); - conn = (TpBaseConnection *) priv->conn; + if (request_token != NULL) + initiator = conn->self_handle; + else + initiator = handle; - object_path = g_strdup_printf ("%s/ImChannel%u", - conn->object_path, handle); chan = g_object_new (GABBLE_TYPE_IM_CHANNEL, "connection", priv->conn, - "object-path", object_path, "handle", handle, "initiator-handle", initiator, "requested", (handle != initiator), NULL); - DEBUG ("object path %s", object_path); tp_base_channel_register ((TpBaseChannel *) chan); - g_free (object_path); g_signal_connect (chan, "closed", (GCallback) im_channel_closed_cb, fac); @@ -390,13 +371,55 @@ new_im_channel (GabbleImFactory *fac, return chan; } +/* + * get_channel_for_incoming_message: + * @self: a factory + * @jid: a contact's JID + * @create_if_missing: if %TRUE, a new channel will be created if there is no + * existing channel to @jid + * + * Retrieves a 1-1 text channel to a particular contact. If no channel is open + * to @jid, it will be created only if @create_if_missing is %TRUE. If @jid is + * not of the form 'user@domain' (optionally with a resource), no channel will + * be opened. + * + * Returns: an IM channel to @jid, or %NULL + */ +static GabbleIMChannel * +get_channel_for_incoming_message ( + GabbleImFactory *self, + const gchar *jid, + gboolean create_if_missing) +{ + GabbleImFactoryPrivate *priv = self->priv; + TpBaseConnection *conn = (TpBaseConnection *) priv->conn; + TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (conn, + TP_HANDLE_TYPE_CONTACT); + TpHandle handle; + GabbleIMChannel *chan; + + g_return_val_if_fail (jid != NULL, NULL); + + handle = tp_handle_ensure (contact_repo, jid, NULL, NULL); + if (handle == 0) + return NULL; + + chan = g_hash_table_lookup (priv->channels, GUINT_TO_POINTER (handle)); + if (chan != NULL) + return chan; + else if (create_if_missing) + return new_im_channel (self, handle, NULL); + else + return NULL; +} + static void gabble_im_factory_close_all (GabbleImFactory *self) { /* Use a temporary variable (the macro does this) because we don't want * im_channel_closed_cb to remove the channel from the hash table a * second time */ - tp_clear_pointer (&self->priv->channels, g_hash_table_destroy); + tp_clear_pointer (&self->priv->channels, g_hash_table_unref); if (self->priv->status_changed_id != 0) { @@ -441,6 +464,92 @@ connection_status_changed_cb (GabbleConnection *conn, } } +static gboolean +im_factory_own_message_cb ( + WockyPorter *porter, + WockyStanza *stanza, + gpointer user_data) +{ + GabbleImFactory *self = GABBLE_IM_FACTORY (user_data); + WockyNode *own_message, *body; + const gchar *to; + gboolean sent_locally; + GabbleIMChannel *chan; + + /* Our stanza filter should guarantee that these are present. */ + own_message = wocky_node_get_child (wocky_stanza_get_top_node (stanza), + "own-message"); + g_return_val_if_fail (own_message != NULL, FALSE); + body = wocky_node_get_child (own_message, "body"); + g_return_val_if_fail (body != NULL, FALSE); + + to = wocky_node_get_attribute (own_message, "to"); + if (to == NULL) + { + DEBUG ("own-message missing to='' attribute; ignoring"); + return FALSE; + } + + /* If self='true', the message was sent by the local user on this machine, + * rather than by the local user on some other machine. We don't really have + * a good way to show this in Messages. Also we don't get told the id='' of + * the original message, which is annoying. + */ + sent_locally = !tp_strdiff ("true", + wocky_node_get_attribute (own_message, "self")); + DEBUG ("this report is for a message to '%s', sent %s", + to, sent_locally ? "locally" : "remotely"); + + /* Don't create a channel for the sole purpose of reporting an own-message. + * This is consistent with not creating a channel to report send errors + * (given that both are delivery reports). + */ + chan = get_channel_for_incoming_message (self, to, FALSE); + if (chan != NULL) + _gabble_im_channel_report_delivery (chan, + TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL, 0, NULL, body->content, + GABBLE_TEXT_CHANNEL_SEND_NO_ERROR, TP_DELIVERY_STATUS_ACCEPTED); + else + DEBUG ("no channel for '%s'; not spawning one just for the delivery report", + to); + + wocky_porter_acknowledge_iq (porter, stanza, NULL); + return TRUE; +} + +static void +porter_available_cb ( + GabbleConnection *conn, + WockyPorter *porter, + gpointer user_data) +{ + GabbleImFactory *self = GABBLE_IM_FACTORY (user_data); + gchar *stream_server; + + g_object_get (conn, "stream-server", &stream_server, NULL); + + if (!tp_strdiff (stream_server, "chat.facebook.com")) + { + wocky_porter_register_handler_from ( + porter, WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_SET, + /* We could use _from_server() if that accepted messages from our + * JID's domain, not just from bare JID, full JID, or no sender + * specified—which would allow the extension to work on other servers + * too—but it doesn't. + */ + stream_server, + WOCKY_PORTER_HANDLER_PRIORITY_NORMAL, + im_factory_own_message_cb, self, + '(', + "own-message", ':', NS_FACEBOOK_MESSAGES, + '(', "body", ')', + ')', NULL); + } + + g_free (stream_server); +} + + static void gabble_im_factory_get_contact_caps (GabbleCapsChannelManager *manager, TpHandle handle, @@ -491,7 +600,7 @@ gabble_im_factory_get_contact_caps (GabbleCapsChannelManager *manager, 1, text_allowed_properties, G_MAXUINT); - g_hash_table_destroy (fixed_properties); + g_hash_table_unref (fixed_properties); g_ptr_array_add (arr, g_value_get_boxed (&monster)); } @@ -560,7 +669,7 @@ gabble_im_factory_type_foreach_channel_class (GType type, func (type, table, im_channel_allowed_properties, user_data); - g_hash_table_destroy (table); + g_hash_table_unref (table); } @@ -570,7 +679,6 @@ gabble_im_factory_requestotron (GabbleImFactory *self, GHashTable *request_properties, gboolean require_new) { - TpBaseConnection *base_conn = (TpBaseConnection *) self->priv->conn; TpHandle handle; GError *error = NULL; TpExportableChannel *channel; @@ -598,7 +706,7 @@ gabble_im_factory_requestotron (GabbleImFactory *self, if (channel == NULL) { - new_im_channel (self, handle, base_conn->self_handle, request_token); + new_im_channel (self, handle, request_token); return TRUE; } diff --git a/src/jingle-content.c b/src/jingle-content.c index 9e8194122..cfa339deb 100644 --- a/src/jingle-content.c +++ b/src/jingle-content.c @@ -508,6 +508,8 @@ gabble_jingle_content_parse_add (GabbleJingleContent *c, GabbleJingleTransportIface *trans = NULL; JingleDialect dialect = gabble_jingle_session_get_dialect (c->session); + priv->created_by_us = FALSE; + desc_node = wocky_node_get_child (content_node, "description"); trans_node = wocky_node_get_child (content_node, "transport"); creator = wocky_node_get_attribute (content_node, "creator"); @@ -550,6 +552,19 @@ gabble_jingle_content_parse_add (GabbleJingleContent *c, } else { + if (creator == NULL && + gabble_jingle_session_peer_has_quirk (c->session, + QUIRK_GOOGLE_WEBMAIL_CLIENT)) + { + if (gabble_jingle_content_creator_is_initiator (c)) + creator = "initiator"; + else + creator = "responder"; + + DEBUG ("Working around GMail omitting creator=''; assuming '%s'", + creator); + } + if ((trans_node == NULL) || (creator == NULL) || (name == NULL)) { SET_BAD_REQ ("missing required content attributes or elements"); @@ -578,7 +593,6 @@ gabble_jingle_content_parse_add (GabbleJingleContent *c, priv->transport_ns = g_strdup (ns); } - priv->created_by_us = FALSE; if (senders == NULL) priv->senders = get_default_senders (c); else diff --git a/src/jingle-factory.c b/src/jingle-factory.c index a24de0740..02b251df2 100644 --- a/src/jingle-factory.c +++ b/src/jingle-factory.c @@ -18,20 +18,20 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include "config.h" #include "jingle-factory.h" #include <stdio.h> #include <stdlib.h> -#include <string.h> #include <glib.h> -#include <loudmouth/loudmouth.h> -#include <libsoup/soup.h> +#include <wocky/wocky-c2s-porter.h> #include <wocky/wocky-utils.h> #define DEBUG_FLAG GABBLE_DEBUG_MEDIA #include "connection.h" +#include "conn-util.h" #include "debug.h" #include "gabble-signals-marshal.h" #include "jingle-share.h" @@ -43,6 +43,8 @@ #include "namespaces.h" #include "util.h" +#include "google-relay.h" + G_DEFINE_TYPE(GabbleJingleFactory, gabble_jingle_factory, G_TYPE_OBJECT); /* signal enum */ @@ -65,14 +67,15 @@ enum struct _GabbleJingleFactoryPrivate { GabbleConnection *conn; - LmMessageHandler *jingle_cb; - LmMessageHandler *jingle_info_cb; + guint jingle_handler_id; + guint jingle_info_handler_id; GHashTable *content_types; GHashTable *transports; /* instances of SESSION_MAP_KEY_FORMAT => GabbleJingleSession. */ GHashTable *sessions; - SoupSession *soup; + + GabbleGoogleRelayResolver *google_resolver; gchar *stun_server; guint16 stun_port; @@ -89,8 +92,10 @@ struct _GabbleJingleFactoryPrivate gboolean dispose_has_run; }; -static LmHandlerResult jingle_cb (LmMessageHandler *handler, - LmConnection *lmconn, LmMessage *message, gpointer user_data); +static gboolean jingle_cb ( + WockyPorter *porter, + WockyStanza *msg, + gpointer user_data); static GabbleJingleSession *create_session (GabbleJingleFactory *fac, const gchar *sid, TpHandle peer, @@ -105,8 +110,10 @@ static void session_terminated_cb (GabbleJingleSession *sess, static void connection_status_changed_cb (GabbleConnection *conn, guint status, guint reason, GabbleJingleFactory *self); - -#define RELAY_HTTP_TIMEOUT 5 +static void connection_porter_available_cb ( + GabbleConnection *conn, + WockyPorter *porter, + gpointer user_data); static gboolean test_mode = FALSE; @@ -133,8 +140,6 @@ gabble_jingle_factory_init (GabbleJingleFactory *obj) priv->content_types = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL); - priv->jingle_cb = NULL; - priv->conn = NULL; priv->dispose_has_run = FALSE; priv->relay_http_port = 80; @@ -253,54 +258,18 @@ take_stun_server (GabbleJingleFactory *self, } -static LmHandlerResult -got_jingle_info_stanza (GabbleJingleFactory *fac, - LmMessage *message) +static void +got_jingle_info_stanza ( + GabbleJingleFactory *fac, + WockyStanza *stanza) { - GabbleJingleFactoryPrivate *priv = fac->priv; - LmMessageSubType sub_type; - WockyNode *query_node, *node; - const gchar *from = wocky_stanza_get_from (message); - GError *error = NULL; - - if (from != NULL) - { - TpBaseConnection *base_conn = TP_BASE_CONNECTION (priv->conn); - TpHandleRepoIface *contact_repo = tp_base_connection_get_handles ( - base_conn, TP_HANDLE_TYPE_CONTACT); - TpHandle sender = tp_handle_lookup (contact_repo, from, NULL, NULL); + WockyNode *node, *query_node; - if (sender != base_conn->self_handle) - { - DEBUG ("ignoring jingleinfo from '%s', not ourself nor the server", - from); - return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; - } - } - - query_node = lm_message_node_get_child_with_namespace ( - wocky_stanza_get_top_node (message), "query", NS_GOOGLE_JINGLE_INFO); + query_node = wocky_node_get_child_ns ( + wocky_stanza_get_top_node (stanza), "query", NS_GOOGLE_JINGLE_INFO); if (query_node == NULL) - return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; - - sub_type = lm_message_get_sub_type (message); - - if (wocky_stanza_extract_errors (message, NULL, &error, NULL, NULL)) - { - DEBUG ("jingle info error: %s", - wocky_xmpp_stanza_error_to_string (error)); - g_error_free (error); - return LM_HANDLER_RESULT_REMOVE_MESSAGE; - } - - if (sub_type != LM_MESSAGE_SUB_TYPE_RESULT && - sub_type != LM_MESSAGE_SUB_TYPE_SET) - { - DEBUG ("jingle info: unexpected IQ type, ignoring"); - - return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; - } + return; if (fac->priv->get_stun_from_jingle) node = wocky_node_get_child (query_node, "stun"); @@ -332,19 +301,17 @@ got_jingle_info_stanza (GabbleJingleFactory *fac, } } +#ifdef ENABLE_GOOGLE_RELAY node = wocky_node_get_child (query_node, "relay"); if (node != NULL) { - WockyNode *subnode; - - subnode = wocky_node_get_child (node, "token"); + WockyNode *subnode = wocky_node_get_child (node, "token"); if (subnode != NULL) { - const gchar *token; + const gchar *token = subnode->content; - token = subnode->content; if (token != NULL) { DEBUG ("jingle info: got Google relay token %s", token); @@ -414,75 +381,61 @@ got_jingle_info_stanza (GabbleJingleFactory *fac, } } - - if (sub_type == LM_MESSAGE_SUB_TYPE_SET) - { - _gabble_connection_acknowledge_set_iq (priv->conn, message); - } - - return LM_HANDLER_RESULT_REMOVE_MESSAGE; +#endif /* ENABLE_GOOGLE_RELAY */ } -/* - * jingle_info_cb - * - * Called by loudmouth when we get an incoming <iq>. This handler - * is concerned only with Jingle info queries. - */ -static LmHandlerResult -jingle_info_cb (LmMessageHandler *handler, - LmConnection *lmconn, - LmMessage *message, - gpointer user_data) +static gboolean +jingle_info_cb ( + WockyPorter *porter, + WockyStanza *stanza, + gpointer user_data) { GabbleJingleFactory *fac = GABBLE_JINGLE_FACTORY (user_data); - return got_jingle_info_stanza (fac, message); + got_jingle_info_stanza (fac, stanza); + wocky_porter_acknowledge_iq (porter, stanza, NULL); + + return TRUE; } -static LmHandlerResult -jingle_info_reply_cb (GabbleConnection *conn, - LmMessage *sent_msg, - LmMessage *reply_msg, - GObject *factory_obj, +static void +jingle_info_reply_cb ( + GObject *source, + GAsyncResult *result, gpointer user_data) { - GabbleJingleFactory *fac = GABBLE_JINGLE_FACTORY (factory_obj); + GabbleJingleFactory *fac = GABBLE_JINGLE_FACTORY (user_data); + WockyStanza *reply = NULL; + GError *error = NULL; - return got_jingle_info_stanza (fac, reply_msg); + if (conn_util_send_iq_finish (GABBLE_CONNECTION (source), result, &reply, + &error)) + { + got_jingle_info_stanza (fac, reply); + } + else + { + DEBUG ("jingle info request failed: %s", error->message); + g_clear_error (&error); + } + + tp_clear_object (&reply); } static void jingle_info_send_request (GabbleJingleFactory *fac) { GabbleJingleFactoryPrivate *priv = fac->priv; - TpBaseConnection *base = (TpBaseConnection *) priv->conn; - LmMessage *msg; - WockyNode *node; - const gchar *jid; - GError *error = NULL; - TpHandleRepoIface *contact_handles = tp_base_connection_get_handles (base, - TP_HANDLE_TYPE_CONTACT); + const gchar *jid = conn_util_get_bare_self_jid (priv->conn); + WockyStanza *stanza = wocky_stanza_build ( + WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_GET, NULL, jid, + '(', "query", ':', NS_GOOGLE_JINGLE_INFO, ')', NULL); - jid = tp_handle_inspect (contact_handles, base->self_handle); - msg = lm_message_new_with_sub_type (jid, LM_MESSAGE_TYPE_IQ, - LM_MESSAGE_SUB_TYPE_GET); + conn_util_send_iq_async (priv->conn, stanza, NULL, jingle_info_reply_cb, fac); - node = wocky_node_add_child_with_content ( - wocky_stanza_get_top_node (msg), "query", NULL); - node->ns = g_quark_from_string (NS_GOOGLE_JINGLE_INFO); - - if (!_gabble_connection_send_with_reply (priv->conn, msg, - jingle_info_reply_cb, G_OBJECT (fac), fac, &error)) - { - DEBUG ("jingle info send failed: %s\n", error->message); - g_error_free (error); - } - - lm_message_unref (msg); + g_object_unref (stanza); } - static void gabble_jingle_factory_dispose (GObject *object) { @@ -495,10 +448,10 @@ gabble_jingle_factory_dispose (GObject *object) DEBUG ("dispose called"); priv->dispose_has_run = TRUE; - tp_clear_object (&priv->soup); - tp_clear_pointer (&priv->sessions, g_hash_table_destroy); - tp_clear_pointer (&priv->content_types, g_hash_table_destroy); - tp_clear_pointer (&priv->transports, g_hash_table_destroy); + tp_clear_pointer (&priv->google_resolver, gabble_google_relay_resolver_destroy); + tp_clear_pointer (&priv->sessions, g_hash_table_unref); + tp_clear_pointer (&priv->content_types, g_hash_table_unref); + tp_clear_pointer (&priv->transports, g_hash_table_unref); tp_clear_pointer (&priv->stun_server, g_free); tp_clear_pointer (&priv->fallback_stun_server, g_free); tp_clear_pointer (&priv->relay_token, g_free); @@ -546,32 +499,26 @@ gabble_jingle_factory_set_property (GObject *object, } } -static GObject * -gabble_jingle_factory_constructor (GType type, - guint n_props, - GObjectConstructParam *props) +static void +gabble_jingle_factory_constructed (GObject *obj) { - GObject *obj; - GabbleJingleFactory *self; - GabbleJingleFactoryPrivate *priv; - - obj = G_OBJECT_CLASS (gabble_jingle_factory_parent_class)-> - constructor (type, n_props, props); + GabbleJingleFactory *self = GABBLE_JINGLE_FACTORY (obj); + GabbleJingleFactoryPrivate *priv = self->priv; + GObjectClass *parent = G_OBJECT_CLASS (gabble_jingle_factory_parent_class); - self = GABBLE_JINGLE_FACTORY (obj); - priv = self->priv; + if (parent->constructed != NULL) + parent->constructed (obj); - /* FIXME: why was this in _constructed in media factory? */ gabble_signal_connect_weak (priv->conn, "status-changed", (GCallback) connection_status_changed_cb, G_OBJECT (self)); + gabble_signal_connect_weak (priv->conn, "porter-available", + (GCallback) connection_porter_available_cb, G_OBJECT (self)); jingle_share_register (self); jingle_media_rtp_register (self); jingle_transport_google_register (self); jingle_transport_rawudp_register (self); jingle_transport_iceudp_register (self); - - return obj; } static void @@ -582,7 +529,7 @@ gabble_jingle_factory_class_init (GabbleJingleFactoryClass *cls) g_type_class_add_private (cls, sizeof (GabbleJingleFactoryPrivate)); - object_class->constructor = gabble_jingle_factory_constructor; + object_class->constructed = gabble_jingle_factory_constructed; object_class->get_property = gabble_jingle_factory_get_property; object_class->set_property = gabble_jingle_factory_set_property; object_class->dispose = gabble_jingle_factory_dispose; @@ -619,22 +566,6 @@ connection_status_changed_cb (GabbleConnection *conn, { case TP_CONNECTION_STATUS_CONNECTING: g_assert (priv->conn != NULL); - - g_assert (priv->jingle_cb == NULL); - g_assert (priv->jingle_info_cb == NULL); - - priv->jingle_cb = lm_message_handler_new (jingle_cb, - self, NULL); - lm_connection_register_message_handler (priv->conn->lmconn, - priv->jingle_cb, LM_MESSAGE_TYPE_IQ, - LM_HANDLER_PRIORITY_NORMAL); - - priv->jingle_info_cb = lm_message_handler_new ( - jingle_info_cb, self, NULL); - lm_connection_register_message_handler (priv->conn->lmconn, - priv->jingle_info_cb, LM_MESSAGE_TYPE_IQ, - LM_HANDLER_PRIORITY_NORMAL); - break; case TP_CONNECTION_STATUS_CONNECTED: @@ -675,20 +606,44 @@ connection_status_changed_cb (GabbleConnection *conn, break; case TP_CONNECTION_STATUS_DISCONNECTED: - if (priv->jingle_cb != NULL) + if (priv->jingle_handler_id != 0) { - lm_connection_unregister_message_handler (priv->conn->lmconn, - priv->jingle_cb, LM_MESSAGE_TYPE_IQ); - lm_connection_unregister_message_handler (priv->conn->lmconn, - priv->jingle_info_cb, LM_MESSAGE_TYPE_IQ); + WockyPorter *p = wocky_session_get_porter (priv->conn->session); + + wocky_porter_unregister_handler (p, priv->jingle_handler_id); + wocky_porter_unregister_handler (p, priv->jingle_info_handler_id); + priv->jingle_handler_id = 0; + priv->jingle_info_handler_id = 0; } - tp_clear_pointer (&priv->jingle_cb, lm_message_handler_unref); - tp_clear_pointer (&priv->jingle_info_cb, lm_message_handler_unref); break; } } +static void +connection_porter_available_cb ( + GabbleConnection *conn, + WockyPorter *porter, + gpointer user_data) +{ + GabbleJingleFactory *self = GABBLE_JINGLE_FACTORY (user_data); + GabbleJingleFactoryPrivate *priv = self->priv; + + g_assert (priv->jingle_handler_id == 0); + + /* TODO: we could match different dialects here maybe? */ + priv->jingle_handler_id = wocky_porter_register_handler_from_anyone (porter, + WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_SET, + WOCKY_PORTER_HANDLER_PRIORITY_NORMAL, jingle_cb, self, + NULL); + + priv->jingle_info_handler_id = wocky_c2s_porter_register_handler_from_server ( + WOCKY_C2S_PORTER (porter), + WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_SET, + WOCKY_PORTER_HANDLER_PRIORITY_NORMAL, jingle_info_cb, self, + '(', "query", ':', NS_GOOGLE_JINGLE_INFO, ')', NULL); +} + /* The 'session' map is keyed by: * "<peer's handle>\n<peer's jid>\n<session id>" */ @@ -781,11 +736,11 @@ ensure_session (GabbleJingleFactory *self, return sess; } -static LmHandlerResult -jingle_cb (LmMessageHandler *handler, - LmConnection *lmconn, - LmMessage *msg, - gpointer user_data) +static gboolean +jingle_cb ( + WockyPorter *porter, + WockyStanza *msg, + gpointer user_data) { GabbleJingleFactory *self = GABBLE_JINGLE_FACTORY (user_data); GabbleJingleFactoryPrivate *priv = self->priv; @@ -798,10 +753,10 @@ jingle_cb (LmMessageHandler *handler, /* see if it's a jingle message and detect dialect */ sid = gabble_jingle_session_detect (msg, &action, &dialect); - from = wocky_node_get_attribute (lm_message_get_node (msg), "from"); + from = wocky_stanza_get_from (msg); if (sid == NULL || from == NULL) - return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; + return FALSE; sess = ensure_session (self, sid, from, action, dialect, &new_session, &error); @@ -819,7 +774,7 @@ jingle_cb (LmMessageHandler *handler, /* all went well, we can acknowledge the IQ */ _gabble_connection_acknowledge_set_iq (priv->conn, msg); - return LM_HANDLER_RESULT_REMOVE_MESSAGE; + return TRUE; REQUEST_ERROR: g_assert (error != NULL); @@ -831,7 +786,7 @@ REQUEST_ERROR: if (sess != NULL && new_session) gabble_jingle_session_terminate (sess, JINGLE_REASON_UNKNOWN, NULL, NULL); - return LM_HANDLER_RESULT_REMOVE_MESSAGE; + return TRUE; } /* @@ -987,204 +942,6 @@ gabble_jingle_factory_get_stun_server (GabbleJingleFactory *self, return TRUE; } -typedef struct -{ - GPtrArray *relays; - guint component; - guint requests_to_do; - GabbleJingleFactoryRelaySessionCb callback; - gpointer user_data; -} RelaySessionData; - -static RelaySessionData * -relay_session_data_new (guint requests_to_do, - GabbleJingleFactoryRelaySessionCb callback, - gpointer user_data) -{ - RelaySessionData *rsd = g_slice_new0 (RelaySessionData); - - rsd->relays = g_ptr_array_sized_new (requests_to_do); - rsd->component = 1; - rsd->requests_to_do = requests_to_do; - rsd->callback = callback; - rsd->user_data = user_data; - - return rsd; -} - -/* This is a GSourceFunc */ -static gboolean -relay_session_data_call (gpointer p) -{ - RelaySessionData *rsd = p; - - g_assert (rsd->callback != NULL); - - rsd->callback (rsd->relays, rsd->user_data); - - return FALSE; -} - -/* This is a GDestroyNotify */ -static void -relay_session_data_destroy (gpointer p) -{ - RelaySessionData *rsd = p; - - g_ptr_array_foreach (rsd->relays, (GFunc) g_hash_table_destroy, NULL); - g_ptr_array_free (rsd->relays, TRUE); - - g_slice_free (RelaySessionData, rsd); -} - -static void -translate_relay_info (GPtrArray *relays, - const gchar *relay_ip, - const gchar *username, - const gchar *password, - const gchar *static_type, - const gchar *port_string, - guint component) -{ - GHashTable *asv; - guint port = 0; - - if (port_string == NULL) - { - DEBUG ("no relay port for %s found", static_type); - return; - } - - port = atoi (port_string); - - if (port == 0 || port > G_MAXUINT16) - { - DEBUG ("failed to parse relay port '%s' for %s", port_string, - static_type); - return; - } - - DEBUG ("type=%s ip=%s port=%u username=%s password=%s component=%u", - static_type, relay_ip, port, username, password, component); - /* keys are static, values are slice-allocated */ - asv = g_hash_table_new_full (g_str_hash, g_str_equal, - NULL, (GDestroyNotify) tp_g_value_slice_free); - g_hash_table_insert (asv, "ip", - tp_g_value_slice_new_string (relay_ip)); - g_hash_table_insert (asv, "type", - tp_g_value_slice_new_static_string (static_type)); - g_hash_table_insert (asv, "port", - tp_g_value_slice_new_uint (port)); - g_hash_table_insert (asv, "username", - tp_g_value_slice_new_string (username)); - g_hash_table_insert (asv, "password", - tp_g_value_slice_new_string (password)); - g_hash_table_insert (asv, "component", - tp_g_value_slice_new_uint (component)); - - g_ptr_array_add (relays, asv); -} - -static void -on_http_response (SoupSession *soup, - SoupMessage *msg, - gpointer user_data) -{ - RelaySessionData *rsd = user_data; - - if (msg->status_code != 200) - { - DEBUG ("Google session creation failed, relaying not used: %d %s", - msg->status_code, msg->reason_phrase); - } - else - { - /* parse a=b lines into GHashTable - * (key, value both borrowed from items of the strv 'lines') */ - GHashTable *map = g_hash_table_new (g_str_hash, g_str_equal); - gchar **lines; - guint i; - const gchar *relay_ip; - const gchar *relay_udp_port; - const gchar *relay_tcp_port; - const gchar *relay_ssltcp_port; - const gchar *username; - const gchar *password; - gchar *escaped_str; - - escaped_str = g_strescape (msg->response_body->data, "\r\n"); - DEBUG ("Response from Google:\n====\n%s\n====", escaped_str); - g_free (escaped_str); - - lines = g_strsplit (msg->response_body->data, "\n", 0); - - if (lines != NULL) - { - for (i = 0; lines[i] != NULL; i++) - { - gchar *delim = strchr (lines[i], '='); - size_t len; - - if (delim == NULL || delim == lines[i]) - { - /* ignore empty keys or lines without '=' */ - continue; - } - - len = strlen (lines[i]); - - if (lines[i][len - 1] == '\r') - { - lines[i][len - 1] = '\0'; - } - - *delim = '\0'; - g_hash_table_insert (map, lines[i], delim + 1); - } - } - - relay_ip = g_hash_table_lookup (map, "relay.ip"); - relay_udp_port = g_hash_table_lookup (map, "relay.udp_port"); - relay_tcp_port = g_hash_table_lookup (map, "relay.tcp_port"); - relay_ssltcp_port = g_hash_table_lookup (map, "relay.ssltcp_port"); - username = g_hash_table_lookup (map, "username"); - password = g_hash_table_lookup (map, "password"); - - if (relay_ip == NULL) - { - DEBUG ("No relay.ip found"); - } - else if (username == NULL) - { - DEBUG ("No username found"); - } - else if (password == NULL) - { - DEBUG ("No password found"); - } - else - { - translate_relay_info (rsd->relays, relay_ip, username, password, - "udp", relay_udp_port, rsd->component); - translate_relay_info (rsd->relays, relay_ip, username, password, - "tcp", relay_tcp_port, rsd->component); - translate_relay_info (rsd->relays, relay_ip, username, password, - "tls", relay_ssltcp_port, rsd->component); - } - - g_strfreev (lines); - g_hash_table_destroy (map); - } - - rsd->component++; - - if ((--rsd->requests_to_do) == 0) - { - relay_session_data_call (rsd); - relay_session_data_destroy (rsd); - } -} - void gabble_jingle_factory_create_google_relay_session ( GabbleJingleFactory *fac, @@ -1193,56 +950,15 @@ gabble_jingle_factory_create_google_relay_session ( gpointer user_data) { GabbleJingleFactoryPrivate *priv = fac->priv; - gchar *url; - guint i; - RelaySessionData *rsd; g_return_if_fail (callback != NULL); - rsd = relay_session_data_new (components, callback, user_data); - - if (fac->priv->relay_server == NULL) + if (priv->google_resolver == NULL) { - DEBUG ("No relay server provided, not creating google relay session"); - g_idle_add_full (G_PRIORITY_DEFAULT, relay_session_data_call, rsd, - relay_session_data_destroy); - return; - } - - if (fac->priv->relay_token == NULL) - { - DEBUG ("No relay token provided, not creating google relay session"); - g_idle_add_full (G_PRIORITY_DEFAULT, relay_session_data_call, rsd, - relay_session_data_destroy); - return; - } - - if (priv->soup == NULL) - { - priv->soup = soup_session_async_new (); - - /* If we don't get answer in a few seconds, relay won't do - * us much help anyways. */ - g_object_set (priv->soup, "timeout", RELAY_HTTP_TIMEOUT, NULL); - } - - url = g_strdup_printf ("http://%s:%d/create_session", - fac->priv->relay_server, fac->priv->relay_http_port); - - for (i = 0; i < components; i++) - { - SoupMessage *msg = soup_message_new ("GET", url); - - DEBUG ("Trying to create a new relay session on %s", url); - - /* libjingle sets both headers, so shall we */ - soup_message_headers_append (msg->request_headers, - "X-Talk-Google-Relay-Auth", fac->priv->relay_token); - soup_message_headers_append (msg->request_headers, - "X-Google-Relay-Auth", fac->priv->relay_token); - - soup_session_queue_message (priv->soup, msg, on_http_response, rsd); + priv->google_resolver = gabble_google_relay_resolver_new (); } - g_free (url); + gabble_google_relay_resolver_resolve (priv->google_resolver, + components, priv->relay_server, priv->relay_http_port, priv->relay_token, + callback, user_data); } diff --git a/src/jingle-media-rtp.c b/src/jingle-media-rtp.c index 182c52e17..2114386d0 100644 --- a/src/jingle-media-rtp.c +++ b/src/jingle-media-rtp.c @@ -337,7 +337,9 @@ static void transport_created (GabbleJingleContent *content, dialect = gabble_jingle_session_get_dialect (content->session); if (priv->media_type == JINGLE_MEDIA_TYPE_VIDEO && - JINGLE_IS_GOOGLE_DIALECT (dialect)) + (JINGLE_IS_GOOGLE_DIALECT (dialect) || + gabble_jingle_session_peer_has_quirk (content->session, + QUIRK_GOOGLE_WEBMAIL_CLIENT))) { jingle_transport_google_set_component_name (gtrans, "video_rtp", 1); jingle_transport_google_set_component_name (gtrans, "video_rtcp", 2); @@ -613,7 +615,7 @@ codec_update_coherent (const JingleCodec *old_c, return FALSE; } - if (tp_strdiff (new_c->name, old_c->name)) + if (g_ascii_strcasecmp (new_c->name, old_c->name)) { g_set_error (e, domain, code, "tried to change codec %u's name from %s to %s", diff --git a/src/jingle-session.c b/src/jingle-session.c index 8d060ec00..039881b0e 100644 --- a/src/jingle-session.c +++ b/src/jingle-session.c @@ -30,7 +30,7 @@ #define DEBUG_FLAG GABBLE_DEBUG_MEDIA -#include "capabilities.h" +#include "gabble/capabilities.h" #include "connection.h" #include "conn-presence.h" #include "debug.h" @@ -218,10 +218,10 @@ gabble_jingle_session_dispose (GObject *object) g_assert ((priv->state == JINGLE_STATE_PENDING_CREATED) || (priv->state == JINGLE_STATE_ENDED)); - g_hash_table_destroy (priv->initiator_contents); + g_hash_table_unref (priv->initiator_contents); priv->initiator_contents = NULL; - g_hash_table_destroy (priv->responder_contents); + g_hash_table_unref (priv->responder_contents); priv->responder_contents = NULL; tp_handle_unref (contact_repo, sess->peer); @@ -368,7 +368,7 @@ gabble_jingle_session_constructed (GObject *object) { /* The peer jid isn't exactly what is in the contact repo so it will have * a resource */ - if (gabble_decode_jid (priv->peer_jid, NULL, NULL, + if (wocky_decode_jid (priv->peer_jid, NULL, NULL, &priv->peer_resource)) { /* fake for gcc */; @@ -631,6 +631,21 @@ static void set_state (GabbleJingleSession *sess, JingleState state, JingleReason termination_reason, const gchar *text); static GabbleJingleContent *_get_any_content (GabbleJingleSession *session); +gboolean +gabble_jingle_session_peer_has_quirk ( + GabbleJingleSession *self, + const gchar *quirk) +{ + GabbleJingleSessionPrivate *priv = self->priv; + GabblePresence *presence = gabble_presence_cache_get ( + priv->conn->presence_cache, self->peer); + + return (presence != NULL && + priv->peer_resource != NULL && + gabble_presence_resource_has_caps (presence, priv->peer_resource, + gabble_capability_set_predicate_has, quirk)); +} + static gboolean lookup_content (GabbleJingleSession *sess, const gchar *name, @@ -668,13 +683,8 @@ lookup_content (GabbleJingleSession *sess, * of the moon, and get kind of confused in the process), and we try to * pick globally-unique content names. */ - GabblePresence *presence = gabble_presence_cache_get ( - priv->conn->presence_cache, sess->peer); - - if (creator == NULL && presence != NULL && - priv->peer_resource != NULL && - gabble_presence_resource_has_caps (presence, priv->peer_resource, - gabble_capability_set_predicate_has, + if (creator == NULL && + gabble_jingle_session_peer_has_quirk (sess, QUIRK_OMITS_CONTENT_CREATORS)) { DEBUG ("working around missing 'creator' attribute"); @@ -2078,7 +2088,7 @@ gabble_jingle_session_terminate (GabbleJingleSession *sess, wocky_node_add_child_with_content (r, reason_elt, NULL); - if (!CHECK_STR_EMPTY(text)) + if (!tp_str_empty (text)) wocky_node_add_child_with_content (r, "text", text); } @@ -2350,7 +2360,8 @@ gabble_jingle_session_get_remote_ringing (GabbleJingleSession *sess) gboolean gabble_jingle_session_can_modify_contents (GabbleJingleSession *sess) { - return !JINGLE_IS_GOOGLE_DIALECT (sess->priv->dialect); + return !JINGLE_IS_GOOGLE_DIALECT (sess->priv->dialect) && + !gabble_jingle_session_peer_has_quirk (sess, QUIRK_GOOGLE_WEBMAIL_CLIENT); } JingleDialect diff --git a/src/jingle-session.h b/src/jingle-session.h index fda18e3b2..2f02df405 100644 --- a/src/jingle-session.h +++ b/src/jingle-session.h @@ -107,6 +107,9 @@ const gchar *gabble_jingle_session_get_sid (GabbleJingleSession *sess); JingleDialect gabble_jingle_session_get_dialect (GabbleJingleSession *sess); gboolean gabble_jingle_session_can_modify_contents (GabbleJingleSession *sess); +gboolean gabble_jingle_session_peer_has_quirk ( + GabbleJingleSession *self, + const gchar *quirk); typedef void (*JingleReplyHandler) (GObject *, gboolean success, LmMessage *reply); diff --git a/src/jingle-transport-google.c b/src/jingle-transport-google.c index c63bb33c5..67e0e3e4f 100644 --- a/src/jingle-transport-google.c +++ b/src/jingle-transport-google.c @@ -110,7 +110,7 @@ gabble_jingle_transport_google_dispose (GObject *object) DEBUG ("dispose called"); priv->dispose_has_run = TRUE; - g_hash_table_destroy (priv->component_names); + g_hash_table_unref (priv->component_names); priv->component_names = NULL; jingle_transport_free_candidates (priv->remote_candidates); diff --git a/src/jingle-transport-iface.c b/src/jingle-transport-iface.c index 671eac20d..fe4223623 100644 --- a/src/jingle-transport-iface.c +++ b/src/jingle-transport-iface.c @@ -107,7 +107,7 @@ gabble_jingle_transport_iface_can_accept (GabbleJingleTransportIface *self) if (state != JINGLE_TRANSPORT_STATE_CONNECTED) return FALSE; - /* Only Raw UDP *needs* contents in order to accept. */ + /* Only Raw UDP *needs* candidates in order to accept. */ if (m != NULL) return m (self); else diff --git a/src/legacy-caps.h b/src/legacy-caps.h index 080ac3b0e..e2de46c37 100644 --- a/src/legacy-caps.h +++ b/src/legacy-caps.h @@ -23,7 +23,7 @@ #include <glib-object.h> -#include "capabilities.h" +#include "gabble/capabilities.h" typedef void (*TypeFlagsToCapsFunc) (guint typeflags, GabbleCapabilitySet *caps); typedef guint (*CapsToTypeFlagsFunc) (const GabbleCapabilitySet *caps); diff --git a/src/main.c b/src/main.c index e6fc2288f..462183c7d 100644 --- a/src/main.c +++ b/src/main.c @@ -21,7 +21,12 @@ #include "gabble.h" int -main (int argc, +#ifdef BUILD_AS_ANDROID_SERVICE +telepathy_gabble_main +#else +main +#endif + (int argc, char **argv) { gabble_init (); diff --git a/src/media-channel-hold.c b/src/media-channel-hold.c index 93496aa0f..ce657e646 100644 --- a/src/media-channel-hold.c +++ b/src/media-channel-hold.c @@ -366,7 +366,7 @@ gabble_media_channel_get_call_states (TpSvcChannelInterfaceCallState *iface, tp_svc_channel_interface_call_state_return_from_get_call_states (context, states); - g_hash_table_destroy (states); + g_hash_table_unref (states); } diff --git a/src/media-channel.c b/src/media-channel.c index 4e2341a57..d57ed4012 100644 --- a/src/media-channel.c +++ b/src/media-channel.c @@ -450,9 +450,7 @@ gabble_media_channel_constructor (GType type, guint n_props, /* If this is a Google session, let's set ImmutableStreams */ if (priv->session != NULL) { - JingleDialect d = gabble_jingle_session_get_dialect (priv->session); - - priv->immutable_streams = JINGLE_IS_GOOGLE_DIALECT (d); + priv->immutable_streams = !gabble_jingle_session_can_modify_contents (priv->session); } /* If there's no session yet, but we know who the peer will be, and we have * presence for them, we can set ImmutableStreams using the same algorithm as @@ -980,7 +978,7 @@ gabble_media_channel_dispose (GObject *object) { g_ptr_array_foreach (priv->delayed_request_streams, (GFunc) destroy_request, NULL); - g_ptr_array_free (priv->delayed_request_streams, TRUE); + g_ptr_array_unref (priv->delayed_request_streams); priv->delayed_request_streams = NULL; } @@ -997,7 +995,7 @@ gabble_media_channel_dispose (GObject *object) * removed when the call ended. */ g_assert (priv->streams->len == 0); - g_ptr_array_free (priv->streams, TRUE); + g_ptr_array_unref (priv->streams); priv->streams = NULL; if (G_OBJECT_CLASS (gabble_media_channel_parent_class)->dispose) @@ -1162,7 +1160,7 @@ gabble_media_channel_get_session_handlers (TpSvcChannelInterfaceMediaSignalling tp_svc_channel_interface_media_signalling_return_from_get_session_handlers ( context, ret); g_ptr_array_foreach (ret, (GFunc) g_value_array_free, NULL); - g_ptr_array_free (ret, TRUE); + g_ptr_array_unref (ret); } /** @@ -1255,7 +1253,7 @@ gabble_media_channel_list_streams (TpSvcChannelTypeStreamedMedia *iface, tp_svc_channel_type_streamed_media_return_from_list_streams (context, ret); g_ptr_array_foreach (ret, (GFunc) g_value_array_free, NULL); - g_ptr_array_free (ret, TRUE); + g_ptr_array_unref (ret); } @@ -1391,7 +1389,7 @@ gabble_media_channel_remove_streams (TpSvcChannelTypeStreamedMedia *iface, } OUT: - g_ptr_array_free (stream_objs, TRUE); + g_ptr_array_unref (stream_objs); if (error) { @@ -1551,7 +1549,7 @@ pending_stream_request_maybe_satisfy (PendingStreamRequest *p, p->succeeded_cb (p->context, ret); g_ptr_array_foreach (ret, (GFunc) g_value_array_free, NULL); - g_ptr_array_free (ret, TRUE); + g_ptr_array_unref (ret); p->context = NULL; return TRUE; } @@ -1799,7 +1797,7 @@ destroy_request (struct _delayed_request_streams_ctx *ctx, g_error_free (error); } - g_array_free (ctx->types, TRUE); + g_array_unref (ctx->types); g_slice_free (struct _delayed_request_streams_ctx, ctx); } @@ -1897,7 +1895,7 @@ media_channel_request_streams (GabbleMediaChannel *self, DEBUG ("no streams to request"); succeeded_cb (context, empty); - g_ptr_array_free (empty, TRUE); + g_ptr_array_unref (empty); return; } @@ -1943,7 +1941,7 @@ media_channel_request_streams (GabbleMediaChannel *self, context); priv->pending_stream_requests = g_list_prepend (priv->pending_stream_requests, psr); - g_ptr_array_free (contents, TRUE); + g_ptr_array_unref (contents); /* signal acceptance */ gabble_jingle_session_accept (priv->session); @@ -2043,7 +2041,7 @@ gabble_media_channel_request_initial_streams (GabbleMediaChannel *chan, media_channel_request_streams (chan, priv->initial_peer, types, succeeded_cb, failed_cb, user_data); - g_array_free (types, TRUE); + g_array_unref (types); } static gboolean @@ -2442,7 +2440,7 @@ session_terminated_cb (GabbleJingleSession *session, /* All the streams should have closed. */ g_assert (priv->streams->len == 0); - g_ptr_array_free (tmp, TRUE); + g_ptr_array_unref (tmp); } /* remove the session */ @@ -3072,7 +3070,7 @@ gabble_media_channel_error (TpSvcMediaSessionHandler *iface, gabble_media_stream_error (stream, errno, message, NULL); } - g_ptr_array_free (tmp, TRUE); + g_ptr_array_unref (tmp); tp_svc_media_session_handler_return_from_error (context); } diff --git a/src/media-factory.c b/src/media-factory.c index 4a2c7c704..4eb902c12 100644 --- a/src/media-factory.c +++ b/src/media-factory.c @@ -20,8 +20,6 @@ #include "config.h" #include "media-factory.h" -#define DBUS_API_SUBJECT_TO_CHANGE - #include <stdlib.h> #include <string.h> @@ -36,7 +34,7 @@ #define DEBUG_FLAG GABBLE_DEBUG_MEDIA -#include "caps-channel-manager.h" +#include "gabble/caps-channel-manager.h" #include "connection.h" #include "debug.h" #include "jingle-factory.h" @@ -681,13 +679,13 @@ gabble_media_factory_type_foreach_channel_class (GType type, func (type, table, named_channel_allowed_properties, user_data); - g_hash_table_destroy (table); + g_hash_table_unref (table); table = gabble_media_factory_call_channel_class (); func (type, table, call_channel_allowed_properties, user_data); - g_hash_table_destroy (table); + g_hash_table_unref (table); } @@ -1208,7 +1206,8 @@ gabble_media_factory_represent_client (GabbleCapsChannelManager *manager, const gchar *client_name, const GPtrArray *filters, const gchar * const *cap_tokens, - GabbleCapabilitySet *cap_set) + GabbleCapabilitySet *cap_set, + GPtrArray *data_forms) { static GQuark q_gtalk_p2p = 0, q_ice_udp = 0, q_h264 = 0; static GQuark qc_gtalk_p2p = 0, qc_ice_udp = 0, qc_h264 = 0; diff --git a/src/media-stream.c b/src/media-stream.c index da22ebc5d..09570ebba 100644 --- a/src/media-stream.c +++ b/src/media-stream.c @@ -189,7 +189,7 @@ gabble_media_stream_new (const gchar *object_path, NULL); if (empty != NULL) - g_ptr_array_free (empty, TRUE); + g_ptr_array_unref (empty); return result; } @@ -743,15 +743,15 @@ gabble_media_stream_codec_choice (TpSvcMediaStreamHandler *iface, gboolean gabble_media_stream_error (GabbleMediaStream *self, - guint errno, + guint errnum, const gchar *message, GError **error) { g_assert (GABBLE_IS_MEDIA_STREAM (self)); DEBUG ( "Media.StreamHandler::Error called, error %u (%s) -- emitting signal", - errno, message); - g_signal_emit (self, signals[ERROR], 0, errno, message); + errnum, message); + g_signal_emit (self, signals[ERROR], 0, errnum, message); return TRUE; } @@ -765,14 +765,14 @@ gabble_media_stream_error (GabbleMediaStream *self, */ static void gabble_media_stream_error_async (TpSvcMediaStreamHandler *iface, - guint errno, + guint errnum, const gchar *message, DBusGMethodInvocation *context) { GabbleMediaStream *self = GABBLE_MEDIA_STREAM (iface); GError *error = NULL; - if (gabble_media_stream_error (self, errno, message, &error)) + if (gabble_media_stream_error (self, errnum, message, &error)) { tp_svc_media_stream_handler_return_from_error (context); } @@ -1681,7 +1681,7 @@ new_remote_candidates_cb (GabbleJingleContent *content, g_free (candidate_id); g_value_unset (&transport); - g_ptr_array_free (transports, TRUE); + g_ptr_array_unref (transports); g_ptr_array_add (candidates, g_value_get_boxed (&candidate)); } diff --git a/src/message-util.c b/src/message-util.c index c38efec42..e8832ae55 100644 --- a/src/message-util.c +++ b/src/message-util.c @@ -37,12 +37,12 @@ #include "util.h" -static void -_add_chat_state (LmMessage *msg, +void +gabble_message_util_add_chat_state (WockyStanza *stanza, TpChannelChatState state) { WockyNode *node = NULL; - WockyNode *n = wocky_stanza_get_top_node (msg); + WockyNode *n = wocky_stanza_get_top_node (stanza); switch (state) { @@ -67,31 +67,32 @@ _add_chat_state (LmMessage *msg, node->ns = g_quark_from_static_string (NS_CHAT_STATES); } - /** - * gabble_message_util_send_message: - * @obj: a channel implementation featuring TpMessageMixin - * @conn: the connection owning this channel + * gabble_message_util_build_stanza * @message: the message to be sent - * @flags: the flags used if sending is successful + * @conn: the connection owning this channel * @subtype: the Loudmouth message subtype * @state: the Telepathy chat state, or -1 if unknown or not applicable * @recipient: the recipient's JID * @send_nick: whether to include our own nick in the message + * @token: return the message id + * @error: return the error if operation failed + * + * Returns: The wocky stanza for the message */ -void -gabble_message_util_send_message (GObject *obj, + +WockyStanza * +gabble_message_util_build_stanza (TpMessage *message, GabbleConnection *conn, - TpMessage *message, - TpMessageSendingFlags flags, LmMessageSubType subtype, TpChannelChatState state, const char *recipient, - gboolean send_nick) + gboolean send_nick, + gchar **token, + GError **error) { - GError *error = NULL; const GHashTable *part; - LmMessage *msg; + WockyStanza *stanza = NULL; WockyNode *node; guint type = TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL; gboolean result = TRUE; @@ -99,12 +100,12 @@ gabble_message_util_send_message (GObject *obj, gchar *id = NULL; guint n_parts; -#define INVALID_ARGUMENT(msg, ...) \ +#define RETURN_INVALID_ARGUMENT(msg, ...) \ G_STMT_START { \ DEBUG (msg , ## __VA_ARGS__); \ - g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, \ + g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, \ msg , ## __VA_ARGS__); \ - goto despair_island; \ + return NULL; \ } G_STMT_END part = tp_message_peek (message, 0); @@ -113,15 +114,15 @@ gabble_message_util_send_message (GObject *obj, type = tp_asv_get_uint32 (part, "message-type", &result); if (!result) - INVALID_ARGUMENT ("message-type must be a 32-bit unsigned integer"); + RETURN_INVALID_ARGUMENT ("message-type must be a 32-bit unsigned integer"); if (type >= NUM_TP_CHANNEL_TEXT_MESSAGE_TYPES) - INVALID_ARGUMENT ("invalid message type: %u", type); + RETURN_INVALID_ARGUMENT ("invalid message type: %u", type); n_parts = tp_message_count_parts (message); if (n_parts != 2) - INVALID_ARGUMENT ("message must contain exactly 1 part, not %u", + RETURN_INVALID_ARGUMENT ("message must contain exactly 1 part, not %u", (n_parts - 1)); part = tp_message_peek (message, 1); @@ -129,12 +130,10 @@ gabble_message_util_send_message (GObject *obj, text = tp_asv_get_string (part, "content"); if (content_type == NULL || tp_strdiff (content_type, "text/plain")) - INVALID_ARGUMENT ("message must be text/plain"); + RETURN_INVALID_ARGUMENT ("message must be text/plain"); if (text == NULL) - INVALID_ARGUMENT ("content must be a UTF-8 string"); - - /* Okay, it's valid. Let's send it. */ + RETURN_INVALID_ARGUMENT ("content must be a UTF-8 string"); if (!subtype) { @@ -150,9 +149,9 @@ gabble_message_util_send_message (GObject *obj, } } - msg = lm_message_new_with_sub_type (recipient, LM_MESSAGE_TYPE_MESSAGE, + stanza = lm_message_new_with_sub_type (recipient, LM_MESSAGE_TYPE_MESSAGE, subtype); - node = wocky_stanza_get_top_node (msg); + node = wocky_stanza_get_top_node (stanza); /* Generate a UUID for the message */ id = gabble_generate_id (); wocky_node_set_attribute (node, "id", id); @@ -173,24 +172,15 @@ gabble_message_util_send_message (GObject *obj, wocky_node_add_child_with_content (node, "body", text); } - _add_chat_state (msg, state); + gabble_message_util_add_chat_state (stanza, state); - result = _gabble_connection_send (conn, msg, &error); - lm_message_unref (msg); - - if (!result) - goto despair_island; - - tp_message_mixin_sent (obj, message, flags, id, NULL); - g_free (id); - - return; + if (token != NULL) + *token = id; + else + g_free (id); -despair_island: - g_assert (error != NULL); - tp_message_mixin_sent (obj, message, 0, NULL, error); - g_error_free (error); - g_free (id); + gabble_connection_update_last_use (conn); + return stanza; } @@ -217,7 +207,7 @@ gabble_message_util_send_chat_state (GObject *obj, LM_MESSAGE_TYPE_MESSAGE, subtype); gboolean result; - _add_chat_state (msg, state); + gabble_message_util_add_chat_state (msg, state); result = _gabble_connection_send (conn, msg, error); lm_message_unref (msg); @@ -464,6 +454,14 @@ gabble_message_util_parse_incoming_message (LmMessage *message, if (body != NULL) { + if (lm_message_node_get_child_with_namespace ( + wocky_stanza_get_top_node (message), + "google-rbc-announcement", "google:metadata") != NULL) + { + /* Fixes: https://bugs.freedesktop.org/show_bug.cgi?id=36647 */ + return FALSE; + } + if (type == NULL && lm_message_node_get_child_with_namespace ( wocky_stanza_get_top_node (message), diff --git a/src/message-util.h b/src/message-util.h index 7e8d743e6..8b63c7adb 100644 --- a/src/message-util.h +++ b/src/message-util.h @@ -24,16 +24,20 @@ #include <telepathy-glib/message-mixin.h> #include <loudmouth/loudmouth.h> +#include <wocky/wocky.h> #include <wocky/wocky-xmpp-error.h> #include "connection.h" G_BEGIN_DECLS -void gabble_message_util_send_message (GObject *obj, - GabbleConnection *conn, TpMessage *message, TpMessageSendingFlags flags, - LmMessageSubType subtype, TpChannelChatState state, const char *recipient, - gboolean send_nick); +void gabble_message_util_add_chat_state (WockyStanza *stanza, + TpChannelChatState state); + +WockyStanza * gabble_message_util_build_stanza (TpMessage *message, + GabbleConnection *conn, LmMessageSubType subtype, + TpChannelChatState state, const char *recipient, gboolean send_nick, + gchar **token, GError **error); gboolean gabble_message_util_send_chat_state (GObject *obj, GabbleConnection *conn, LmMessageSubType subtype, TpChannelChatState state, diff --git a/src/muc-channel.c b/src/muc-channel.c index a758399d2..5cb55d8b3 100644 --- a/src/muc-channel.c +++ b/src/muc-channel.c @@ -26,6 +26,7 @@ #include <string.h> #include <wocky/wocky-muc.h> +#include <wocky/wocky-utils.h> #include <wocky/wocky-xmpp-error.h> #include <dbus/dbus-glib.h> @@ -40,17 +41,19 @@ #define DEBUG_FLAG GABBLE_DEBUG_MUC #include "connection.h" #include "conn-aliasing.h" +#include "conn-util.h" #include "debug.h" #include "disco.h" +#include "error.h" #include "message-util.h" +#include "room-config.h" #include "namespaces.h" #include "presence.h" #include "util.h" #include "presence-cache.h" - #include "call-muc-channel.h" - #include "gabble-signals-marshal.h" +#include "gabble-enumtypes.h" #define DEFAULT_JOIN_TIMEOUT 180 #define DEFAULT_LEAVE_TIMEOUT 180 @@ -61,6 +64,7 @@ static void password_iface_init (gpointer, gpointer); static void chat_state_iface_init (gpointer, gpointer); +static void subject_iface_init (gpointer, gpointer); static void gabble_muc_channel_start_call_creation (GabbleMucChannel *gmuc, GHashTable *request); static void muc_call_channel_finish_requests (GabbleMucChannel *self, @@ -69,8 +73,6 @@ static void muc_call_channel_finish_requests (GabbleMucChannel *self, G_DEFINE_TYPE_WITH_CODE (GabbleMucChannel, gabble_muc_channel, TP_TYPE_BASE_CHANNEL, - G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_PROPERTIES_INTERFACE, - tp_properties_mixin_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_GROUP, tp_group_mixin_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_PASSWORD, @@ -80,8 +82,13 @@ G_DEFINE_TYPE_WITH_CODE (GabbleMucChannel, gabble_muc_channel, G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_MESSAGES, tp_message_mixin_messages_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_CHAT_STATE, - chat_state_iface_init) + chat_state_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_CONFERENCE, NULL); + G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_ROOM, NULL); + G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_ROOM_CONFIG, + tp_base_room_config_iface_init); + G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_SUBJECT, + subject_iface_init); ) static void gabble_muc_channel_send (GObject *obj, TpMessage *message, @@ -91,10 +98,12 @@ static void gabble_muc_channel_close (TpBaseChannel *base); static const gchar *gabble_muc_channel_interfaces[] = { TP_IFACE_CHANNEL_INTERFACE_GROUP, TP_IFACE_CHANNEL_INTERFACE_PASSWORD, - TP_IFACE_PROPERTIES_INTERFACE, TP_IFACE_CHANNEL_INTERFACE_CHAT_STATE, TP_IFACE_CHANNEL_INTERFACE_MESSAGES, TP_IFACE_CHANNEL_INTERFACE_CONFERENCE, + TP_IFACE_CHANNEL_INTERFACE_ROOM, + TP_IFACE_CHANNEL_INTERFACE_ROOM_CONFIG, + TP_IFACE_CHANNEL_INTERFACE_SUBJECT, NULL }; @@ -127,10 +136,17 @@ enum PROP_INITIAL_INVITEE_HANDLES, PROP_INITIAL_INVITEE_IDS, PROP_ORIGINAL_CHANNELS, + PROP_ROOM_NAME, + PROP_SERVER, + + PROP_SUBJECT, + PROP_SUBJECT_ACTOR, + PROP_SUBJECT_TIMESTAMP, + PROP_CAN_SET_SUBJECT, + LAST_PROPERTY }; -#ifdef ENABLE_DEBUG static const gchar *muc_states[] = { "MUC_STATE_CREATED", @@ -139,83 +155,6 @@ static const gchar *muc_states[] = "MUC_STATE_JOINED", "MUC_STATE_ENDED", }; -#endif - -/* role and affiliation enums */ -typedef enum { - ROLE_NONE = 0, - ROLE_VISITOR, - ROLE_PARTICIPANT, - ROLE_MODERATOR, - - NUM_ROLES, - - INVALID_ROLE, -} GabbleMucRole; - -typedef enum { - AFFILIATION_NONE = 0, - AFFILIATION_MEMBER, - AFFILIATION_ADMIN, - AFFILIATION_OWNER, - - NUM_AFFILIATIONS, - - INVALID_AFFILIATION, -} GabbleMucAffiliation; - -/* room properties */ -enum -{ - ROOM_PROP_ANONYMOUS = 0, - ROOM_PROP_INVITE_ONLY, - ROOM_PROP_INVITE_RESTRICTED, - ROOM_PROP_MODERATED, - ROOM_PROP_NAME, - ROOM_PROP_DESCRIPTION, - ROOM_PROP_PASSWORD, - ROOM_PROP_PASSWORD_REQUIRED, - ROOM_PROP_PERSISTENT, - ROOM_PROP_PRIVATE, - ROOM_PROP_SUBJECT, - ROOM_PROP_SUBJECT_CONTACT, - ROOM_PROP_SUBJECT_TIMESTAMP, - - NUM_ROOM_PROPS, - - INVALID_ROOM_PROP, -}; - -const TpPropertySignature room_property_signatures[NUM_ROOM_PROPS] = { - /* Part of the room definition: modifiable by owners only */ - { "anonymous", G_TYPE_BOOLEAN }, /* impl: READ, WRITE */ - { "invite-only", G_TYPE_BOOLEAN }, /* impl: READ, WRITE */ - { "invite-restricted", G_TYPE_BOOLEAN }, /* impl: WRITE */ - { "moderated", G_TYPE_BOOLEAN }, /* impl: READ, WRITE */ - { "name", G_TYPE_STRING }, /* impl: READ, WRITE */ - - /* Part of the room definition: might be modifiable by the owner, or - * not at all */ - { "description", G_TYPE_STRING }, /* impl: READ, WRITE */ - - /* Part of the room definition: modifiable by owners only */ - { "password", G_TYPE_STRING }, /* impl: WRITE */ - { "password-required", G_TYPE_BOOLEAN }, /* impl: READ, WRITE */ - { "persistent", G_TYPE_BOOLEAN }, /* impl: READ, WRITE */ - { "private", G_TYPE_BOOLEAN }, /* impl: READ, WRITE */ - - /* fd.o#13157: currently assumed to be modifiable by everyone in the - * room (role >= VISITOR). When that bug is fixed, it will be: */ - /* Modifiable via special <message/>s, if the user's role is high enough; - * "high enough" is defined by the muc#roominfo_changesubject and - * muc#roomconfig_changesubject settings. */ - { "subject", G_TYPE_STRING }, /* impl: READ, WRITE */ - - /* Special: implicitly set to "myself" and "now", respectively, by - * changing subject. */ - { "subject-contact", G_TYPE_UINT }, /* impl: READ */ - { "subject-timestamp", G_TYPE_UINT }, /* impl: READ */ -}; /* private structures */ struct _GabbleMucChannelPrivate @@ -227,21 +166,33 @@ struct _GabbleMucChannelPrivate guint poll_timer_id; guint leave_timer_id; - TpChannelPasswordFlags password_flags; + gboolean must_provide_password; DBusGMethodInvocation *password_ctx; - gchar *password; const gchar *jid; gboolean requested; guint nick_retry_count; GString *self_jid; - GabbleMucRole self_role; - GabbleMucAffiliation self_affil; + WockyMucRole self_role; + WockyMucAffiliation self_affil; guint recv_id; - TpPropertiesContext *properties_ctx; + TpBaseRoomConfig *room_config; + GHashTable *properties_being_updated; + + /* Room interface */ + gchar *room_name; + gchar *server; + + /* Subject interface */ + gchar *subject; + gchar *subject_actor; + gint64 subject_timestamp; + gboolean can_set_subject; + DBusGMethodInvocation *set_subject_context; + gchar *set_subject_stanza_id; gboolean ready; gboolean dispose_has_run; @@ -268,6 +219,12 @@ struct _GabbleMucChannelPrivate char **initial_ids; }; +typedef struct { + GabbleMucChannel *channel; + TpMessage *message; + gchar *token; +} _GabbleMUCSendMessageCtx; + static void gabble_muc_channel_init (GabbleMucChannel *self) { @@ -290,7 +247,7 @@ static void handle_fill_presence (WockyMuc *muc, static void handle_renamed (GObject *source, WockyStanza *stanza, - GHashTable *code, + guint codes, gpointer data); static void handle_error (GObject *source, @@ -301,12 +258,12 @@ static void handle_error (GObject *source, static void handle_join (WockyMuc *muc, WockyStanza *stanza, - GHashTable *code, + guint codes, gpointer data); static void handle_parted (GObject *source, WockyStanza *stanza, - GHashTable *code, + guint codes, const gchar *actor_jid, const gchar *why, const gchar *msg, @@ -314,7 +271,7 @@ static void handle_parted (GObject *source, static void handle_left (GObject *source, WockyStanza *stanza, - GHashTable *code, + guint codes, WockyMucMember *who, const gchar *actor_jid, const gchar *why, @@ -323,7 +280,7 @@ static void handle_left (GObject *source, static void handle_presence (GObject *source, WockyStanza *stanza, - GHashTable *code, + guint codes, WockyMucMember *who, gpointer data); @@ -339,7 +296,7 @@ static void handle_message (GObject *source, WockyStanza *stanza, WockyMucMsgType type, const gchar *xmpp_id, - time_t stamp, + GDateTime *datetime, WockyMucMember *who, const gchar *text, const gchar *subject, @@ -350,13 +307,25 @@ static void handle_errmsg (GObject *source, WockyStanza *stanza, WockyMucMsgType type, const gchar *xmpp_id, - time_t stamp, + GDateTime *datetime, WockyMucMember *who, const gchar *text, WockyXmppError error, WockyXmppErrorType etype, gpointer data); +/* Signatures for some other stuff. */ + +static void _gabble_muc_channel_handle_subject (GabbleMucChannel *chan, + TpHandleType handle_type, + TpHandle sender, GDateTime *datetime, const gchar *subject, + LmMessage *msg); +static void _gabble_muc_channel_receive (GabbleMucChannel *chan, + TpChannelTextMessageType msg_type, TpHandleType handle_type, + TpHandle sender, GDateTime *datetime, const gchar *id, const gchar *text, + LmMessage *msg, TpChannelTextSendError send_error, + TpDeliveryStatus delivery_status); + static void gabble_muc_channel_constructed (GObject *obj) { @@ -366,6 +335,7 @@ gabble_muc_channel_constructed (GObject *obj) TpBaseConnection *base_conn = tp_base_channel_get_connection (base); TpHandleRepoIface *room_handles, *contact_handles; TpHandle target, initiator, self_handle; + gchar *tmp; TpChannelTextMessageType types[] = { TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL, TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION, @@ -377,6 +347,7 @@ gabble_muc_channel_constructed (GObject *obj) }; void (*chain_up) (GObject *) = ((GObjectClass *) gabble_muc_channel_parent_class)->constructed; + gboolean ok; if (chain_up != NULL) chain_up (obj); @@ -403,8 +374,8 @@ gabble_muc_channel_constructed (GObject *obj) * at the end of this function */ /* initialize our own role and affiliation */ - priv->self_role = ROLE_NONE; - priv->self_affil = AFFILIATION_NONE; + priv->self_role = WOCKY_MUC_ROLE_NONE; + priv->self_affil = WOCKY_MUC_AFFILIATION_NONE; /* initialise the wocky muc object */ { @@ -456,10 +427,6 @@ gabble_muc_channel_constructed (GObject *obj) TP_CHANNEL_GROUP_FLAG_CAN_ADD, 0); - /* initialize properties mixin */ - tp_properties_mixin_init (obj, G_STRUCT_OFFSET ( - GabbleMucChannel, properties)); - /* initialize message mixin */ tp_message_mixin_init (obj, G_STRUCT_OFFSET (GabbleMucChannel, message_mixin), base_conn); @@ -471,6 +438,54 @@ gabble_muc_channel_constructed (GObject *obj) tp_group_mixin_add_handle_owner (obj, self_handle, base_conn->self_handle); + /* Room interface */ + g_object_get (self, + "target-id", &tmp, + NULL); + + if (priv->room_name != NULL) + ok = wocky_decode_jid (tmp, NULL, &(priv->server), NULL); + else + ok = wocky_decode_jid (tmp, &(priv->room_name), &(priv->server), NULL); + g_free (tmp); + + /* Asserting here is fine because the target ID has already been + * checked so we know it's valid. */ + g_assert (ok); + + priv->subject = NULL; + priv->subject_actor = NULL; + priv->subject_timestamp = G_MAXINT64; + /* fd.o#13157: The subject is currently assumed to be modifiable by everyone + * in the room (role >= VISITOR). When that bug is fixed, it will be: */ + /* Modifiable via special <message/>s, if the user's role is high enough; + * "high enough" is defined by the muc#roominfo_changesubject and + * muc#roomconfig_changesubject settings. */ + priv->can_set_subject = TRUE; + + { + TpBaseRoomConfigProperty mutable_properties[] = { + TP_BASE_ROOM_CONFIG_ANONYMOUS, + TP_BASE_ROOM_CONFIG_INVITE_ONLY, + TP_BASE_ROOM_CONFIG_MODERATED, + TP_BASE_ROOM_CONFIG_TITLE, + TP_BASE_ROOM_CONFIG_PERSISTENT, + TP_BASE_ROOM_CONFIG_PRIVATE, + TP_BASE_ROOM_CONFIG_PASSWORD_PROTECTED, + TP_BASE_ROOM_CONFIG_PASSWORD, + }; + guint i; + + priv->room_config = + (TpBaseRoomConfig *) gabble_room_config_new ((TpBaseChannel *) self); + for (i = 0; i < G_N_ELEMENTS (mutable_properties); i++) + tp_base_room_config_set_property_mutable (priv->room_config, + mutable_properties[i], TRUE); + + /* Just to get those mutable properties out there. */ + tp_base_room_config_emit_properties_changed (priv->room_config); + } + if (priv->invited) { /* invited: add ourself to local pending and the inviter to members */ @@ -504,12 +519,112 @@ gabble_muc_channel_constructed (GObject *obj) g_array_append_val (members, self_handle); tp_group_mixin_add_members (obj, members, "", &error); g_assert (error == NULL); - g_array_free (members, TRUE); + g_array_unref (members); } tp_handle_unref (contact_handles, self_handle); } +typedef struct { + const gchar *var; + const gchar *config_property_name; + gboolean value; +} FeatureMapping; + +static FeatureMapping * +lookup_feature (const gchar *var) +{ + static FeatureMapping features[] = { + { "muc_nonanonymous", "anonymous", FALSE }, + { "muc_semianonymous", "anonymous", TRUE }, + { "muc_anonymous", "anonymous", TRUE }, + + { "muc_open", "invite-only", FALSE }, + { "muc_membersonly", "invite-only", TRUE }, + + { "muc_unmoderated", "moderated", FALSE }, + { "muc_moderated", "moderated", TRUE }, + + { "muc_unsecure", "password-protected", FALSE }, + { "muc_unsecured", "password-protected", FALSE }, + { "muc_passwordprotected", "password-protected", TRUE }, + + { "muc_temporary", "persistent", FALSE }, + { "muc_persistent", "persistent", TRUE }, + + { "muc_public", "private", FALSE }, + { "muc_hidden", "private", TRUE }, + + /* The MUC namespace is included as a feature in disco results. We ignore + * it here. + */ + { NS_MUC, NULL, FALSE }, + + { NULL } + }; + FeatureMapping *f; + + for (f = features; f->var != NULL; f++) + if (strcmp (var, f->var) == 0) + return f; + + return NULL; +} + +static const gchar * +map_feature ( + WockyNode *feature, + GValue *value) +{ + const gchar *var = wocky_node_get_attribute (feature, "var"); + FeatureMapping *f; + + if (var == NULL) + return NULL; + + f = lookup_feature (var); + + if (f == NULL) + { + DEBUG ("unhandled feature '%s'", var); + return NULL; + } + + if (f->config_property_name != NULL) + { + g_value_init (value, G_TYPE_BOOLEAN); + g_value_set_boolean (value, f->value); + } + + return f->config_property_name; +} + +static const gchar * +handle_form ( + WockyNode *x, + GValue *value) +{ + WockyNodeIter j; + WockyNode *field; + + wocky_node_iter_init (&j, x, "field", NULL); + while (wocky_node_iter_next (&j, &field)) + { + const gchar *var = wocky_node_get_attribute (field, "var"); + const gchar *description; + + if (tp_strdiff (var, "muc#roominfo_description")) + continue; + + description = wocky_node_get_content_from_child (field, "value"); + g_value_init (value, G_TYPE_STRING); + g_value_set_string (value, description != NULL ? description : ""); + return "description"; + } + + return NULL; +} + static void properties_disco_cb (GabbleDisco *disco, GabbleDiscoRequest *request, @@ -520,10 +635,8 @@ properties_disco_cb (GabbleDisco *disco, gpointer user_data) { GabbleMucChannel *chan = user_data; - TpIntSet *changed_props_val, *changed_props_flags; + GabbleMucChannelPrivate *priv = chan->priv; WockyNode *lm_node; - const gchar *str; - GValue val = { 0, }; NodeIter i; g_assert (GABBLE_IS_MUC_CHANNEL (chan)); @@ -534,9 +647,6 @@ properties_disco_cb (GabbleDisco *disco, return; } - changed_props_val = tp_intset_sized_new (NUM_ROOM_PROPS); - changed_props_flags = tp_intset_sized_new (NUM_ROOM_PROPS); - /* * Update room definition. */ @@ -555,186 +665,38 @@ properties_disco_cb (GabbleDisco *disco, !tp_strdiff (type, "text") && name != NULL) { - g_value_init (&val, G_TYPE_STRING); - g_value_set_string (&val, name); - - tp_properties_mixin_change_value (G_OBJECT (chan), ROOM_PROP_NAME, - &val, changed_props_val); - - tp_properties_mixin_change_flags (G_OBJECT (chan), ROOM_PROP_NAME, - TP_PROPERTY_FLAG_READ, - 0, changed_props_flags); - - g_value_unset (&val); + g_object_set (priv->room_config, "title", name, NULL); } } for (i = node_iter (query_result); i; i = node_iter_next (i)) { - guint prop_id = INVALID_ROOM_PROP; + const gchar *config_property_name = NULL; WockyNode *child = node_iter_data (i); + GValue val = { 0, }; if (strcmp (child->name, "feature") == 0) { - str = wocky_node_get_attribute (child, "var"); - if (str == NULL) - continue; - - /* ROOM_PROP_ANONYMOUS */ - if (strcmp (str, "muc_nonanonymous") == 0) - { - prop_id = ROOM_PROP_ANONYMOUS; - g_value_init (&val, G_TYPE_BOOLEAN); - g_value_set_boolean (&val, FALSE); - } - else if (strcmp (str, "muc_semianonymous") == 0 || - strcmp (str, "muc_anonymous") == 0) - { - prop_id = ROOM_PROP_ANONYMOUS; - g_value_init (&val, G_TYPE_BOOLEAN); - g_value_set_boolean (&val, TRUE); - } - - /* ROOM_PROP_INVITE_ONLY */ - else if (strcmp (str, "muc_open") == 0) - { - prop_id = ROOM_PROP_INVITE_ONLY; - g_value_init (&val, G_TYPE_BOOLEAN); - g_value_set_boolean (&val, FALSE); - } - else if (strcmp (str, "muc_membersonly") == 0) - { - prop_id = ROOM_PROP_INVITE_ONLY; - g_value_init (&val, G_TYPE_BOOLEAN); - g_value_set_boolean (&val, TRUE); - } - - /* ROOM_PROP_MODERATED */ - else if (strcmp (str, "muc_unmoderated") == 0) - { - prop_id = ROOM_PROP_MODERATED; - g_value_init (&val, G_TYPE_BOOLEAN); - g_value_set_boolean (&val, FALSE); - } - else if (strcmp (str, "muc_moderated") == 0) - { - prop_id = ROOM_PROP_MODERATED; - g_value_init (&val, G_TYPE_BOOLEAN); - g_value_set_boolean (&val, TRUE); - } - - /* ROOM_PROP_PASSWORD_REQUIRED */ - else if (strcmp (str, "muc_unsecure") == 0 || - strcmp (str, "muc_unsecured") == 0) - { - prop_id = ROOM_PROP_PASSWORD_REQUIRED; - g_value_init (&val, G_TYPE_BOOLEAN); - g_value_set_boolean (&val, FALSE); - } - else if (strcmp (str, "muc_passwordprotected") == 0) - { - prop_id = ROOM_PROP_PASSWORD_REQUIRED; - g_value_init (&val, G_TYPE_BOOLEAN); - g_value_set_boolean (&val, TRUE); - } - - /* ROOM_PROP_PERSISTENT */ - else if (strcmp (str, "muc_temporary") == 0) - { - prop_id = ROOM_PROP_PERSISTENT; - g_value_init (&val, G_TYPE_BOOLEAN); - g_value_set_boolean (&val, FALSE); - } - else if (strcmp (str, "muc_persistent") == 0) - { - prop_id = ROOM_PROP_PERSISTENT; - g_value_init (&val, G_TYPE_BOOLEAN); - g_value_set_boolean (&val, TRUE); - } - - /* ROOM_PROP_PRIVATE */ - else if (strcmp (str, "muc_public") == 0) - { - prop_id = ROOM_PROP_PRIVATE; - g_value_init (&val, G_TYPE_BOOLEAN); - g_value_set_boolean (&val, FALSE); - } - else if (strcmp (str, "muc_hidden") == 0) - { - prop_id = ROOM_PROP_PRIVATE; - g_value_init (&val, G_TYPE_BOOLEAN); - g_value_set_boolean (&val, TRUE); - } - - /* Ignored */ - else if (strcmp (str, NS_MUC) == 0) - { - } - - /* Unhandled */ - else - { - DEBUG ("unhandled feature '%s'", str); - } + config_property_name = map_feature (child, &val); } - else if (strcmp (child->name, "x") == 0) + else if (strcmp (child->name, "x") == 0 && + wocky_node_has_ns (child, NS_X_DATA)) { - if (wocky_node_has_ns (child, NS_X_DATA)) - { - NodeIter j; - - for (j = node_iter (child); j; j = node_iter_next (j)) - { - WockyNode *field = node_iter_data (j); - WockyNode *value_node; - - if (strcmp (field->name, "field") != 0) - continue; - - str = wocky_node_get_attribute (field, "var"); - if (str == NULL) - continue; - - if (strcmp (str, "muc#roominfo_description") != 0) - continue; - - value_node = wocky_node_get_child (field, "value"); - if (value_node == NULL) - continue; - - str = value_node->content; - if (str == NULL) - { - str = ""; - } - - prop_id = ROOM_PROP_DESCRIPTION; - g_value_init (&val, G_TYPE_STRING); - g_value_set_string (&val, str); - } - } + config_property_name = handle_form (child, &val); } - if (prop_id != INVALID_ROOM_PROP) + if (config_property_name != NULL) { - tp_properties_mixin_change_value (G_OBJECT (chan), prop_id, &val, - changed_props_val); - - tp_properties_mixin_change_flags (G_OBJECT (chan), prop_id, - TP_PROPERTY_FLAG_READ, - 0, changed_props_flags); + g_object_set_property ((GObject *) priv->room_config, config_property_name, &val); g_value_unset (&val); } } - /* - * Emit signals. + /* This could be the first time we've fetched the room properties, or it + * could be a later time; either way, this method does the right thing. */ - tp_properties_mixin_emit_changed (G_OBJECT (chan), changed_props_val); - tp_properties_mixin_emit_flags (G_OBJECT (chan), changed_props_flags); - tp_intset_destroy (changed_props_val); - tp_intset_destroy (changed_props_flags); + tp_base_room_config_set_retrieved (priv->room_config); } static void @@ -784,7 +746,7 @@ create_room_identity (GabbleMucChannel *chan) */ gchar *local_part; - g_assert (gabble_decode_jid (alias, &local_part, NULL, NULL)); + g_assert (wocky_decode_jid (alias, &local_part, NULL, NULL)); g_assert (local_part != NULL); g_free (alias); @@ -802,12 +764,10 @@ create_room_identity (GabbleMucChannel *chan) } static void -send_join_request (GabbleMucChannel *gmuc, - const gchar *password) +send_join_request (GabbleMucChannel *gmuc) { GabbleMucChannelPrivate *priv = gmuc->priv; - g_object_set (priv->wmuc, "password", password, NULL); wocky_muc_join (priv->wmuc, NULL); } @@ -884,6 +844,24 @@ gabble_muc_channel_get_property (GObject *object, * which we can't do anyway in XMPP. */ g_value_take_boxed (value, g_hash_table_new (NULL, NULL)); break; + case PROP_ROOM_NAME: + g_value_set_string (value, priv->room_name); + break; + case PROP_SERVER: + g_value_set_string (value, priv->server); + break; + case PROP_SUBJECT: + g_value_set_string (value, priv->subject); + break; + case PROP_SUBJECT_ACTOR: + g_value_set_string (value, priv->subject_actor); + break; + case PROP_SUBJECT_TIMESTAMP: + g_value_set_int64 (value, priv->subject_timestamp); + break; + case PROP_CAN_SET_SUBJECT: + g_value_set_boolean (value, priv->can_set_subject); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -931,6 +909,9 @@ gabble_muc_channel_set_property (GObject *object, case PROP_INITIAL_INVITEE_IDS: priv->initial_ids = g_value_dup_boxed (value); break; + case PROP_ROOM_NAME: + priv->room_name = g_value_dup_string (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -943,8 +924,6 @@ static gboolean gabble_muc_channel_add_member (GObject *obj, TpHandle handle, const gchar *message, GError **error); static gboolean gabble_muc_channel_remove_member (GObject *obj, TpHandle handle, const gchar *message, GError **error); -static gboolean gabble_muc_channel_do_set_properties (GObject *obj, - TpPropertiesContext *ctx, GError **error); static void gabble_muc_channel_fill_immutable_properties ( @@ -964,6 +943,8 @@ gabble_muc_channel_fill_immutable_properties ( TP_IFACE_CHANNEL_INTERFACE_MESSAGES, "DeliveryReportingSupport", TP_IFACE_CHANNEL_INTERFACE_MESSAGES, "SupportedContentTypes", TP_IFACE_CHANNEL_INTERFACE_MESSAGES, "MessageTypes", + TP_IFACE_CHANNEL_INTERFACE_ROOM, "RoomName", + TP_IFACE_CHANNEL_INTERFACE_ROOM, "Server", NULL); } @@ -979,6 +960,38 @@ gabble_muc_channel_class_init (GabbleMucChannelClass *gabble_muc_channel_class) { "OriginalChannels", "original-channels", NULL }, { NULL } }; + static TpDBusPropertiesMixinPropImpl room_props[] = { + { "RoomName", "room-name", NULL, }, + { "Server", "server", NULL }, + { NULL } + }; + static TpDBusPropertiesMixinPropImpl subject_props[] = { + { "Subject", "subject", NULL }, + { "Actor", "subject-actor", NULL }, + { "Timestamp", "subject-timestamp", NULL }, + { "CanSet", "can-set-subject", NULL }, + { NULL } + }; + + static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = { + { TP_IFACE_CHANNEL_INTERFACE_CONFERENCE, + tp_dbus_properties_mixin_getter_gobject_properties, + NULL, + conference_props, + }, + { TP_IFACE_CHANNEL_INTERFACE_ROOM, + tp_dbus_properties_mixin_getter_gobject_properties, + NULL, + room_props, + }, + { TP_IFACE_CHANNEL_INTERFACE_SUBJECT, + tp_dbus_properties_mixin_getter_gobject_properties, + NULL, + subject_props, + }, + { NULL } + }; + GObjectClass *object_class = G_OBJECT_CLASS (gabble_muc_channel_class); TpBaseChannelClass *base_class = TP_BASE_CHANNEL_CLASS (object_class); GParamSpec *param_spec; @@ -1065,6 +1078,50 @@ gabble_muc_channel_class_init (GabbleMucChannelClass *gabble_muc_channel_class) g_object_class_install_property (object_class, PROP_ORIGINAL_CHANNELS, param_spec); + param_spec = g_param_spec_string ("room-name", + "RoomName", + "The human-readable identifier of a chat room.", + "", + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_ROOM_NAME, + param_spec); + + param_spec = g_param_spec_string ("server", + "Server", + "the DNS name of the server hosting this channel", + "", + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_SERVER, + param_spec); + + param_spec = g_param_spec_string ("subject", + "Subject.Subject", "The subject of the room", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_SUBJECT, param_spec); + + param_spec = g_param_spec_string ("subject-actor", + "Subject.Actor", "The JID of the contact who last changed the subject", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_SUBJECT_ACTOR, + param_spec); + + param_spec = g_param_spec_int64 ("subject-timestamp", + "Subject.Timestamp", + "The UNIX timestamp at which the subject was last changed", + G_MININT64, G_MAXINT64, 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_SUBJECT_TIMESTAMP, + param_spec); + + param_spec = g_param_spec_boolean ("can-set-subject", + "Subject.CanSet", "Whether we believe we can set the subject", + TRUE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_CAN_SET_SUBJECT, + param_spec); + signals[READY] = g_signal_new ("ready", G_OBJECT_CLASS_TYPE (gabble_muc_channel_class), @@ -1128,19 +1185,12 @@ gabble_muc_channel_class_init (GabbleMucChannelClass *gabble_muc_channel_class) GABBLE_TYPE_CALL_MUC_CHANNEL, G_TYPE_POINTER); - tp_properties_mixin_class_init (object_class, - G_STRUCT_OFFSET (GabbleMucChannelClass, - properties_class), - room_property_signatures, NUM_ROOM_PROPS, - gabble_muc_channel_do_set_properties); - - - tp_dbus_properties_mixin_implement_interface (object_class, - TP_IFACE_QUARK_CHANNEL_INTERFACE_CONFERENCE, - tp_dbus_properties_mixin_getter_gobject_properties, NULL, - conference_props); + gabble_muc_channel_class->dbus_props_class.interfaces = prop_interfaces; + tp_dbus_properties_mixin_class_init (object_class, + G_STRUCT_OFFSET (GabbleMucChannelClass, dbus_props_class)); tp_message_mixin_init_dbus_properties (object_class); + tp_base_room_config_register_class (base_class); tp_group_mixin_class_init (object_class, G_STRUCT_OFFSET (GabbleMucChannelClass, group_class), @@ -1173,6 +1223,7 @@ gabble_muc_channel_dispose (GObject *object) tp_clear_object (&priv->wmuc); tp_clear_object (&priv->requests_cancellable); + tp_clear_object (&priv->room_config); if (G_OBJECT_CLASS (gabble_muc_channel_parent_class)->dispose) G_OBJECT_CLASS (gabble_muc_channel_parent_class)->dispose (object); @@ -1193,8 +1244,6 @@ gabble_muc_channel_finalize (GObject *object) g_string_free (priv->self_jid, TRUE); } - g_free (priv->password); - if (priv->initial_channels != NULL) { g_boxed_free (TP_ARRAY_TYPE_OBJECT_PATH_LIST, priv->initial_channels); @@ -1213,7 +1262,11 @@ gabble_muc_channel_finalize (GObject *object) priv->initial_ids = NULL; } - tp_properties_mixin_finalize (object); + g_free (priv->room_name); + g_free (priv->server); + g_free (priv->subject); + g_free (priv->subject_actor); + tp_group_mixin_finalize (object); tp_message_mixin_finalize (object); @@ -1257,9 +1310,9 @@ clear_leave_timer (GabbleMucChannel *chan) } static void -change_password_flags (GabbleMucChannel *chan, - TpChannelPasswordFlags add, - TpChannelPasswordFlags del) +change_must_provide_password ( + GabbleMucChannel *chan, + gboolean must_provide_password) { GabbleMucChannelPrivate *priv; TpChannelPasswordFlags added, removed; @@ -1268,20 +1321,27 @@ change_password_flags (GabbleMucChannel *chan, priv = chan->priv; - added = add & ~priv->password_flags; - priv->password_flags |= added; + if (priv->must_provide_password == !!must_provide_password) + return; - removed = del & priv->password_flags; - priv->password_flags &= ~removed; + priv->must_provide_password = !!must_provide_password; - if (add != 0 || del != 0) + if (must_provide_password) { - DEBUG ("emitting password flags changed, added 0x%X, removed 0x%X", - added, removed); - - tp_svc_channel_interface_password_emit_password_flags_changed ( - chan, added, removed); + added = TP_CHANNEL_PASSWORD_FLAG_PROVIDE; + removed = 0; + } + else + { + added = 0; + removed = TP_CHANNEL_PASSWORD_FLAG_PROVIDE; } + + DEBUG ("emitting password flags changed, added 0x%X, removed 0x%X", + added, removed); + + tp_svc_channel_interface_password_emit_password_flags_changed ( + chan, added, removed); } static void @@ -1297,7 +1357,7 @@ provide_password_return_if_pending (GabbleMucChannel *chan, gboolean success) if (success) { - change_password_flags (chan, 0, TP_CHANNEL_PASSWORD_FLAG_PROVIDE); + change_must_provide_password (chan, FALSE); } } @@ -1364,10 +1424,6 @@ channel_state_changed (GabbleMucChannel *chan, priv->poll_timer_id = g_timeout_add_seconds (interval, timeout_poll, chan); - - /* no need to keep this around any longer, if it's set */ - g_free (priv->password); - priv->password = NULL; } else if (new_state == MUC_STATE_ENDED) { @@ -1384,6 +1440,22 @@ channel_state_changed (GabbleMucChannel *chan, } } +static void +return_from_set_subject ( + GabbleMucChannel *self, + const GError *error) +{ + GabbleMucChannelPrivate *priv = self->priv; + + if (error == NULL) + tp_svc_channel_interface_subject_return_from_set_subject ( + priv->set_subject_context); + else + dbus_g_method_return_error (priv->set_subject_context, error); + + priv->set_subject_context = NULL; + tp_clear_pointer (&priv->set_subject_stanza_id, g_free); +} static void close_channel (GabbleMucChannel *chan, const gchar *reason, @@ -1437,7 +1509,10 @@ close_channel (GabbleMucChannel *chan, const gchar *reason, handles = tp_handle_set_to_array (chan->group.members); gabble_presence_cache_update_many (conn->presence_cache, handles, NULL, GABBLE_PRESENCE_UNKNOWN, NULL, 0); - g_array_free (handles, TRUE); + g_array_unref (handles); + + if (priv->set_subject_context != NULL) + return_from_set_subject (chan, &error); g_object_set (chan, "state", MUC_STATE_ENDED, NULL); g_object_unref (chan); @@ -1482,7 +1557,7 @@ handle_nick_conflict (GabbleMucChannel *chan, */ g_assert (from != NULL); - if (index (from, '/') != NULL && tp_strdiff (from, priv->self_jid->str)) + if (strchr (from, '/') != NULL && tp_strdiff (from, priv->self_jid->str)) { DEBUG ("ignoring spurious conflict message for %s", from); return TRUE; @@ -1517,117 +1592,99 @@ handle_nick_conflict (GabbleMucChannel *chan, tp_handle_unref (contact_repo, self_handle); priv->nick_retry_count++; - send_join_request (chan, priv->password); + send_join_request (chan); return TRUE; } -static LmHandlerResult -room_created_submit_reply_cb (GabbleConnection *conn, LmMessage *sent_msg, - LmMessage *reply_msg, GObject *object, - gpointer user_data) +static void +room_created_submit_reply_cb ( + GObject *source, + GAsyncResult *result, + gpointer user_data) { - if (lm_message_get_sub_type (reply_msg) != LM_MESSAGE_SUB_TYPE_RESULT) - { - DEBUG ("failed to submit room config"); - } - - return LM_HANDLER_RESULT_REMOVE_MESSAGE; + if (conn_util_send_iq_finish (GABBLE_CONNECTION (source), result, NULL, NULL)) + DEBUG ("failed to submit room config"); } static WockyNode * -config_form_get_form_node (LmMessage *msg) +config_form_get_form_node (WockyStanza *stanza) { - WockyNode *node; - NodeIter i; + WockyNode *query, *x; + WockyNodeIter i; /* find the query node */ - node = wocky_node_get_child (wocky_stanza_get_top_node (msg), "query"); - if (node == NULL) + query = wocky_node_get_child (wocky_stanza_get_top_node (stanza), "query"); + if (query == NULL) return NULL; /* then the form node */ - for (i = node_iter (node); i; i = node_iter_next (i)) + wocky_node_iter_init (&i, query, "x", NS_X_DATA); + while (wocky_node_iter_next (&i, &x)) { - WockyNode *child = node_iter_data (i); - - if (tp_strdiff (child->name, "x")) - { - continue; - } - - if (!wocky_node_has_ns (child, NS_X_DATA)) - { - continue; - } - - if (tp_strdiff (wocky_node_get_attribute (child, "type"), "form")) - { - continue; - } - - return child; + if (!tp_strdiff (wocky_node_get_attribute (x, "type"), "form")) + return x; } return NULL; } -static LmHandlerResult -perms_config_form_reply_cb (GabbleConnection *conn, LmMessage *sent_msg, - LmMessage *reply_msg, GObject *object, - gpointer user_data) +static void +perms_config_form_reply_cb ( + GObject *source, + GAsyncResult *result, + gpointer user_data) { - GabbleMucChannel *chan = GABBLE_MUC_CHANNEL (object); - GabbleMucChannelPrivate *priv = chan->priv; - WockyNode *form_node; - NodeIter i; + GabbleMucChannel *self = GABBLE_MUC_CHANNEL (user_data); + GabbleMucChannelPrivate *priv = self->priv; + WockyStanza *reply = NULL; + WockyNode *form_node, *field; + WockyNodeIter i; - if (lm_message_get_sub_type (reply_msg) != LM_MESSAGE_SUB_TYPE_RESULT) + if (!conn_util_send_iq_finish (GABBLE_CONNECTION (source), result, &reply, NULL)) { - DEBUG ("request for config form denied, property permissions " + DEBUG ("request for config form failed, property permissions " "will be inaccurate"); goto OUT; } /* just in case our affiliation has changed in the meantime */ - if (priv->self_affil != AFFILIATION_OWNER) + if (priv->self_affil != WOCKY_MUC_AFFILIATION_OWNER) goto OUT; - form_node = config_form_get_form_node (reply_msg); + form_node = config_form_get_form_node (reply); if (form_node == NULL) { - DEBUG ("form node node found, property permissions will be inaccurate"); + DEBUG ("form node not found, property permissions will be inaccurate"); goto OUT; } - for (i = node_iter (form_node); i; i = node_iter_next (i)) + wocky_node_iter_init (&i, form_node, "field", NULL); + while (wocky_node_iter_next (&i, &field)) { - const gchar *var; - WockyNode *node = node_iter_data (i); + const gchar *var = wocky_node_get_attribute (field, "var"); - if (strcmp (node->name, "field") != 0) - continue; - - var = wocky_node_get_attribute (node, "var"); - if (var == NULL) - continue; - - if (strcmp (var, "muc#roomconfig_roomdesc") == 0 || - strcmp (var, "muc#owner_roomdesc") == 0) + if (!tp_strdiff (var, "muc#roomconfig_roomdesc") || + !tp_strdiff (var, "muc#owner_roomdesc")) { - if (tp_properties_mixin_is_readable (G_OBJECT (chan), - ROOM_PROP_DESCRIPTION)) - { - tp_properties_mixin_change_flags (G_OBJECT (chan), - ROOM_PROP_DESCRIPTION, TP_PROPERTY_FLAG_WRITE, 0, - NULL); - - goto OUT; - } + tp_base_room_config_set_property_mutable (priv->room_config, + TP_BASE_ROOM_CONFIG_DESCRIPTION, TRUE); + tp_base_room_config_emit_properties_changed (priv->room_config); + break; } } OUT: - return LM_HANDLER_RESULT_REMOVE_MESSAGE; + tp_clear_object (&reply); + g_object_unref (self); +} + +static void +emit_subject_changed (GabbleMucChannel *chan) +{ + const gchar *changed[] = { "Subject", "Actor", "Timestamp", NULL }; + + tp_dbus_properties_mixin_emit_properties_changed (G_OBJECT (chan), + TP_IFACE_CHANNEL_INTERFACE_SUBJECT, changed); } static void @@ -1636,8 +1693,6 @@ update_permissions (GabbleMucChannel *chan) GabbleMucChannelPrivate *priv = chan->priv; TpBaseChannel *base = TP_BASE_CHANNEL (chan); TpChannelGroupFlags grp_flags_add, grp_flags_rem; - TpPropertyFlags prop_flags_add, prop_flags_rem; - TpIntSet *changed_props_val, *changed_props_flags; /* * Update group flags. @@ -1646,7 +1701,7 @@ update_permissions (GabbleMucChannel *chan) TP_CHANNEL_GROUP_FLAG_MESSAGE_ADD; grp_flags_rem = 0; - if (priv->self_role == ROLE_MODERATOR) + if (priv->self_role == WOCKY_MUC_ROLE_MODERATOR) { grp_flags_add |= TP_CHANNEL_GROUP_FLAG_CAN_REMOVE | TP_CHANNEL_GROUP_FLAG_MESSAGE_REMOVE; @@ -1659,172 +1714,34 @@ update_permissions (GabbleMucChannel *chan) tp_group_mixin_change_flags ((GObject *) chan, grp_flags_add, grp_flags_rem); + /* Update RoomConfig.CanUpdateConfiguration */ - /* - * Update write capabilities based on room configuration - * and own role and affiliation. - */ - - changed_props_val = tp_intset_sized_new (NUM_ROOM_PROPS); - changed_props_flags = tp_intset_sized_new (NUM_ROOM_PROPS); - - /* - * Subject - * - * FIXME: this might be allowed for participants/moderators only, - * so for now just rely on the server making that call. - */ - - if (priv->self_role >= ROLE_VISITOR) + /* The room configuration is part of the "room definition", so is defined by + * the XEP to be editable only by owners. */ + if (priv->self_affil == WOCKY_MUC_AFFILIATION_OWNER) { - prop_flags_add = TP_PROPERTY_FLAG_WRITE; - prop_flags_rem = 0; + tp_base_room_config_set_can_update_configuration (priv->room_config, TRUE); } else { - prop_flags_add = 0; - prop_flags_rem = TP_PROPERTY_FLAG_WRITE; + tp_base_room_config_set_can_update_configuration (priv->room_config, FALSE); } - tp_properties_mixin_change_flags (G_OBJECT (chan), - ROOM_PROP_SUBJECT, prop_flags_add, prop_flags_rem, - changed_props_flags); - - /* The room properties below are part of the "room definition", so are - * defined by the XEP to be editable only by owners. */ + tp_base_room_config_emit_properties_changed (priv->room_config); - if (priv->self_affil == AFFILIATION_OWNER) - { - prop_flags_add = TP_PROPERTY_FLAG_WRITE; - prop_flags_rem = 0; - } - else - { - prop_flags_add = 0; - prop_flags_rem = TP_PROPERTY_FLAG_WRITE; - } - - tp_properties_mixin_change_flags (G_OBJECT (chan), - ROOM_PROP_ANONYMOUS, prop_flags_add, prop_flags_rem, - changed_props_flags); - - tp_properties_mixin_change_flags (G_OBJECT (chan), - ROOM_PROP_INVITE_ONLY, prop_flags_add, prop_flags_rem, - changed_props_flags); - - tp_properties_mixin_change_flags (G_OBJECT (chan), - ROOM_PROP_INVITE_RESTRICTED, prop_flags_add, prop_flags_rem, - changed_props_flags); - - tp_properties_mixin_change_flags (G_OBJECT (chan), - ROOM_PROP_MODERATED, prop_flags_add, prop_flags_rem, - changed_props_flags); - - tp_properties_mixin_change_flags (G_OBJECT (chan), - ROOM_PROP_NAME, prop_flags_add, prop_flags_rem, - changed_props_flags); - - tp_properties_mixin_change_flags (G_OBJECT (chan), - ROOM_PROP_PASSWORD, prop_flags_add, prop_flags_rem, - changed_props_flags); - - tp_properties_mixin_change_flags (G_OBJECT (chan), - ROOM_PROP_PASSWORD_REQUIRED, prop_flags_add, prop_flags_rem, - changed_props_flags); - - tp_properties_mixin_change_flags (G_OBJECT (chan), - ROOM_PROP_PERSISTENT, prop_flags_add, prop_flags_rem, - changed_props_flags); - - tp_properties_mixin_change_flags (G_OBJECT (chan), - ROOM_PROP_PRIVATE, prop_flags_add, prop_flags_rem, - changed_props_flags); - - if (priv->self_affil == AFFILIATION_OWNER) + if (priv->self_affil == WOCKY_MUC_AFFILIATION_OWNER) { /* request the configuration form purely to see if the description * is writable by us in this room. sigh. GO MUC!!! */ - LmMessage *msg; - WockyNode *node; - GError *error = NULL; - gboolean success; - - msg = lm_message_new_with_sub_type (priv->jid, - LM_MESSAGE_TYPE_IQ, LM_MESSAGE_SUB_TYPE_GET); - node = wocky_node_add_child_with_content ( - wocky_stanza_get_top_node (msg), "query", NULL); - node->ns = g_quark_from_string (NS_MUC_OWNER); - - success = _gabble_connection_send_with_reply ( - GABBLE_CONNECTION (tp_base_channel_get_connection (base)), msg, - perms_config_form_reply_cb, G_OBJECT (chan), NULL, - &error); - - lm_message_unref (msg); - - if (!success) - { - DEBUG ("failed to request config form: %s", error->message); - g_error_free (error); - } - } - else - { - /* mark description unwritable if we're no longer an owner */ - tp_properties_mixin_change_flags (G_OBJECT (chan), - ROOM_PROP_DESCRIPTION, 0, TP_PROPERTY_FLAG_WRITE, - changed_props_flags); - } - - /* - * Emit signals. - */ - tp_properties_mixin_emit_changed (G_OBJECT (chan), changed_props_val); - tp_properties_mixin_emit_flags (G_OBJECT (chan), changed_props_flags); - tp_intset_destroy (changed_props_val); - tp_intset_destroy (changed_props_flags); -} + GabbleConnection *conn = GABBLE_CONNECTION ( + tp_base_channel_get_connection (base)); + WockyStanza *stanza = wocky_stanza_build ( + WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_GET, NULL, priv->jid, + '(', "query", ':', WOCKY_NS_MUC_OWNER, ')', NULL); - - -/* ************************************************************************* */ -/* wocky MUC implementation */ -static GabbleMucRole -get_role_from_backend (WockyMucRole role) -{ - switch (role) - { - case WOCKY_MUC_ROLE_NONE: - return ROLE_NONE; - case WOCKY_MUC_ROLE_VISITOR: - return ROLE_VISITOR; - case WOCKY_MUC_ROLE_PARTICIPANT: - return ROLE_PARTICIPANT; - case WOCKY_MUC_ROLE_MODERATOR: - return ROLE_MODERATOR; - default: - DEBUG ("unknown role '%d' -- defaulting to ROLE_VISITOR", role); - return ROLE_VISITOR; - } -} - -static GabbleMucAffiliation -get_aff_from_backend (WockyMucAffiliation aff) -{ - switch (aff) - { - case WOCKY_MUC_AFFILIATION_OUTCAST: - case WOCKY_MUC_AFFILIATION_NONE: - return AFFILIATION_NONE; - case WOCKY_MUC_AFFILIATION_MEMBER: - return AFFILIATION_MEMBER; - case WOCKY_MUC_AFFILIATION_ADMIN: - return AFFILIATION_ADMIN; - case WOCKY_MUC_AFFILIATION_OWNER: - return AFFILIATION_OWNER; - default: - DEBUG ("unknown affiliation %d -- defaulting to AFFILIATION_NONE", aff); - return AFFILIATION_NONE; + conn_util_send_iq_async (conn, stanza, NULL, perms_config_form_reply_cb, + g_object_ref (chan)); + g_object_unref (stanza); } } @@ -1860,8 +1777,8 @@ handle_error (GObject *source, return; } - DEBUG ("password required to join, changing password flags"); - change_password_flags (gmuc, TP_CHANNEL_PASSWORD_FLAG_PROVIDE, 0); + DEBUG ("password required to join; signalling"); + change_must_provide_password (gmuc, TRUE); g_object_set (gmuc, "state", MUC_STATE_AUTH, NULL); } else @@ -1991,13 +1908,27 @@ handle_tube_presence (GabbleMucChannel *gmuc, gabble_tubes_channel_presence_updated (priv->tube, from, node); } +static TpChannelGroupChangeReason +muc_status_codes_to_change_reason (guint codes) +{ + if ((codes & WOCKY_MUC_CODE_BANNED) != 0) + return TP_CHANNEL_GROUP_CHANGE_REASON_BANNED; + else if ((codes & ( WOCKY_MUC_CODE_KICKED + | WOCKY_MUC_CODE_KICKED_AFFILIATION + | WOCKY_MUC_CODE_KICKED_ROOM_PRIVATISED + | WOCKY_MUC_CODE_KICKED_SHUTDOWN + )) != 0) + return TP_CHANNEL_GROUP_CHANGE_REASON_KICKED; + else + return TP_CHANNEL_GROUP_CHANGE_REASON_NONE; +} /* connect to wocky-muc:SIG_PARTED, which we will receive when the MUC tells * * us that we have left the channel */ static void handle_parted (GObject *source, WockyStanza *stanza, - GHashTable *code, + guint codes, const gchar *actor_jid, const gchar *why, const gchar *msg, @@ -2014,14 +1945,6 @@ handle_parted (GObject *source, TpIntSet *handles = NULL; TpHandle member = 0; TpHandle actor = 0; - int x = 0; - static const gpointer banned = GUINT_TO_POINTER (WOCKY_MUC_CODE_BANNED); - static const gpointer const kicked[] = - { GUINT_TO_POINTER (WOCKY_MUC_CODE_KICKED), - GUINT_TO_POINTER (WOCKY_MUC_CODE_KICKED_AFFILIATION), - GUINT_TO_POINTER (WOCKY_MUC_CODE_KICKED_ROOM_PRIVATISED), - GUINT_TO_POINTER (WOCKY_MUC_CODE_KICKED_SHUTDOWN), - NULL }; const char *jid = wocky_muc_jid (wmuc); DEBUG ("called with jid='%s'", jid); @@ -2060,14 +1983,7 @@ handle_parted (GObject *source, DEBUG ("ignoring invalid actor JID %s", actor_jid); } - if (g_hash_table_lookup (code, banned) != NULL) - reason = TP_CHANNEL_GROUP_CHANGE_REASON_BANNED; - else - for (x = 0; kicked[x] != NULL; x++) - { - if (g_hash_table_lookup (code, kicked[x]) != NULL) - reason = TP_CHANNEL_GROUP_CHANGE_REASON_KICKED; - } + reason = muc_status_codes_to_change_reason (codes); /* handle_tube_presence creates tubes if need be, so bypass it here: */ if (priv->tube != NULL) @@ -2088,7 +2004,7 @@ handle_parted (GObject *source, static void handle_left (GObject *source, WockyStanza *stanza, - GHashTable *code, + guint codes, WockyMucMember *who, const gchar *actor_jid, const gchar *why, @@ -2105,14 +2021,6 @@ handle_left (GObject *source, TpIntSet *handles = NULL; TpHandle member = 0; TpHandle actor = 0; - int x = 0; - static const gpointer banned = GUINT_TO_POINTER (WOCKY_MUC_CODE_BANNED); - static const gpointer const kicked[] = - { GUINT_TO_POINTER (WOCKY_MUC_CODE_KICKED), - GUINT_TO_POINTER (WOCKY_MUC_CODE_KICKED_AFFILIATION), - GUINT_TO_POINTER (WOCKY_MUC_CODE_KICKED_ROOM_PRIVATISED), - GUINT_TO_POINTER (WOCKY_MUC_CODE_KICKED_SHUTDOWN), - NULL }; member = tp_handle_ensure (contact_repo, who->from, NULL, NULL); @@ -2132,14 +2040,7 @@ handle_left (GObject *source, DEBUG ("ignoring invalid actor JID %s", actor_jid); } - if (g_hash_table_lookup (code, banned) != NULL) - reason = TP_CHANNEL_GROUP_CHANGE_REASON_BANNED; - else - for (x = 0; kicked[x] != NULL; x++) - { - if (g_hash_table_lookup (code, kicked[x]) != NULL) - reason = TP_CHANNEL_GROUP_CHANGE_REASON_KICKED; - } + reason = muc_status_codes_to_change_reason (codes); /* handle_tube_presence creates tubes if need be, so bypass it here: */ if (priv->tube != NULL) @@ -2170,8 +2071,8 @@ handle_perms (GObject *source, GabbleMucChannelPrivate *priv = gmuc->priv; TpHandle myself = TP_GROUP_MIXIN (gmuc)->self_handle; - priv->self_role = get_role_from_backend (wocky_muc_role (wmuc)); - priv->self_affil = get_aff_from_backend (wocky_muc_affiliation (wmuc)); + priv->self_role = wocky_muc_role (wmuc); + priv->self_affil = wocky_muc_affiliation (wmuc); room_properties_update (gmuc); update_permissions (gmuc); @@ -2218,7 +2119,7 @@ handle_fill_presence (WockyMuc *muc, static void handle_renamed (GObject *source, WockyStanza *stanza, - GHashTable *code, + guint codes, gpointer data) { WockyMuc *wmuc = WOCKY_MUC (source); @@ -2300,7 +2201,7 @@ update_roster_presence (GabbleMucChannel *gmuc, static void handle_join (WockyMuc *muc, WockyStanza *stanza, - GHashTable *code, + guint codes, gpointer data) { GabbleMucChannel *gmuc = GABBLE_MUC_CHANNEL (data); @@ -2334,42 +2235,24 @@ handle_join (WockyMuc *muc, tp_handle_set_peek (members), NULL, NULL, NULL, 0, 0); /* accept the config of the room if it was created for us: */ - if (g_hash_table_lookup (code, (gpointer) WOCKY_MUC_CODE_NEW_ROOM)) + if (codes & WOCKY_MUC_CODE_NEW_ROOM) { - GError *error = NULL; - gboolean sent = FALSE; WockyStanza *accept = wocky_stanza_build ( WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_SET, - NULL, NULL, + NULL, gmuc->priv->jid, '(', "query", ':', WOCKY_NS_MUC_OWNER, '(', "x", ':', WOCKY_XMPP_NS_DATA, '@', "type", "submit", ')', ')', NULL); - - sent = _gabble_connection_send_with_reply ( - GABBLE_CONNECTION (base_conn), accept, - room_created_submit_reply_cb, data, NULL, &error); - + conn_util_send_iq_async (GABBLE_CONNECTION (base_conn), accept, NULL, + room_created_submit_reply_cb, NULL); g_object_unref (accept); - - if (!sent) - { - DEBUG ("failed to send submit message: %s", error->message); - g_error_free (error); - - g_object_unref (accept); - close_channel (gmuc, NULL, TRUE, 0, - TP_CHANNEL_GROUP_CHANGE_REASON_NONE); - - goto out; - } } g_object_set (gmuc, "state", MUC_STATE_JOINED, NULL); - out: tp_handle_unref (contact_repo, myself); tp_handle_set_destroy (members); tp_handle_set_destroy (owners); @@ -2382,7 +2265,7 @@ handle_join (WockyMuc *muc, static void handle_presence (GObject *source, WockyStanza *stanza, - GHashTable *code, + guint codes, WockyMucMember *who, gpointer data) { @@ -2456,7 +2339,7 @@ handle_message (GObject *source, WockyStanza *stanza, WockyMucMsgType type, const gchar *xmpp_id, - time_t stamp, + GDateTime *datetime, WockyMucMember *who, const gchar *text, const gchar *subject, @@ -2508,7 +2391,7 @@ handle_message (GObject *source, if (text != NULL) _gabble_muc_channel_receive (gmuc, - msg_type, handle_type, from, stamp, xmpp_id, text, stanza, + msg_type, handle_type, from, datetime, xmpp_id, text, stanza, GABBLE_TEXT_CHANNEL_SEND_NO_ERROR, TP_DELIVERY_STATUS_DELIVERED); if (from_member && state != WOCKY_MUC_MSG_STATE_NONE) @@ -2531,12 +2414,14 @@ handle_message (GObject *source, default: tp_msg_state = TP_CHANNEL_CHAT_STATE_ACTIVE; } - _gabble_muc_channel_state_receive (gmuc, tp_msg_state, from); + + tp_svc_channel_interface_chat_state_emit_chat_state_changed (gmuc, + from, tp_msg_state); } if (subject != NULL) - _gabble_muc_channel_handle_subject (gmuc, msg_type, handle_type, from, - stamp, subject, stanza); + _gabble_muc_channel_handle_subject (gmuc, handle_type, from, + datetime, subject, stanza); tp_handle_unref (repo, from); } @@ -2546,7 +2431,7 @@ handle_errmsg (GObject *source, WockyStanza *stanza, WockyMucMsgType type, const gchar *xmpp_id, - time_t stamp, + GDateTime *datetime, WockyMucMember *who, const gchar *text, WockyXmppError error, @@ -2554,6 +2439,7 @@ handle_errmsg (GObject *source, gpointer data) { GabbleMucChannel *gmuc = GABBLE_MUC_CHANNEL (data); + GabbleMucChannelPrivate *priv = gmuc->priv; TpBaseChannel *base = TP_BASE_CHANNEL (gmuc); TpBaseConnection *conn = tp_base_channel_get_connection (base); gboolean from_member = (who != NULL); @@ -2562,6 +2448,7 @@ handle_errmsg (GObject *source, TpHandleRepoIface *repo = NULL; TpHandleType handle_type; TpHandle from = 0; + const gchar *subject; if (from_member) { @@ -2593,7 +2480,23 @@ handle_errmsg (GObject *source, if (text != NULL) _gabble_muc_channel_receive (gmuc, TP_CHANNEL_TEXT_MESSAGE_TYPE_NOTICE, - handle_type, from, stamp, xmpp_id, text, stanza, tp_err, ds); + handle_type, from, datetime, xmpp_id, text, stanza, tp_err, ds); + + /* FIXME: this is stupid. WockyMuc gives us the subject for non-errors, but + * doesn't bother for errors. + */ + subject = wocky_node_get_content_from_child ( + wocky_stanza_get_top_node (stanza), "subject"); + + /* The server is under no obligation to echo the <subject> element back if it + * sends us an error. Fortunately, it should preserve the id='' element so we + * can check for that instead. + */ + if (subject != NULL || + (priv->set_subject_stanza_id != NULL && + !tp_strdiff (xmpp_id, priv->set_subject_stanza_id))) + _gabble_muc_channel_handle_subject (gmuc, + handle_type, from, datetime, subject, stanza); tp_handle_unref (repo, from); } @@ -2604,43 +2507,34 @@ handle_errmsg (GObject *source, */ void _gabble_muc_channel_handle_subject (GabbleMucChannel *chan, - TpChannelTextMessageType msg_type, TpHandleType handle_type, TpHandle sender, - time_t timestamp, + GDateTime *datetime, const gchar *subject, LmMessage *msg) { GabbleMucChannelPrivate *priv; - TpIntSet *changed_values, *changed_flags; - GValue val = { 0, }; + const gchar *actor; GError *error = NULL; + gint64 timestamp = datetime != NULL ? + g_date_time_to_unix (datetime) : G_MAXINT64; g_assert (GABBLE_IS_MUC_CHANNEL (chan)); priv = chan->priv; - if (priv->properties_ctx) - { - tp_properties_context_remove (priv->properties_ctx, - ROOM_PROP_SUBJECT); - } - if (wocky_stanza_extract_errors (msg, NULL, &error, NULL, NULL)) { - if (priv->properties_ctx) + if (priv->set_subject_context != NULL) { - error->domain = TP_ERRORS; - error->code = TP_ERROR_PERMISSION_DENIED; + GError *tp_error = NULL; - if (tp_str_empty (error->message)) - { - g_free (error->message); - error->message = g_strdup ("failed to change subject"); - } + gabble_set_tp_error_from_wocky (error, &tp_error); + if (tp_str_empty (tp_error->message)) + g_prefix_error (&tp_error, "failed to change subject"); - tp_properties_context_return (priv->properties_ctx, error); - priv->properties_ctx = NULL; + return_from_set_subject (chan, tp_error); + g_clear_error (&tp_error); /* Get the properties into a consistent state. */ room_properties_update (chan); @@ -2650,71 +2544,35 @@ _gabble_muc_channel_handle_subject (GabbleMucChannel *chan, return; } - DEBUG ("updating new property value for subject"); - - changed_values = tp_intset_sized_new (NUM_ROOM_PROPS); - changed_flags = tp_intset_sized_new (NUM_ROOM_PROPS); - - /* ROOM_PROP_SUBJECT */ - g_value_init (&val, G_TYPE_STRING); - g_value_set_string (&val, subject); - - tp_properties_mixin_change_value (G_OBJECT (chan), - ROOM_PROP_SUBJECT, &val, changed_values); - - tp_properties_mixin_change_flags (G_OBJECT (chan), - ROOM_PROP_SUBJECT, TP_PROPERTY_FLAG_READ, 0, - changed_flags); - - g_value_unset (&val); - - /* ROOM_PROP_SUBJECT_CONTACT */ - g_value_init (&val, G_TYPE_UINT); + /* Channel.Interface.Subject properties */ if (handle_type == TP_HANDLE_TYPE_CONTACT) { - g_value_set_uint (&val, sender); + TpHandleRepoIface *contact_handles = tp_base_connection_get_handles ( + tp_base_channel_get_connection (TP_BASE_CHANNEL (chan)), + handle_type); + + actor = tp_handle_inspect (contact_handles, sender); } else { - g_value_set_uint (&val, 0); + actor = ""; } - tp_properties_mixin_change_value (G_OBJECT (chan), - ROOM_PROP_SUBJECT_CONTACT, &val, changed_values); - - tp_properties_mixin_change_flags (G_OBJECT (chan), - ROOM_PROP_SUBJECT_CONTACT, TP_PROPERTY_FLAG_READ, 0, - changed_flags); - - g_value_unset (&val); - - /* ROOM_PROP_SUBJECT_TIMESTAMP */ - g_value_init (&val, G_TYPE_UINT); - g_value_set_uint (&val, timestamp); - - tp_properties_mixin_change_value (G_OBJECT (chan), - ROOM_PROP_SUBJECT_TIMESTAMP, &val, changed_values); + g_free (priv->subject); + g_free (priv->subject_actor); + priv->subject = g_strdup (subject); + priv->subject_actor = g_strdup (actor); + priv->subject_timestamp = timestamp; - tp_properties_mixin_change_flags (G_OBJECT (chan), - ROOM_PROP_SUBJECT_TIMESTAMP, TP_PROPERTY_FLAG_READ, 0, - changed_flags); - - g_value_unset (&val); + DEBUG ("Subject changed to '%s' by '%s' at %" G_GINT64_FORMAT "", + subject, actor, timestamp); /* Emit signals */ - tp_properties_mixin_emit_changed (G_OBJECT (chan), changed_values); - tp_properties_mixin_emit_flags (G_OBJECT (chan), changed_flags); - tp_intset_destroy (changed_values); - tp_intset_destroy (changed_flags); + emit_subject_changed (chan); - if (priv->properties_ctx) - { - if (tp_properties_context_return_if_done (priv->properties_ctx)) - { - priv->properties_ctx = NULL; - } - } + if (priv->set_subject_context != NULL) + return_from_set_subject (chan, NULL); } /** @@ -2725,7 +2583,7 @@ _gabble_muc_channel_receive (GabbleMucChannel *chan, TpChannelTextMessageType msg_type, TpHandleType sender_handle_type, TpHandle sender, - time_t timestamp, + GDateTime *datetime, const gchar *id, const gchar *text, LmMessage *msg, @@ -2739,6 +2597,7 @@ _gabble_muc_channel_receive (GabbleMucChannel *chan, gboolean is_echo; gboolean is_error; gchar *tmp; + gint64 timestamp = datetime != NULL ? g_date_time_to_unix (datetime): 0; g_assert (GABBLE_IS_MUC_CHANNEL (chan)); @@ -2852,24 +2711,6 @@ _gabble_muc_channel_receive (GabbleMucChannel *chan, } } -/** - * _gabble_muc_channel_state_receive - * - * Send the D-BUS signal ChatStateChanged - * on org.freedesktop.Telepathy.Channel.Interface.ChatState - */ -void -_gabble_muc_channel_state_receive (GabbleMucChannel *chan, - guint state, - guint from_handle) -{ - g_assert (state < NUM_TP_CHANNEL_CHAT_STATES); - g_assert (GABBLE_IS_MUC_CHANNEL (chan)); - - tp_svc_channel_interface_chat_state_emit_chat_state_changed (chan, - from_handle, state); -} - static void gabble_muc_channel_close (TpBaseChannel *base) { @@ -2897,7 +2738,7 @@ gabble_muc_channel_get_password_flags (TpSvcChannelInterfacePassword *iface, priv = self->priv; tp_svc_channel_interface_password_return_from_get_password_flags (context, - priv->password_flags); + priv->must_provide_password ? TP_CHANNEL_PASSWORD_FLAG_PROVIDE : 0); } @@ -2922,7 +2763,7 @@ gabble_muc_channel_provide_password (TpSvcChannelInterfacePassword *iface, priv = self->priv; - if ((priv->password_flags & TP_CHANNEL_PASSWORD_FLAG_PROVIDE) == 0 || + if (!priv->must_provide_password || priv->password_ctx != NULL) { GError error = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE, @@ -2931,11 +2772,40 @@ gabble_muc_channel_provide_password (TpSvcChannelInterfacePassword *iface, } else { - send_join_request (self, password); + g_object_set (priv->wmuc, "password", password, NULL); + send_join_request (self); priv->password_ctx = context; } } +static void +_gabble_muc_channel_message_sent_cb (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + WockyPorter *porter = WOCKY_PORTER (source); + _GabbleMUCSendMessageCtx *context = user_data; + GabbleMucChannel *chan = context->channel; + TpMessage *message = context->message; + GError *error = NULL; + + if (wocky_porter_send_finish (porter, res, &error)) + { + tp_message_mixin_sent ((GObject *) chan, message, + TP_MESSAGE_SENDING_FLAG_REPORT_DELIVERY, context->token, NULL); + } + else + { + tp_message_mixin_sent ((GObject *) chan, context->message, + TP_MESSAGE_SENDING_FLAG_REPORT_DELIVERY, NULL, error); + g_free (error); + } + + g_object_unref (context->channel); + g_object_unref (context->message); + g_free (context->token); + g_slice_free (_GabbleMUCSendMessageCtx, context); +} /** * gabble_muc_channel_send @@ -2952,14 +2822,35 @@ gabble_muc_channel_send (GObject *obj, GabbleMucChannel *self = GABBLE_MUC_CHANNEL (obj); TpBaseChannel *base = TP_BASE_CHANNEL (self); GabbleMucChannelPrivate *priv = self->priv; + GabbleConnection *gabble_conn = + GABBLE_CONNECTION (tp_base_channel_get_connection (base)); + _GabbleMUCSendMessageCtx *context = NULL; + WockyStanza *stanza = NULL; + WockyPorter *porter = NULL; + GError *error = NULL; + gchar *id = NULL; - flags &= TP_MESSAGE_SENDING_FLAG_REPORT_DELIVERY; - - gabble_message_util_send_message (obj, - GABBLE_CONNECTION (tp_base_channel_get_connection (base)), - message, flags, + stanza = gabble_message_util_build_stanza (message, gabble_conn, LM_MESSAGE_SUB_TYPE_GROUPCHAT, TP_CHANNEL_CHAT_STATE_ACTIVE, - priv->jid, FALSE /* send nick */); + priv->jid, FALSE, &id, &error); + + if (stanza != NULL) + { + context = g_slice_new0 (_GabbleMUCSendMessageCtx); + context->channel = g_object_ref (obj); + context->message = g_object_ref (message); + context->token = id; + porter = gabble_connection_dup_porter (gabble_conn); + wocky_porter_send_async (porter, stanza, NULL, + _gabble_muc_channel_message_sent_cb, context); + g_object_unref (stanza); + g_object_unref (porter); + } + else + { + tp_message_mixin_sent (obj, message, flags, NULL, error); + g_error_free (error); + } } gboolean @@ -3048,7 +2939,7 @@ gabble_muc_channel_add_member (GObject *obj, tp_intset_add (set_remove_members, g_array_index (arr_members, guint, 0)); } - g_array_free (arr_members, TRUE); + g_array_unref (arr_members); tp_intset_add (set_remote_pending, handle); @@ -3064,7 +2955,7 @@ gabble_muc_channel_add_member (GObject *obj, tp_intset_destroy (set_remote_pending); /* seek to enter the room */ - send_join_request (self, NULL); + send_join_request (self); g_object_set (obj, "state", MUC_STATE_INITIATED, NULL); /* deny adding */ @@ -3162,147 +3053,206 @@ gabble_muc_channel_remove_member (GObject *obj, } -static LmHandlerResult request_config_form_reply_cb (GabbleConnection *conn, - LmMessage *sent_msg, LmMessage *reply_msg, GObject *object, +static void request_config_form_reply_cb ( + GObject *source, + GAsyncResult *result, gpointer user_data); -static gboolean -gabble_muc_channel_do_set_properties (GObject *obj, - TpPropertiesContext *ctx, - GError **error) +void +gabble_muc_channel_update_configuration_async ( + GabbleMucChannel *self, + GHashTable *validated_properties, + GAsyncReadyCallback callback, + gpointer user_data) { - GabbleMucChannel *self = GABBLE_MUC_CHANNEL (obj); GabbleMucChannelPrivate *priv = self->priv; - TpBaseChannel *base = TP_BASE_CHANNEL (obj); + TpBaseChannel *base = (TpBaseChannel *) self; GabbleConnection *conn = GABBLE_CONNECTION (tp_base_channel_get_connection (base)); - LmMessage *msg; - WockyNode *node; - gboolean success; + WockyStanza *stanza; + GSimpleAsyncResult *result = g_simple_async_result_new ((GObject *) self, + callback, user_data, gabble_muc_channel_update_configuration_async); - g_assert (priv->properties_ctx == NULL); + g_assert (priv->properties_being_updated == NULL); - /* Changing subject? */ - if (tp_properties_context_has (ctx, ROOM_PROP_SUBJECT)) - { - const gchar *str; + stanza = wocky_stanza_build ( + WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_GET, + NULL, priv->jid, + '(', "query", ':', WOCKY_NS_MUC_OWNER, ')', NULL); + conn_util_send_iq_async (conn, stanza, NULL, + request_config_form_reply_cb, result); + g_object_unref (stanza); - str = g_value_get_string (tp_properties_context_get (ctx, - ROOM_PROP_SUBJECT)); + priv->properties_being_updated = g_hash_table_ref (validated_properties); +} - msg = lm_message_new_with_sub_type (priv->jid, - LM_MESSAGE_TYPE_MESSAGE, LM_MESSAGE_SUB_TYPE_GROUPCHAT); - wocky_node_add_child_with_content ( - wocky_stanza_get_top_node (msg), "subject", str); +gboolean +gabble_muc_channel_update_configuration_finish ( + GabbleMucChannel *self, + GAsyncResult *result, + GError **error) +{ + wocky_implement_finish_void (self, + gabble_muc_channel_update_configuration_async); +} - success = _gabble_connection_send (conn, msg, error); +typedef const gchar * (*MapFieldFunc) (const GValue *value); - lm_message_unref (msg); +typedef struct { + const gchar *var; + TpBaseRoomConfigProperty prop_id; + MapFieldFunc map; +} ConfigFormMapping; - if (!success) - return FALSE; - } +static const gchar * +map_bool (const GValue *value) +{ + return g_value_get_boolean (value) ? "1" : "0"; +} - /* Changing any other properties? */ - if (tp_properties_context_has_other_than (ctx, ROOM_PROP_SUBJECT)) - { - msg = lm_message_new_with_sub_type (priv->jid, - LM_MESSAGE_TYPE_IQ, LM_MESSAGE_SUB_TYPE_GET); - node = wocky_node_add_child_with_content ( - wocky_stanza_get_top_node (msg), "query", NULL); - node->ns = g_quark_from_string (NS_MUC_OWNER); +static const gchar * +map_bool_inverted (const GValue *value) +{ + return g_value_get_boolean (value) ? "0" : "1"; +} + +static const gchar * +map_roomconfig_whois (const GValue *value) +{ + return g_value_get_boolean (value) ? "moderators" : "anyone"; +} - success = _gabble_connection_send_with_reply (conn, msg, - request_config_form_reply_cb, G_OBJECT (obj), NULL, - error); +static const gchar * +map_owner_whois (const GValue *value) +{ + return g_value_get_boolean (value) ? "admins" : "anyone"; +} - lm_message_unref (msg); +static ConfigFormMapping form_mappings[] = { + { "anonymous", TP_BASE_ROOM_CONFIG_ANONYMOUS, map_bool }, + { "muc#roomconfig_whois", TP_BASE_ROOM_CONFIG_ANONYMOUS, map_roomconfig_whois }, + { "muc#owner_whois", TP_BASE_ROOM_CONFIG_ANONYMOUS, map_owner_whois }, - if (!success) - return FALSE; - } + { "members_only", TP_BASE_ROOM_CONFIG_INVITE_ONLY, map_bool }, + { "muc#roomconfig_membersonly", TP_BASE_ROOM_CONFIG_INVITE_ONLY, map_bool }, + { "muc#owner_inviteonly", TP_BASE_ROOM_CONFIG_INVITE_ONLY, map_bool }, - priv->properties_ctx = ctx; - return TRUE; + { "moderated", TP_BASE_ROOM_CONFIG_MODERATED, map_bool }, + { "muc#roomconfig_moderatedroom", TP_BASE_ROOM_CONFIG_MODERATED, map_bool }, + { "muc#owner_moderatedroom", TP_BASE_ROOM_CONFIG_MODERATED, map_bool }, + + { "title", TP_BASE_ROOM_CONFIG_TITLE, g_value_get_string }, + { "muc#roomconfig_roomname", TP_BASE_ROOM_CONFIG_TITLE, g_value_get_string }, + { "muc#owner_roomname", TP_BASE_ROOM_CONFIG_TITLE, g_value_get_string }, + + { "muc#roomconfig_roomdesc", TP_BASE_ROOM_CONFIG_DESCRIPTION, g_value_get_string }, + { "muc#owner_roomdesc", TP_BASE_ROOM_CONFIG_DESCRIPTION, g_value_get_string }, + + { "password", TP_BASE_ROOM_CONFIG_PASSWORD, g_value_get_string }, + { "muc#roomconfig_roomsecret", TP_BASE_ROOM_CONFIG_PASSWORD, g_value_get_string }, + { "muc#owner_roomsecret", TP_BASE_ROOM_CONFIG_PASSWORD, g_value_get_string }, + + { "password_protected", TP_BASE_ROOM_CONFIG_PASSWORD_PROTECTED, map_bool }, + { "muc#roomconfig_passwordprotectedroom", TP_BASE_ROOM_CONFIG_PASSWORD_PROTECTED, map_bool }, + { "muc#owner_passwordprotectedroom", TP_BASE_ROOM_CONFIG_PASSWORD_PROTECTED, map_bool }, + + { "persistent", TP_BASE_ROOM_CONFIG_PERSISTENT, map_bool }, + { "muc#roomconfig_persistentroom", TP_BASE_ROOM_CONFIG_PERSISTENT, map_bool }, + { "muc#owner_persistentroom", TP_BASE_ROOM_CONFIG_PERSISTENT, map_bool }, + + { "public", TP_BASE_ROOM_CONFIG_PRIVATE, map_bool_inverted }, + { "muc#roomconfig_publicroom", TP_BASE_ROOM_CONFIG_PRIVATE, map_bool_inverted }, + { "muc#owner_publicroom", TP_BASE_ROOM_CONFIG_PRIVATE, map_bool_inverted }, + + { NULL } +}; + +static ConfigFormMapping * +lookup_config_form_field (const gchar *var) +{ + ConfigFormMapping *f; + + for (f = form_mappings; f->var != NULL; f++) + if (strcmp (var, f->var) == 0) + return f; + + DEBUG ("unknown field %s", var); + + return NULL; } -static LmHandlerResult request_config_form_submit_reply_cb ( - GabbleConnection *conn, LmMessage *sent_msg, LmMessage *reply_msg, - GObject *object, gpointer user_data); +static void request_config_form_submit_reply_cb ( + GObject *source, + GAsyncResult *result, + gpointer user_data); -static LmHandlerResult -request_config_form_reply_cb (GabbleConnection *conn, LmMessage *sent_msg, - LmMessage *reply_msg, GObject *object, - gpointer user_data) +static void +request_config_form_reply_cb ( + GObject *source, + GAsyncResult *result, + gpointer user_data) { - GabbleMucChannel *chan = GABBLE_MUC_CHANNEL (object); - TpBaseChannel *base = TP_BASE_CHANNEL (chan); + GabbleConnection *conn = GABBLE_CONNECTION (source); + GSimpleAsyncResult *update_result = G_SIMPLE_ASYNC_RESULT (user_data); + GabbleMucChannel *chan = GABBLE_MUC_CHANNEL ( + g_async_result_get_source_object ((GAsyncResult *) update_result)); GabbleMucChannelPrivate *priv = chan->priv; - TpPropertiesContext *ctx = priv->properties_ctx; + GHashTable *properties = priv->properties_being_updated; + WockyStanza *reply = NULL; + WockyStanza *submit_iq = NULL; + WockyNode *form_node, *submit_node, *child; GError *error = NULL; - LmMessage *msg = NULL; - WockyNode *submit_node, *form_node, *node; guint i, props_left; - NodeIter j; + WockyNodeIter j; - if (lm_message_get_sub_type (reply_msg) != LM_MESSAGE_SUB_TYPE_RESULT) + if (!conn_util_send_iq_finish (conn, result, &reply, &error)) { - error = g_error_new (TP_ERRORS, TP_ERROR_PERMISSION_DENIED, - "request for configuration form denied"); - + g_prefix_error (&error, "failed to request configuration form: "); goto OUT; } - form_node = config_form_get_form_node (reply_msg); + form_node = config_form_get_form_node (reply); if (form_node == NULL) - goto PARSE_ERROR; + { + g_set_error (&error, TP_ERROR, TP_ERROR_SERVICE_CONFUSED, + "MUC configuration form didn't actually contain a form"); + goto OUT; + } /* initialize */ - msg = lm_message_new_with_sub_type (priv->jid, LM_MESSAGE_TYPE_IQ, - LM_MESSAGE_SUB_TYPE_SET); - - node = wocky_node_add_child_with_content ( - wocky_stanza_get_top_node (msg), "query", NULL); - node->ns = g_quark_from_string (NS_MUC_OWNER); - - submit_node = wocky_node_add_child_with_content (node, "x", NULL); - submit_node->ns = g_quark_from_static_string (NS_X_DATA); - wocky_node_set_attribute (submit_node, - "type", "submit"); + submit_iq = wocky_stanza_build ( + WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_SET, + NULL, priv->jid, + '(', + "query", ':', WOCKY_NS_MUC_OWNER, + '(', + "x", ':', WOCKY_XMPP_NS_DATA, + '@', "type", "submit", + '*', &submit_node, + ')', + ')', NULL); /* we assume that the number of props will fit in a guint on all supported * platforms, so fail at compile time if this is no longer the case */ -#if NUM_ROOM_PROPS > 32 +#if TP_NUM_BASE_ROOM_CONFIG_PROPERTIES > 32 #error GabbleMUCChannel request_config_form_reply_cb needs porting to TpIntSet #endif props_left = 0; - for (i = 0; i < NUM_ROOM_PROPS; i++) + for (i = 0; i < TP_NUM_BASE_ROOM_CONFIG_PROPERTIES; i++) { - if (i == ROOM_PROP_SUBJECT) - continue; - - if (tp_properties_context_has (ctx, i)) + if (g_hash_table_lookup (properties, GUINT_TO_POINTER (i)) != NULL) props_left |= 1 << i; } - for (j = node_iter (form_node); j; j = node_iter_next (j)) + wocky_node_iter_init (&j, form_node, "field", NULL); + while (wocky_node_iter_next (&j, &child)) { - const gchar *var; + const gchar *var, *type_str; WockyNode *field_node; - WockyNode *child = node_iter_data (j); - guint id; - GType type; - gboolean invert; - const gchar *val_str = NULL, *type_str; - gboolean val_bool; - - if (strcmp (child->name, "field") != 0) - { - DEBUG ("skipping node '%s'", child->name); - continue; - } + ConfigFormMapping *f; + GValue *value = NULL; var = wocky_node_get_attribute (child, "var"); if (var == NULL) { @@ -3311,252 +3261,132 @@ request_config_form_reply_cb (GabbleConnection *conn, LmMessage *sent_msg, continue; } - id = INVALID_ROOM_PROP; - type = G_TYPE_BOOLEAN; - invert = FALSE; - - if (strcmp (var, "anonymous") == 0) - { - id = ROOM_PROP_ANONYMOUS; - } - else if (strcmp (var, "muc#roomconfig_whois") == 0) - { - id = ROOM_PROP_ANONYMOUS; - - if (tp_properties_context_has (ctx, id)) - { - val_bool = g_value_get_boolean ( - tp_properties_context_get (ctx, id)); - val_str = (val_bool) ? "moderators" : "anyone"; - } - } - else if (strcmp (var, "muc#owner_whois") == 0) - { - id = ROOM_PROP_ANONYMOUS; - - if (tp_properties_context_has (ctx, id)) - { - val_bool = g_value_get_boolean ( - tp_properties_context_get (ctx, id)); - val_str = (val_bool) ? "admins" : "anyone"; - } - } - else if (strcmp (var, "members_only") == 0 || - strcmp (var, "muc#roomconfig_membersonly") == 0 || - strcmp (var, "muc#owner_inviteonly") == 0) - { - id = ROOM_PROP_INVITE_ONLY; - } - else if (strcmp (var, "muc#roomconfig_allowinvites") == 0) - { - id = ROOM_PROP_INVITE_RESTRICTED; - invert = TRUE; - } - else if (strcmp (var, "moderated") == 0 || - strcmp (var, "muc#roomconfig_moderatedroom") == 0 || - strcmp (var, "muc#owner_moderatedroom") == 0) - { - id = ROOM_PROP_MODERATED; - } - else if (strcmp (var, "title") == 0 || - strcmp (var, "muc#roomconfig_roomname") == 0 || - strcmp (var, "muc#owner_roomname") == 0) - { - id = ROOM_PROP_NAME; - type = G_TYPE_STRING; - } - else if (strcmp (var, "muc#roomconfig_roomdesc") == 0 || - strcmp (var, "muc#owner_roomdesc") == 0) - { - id = ROOM_PROP_DESCRIPTION; - type = G_TYPE_STRING; - } - else if (strcmp (var, "password") == 0 || - strcmp (var, "muc#roomconfig_roomsecret") == 0 || - strcmp (var, "muc#owner_roomsecret") == 0) - { - id = ROOM_PROP_PASSWORD; - type = G_TYPE_STRING; - } - else if (strcmp (var, "password_protected") == 0 || - strcmp (var, "muc#roomconfig_passwordprotectedroom") == 0 || - strcmp (var, "muc#owner_passwordprotectedroom") == 0) - { - id = ROOM_PROP_PASSWORD_REQUIRED; - } - else if (strcmp (var, "persistent") == 0 || - strcmp (var, "muc#roomconfig_persistentroom") == 0 || - strcmp (var, "muc#owner_persistentroom") == 0) - { - id = ROOM_PROP_PERSISTENT; - } - else if (strcmp (var, "public") == 0 || - strcmp (var, "muc#roomconfig_publicroom") == 0 || - strcmp (var, "muc#owner_publicroom") == 0) - { - id = ROOM_PROP_PRIVATE; - invert = TRUE; - } - else - { - DEBUG ("ignoring field '%s'", var); - } + f = lookup_config_form_field (var); /* add the corresponding field node to the reply message */ - field_node = wocky_node_add_child_with_content (submit_node, "field", NULL); + field_node = wocky_node_add_child (submit_node, "field"); wocky_node_set_attribute (field_node, "var", var); type_str = wocky_node_get_attribute (child, "type"); - if (type_str) + if (type_str != NULL) { wocky_node_set_attribute (field_node, "type", type_str); } - if (id != INVALID_ROOM_PROP && tp_properties_context_has (ctx, id)) - { - /* Known property and we have a value to set */ - DEBUG ("looking up %s... has=%d", room_property_signatures[id].name, - tp_properties_context_has (ctx, id)); + if (f != NULL) + value = g_hash_table_lookup (properties, GUINT_TO_POINTER (f->prop_id)); - if (!val_str) - { - const GValue *provided_value; - - provided_value = tp_properties_context_get (ctx, id); - - switch (type) { - case G_TYPE_BOOLEAN: - val_bool = g_value_get_boolean (provided_value); - if (invert) - val_bool = !val_bool; - val_str = val_bool ? "1" : "0"; - break; - case G_TYPE_STRING: - val_str = g_value_get_string (provided_value); - break; - default: - g_assert_not_reached (); - } - } - - DEBUG ("Setting value %s for %s", val_str, var); + if (value != NULL) + { + const gchar *val_str; - props_left &= ~(1 << id); + /* Known property and we have a value to set */ + DEBUG ("transforming %s...", + wocky_enum_to_nick (TP_TYPE_BASE_ROOM_CONFIG_PROPERTY, + f->prop_id)); + g_assert (f->map != NULL); + val_str = f->map (value); /* add the corresponding value node(s) to the reply message */ + DEBUG ("Setting value %s for %s", val_str, var); wocky_node_add_child_with_content (field_node, "value", val_str); + + props_left &= ~(1 << f->prop_id); } else { /* Copy all the <value> nodes */ - NodeIter k; - - for (k = node_iter (child); k; k = node_iter_next (k)) - { - WockyNode *value_node = node_iter_data (k); + WockyNodeIter k; + WockyNode *value_node; - if (tp_strdiff (value_node->name, "value")) - /* Not a value, skip it */ - continue; - - wocky_node_add_child_with_content (field_node, "value", - value_node->content); - } + wocky_node_iter_init (&k, child, "value", NULL); + while (wocky_node_iter_next (&k, &value_node)) + wocky_node_add_child_with_content (field_node, "value", + value_node->content); } } if (props_left != 0) { + GString *unsubstituted = g_string_new (""); + printf (TP_ANSI_BOLD_ON TP_ANSI_FG_WHITE TP_ANSI_BG_RED "\n%s: the following properties were not substituted:\n", G_STRFUNC); - for (i = 0; i < NUM_ROOM_PROPS; i++) + for (i = 0; i < TP_NUM_BASE_ROOM_CONFIG_PROPERTIES; i++) { if ((props_left & (1 << i)) != 0) { - printf (" %s\n", room_property_signatures[i].name); + const gchar *name = wocky_enum_to_nick ( + TP_TYPE_BASE_ROOM_CONFIG_PROPERTY, i); + printf (" %s\n", name); + + if (unsubstituted->len > 0) + g_string_append (unsubstituted, ", "); + + g_string_append (unsubstituted, name); } } printf ("\nthis is a MUC server compatibility bug in gabble, please " "report it with a full debug log attached (running gabble " - "with LM_DEBUG=net)" TP_ANSI_RESET "\n\n"); + "with WOCKY_DEBUG=xmpp)" TP_ANSI_RESET "\n\n"); fflush (stdout); - error = g_error_new (TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, - "not all properties were substituted"); + error = g_error_new (TP_ERRORS, TP_ERROR_SERVICE_CONFUSED, + "Couldn't find fields corresponding to %s in the muc#owner form. " + "This is a MUC server compatibility bug in Gabble.", + unsubstituted->str); + g_string_free (unsubstituted, TRUE); goto OUT; } - _gabble_connection_send_with_reply ( - GABBLE_CONNECTION (tp_base_channel_get_connection (base)), msg, - request_config_form_submit_reply_cb, G_OBJECT (object), - NULL, &error); - - goto OUT; - -PARSE_ERROR: - error = g_error_new (TP_ERRORS, TP_ERROR_NOT_AVAILABLE, - "error parsing reply from server"); + conn_util_send_iq_async (conn, submit_iq, NULL, + request_config_form_submit_reply_cb, update_result); OUT: - if (error) + if (error != NULL) { - tp_properties_context_return (ctx, error); - priv->properties_ctx = NULL; + g_simple_async_result_set_from_error (update_result, error); + g_simple_async_result_complete (update_result); + g_object_unref (update_result); + tp_clear_pointer (&priv->properties_being_updated, g_hash_table_unref); + g_clear_error (&error); } - if (msg) - lm_message_unref (msg); - - return LM_HANDLER_RESULT_REMOVE_MESSAGE; + tp_clear_object (&reply); + tp_clear_object (&submit_iq); + g_object_unref (chan); } -static LmHandlerResult -request_config_form_submit_reply_cb (GabbleConnection *conn, - LmMessage *sent_msg, - LmMessage *reply_msg, - GObject *object, - gpointer user_data) +static void +request_config_form_submit_reply_cb ( + GObject *source, + GAsyncResult *result, + gpointer user_data) { - GabbleMucChannel *chan = GABBLE_MUC_CHANNEL (object); + GSimpleAsyncResult *update_result = G_SIMPLE_ASYNC_RESULT (user_data); + GabbleMucChannel *chan = GABBLE_MUC_CHANNEL ( + g_async_result_get_source_object ((GAsyncResult *) update_result)); GabbleMucChannelPrivate *priv = chan->priv; - TpPropertiesContext *ctx = priv->properties_ctx; GError *error = NULL; - gboolean returned; - - if (lm_message_get_sub_type (reply_msg) != LM_MESSAGE_SUB_TYPE_RESULT) - { - error = g_error_new (TP_ERRORS, TP_ERROR_PERMISSION_DENIED, - "submitted configuration form was rejected"); - } - if (!error) + if (!conn_util_send_iq_finish (GABBLE_CONNECTION (source), result, NULL, &error)) { - guint i; - - for (i = 0; i < NUM_ROOM_PROPS; i++) - { - if (i != ROOM_PROP_SUBJECT) - tp_properties_context_remove (ctx, i); - } - - returned = tp_properties_context_return_if_done (ctx); + g_prefix_error (&error, "submitted configuration form was rejected: "); + g_simple_async_result_set_from_error (update_result, error); + g_clear_error (&error); } - else - { - tp_properties_context_return (ctx, error); - returned = TRUE; - /* Get the properties into a consistent state. */ - room_properties_update (chan); - } + g_simple_async_result_complete (update_result); + tp_clear_pointer (&priv->properties_being_updated, g_hash_table_unref); - if (returned) - priv->properties_ctx = NULL; + /* Get the properties into a consistent state. */ + room_properties_update (chan); - return LM_HANDLER_RESULT_REMOVE_MESSAGE; + g_object_unref (chan); + g_object_unref (update_result); } /** @@ -3893,6 +3723,96 @@ gabble_muc_channel_teardown (GabbleMucChannel *gmuc) } static void +sent_subject_cb ( + GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GabbleMucChannel *self = GABBLE_MUC_CHANNEL (user_data); + GabbleMucChannelPrivate *priv = self->priv; + GError *error = NULL; + + if (!wocky_porter_send_finish (WOCKY_PORTER (source), result, &error)) + { + DEBUG ("buh, failed to send a <message> to change the subject: %s", + error->message); + + if (priv->set_subject_context != NULL) + { + GError *tp_error = NULL; + + gabble_set_tp_error_from_wocky (error, &tp_error); + return_from_set_subject (self, tp_error); + g_clear_error (&tp_error); + } + + g_clear_error (&error); + } + /* otherwise, we wait for a reply! */ + + g_object_unref (self); +} + +static void +gabble_muc_channel_set_subject (TpSvcChannelInterfaceSubject *iface, + const gchar *subject, + DBusGMethodInvocation *context) +{ + GabbleMucChannel *self = GABBLE_MUC_CHANNEL (iface); + GabbleMucChannelPrivate *priv = self->priv; + GabbleConnection *conn = GABBLE_CONNECTION (tp_base_channel_get_connection ( + TP_BASE_CHANNEL (self))); + WockyPorter *porter = wocky_session_get_porter (conn->session); + + if (priv->state < MUC_STATE_JOINED) + { + GError error = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE, + "Steady on. You're not in the room yet" }; + + dbus_g_method_return_error (context, &error); + } + else if (priv->state > MUC_STATE_JOINED || priv->closing) + { + GError error = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE, + "Already left/leaving the room" }; + + dbus_g_method_return_error (context, &error); + } + else if (priv->set_subject_context != NULL) + { + GError error = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE, + "Hey! Stop changing the subject! (Your last request is still in " + "flight.)" }; + + dbus_g_method_return_error (context, &error); + } + else + { + WockyXmppConnection *xmpp_conn; + WockyStanza *stanza; + + g_assert (priv->set_subject_stanza_id == NULL); + g_object_get (porter, + "connection", &xmpp_conn, + NULL); + priv->set_subject_stanza_id = wocky_xmpp_connection_new_id (xmpp_conn); + g_object_unref (xmpp_conn); + + stanza = wocky_stanza_build ( + WOCKY_STANZA_TYPE_MESSAGE, WOCKY_STANZA_SUB_TYPE_GROUPCHAT, + NULL, priv->jid, + '@', "id", priv->set_subject_stanza_id, + '(', "subject", '$', subject, ')', + NULL); + + priv->set_subject_context = context; + wocky_porter_send_async (porter, stanza, NULL, sent_subject_cb, + g_object_ref (self)); + g_object_unref (stanza); + } +} + +static void password_iface_init (gpointer g_iface, gpointer iface_data) { TpSvcChannelInterfacePasswordClass *klass = @@ -3916,3 +3836,15 @@ chat_state_iface_init (gpointer g_iface, gpointer iface_data) IMPLEMENT(set_chat_state); #undef IMPLEMENT } + +static void +subject_iface_init (gpointer g_iface, gpointer iface_data) +{ + TpSvcChannelInterfaceSubjectClass *klass = + (TpSvcChannelInterfaceSubjectClass *) g_iface; + +#define IMPLEMENT(x) tp_svc_channel_interface_subject_implement_##x (\ + klass, gabble_muc_channel_##x) + IMPLEMENT(set_subject); +#undef IMPLEMENT +} diff --git a/src/muc-channel.h b/src/muc-channel.h index 770c3f02b..ca8ce2296 100644 --- a/src/muc-channel.h +++ b/src/muc-channel.h @@ -30,7 +30,6 @@ #include <telepathy-glib/dbus-properties-mixin.h> #include <telepathy-glib/group-mixin.h> #include <telepathy-glib/message-mixin.h> -#include <telepathy-glib/properties-mixin.h> #include "types.h" #include "tubes-channel.h" @@ -53,14 +52,13 @@ struct _GabbleMucChannelClass { TpBaseChannelClass parent_class; TpGroupMixinClass group_class; - TpPropertiesMixinClass properties_class; + TpDBusPropertiesMixinClass dbus_props_class; }; struct _GabbleMucChannel { TpBaseChannel parent; TpGroupMixin group; - TpPropertiesMixin properties; TpMessageMixin message_mixin; GabbleMucChannelPrivate *priv; @@ -86,22 +84,6 @@ GType gabble_muc_channel_get_type (void); GabbleMucChannelClass)) gboolean _gabble_muc_channel_is_ready (GabbleMucChannel *chan); -void _gabble_muc_channel_presence_error (GabbleMucChannel *chan, - const gchar *jid, WockyNode *pres_node); -void _gabble_muc_channel_member_presence_updated (GabbleMucChannel *chan, - TpHandle handle, LmMessage *message, WockyNode *x_node, - WockyNode *item_node); -void _gabble_muc_channel_handle_subject (GabbleMucChannel *chan, - TpChannelTextMessageType msg_type, TpHandleType handle_type, - TpHandle sender, time_t timestamp, const gchar *subject, LmMessage *msg); -void _gabble_muc_channel_receive (GabbleMucChannel *chan, - TpChannelTextMessageType msg_type, TpHandleType handle_type, - TpHandle sender, time_t timestamp, const gchar *id, const gchar *text, - LmMessage *msg, TpChannelTextSendError send_error, - TpDeliveryStatus delivery_status); - -void _gabble_muc_channel_state_receive (GabbleMucChannel *chan, - guint state, guint from_handle); void gabble_muc_channel_send_presence (GabbleMucChannel *chan); @@ -130,6 +112,16 @@ gboolean gabble_muc_channel_request_call_finish (GabbleMucChannel *gmuc, gboolean gabble_muc_channel_handle_jingle_session (GabbleMucChannel *channel, GabbleJingleSession *session); +void gabble_muc_channel_update_configuration_async ( + GabbleMucChannel *self, + GHashTable *validated_properties, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean gabble_muc_channel_update_configuration_finish ( + GabbleMucChannel *self, + GAsyncResult *result, + GError **error); + void gabble_muc_channel_teardown (GabbleMucChannel *gmuc); void gabble_muc_channel_close_tube (GabbleMucChannel *gmuc); diff --git a/src/muc-factory.c b/src/muc-factory.c index b6bb486d5..f5c324968 100644 --- a/src/muc-factory.c +++ b/src/muc-factory.c @@ -20,13 +20,12 @@ #include "config.h" #include "muc-factory.h" -#define DBUS_API_SUBJECT_TO_CHANGE - #include <string.h> #include <dbus/dbus-glib.h> #include <dbus/dbus-glib-lowlevel.h> #include <loudmouth/loudmouth.h> +#include <wocky/wocky-utils.h> #include <telepathy-glib/channel-manager.h> #include <telepathy-glib/dbus.h> #include <telepathy-glib/interfaces.h> @@ -36,7 +35,7 @@ #define DEBUG_FLAG GABBLE_DEBUG_MUC -#include "caps-channel-manager.h" +#include "gabble/caps-channel-manager.h" #include "connection.h" #include "conn-olpc.h" #include "debug.h" @@ -99,17 +98,14 @@ struct _GabbleMucFactoryPrivate gboolean dispose_has_run; }; -#define GABBLE_MUC_FACTORY_GET_PRIVATE(o) \ - (G_TYPE_INSTANCE_GET_PRIVATE ((o), GABBLE_TYPE_MUC_FACTORY, \ - GabbleMucFactoryPrivate)) - static GObject *gabble_muc_factory_constructor (GType type, guint n_props, GObjectConstructParam *props); static void gabble_muc_factory_init (GabbleMucFactory *fac) { - GabbleMucFactoryPrivate *priv = GABBLE_MUC_FACTORY_GET_PRIVATE (fac); + GabbleMucFactoryPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (fac, + GABBLE_TYPE_MUC_FACTORY, GabbleMucFactoryPrivate); fac->priv = priv; @@ -150,7 +146,7 @@ static void gabble_muc_factory_dispose (GObject *object) { GabbleMucFactory *fac = GABBLE_MUC_FACTORY (object); - GabbleMucFactoryPrivate *priv = GABBLE_MUC_FACTORY_GET_PRIVATE (fac); + GabbleMucFactoryPrivate *priv = fac->priv; if (priv->dispose_has_run) return; @@ -166,7 +162,7 @@ gabble_muc_factory_dispose (GObject *object) g_hash_table_foreach (priv->disco_requests, cancel_disco_request, priv->conn->disco); - g_hash_table_destroy (priv->disco_requests); + g_hash_table_unref (priv->disco_requests); if (G_OBJECT_CLASS (gabble_muc_factory_parent_class)->dispose) G_OBJECT_CLASS (gabble_muc_factory_parent_class)->dispose (object); @@ -179,7 +175,7 @@ gabble_muc_factory_get_property (GObject *object, GParamSpec *pspec) { GabbleMucFactory *fac = GABBLE_MUC_FACTORY (object); - GabbleMucFactoryPrivate *priv = GABBLE_MUC_FACTORY_GET_PRIVATE (fac); + GabbleMucFactoryPrivate *priv = fac->priv; switch (property_id) { case PROP_CONNECTION: @@ -198,7 +194,7 @@ gabble_muc_factory_set_property (GObject *object, GParamSpec *pspec) { GabbleMucFactory *fac = GABBLE_MUC_FACTORY (object); - GabbleMucFactoryPrivate *priv = GABBLE_MUC_FACTORY_GET_PRIVATE (fac); + GabbleMucFactoryPrivate *priv = fac->priv; switch (property_id) { case PROP_CONNECTION: @@ -242,7 +238,7 @@ static void muc_channel_closed_cb (GabbleMucChannel *chan, gpointer user_data) { GabbleMucFactory *fac = GABBLE_MUC_FACTORY (user_data); - GabbleMucFactoryPrivate *priv = GABBLE_MUC_FACTORY_GET_PRIVATE (fac); + GabbleMucFactoryPrivate *priv = fac->priv; TpHandle room_handle; tp_channel_manager_emit_channel_closed_for_object (fac, @@ -265,7 +261,7 @@ muc_ready_cb (GabbleMucChannel *text_chan, gpointer data) { GabbleMucFactory *fac = GABBLE_MUC_FACTORY (data); - GabbleMucFactoryPrivate *priv = GABBLE_MUC_FACTORY_GET_PRIVATE (fac); + GabbleMucFactoryPrivate *priv = fac->priv; GabbleTubesChannel *tubes_chan; GSList *requests_satisfied_text, *requests_satisfied_tubes = NULL; gboolean text_requested; @@ -340,7 +336,7 @@ muc_ready_cb (GabbleMucChannel *text_chan, tp_channel_manager_emit_new_channels (fac, channels); - g_hash_table_destroy (channels); + g_hash_table_unref (channels); } g_hash_table_remove (priv->tubes_needed_for_tube, tubes_chan); @@ -354,7 +350,7 @@ muc_join_error_cb (GabbleMucChannel *chan, gpointer data) { GabbleMucFactory *fac = GABBLE_MUC_FACTORY (data); - GabbleMucFactoryPrivate *priv = GABBLE_MUC_FACTORY_GET_PRIVATE (fac); + GabbleMucFactoryPrivate *priv = fac->priv; GabbleTubesChannel *tubes_chan; GSList *requests_satisfied; GSList *iter; @@ -452,9 +448,10 @@ new_muc_channel (GabbleMucFactory *fac, gboolean requested, GHashTable *initial_channels, GArray *initial_handles, - char **initial_ids) + char **initial_ids, + const char *room_name) { - GabbleMucFactoryPrivate *priv = GABBLE_MUC_FACTORY_GET_PRIVATE (fac); + GabbleMucFactoryPrivate *priv = fac->priv; TpBaseConnection *conn = (TpBaseConnection *) priv->conn; GabbleMucChannel *chan; char *object_path; @@ -497,6 +494,7 @@ new_muc_channel (GabbleMucFactory *fac, "initial-channels", initial_channels_array, "initial-invitee-handles", initial_handles, "initial-invitee-ids", initial_ids, + "room-name", room_name, NULL); g_signal_connect (chan, "closed", (GCallback) muc_channel_closed_cb, fac); @@ -507,7 +505,7 @@ new_muc_channel (GabbleMucFactory *fac, g_hash_table_insert (priv->text_channels, GUINT_TO_POINTER (handle), chan); g_free (object_path); - g_ptr_array_free (initial_channels_array, TRUE); + g_ptr_array_unref (initial_channels_array); g_array_unref (initial_handles); if (_gabble_muc_channel_is_ready (chan)) @@ -529,7 +527,7 @@ do_invite (GabbleMucFactory *fac, TpHandle inviter_handle, const gchar *reason) { - GabbleMucFactoryPrivate *priv = GABBLE_MUC_FACTORY_GET_PRIVATE (fac); + GabbleMucFactoryPrivate *priv = fac->priv; TpHandleRepoIface *room_repo = tp_base_connection_get_handles ( (TpBaseConnection *) priv->conn, TP_HANDLE_TYPE_ROOM); TpHandle room_handle; @@ -547,7 +545,7 @@ do_invite (GabbleMucFactory *fac, GUINT_TO_POINTER (room_handle)) == NULL) { new_muc_channel (fac, room_handle, TRUE, inviter_handle, reason, FALSE, - NULL, NULL, NULL); + NULL, NULL, NULL, NULL); } else { @@ -581,7 +579,7 @@ obsolete_invite_disco_cb (GabbleDisco *self, struct DiscoInviteData *data = (struct DiscoInviteData *) user_data; GabbleMucFactory *fac = GABBLE_MUC_FACTORY (data->factory); - GabbleMucFactoryPrivate *priv = GABBLE_MUC_FACTORY_GET_PRIVATE (fac); + GabbleMucFactoryPrivate *priv = fac->priv; TpHandleRepoIface *contact_repo = tp_base_connection_get_handles ( (TpBaseConnection *) priv->conn, TP_HANDLE_TYPE_CONTACT); WockyNode *identity; @@ -626,7 +624,7 @@ process_muc_invite (GabbleMucFactory *fac, const gchar *from, TpChannelTextSendError send_error) { - GabbleMucFactoryPrivate *priv = GABBLE_MUC_FACTORY_GET_PRIVATE (fac); + GabbleMucFactoryPrivate *priv = fac->priv; TpBaseConnection *conn = (TpBaseConnection *) priv->conn; TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (conn, TP_HANDLE_TYPE_CONTACT); @@ -700,7 +698,7 @@ process_obsolete_invite (GabbleMucFactory *fac, const gchar *body, TpChannelTextSendError send_error) { - GabbleMucFactoryPrivate *priv = GABBLE_MUC_FACTORY_GET_PRIVATE (fac); + GabbleMucFactoryPrivate *priv = fac->priv; TpBaseConnection *conn = (TpBaseConnection *) priv->conn; TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (conn, TP_HANDLE_TYPE_CONTACT); @@ -788,7 +786,7 @@ muc_factory_message_cb (LmMessageHandler *handler, gpointer user_data) { GabbleMucFactory *fac = GABBLE_MUC_FACTORY (user_data); - GabbleMucFactoryPrivate *priv = GABBLE_MUC_FACTORY_GET_PRIVATE (fac); + GabbleMucFactoryPrivate *priv = fac->priv; const gchar *from, *body, *id; time_t stamp; @@ -824,7 +822,7 @@ muc_factory_message_cb (LmMessageHandler *handler, void gabble_muc_factory_broadcast_presence (GabbleMucFactory *self) { - GabbleMucFactoryPrivate *priv = GABBLE_MUC_FACTORY_GET_PRIVATE (self); + GabbleMucFactoryPrivate *priv = self->priv; GHashTableIter iter; gpointer channel = NULL; @@ -842,7 +840,7 @@ gabble_muc_factory_associate_request (GabbleMucFactory *self, gpointer channel, gpointer request) { - GabbleMucFactoryPrivate *priv = GABBLE_MUC_FACTORY_GET_PRIVATE (self); + GabbleMucFactoryPrivate *priv = self->priv; GSList *list = g_hash_table_lookup (priv->queued_requests, channel); g_assert (TP_IS_EXPORTABLE_CHANNEL (channel)); @@ -880,7 +878,7 @@ cancel_queued_requests (gpointer k, static void gabble_muc_factory_close_all (GabbleMucFactory *self) { - GabbleMucFactoryPrivate *priv = GABBLE_MUC_FACTORY_GET_PRIVATE (self); + GabbleMucFactoryPrivate *priv = self->priv; DEBUG ("closing channels"); @@ -895,9 +893,9 @@ gabble_muc_factory_close_all (GabbleMucFactory *self) g_hash_table_foreach_steal (priv->queued_requests, cancel_queued_requests, self); - tp_clear_pointer (&priv->queued_requests, g_hash_table_destroy); - tp_clear_pointer (&priv->text_needed_for_tubes, g_hash_table_destroy); - tp_clear_pointer (&priv->tubes_needed_for_tube, g_hash_table_destroy); + tp_clear_pointer (&priv->queued_requests, g_hash_table_unref); + tp_clear_pointer (&priv->text_needed_for_tubes, g_hash_table_unref); + tp_clear_pointer (&priv->tubes_needed_for_tube, g_hash_table_unref); /* Use a temporary variable because we don't want * muc_channel_closed_cb or tubes_channel_closed_cb to remove the channel @@ -914,7 +912,7 @@ gabble_muc_factory_close_all (GabbleMucFactory *self) while (g_hash_table_iter_next (&iter, NULL, &chan)) gabble_muc_channel_teardown (GABBLE_MUC_CHANNEL (chan)); - g_hash_table_destroy (tmp); + g_hash_table_unref (tmp); } if (priv->message_cb != NULL) @@ -935,7 +933,7 @@ connection_status_changed_cb (GabbleConnection *conn, guint reason, GabbleMucFactory *self) { - GabbleMucFactoryPrivate *priv = GABBLE_MUC_FACTORY_GET_PRIVATE (self); + GabbleMucFactoryPrivate *priv = self->priv; switch (status) { @@ -964,7 +962,7 @@ gabble_muc_factory_constructor (GType type, guint n_props, GObject *obj = G_OBJECT_CLASS (gabble_muc_factory_parent_class)-> constructor (type, n_props, props); GabbleMucFactory *self = GABBLE_MUC_FACTORY (obj); - GabbleMucFactoryPrivate *priv = GABBLE_MUC_FACTORY_GET_PRIVATE (self); + GabbleMucFactoryPrivate *priv = self->priv; priv->status_changed_id = g_signal_connect (priv->conn, "status-changed", (GCallback) connection_status_changed_cb, obj); @@ -1009,7 +1007,7 @@ gabble_muc_factory_foreach_channel (TpChannelManager *manager, gpointer user_data) { GabbleMucFactory *fac = GABBLE_MUC_FACTORY (manager); - GabbleMucFactoryPrivate *priv = GABBLE_MUC_FACTORY_GET_PRIVATE (fac); + GabbleMucFactoryPrivate *priv = fac->priv; struct _ForeachData data; data.user_data = user_data; @@ -1035,7 +1033,8 @@ ensure_muc_channel (GabbleMucFactory *fac, gboolean requested, GHashTable *initial_channels, GArray *initial_handles, - char **initial_ids) + char **initial_ids, + const char *room_name) { TpBaseConnection *base_conn = (TpBaseConnection *) priv->conn; @@ -1044,7 +1043,7 @@ ensure_muc_channel (GabbleMucFactory *fac, if (*ret == NULL) { *ret = new_muc_channel (fac, handle, FALSE, base_conn->self_handle, NULL, - requested, initial_channels, initial_handles, initial_ids); + requested, initial_channels, initial_handles, initial_ids, room_name); return FALSE; } @@ -1062,7 +1061,7 @@ gabble_muc_factory_handle_si_stream_request (GabbleMucFactory *self, const gchar *stream_id, LmMessage *msg) { - GabbleMucFactoryPrivate *priv = GABBLE_MUC_FACTORY_GET_PRIVATE (self); + GabbleMucFactoryPrivate *priv = self->priv; TpHandleRepoIface *room_repo = tp_base_connection_get_handles ( (TpBaseConnection *) priv->conn, TP_HANDLE_TYPE_ROOM); GabbleMucChannel *gmuc = NULL; @@ -1092,15 +1091,15 @@ GabbleMucChannel * gabble_muc_factory_find_text_channel (GabbleMucFactory *self, TpHandle handle) { - GabbleMucFactoryPrivate *priv = GABBLE_MUC_FACTORY_GET_PRIVATE (self); + GabbleMucFactoryPrivate *priv = self->priv; return g_hash_table_lookup (priv->text_channels, GUINT_TO_POINTER (handle)); } static const gchar * const muc_channel_fixed_properties[] = { - TP_IFACE_CHANNEL ".ChannelType", - TP_IFACE_CHANNEL ".TargetHandleType", + TP_PROP_CHANNEL_CHANNEL_TYPE, + TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, NULL }; @@ -1108,18 +1107,20 @@ static const gchar * const * muc_tubes_channel_fixed_properties = muc_channel_fixed_properties; static const gchar * const muc_channel_allowed_properties[] = { - TP_IFACE_CHANNEL ".TargetHandle", - TP_IFACE_CHANNEL ".TargetID", - TP_IFACE_CHANNEL_INTERFACE_CONFERENCE ".InitialChannels", - TP_IFACE_CHANNEL_INTERFACE_CONFERENCE ".InitialInviteeHandles", - TP_IFACE_CHANNEL_INTERFACE_CONFERENCE ".InitialInviteeIDs", - TP_IFACE_CHANNEL_INTERFACE_CONFERENCE ".InvitationMessage", + TP_PROP_CHANNEL_TARGET_HANDLE, + TP_PROP_CHANNEL_TARGET_ID, + TP_PROP_CHANNEL_INTERFACE_CONFERENCE_INITIAL_CHANNELS, + TP_PROP_CHANNEL_INTERFACE_CONFERENCE_INITIAL_INVITEE_HANDLES, + TP_PROP_CHANNEL_INTERFACE_CONFERENCE_INITIAL_INVITEE_IDS, + TP_PROP_CHANNEL_INTERFACE_CONFERENCE_INVITATION_MESSAGE, + TP_PROP_CHANNEL_INTERFACE_ROOM_ROOM_NAME, + TP_PROP_CHANNEL_INTERFACE_ROOM_SERVER, NULL }; static const gchar * const muc_tubes_channel_allowed_properties[] = { - TP_IFACE_CHANNEL ".TargetHandle", - TP_IFACE_CHANNEL ".TargetID", + TP_PROP_CHANNEL_TARGET_HANDLE, + TP_PROP_CHANNEL_TARGET_ID, NULL }; @@ -1134,12 +1135,12 @@ gabble_muc_factory_type_foreach_channel_class (GType type, channel_type_value = tp_g_value_slice_new (G_TYPE_STRING); /* no string value yet - we'll change it for each channel class */ - g_hash_table_insert (table, TP_IFACE_CHANNEL ".ChannelType", + g_hash_table_insert (table, TP_PROP_CHANNEL_CHANNEL_TYPE, channel_type_value); handle_type_value = tp_g_value_slice_new (G_TYPE_UINT); g_value_set_uint (handle_type_value, TP_HANDLE_TYPE_ROOM); - g_hash_table_insert (table, TP_IFACE_CHANNEL ".TargetHandleType", + g_hash_table_insert (table, TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, handle_type_value); /* Channel.Type.Text */ @@ -1171,7 +1172,7 @@ gabble_muc_factory_type_foreach_channel_class (GType type, gabble_media_factory_call_channel_allowed_properties (), user_data); - g_hash_table_destroy (table); + g_hash_table_unref (table); } /* return TRUE if the text_channel associated is ready */ @@ -1181,14 +1182,14 @@ ensure_tubes_channel (GabbleMucFactory *self, GabbleTubesChannel **tubes_chan, gboolean requested) { - GabbleMucFactoryPrivate *priv = GABBLE_MUC_FACTORY_GET_PRIVATE (self); + GabbleMucFactoryPrivate *priv = self->priv; TpBaseConnection *base_conn = (TpBaseConnection *) priv->conn; GabbleMucChannel *text_chan; TpHandle initiator = base_conn->self_handle; gboolean result; result = ensure_muc_channel (self, priv, handle, &text_chan, FALSE, - NULL, NULL, NULL); + NULL, NULL, NULL, NULL); /* this refs the tube channel object */ *tubes_chan = gabble_muc_channel_open_tube (text_chan, initiator, requested); @@ -1207,7 +1208,7 @@ handle_text_channel_request (GabbleMucFactory *self, TpHandle room, GError **error) { - GabbleMucFactoryPrivate *priv = GABBLE_MUC_FACTORY_GET_PRIVATE (self); + GabbleMucFactoryPrivate *priv = self->priv; TpBaseConnection *conn = TP_BASE_CONNECTION (priv->conn); GabbleMucChannel *text_chan; TpHandleSet *handles; @@ -1226,22 +1227,29 @@ handle_text_channel_request (GabbleMucFactory *self, char **initial_ids, **final_ids; const char *invite_msg; + const gchar *room_name, *server_prop; + if (tp_channel_manager_asv_has_unknown_properties (request_properties, muc_channel_fixed_properties, muc_channel_allowed_properties, error)) return FALSE; initial_channels = tp_asv_get_boxed (request_properties, - TP_IFACE_CHANNEL_INTERFACE_CONFERENCE ".InitialChannels", + TP_PROP_CHANNEL_INTERFACE_CONFERENCE_INITIAL_CHANNELS, TP_ARRAY_TYPE_OBJECT_PATH_LIST); initial_handles = tp_asv_get_boxed (request_properties, - TP_IFACE_CHANNEL_INTERFACE_CONFERENCE ".InitialInviteeHandles", + TP_PROP_CHANNEL_INTERFACE_CONFERENCE_INITIAL_INVITEE_HANDLES, DBUS_TYPE_G_UINT_ARRAY); initial_ids = tp_asv_get_boxed (request_properties, - TP_IFACE_CHANNEL_INTERFACE_CONFERENCE ".InitialInviteeIDs", + TP_PROP_CHANNEL_INTERFACE_CONFERENCE_INITIAL_INVITEE_IDS, G_TYPE_STRV); invite_msg = tp_asv_get_string (request_properties, - TP_IFACE_CHANNEL_INTERFACE_CONFERENCE ".InvitationMessage"); + TP_PROP_CHANNEL_INTERFACE_CONFERENCE_INVITATION_MESSAGE); + + room_name = tp_asv_get_string (request_properties, + TP_PROP_CHANNEL_INTERFACE_ROOM_ROOM_NAME); + server_prop = tp_asv_get_string (request_properties, + TP_PROP_CHANNEL_INTERFACE_ROOM_SERVER); handles = tp_handle_set_new (contact_handles); continue_handles = tp_intset_new (); @@ -1339,9 +1347,11 @@ handle_text_channel_request (GabbleMucFactory *self, final_ids[i] = (char *) id; } + /* TargetHandleType=None and TargetHandle=0 */ if (room == 0) { char *uuid, *id, *server = ""; + gchar *tmp = NULL; /* There's no super obvious way to tell.. you can't invite GMail users to * a non-Google MUC (it just doesn't work), and if your own account is on @@ -1365,15 +1375,29 @@ handle_text_channel_request (GabbleMucFactory *self, } } - uuid = gabble_generate_id (); - id = g_strdup_printf ("private-chat-%s%s", uuid, server); + if (server_prop != NULL) + { + tmp = g_strdup_printf ("@%s", server_prop); + server = tmp; + } - DEBUG ("Creating PMUC '%s'", id); + if (room_name != NULL && room_name[0] != '\0') + { + id = g_strdup_printf ("%s%s", room_name, server); + } + else + { + uuid = gabble_generate_id (); + id = g_strdup_printf ("private-chat-%s%s", uuid, server); + g_free (uuid); + + DEBUG ("Creating PMUC '%s'", id); + } room = tp_handle_ensure (room_handles, id, NULL, error); - g_free (uuid); g_free (id); + g_free (tmp); if (room == 0) { @@ -1387,8 +1411,66 @@ handle_text_channel_request (GabbleMucFactory *self, tp_handle_ref (room_handles, room); } + /* Make sure TargetID and RoomName don't conflict. */ + if (room_name != NULL && room_name[0] != '\0') + { + const gchar *target_id = tp_handle_inspect (room_handles, room); + gchar *target_room = NULL; + gboolean ok; + + /* JIDs that are handles must already be valid. */ + ok = wocky_decode_jid (target_id, &target_room, NULL, NULL); + g_assert (ok); + + ok = !tp_strdiff (target_room, room_name); + + if (!ok) + { + g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, + "TargetID's node part (%s) doesn't match RoomName (%s)", + target_room, room_name); + ret = FALSE; + } + + g_free (target_room); + + if (!ok) + goto out; + } + + /* Make sure TargetID and Server don't conflict. */ + if (server_prop != NULL) + { + const gchar *target_id = tp_handle_inspect (room_handles, room); + gchar *target_server = NULL; + gboolean ok = TRUE; + + /* JIDs that are handles must already be valid. */ + ok = wocky_decode_jid (target_id, NULL, &target_server, NULL); + g_assert (ok); + + if (target_server != NULL) + ok = !tp_strdiff (target_server, server_prop); + else + ok = TRUE; + + if (!ok) + { + g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, + "TargetID's domain part (%s) doesn't match Server (%s)", + target_server, server_prop); + ret = FALSE; + } + + g_free (target_server); + + if (!ok) + goto out; + + } + if (ensure_muc_channel (self, priv, room, &text_chan, TRUE, - final_channels, final_handles, final_ids)) + final_channels, final_handles, final_ids, room_name)) { /* channel exists */ @@ -1454,8 +1536,8 @@ out: if (room != 0) tp_handle_unref (room_handles, room); - g_hash_table_destroy (final_channels); - g_array_free (final_handles, TRUE); + g_hash_table_unref (final_channels); + g_array_unref (final_handles); g_free (final_ids); tp_handle_set_destroy (handles); @@ -1472,7 +1554,7 @@ handle_tubes_channel_request (GabbleMucFactory *self, TpHandle handle, GError **error) { - GabbleMucFactoryPrivate *priv = GABBLE_MUC_FACTORY_GET_PRIVATE (self); + GabbleMucFactoryPrivate *priv = self->priv; GabbleTubesChannel *tube = NULL; GabbleMucChannel *gmuc = NULL; @@ -1529,7 +1611,7 @@ handle_tube_channel_request (GabbleMucFactory *self, GError **error) { - GabbleMucFactoryPrivate *priv = GABBLE_MUC_FACTORY_GET_PRIVATE (self); + GabbleMucFactoryPrivate *priv = self->priv; gboolean can_announce_now = TRUE; gboolean tubes_channel_created = FALSE; GabbleTubesChannel *tube = NULL; @@ -1575,7 +1657,7 @@ handle_tube_channel_request (GabbleMucFactory *self, g_hash_table_insert (channels, new_channel, request_tokens); tp_channel_manager_emit_new_channels (self, channels); - g_hash_table_destroy (channels); + g_hash_table_unref (channels); g_slist_free (request_tokens); } else @@ -1617,12 +1699,12 @@ handle_stream_tube_channel_request (GabbleMucFactory *self, /* "Service" is a mandatory, not-fixed property */ service = tp_asv_get_string (request_properties, - TP_IFACE_CHANNEL_TYPE_STREAM_TUBE ".Service"); + TP_PROP_CHANNEL_TYPE_STREAM_TUBE_SERVICE); if (service == NULL) { g_set_error (error, TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED, "Request does not contain the mandatory property '%s'", - TP_IFACE_CHANNEL_TYPE_STREAM_TUBE ".Service"); + TP_PROP_CHANNEL_TYPE_STREAM_TUBE_SERVICE); return FALSE; } @@ -1648,12 +1730,12 @@ handle_dbus_tube_channel_request (GabbleMucFactory *self, /* "ServiceName" is a mandatory, not-fixed property */ service = tp_asv_get_string (request_properties, - TP_IFACE_CHANNEL_TYPE_DBUS_TUBE ".ServiceName"); + TP_PROP_CHANNEL_TYPE_DBUS_TUBE_SERVICE_NAME); if (service == NULL) { g_set_error (error, TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED, "Request does not contain the mandatory property '%s'", - TP_IFACE_CHANNEL_TYPE_DBUS_TUBE ".ServiceName"); + TP_PROP_CHANNEL_TYPE_DBUS_TUBE_SERVICE_NAME); return FALSE; } @@ -1695,7 +1777,7 @@ handle_call_channel_request (GabbleMucFactory *self, TpHandle handle, GError **error) { - GabbleMucFactoryPrivate *priv = GABBLE_MUC_FACTORY_GET_PRIVATE (self); + GabbleMucFactoryPrivate *priv = self->priv; gboolean initial_audio, initial_video; GabbleMucChannel *muc; GabbleCallMucChannel *call; @@ -1719,7 +1801,7 @@ handle_call_channel_request (GabbleMucFactory *self, return FALSE; } - ensure_muc_channel (self, priv, handle, &muc, FALSE, NULL, NULL, NULL); + ensure_muc_channel (self, priv, handle, &muc, FALSE, NULL, NULL, NULL, NULL); call = gabble_muc_channel_get_call (muc); @@ -1758,6 +1840,27 @@ error: return FALSE; } +typedef gboolean (*ChannelTypeHandlerFunc) ( + GabbleMucFactory *self, + gpointer request_token, + GHashTable *request_properties, + gboolean require_new, + TpHandle room, + GError **error); + +typedef struct { + const gchar *channel_type; + ChannelTypeHandlerFunc f; +} ChannelTypeHandler; + +static ChannelTypeHandler channel_type_handlers[] = { + { TP_IFACE_CHANNEL_TYPE_TEXT, handle_text_channel_request }, + { TP_IFACE_CHANNEL_TYPE_TUBES, handle_tubes_channel_request }, + { TP_IFACE_CHANNEL_TYPE_STREAM_TUBE, handle_stream_tube_channel_request }, + { TP_IFACE_CHANNEL_TYPE_DBUS_TUBE, handle_dbus_tube_channel_request }, + { TPY_IFACE_CHANNEL_TYPE_CALL, handle_call_channel_request }, + { NULL } +}; static gboolean gabble_muc_factory_request (GabbleMucFactory *self, @@ -1768,80 +1871,57 @@ gabble_muc_factory_request (GabbleMucFactory *self, GError *error = NULL; TpHandleType handle_type; TpHandle handle; - gboolean conference; + gboolean conference, room; const gchar *channel_type; + ChannelTypeHandler *h; handle_type = tp_asv_get_uint32 (request_properties, - TP_IFACE_CHANNEL ".TargetHandleType", NULL); + TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, NULL); channel_type = tp_asv_get_string (request_properties, - TP_IFACE_CHANNEL ".ChannelType"); + TP_PROP_CHANNEL_CHANNEL_TYPE); /* Conference channels can be anonymous (HandleTypeNone) */ conference = (handle_type == TP_HANDLE_TYPE_NONE && !tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_TEXT) && (g_hash_table_lookup (request_properties, - TP_IFACE_CHANNEL_INTERFACE_CONFERENCE ".InitialChannels") || + TP_PROP_CHANNEL_INTERFACE_CONFERENCE_INITIAL_CHANNELS) || g_hash_table_lookup (request_properties, - TP_IFACE_CHANNEL_INTERFACE_CONFERENCE ".InitialInviteeHandles") || + TP_PROP_CHANNEL_INTERFACE_CONFERENCE_INITIAL_INVITEE_HANDLES) || g_hash_table_lookup (request_properties, - TP_IFACE_CHANNEL_INTERFACE_CONFERENCE ".InitialInviteeIDs"))); + TP_PROP_CHANNEL_INTERFACE_CONFERENCE_INITIAL_INVITEE_IDS))); - /* the channel must either be a room, or a new conference */ - if (handle_type != TP_HANDLE_TYPE_ROOM && !conference) - return FALSE; + room = (handle_type == TP_HANDLE_TYPE_NONE + && !tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_TEXT) + && g_hash_table_lookup (request_properties, + TP_PROP_CHANNEL_INTERFACE_ROOM_ROOM_NAME)); - if (tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_TEXT) && - tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_TUBES) && - tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_STREAM_TUBE) && - tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_DBUS_TUBE) && - tp_strdiff (channel_type, TPY_IFACE_CHANNEL_TYPE_CALL)) + /* the channel must either be a room, or a new conference */ + if (handle_type != TP_HANDLE_TYPE_ROOM && !conference && !room) return FALSE; /* validity already checked by TpBaseConnection */ handle = tp_asv_get_uint32 (request_properties, - TP_IFACE_CHANNEL ".TargetHandle", NULL); - g_assert (conference || handle != 0); + TP_PROP_CHANNEL_TARGET_HANDLE, NULL); + g_assert (conference || room || handle != 0); - if (!tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_TEXT)) + for (h = channel_type_handlers; h->channel_type != NULL; h++) { - if (handle_text_channel_request (self, request_token, - request_properties, require_new, handle, &error)) - return TRUE; - } - else if (!tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_TUBES)) - { - if (handle_tubes_channel_request (self, request_token, - request_properties, require_new, handle, &error)) - return TRUE; - } - else if (!tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_STREAM_TUBE)) - { - if (handle_stream_tube_channel_request (self, request_token, - request_properties, require_new, handle, &error)) - return TRUE; - } - else if (!tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_DBUS_TUBE)) - { - if (handle_dbus_tube_channel_request (self, request_token, - request_properties, require_new, handle, &error)) - return TRUE; - } - else if (!tp_strdiff (channel_type, TPY_IFACE_CHANNEL_TYPE_CALL)) - { - if (handle_call_channel_request (self, request_token, - request_properties, require_new, handle, &error)) - return TRUE; - } - else - { - g_assert_not_reached (); + if (tp_strdiff (channel_type, h->channel_type)) + continue; + + if (!h->f (self, request_token, request_properties, require_new, + handle, &error)) + { + tp_channel_manager_emit_request_failed (self, request_token, + error->domain, error->code, error->message); + g_error_free (error); + } + + /* We've handled the request one way or another. */ + return TRUE; } - /* Something failed */ - tp_channel_manager_emit_request_failed (self, request_token, - error->domain, error->code, error->message); - g_error_free (error); - return TRUE; + return FALSE; } @@ -1884,7 +1964,7 @@ gboolean gabble_muc_factory_handle_jingle_session (GabbleMucFactory *self, GabbleJingleSession *session) { - GabbleMucFactoryPrivate *priv = GABBLE_MUC_FACTORY_GET_PRIVATE (self); + GabbleMucFactoryPrivate *priv = self->priv; TpHandleRepoIface *room_repo = tp_base_connection_get_handles ( (TpBaseConnection *) priv->conn, TP_HANDLE_TYPE_ROOM); TpHandle room; diff --git a/src/namespaces.h b/src/namespaces.h index 08b00c6b4..4f8415c84 100644 --- a/src/namespaces.h +++ b/src/namespaces.h @@ -84,6 +84,7 @@ /* Jingle ICE-UDP transport */ #define NS_JINGLE_TRANSPORT_ICEUDP "urn:xmpp:jingle:transports:ice-udp:1" +#define NS_LAST "jabber:iq:last" #define NS_MUC "http://jabber.org/protocol/muc" #define NS_MUC_BYTESTREAM "http://telepathy.freedesktop.org/xmpp/protocol/muc-bytestream" #define NS_MUC_USER "http://jabber.org/protocol/muc#user" @@ -117,8 +118,20 @@ #define NS_VERSION "jabber:iq:version" #define NS_GEOLOC "http://jabber.org/protocol/geoloc" #define NS_GOOGLE_MAIL_NOTIFY "google:mail:notify" +#define NS_GOOGLE_SETTING "google:setting" #define NS_TEMPPRES "urn:xmpp:temppres:0" #define NS_GOOGLE_SHARED_STATUS "google:shared-status" +/* This is used by the extension Facebook uses to push you messages you send + * using other devices (or the website). + * + * http://www.youtube.com/watch?v=rSnXE2791yg is not the song I was looking + * for, but it's not bad. + */ +#define NS_FACEBOOK_MESSAGES "http://www.facebook.com/xmpp/messages" + +#define NS_TP_FT_METADATA_SERVICE "http://telepathy.freedesktop.org/xmpp/file-transfer-service" +#define NS_TP_FT_METADATA "http://telepathy.freedesktop.org/xmpp/file-transfer-metadata" + #endif /* __GABBLE_NAMESPACES__H__ */ diff --git a/src/olpc-activity.c b/src/olpc-activity.c index 3bfb965af..7d8028e6d 100644 --- a/src/olpc-activity.c +++ b/src/olpc-activity.c @@ -95,7 +95,7 @@ gabble_olpc_activity_finalize (GObject *object) if (self->properties != NULL) { - g_hash_table_destroy (self->properties); + g_hash_table_unref (self->properties); self->properties = NULL; } @@ -154,7 +154,7 @@ gabble_olpc_activity_set_property (GObject *object, break; case PROP_PROPERTIES: if (self->properties != NULL) - g_hash_table_destroy (self->properties); + g_hash_table_unref (self->properties); self->properties = g_value_get_boxed (value); break; diff --git a/src/plugin-loader.c b/src/plugin-loader.c index ec0aa24a1..2568ff5db 100644 --- a/src/plugin-loader.c +++ b/src/plugin-loader.c @@ -108,7 +108,9 @@ static void gabble_plugin_loader_probe (GabblePluginLoader *self) { GError *error = NULL; - const gchar *directory_name = g_getenv ("GABBLE_PLUGIN_DIR"); + const gchar *directory_names = g_getenv ("GABBLE_PLUGIN_DIR"); + gchar **dir_array; + gchar **ptr; GDir *d; const gchar *file; @@ -118,32 +120,43 @@ gabble_plugin_loader_probe (GabblePluginLoader *self) return; } - if (directory_name == NULL) - directory_name = PLUGIN_DIR; + if (directory_names == NULL) + directory_names = PLUGIN_DIR; - DEBUG ("probing %s", directory_name); - d = g_dir_open (directory_name, 0, &error); +#ifdef G_OS_WIN32 + dir_array = g_strsplit (directory_names, ";", 0); +#else + dir_array = g_strsplit (directory_names, ":", 0); +#endif - if (d == NULL) + for (ptr = dir_array ; *ptr != NULL ; ptr++) { - DEBUG ("%s", error->message); - g_error_free (error); - return; - } + DEBUG ("probing %s", *ptr); + d = g_dir_open (*ptr, 0, &error); - while ((file = g_dir_read_name (d)) != NULL) - { - gchar *path; + if (d == NULL) + { + DEBUG ("%s", error->message); + g_error_free (error); + continue; + } - if (!g_str_has_suffix (file, G_MODULE_SUFFIX)) - continue; + while ((file = g_dir_read_name (d)) != NULL) + { + gchar *path; + + if (!g_str_has_suffix (file, G_MODULE_SUFFIX)) + continue; + + path = g_build_filename (*ptr, file, NULL); + plugin_loader_try_to_load (self, path); + g_free (path); + } - path = g_build_filename (directory_name, file, NULL); - plugin_loader_try_to_load (self, path); - g_free (path); + g_dir_close (d); } - g_dir_close (d); + g_strfreev (dir_array); } #endif @@ -386,7 +399,7 @@ gabble_plugin_loader_create_channel_managers ( continue; g_ptr_array_foreach (managers, copy_to_other_array, out); - g_ptr_array_free (managers, TRUE); + g_ptr_array_unref (managers); } return out; diff --git a/src/presence-cache.c b/src/presence-cache.c index 484cb5f60..1e8cbcc0f 100644 --- a/src/presence-cache.c +++ b/src/presence-cache.c @@ -40,11 +40,14 @@ #include <wocky/wocky-caps-cache.h> #include <wocky/wocky-caps-hash.h> #include <wocky/wocky-disco-identity.h> +#include <wocky/wocky-utils.h> +#include <wocky/wocky-namespaces.h> +#include <wocky/wocky-data-form.h> #define DEBUG_FLAG GABBLE_DEBUG_PRESENCE -#include "capabilities.h" -#include "caps-channel-manager.h" +#include "gabble/capabilities.h" +#include "gabble/caps-channel-manager.h" #include "conn-presence.h" #include "debug.h" #include "disco.h" @@ -250,18 +253,36 @@ capability_info_free (GabbleCapabilityInfo *info) wocky_disco_identity_array_free (info->identities); info->identities = NULL; + if (info->data_forms != NULL) + g_ptr_array_unref (info->data_forms); + info->data_forms = NULL; + tp_intset_destroy (info->guys); g_slice_free (GabbleCapabilityInfo, info); } +static void +replace_data_forms (GabbleCapabilityInfo *info, + GPtrArray *data_forms) +{ + if (data_forms == info->data_forms) + return; + + tp_clear_pointer (&info->data_forms, g_ptr_array_unref); + + if (data_forms != NULL) + info->data_forms = g_ptr_array_ref (data_forms); +} + static guint capability_info_recvd (GabblePresenceCache *cache, const gchar *node, TpHandle handle, GabbleCapabilitySet *cap_set, guint trust_inc, - guint client_types) + guint client_types, + GPtrArray *data_forms) { GabbleCapabilityInfo *info = capability_info_get (cache, node); @@ -292,6 +313,8 @@ capability_info_recvd (GabblePresenceCache *cache, info->client_types = client_types; + replace_data_forms (info, data_forms); + return info->trust; } @@ -464,7 +487,7 @@ gabble_presence_cache_init (GabblePresenceCache *cache) decloak_context_free); priv->location = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, - (GDestroyNotify) g_hash_table_destroy); + (GDestroyNotify) g_hash_table_unref); } static void gabble_presence_cache_add_bundle_caps (GabblePresenceCache *cache, @@ -477,7 +500,9 @@ gabble_presence_cache_add_bundles (GabblePresenceCache *cache) gabble_presence_cache_add_bundle_caps (cache, \ "http://www.google.com/xmpp/client/caps#" cap, features); \ gabble_presence_cache_add_bundle_caps (cache, \ - "http://talk.google.com/xmpp/client/caps#" cap, features); + "http://talk.google.com/xmpp/client/caps#" cap, features); \ + gabble_presence_cache_add_bundle_caps (cache, \ + "http://www.android.com/gtalk/client/caps#" cap, features); /* Cache various bundle from the Google Talk clients as trusted. Some old * versions of Google Talk do not reply correctly to discovery requests. @@ -560,7 +585,7 @@ gabble_presence_cache_dispose (GObject *object) priv->unsure_id = 0; } - tp_clear_pointer (&priv->decloak_requests, g_hash_table_destroy); + tp_clear_pointer (&priv->decloak_requests, g_hash_table_unref); tp_clear_pointer (&priv->decloak_handles, tp_handle_set_destroy); g_assert (priv->lm_message_cb == NULL); @@ -568,11 +593,11 @@ gabble_presence_cache_dispose (GObject *object) g_signal_handler_disconnect (priv->conn, priv->status_changed_cb); - tp_clear_pointer (&priv->presence, g_hash_table_destroy); - tp_clear_pointer (&priv->capabilities, g_hash_table_destroy); - tp_clear_pointer (&priv->disco_pending, g_hash_table_destroy); + tp_clear_pointer (&priv->presence, g_hash_table_unref); + tp_clear_pointer (&priv->capabilities, g_hash_table_unref); + tp_clear_pointer (&priv->disco_pending, g_hash_table_unref); tp_clear_pointer (&priv->presence_handles, tp_handle_set_destroy); - tp_clear_pointer (&priv->location, g_hash_table_destroy); + tp_clear_pointer (&priv->location, g_hash_table_unref); if (G_OBJECT_CLASS (gabble_presence_cache_parent_class)->dispose) G_OBJECT_CLASS (gabble_presence_cache_parent_class)->dispose (object); @@ -1008,7 +1033,8 @@ _parse_node (GabblePresence *presence, DEBUG ("Client is Google Web Client"); gabble_capability_set_add (cap_set, QUIRK_GOOGLE_WEBMAIL_CLIENT); - gabble_presence_set_capabilities (presence, resource, cap_set, serial); + gabble_capability_set_add (cap_set, QUIRK_OMITS_CONTENT_CREATORS); + gabble_presence_set_capabilities (presence, resource, cap_set, NULL, serial); gabble_capability_set_free (cap_set); } } @@ -1138,6 +1164,7 @@ set_caps_for (DiscoWaiter *waiter, GabblePresenceCache *cache, GabbleCapabilitySet *cap_set, guint client_types, + GPtrArray *data_forms, TpHandle responder_handle, const gchar *responder_jid) { @@ -1154,7 +1181,7 @@ set_caps_for (DiscoWaiter *waiter, waiter->handle, responder_handle, responder_jid); gabble_presence_set_capabilities (presence, waiter->resource, cap_set, - waiter->serial); + data_forms, waiter->serial); new_cap_set = gabble_presence_peek_caps (presence); emit_capabilities_update (cache, waiter->handle, old_cap_set, new_cap_set); gabble_capability_set_free (old_cap_set); @@ -1218,6 +1245,29 @@ client_types_from_message (TpHandle handle, return client_types; } +static GPtrArray * +data_forms_from_message (WockyNode *node) +{ + GPtrArray *out = g_ptr_array_new_with_free_func (g_object_unref); + + WockyNodeIter iter; + WockyNode *x_node = NULL; + + wocky_node_iter_init (&iter, node, "x", WOCKY_XMPP_NS_DATA); + while (wocky_node_iter_next (&iter, &x_node)) + { + WockyDataForm *form = wocky_data_form_new_from_node (x_node, NULL); + + /* we've already parsed the reply to check the hash matches, so + * we can already guarantee these data forms will be parsed + * fine */ + if (G_LIKELY (form != NULL)) + g_ptr_array_add (out, form); + } + + return out; +} + static void _signal_presences_updated (GabblePresenceCache *cache, TpHandle handle) @@ -1227,7 +1277,7 @@ _signal_presences_updated (GabblePresenceCache *cache, handles = g_array_sized_new (FALSE, FALSE, sizeof (TpHandle), 1); g_array_append_val (handles, handle); g_signal_emit (cache, signals[PRESENCES_UPDATED], 0, handles); - g_array_free (handles, TRUE); + g_array_unref (handles); } static void @@ -1253,6 +1303,7 @@ _caps_disco_cb (GabbleDisco *disco, gboolean jid_is_valid; gpointer key; guint client_types = 0; + GPtrArray *data_forms = NULL; cache = GABBLE_PRESENCE_CACHE (user_data); priv = cache->priv; @@ -1286,7 +1337,7 @@ _caps_disco_cb (GabbleDisco *disco, } /* If tp_handle_ensure () was happy with the jid, it's valid. */ - jid_is_valid = gabble_decode_jid (jid, NULL, NULL, &resource); + jid_is_valid = wocky_decode_jid (jid, NULL, NULL, &resource); g_assert (jid_is_valid); waiter_self = find_matching_waiter (waiters, handle, resource); g_free (resource); @@ -1301,6 +1352,7 @@ _caps_disco_cb (GabbleDisco *disco, cap_set = gabble_capability_set_new_from_stanza (query_result); client_types = client_types_from_message (handle, query_result, waiter_self->resource); + data_forms = data_forms_from_message (query_result); /* Only 'sha-1' is mandatory to implement by XEP-0115. If the remote contact * uses another hash algorithm, don't check the hash and fallback to the old @@ -1313,10 +1365,16 @@ _caps_disco_cb (GabbleDisco *disco, computed_hash = wocky_caps_hash_compute_from_node (query_result); - if (g_str_equal (waiter_self->ver, computed_hash)) + if (computed_hash == NULL) + { + DEBUG ("Unable to compute caps hash for '%s'.", jid); + trust = 0; + bad_hash = TRUE; + } + else if (g_str_equal (waiter_self->ver, computed_hash)) { trust = capability_info_recvd (cache, node, handle, cap_set, - CAPABILITY_BUNDLE_ENOUGH_TRUST, client_types); + CAPABILITY_BUNDLE_ENOUGH_TRUST, client_types, data_forms); } else { @@ -1331,7 +1389,8 @@ _caps_disco_cb (GabbleDisco *disco, } else { - trust = capability_info_recvd (cache, node, handle, cap_set, 1, client_types); + trust = capability_info_recvd (cache, node, handle, cap_set, 1, + client_types, data_forms); } /* Remove the node from the hash table without freeing the key or list of @@ -1371,7 +1430,8 @@ _caps_disco_cb (GabbleDisco *disco, { DiscoWaiter *waiter = (DiscoWaiter *) i->data; - set_caps_for (waiter, cache, cap_set, client_types, handle, jid); + set_caps_for (waiter, cache, cap_set, client_types, + data_forms, handle, jid); emit_capabilities_discovered (cache, waiter->handle); } @@ -1399,7 +1459,8 @@ _caps_disco_cb (GabbleDisco *disco, g_free (tmp); } - set_caps_for (waiter_self, cache, cap_set, client_types, handle, jid); + set_caps_for (waiter_self, cache, cap_set, client_types, + data_forms, handle, jid); } waiters = g_slist_remove (waiters, waiter_self); @@ -1425,6 +1486,7 @@ _caps_disco_cb (GabbleDisco *disco, } gabble_capability_set_free (cap_set); + g_ptr_array_unref (data_forms); OUT: if (handle) @@ -1491,7 +1553,7 @@ _process_caps_uri (GabblePresenceCache *cache, guint types; gabble_presence_set_capabilities ( - presence, resource, cap_set, serial); + presence, resource, cap_set, info->data_forms, serial); /* We can only get this information from actual disco replies, * so we depend on having this information from the caps cache. */ @@ -1786,6 +1848,16 @@ gabble_presence_parse_presence_message (GabblePresenceCache *cache, return ret; } +/* FIXME: this scrapes nicknames out of <messages>, and relies on im-channel.c + * setting keep_unavailable back to FALSE to make nicknames random peers send + * us disappear once we close the accompanying messages. As a side effect, it + * makes specifying <nick> in MUC messages work, which is questionable + * behaviour. See vcard/test-alias-message.py. + * + * It would be cleaner to make the IM channel stash the nickname if we want it + * to go away when the channel closes, rather than relying on this + * spooky-action-at-a-distance. + */ static LmHandlerResult _parse_message_message (GabblePresenceCache *cache, TpHandle handle, @@ -1918,7 +1990,8 @@ gabble_presence_cache_maybe_remove ( if (NULL == presence) return; - if (presence->status == GABBLE_PRESENCE_OFFLINE && + if ((presence->status == GABBLE_PRESENCE_OFFLINE || + presence->status == GABBLE_PRESENCE_UNKNOWN) && presence->status_message == NULL && !presence->keep_unavailable) { @@ -1956,20 +2029,28 @@ gabble_presence_cache_do_update ( gboolean *update_client_types) { GabblePresenceCachePrivate *priv = cache->priv; - TpHandleRepoIface *contact_repo = tp_base_connection_get_handles ( - (TpBaseConnection *) priv->conn, TP_HANDLE_TYPE_CONTACT); - const gchar *jid; GabblePresence *presence; GabbleCapabilitySet *old_cap_set; const GabbleCapabilitySet *new_cap_set; gboolean ret = FALSE; - jid = tp_handle_inspect (contact_repo, handle); - DEBUG ("%s (%d) resource %s prio %d presence %d message \"%s\"", - jid, handle, - resource == NULL ? "<null>" : resource, - priority, presence_id, - status_message == NULL ? "<null>" : status_message); + if (DEBUGGING) + { + TpHandleRepoIface *contact_repo = tp_base_connection_get_handles ( + (TpBaseConnection *) priv->conn, TP_HANDLE_TYPE_CONTACT); + const gchar *jid = tp_handle_inspect (contact_repo, handle); + const gchar *presence_name = wocky_enum_to_nick ( + GABBLE_TYPE_PRESENCE_ID, presence_id); + + if (presence_name == NULL) + presence_name = "plugin-specific, not an element of GabblePresenceId"; + + DEBUG ("%s (%d) resource %s prio %d presence %d (%s) message \"%s\"", + jid, handle, + resource == NULL ? "<null>" : resource, + priority, presence_id, presence_name, + status_message == NULL ? "<null>" : status_message); + } presence = gabble_presence_cache_get (cache, handle); @@ -2045,7 +2126,7 @@ gabble_presence_cache_update_many ( if (updated->len > 0) g_signal_emit (cache, signals[PRESENCES_UPDATED], 0, updated); - g_array_free (updated, TRUE); + g_array_unref (updated); for (i = 0 ; i < contact_handles->len ; i++) { @@ -2081,7 +2162,8 @@ gabble_presence_cache_add_own_caps ( GabblePresenceCache *cache, const gchar *ver, const GabbleCapabilitySet *cap_set, - const GPtrArray *identities) + const GPtrArray *identities, + GPtrArray *data_forms) { gchar *uri = g_strdup_printf ("%s#%s", NS_GABBLE_CAPS, ver); GabbleCapabilityInfo *info = capability_info_get (cache, uri); @@ -2116,6 +2198,8 @@ gabble_presence_cache_add_own_caps ( info->trust = CAPABILITY_BUNDLE_ENOUGH_TRUST; tp_intset_add (info->guys, cache->priv->conn->parent.self_handle); + replace_data_forms (info, data_forms); + /* FIXME: we should satisfy any waiters for this node now. fd.o bug #24619. */ out: @@ -2210,8 +2294,8 @@ gabble_presence_cache_contacts_added_to_olpc_view (GabblePresenceCache *self, g_signal_emit (self, signals[PRESENCES_UPDATED], 0, changed); } - g_array_free (tmp, TRUE); - g_array_free (changed, TRUE); + g_array_unref (tmp); + g_array_unref (changed); } void @@ -2251,8 +2335,8 @@ gabble_presence_cache_contacts_removed_from_olpc_view ( g_signal_emit (self, signals[PRESENCES_UPDATED], 0, changed); } - g_array_free (tmp, TRUE); - g_array_free (changed, TRUE); + g_array_unref (tmp); + g_array_unref (changed); } static gboolean @@ -2478,3 +2562,20 @@ gabble_presence_cache_disco_in_progress (GabblePresenceCache *cache, return in_progress; } + +TpHandle +gabble_presence_cache_get_handle (GabblePresenceCache *cache, + GabblePresence *presence) +{ + GHashTableIter iter; + gpointer key, val; + + g_hash_table_iter_init (&iter, cache->priv->presence); + while (g_hash_table_iter_next (&iter, &key, &val)) + { + if (presence == val) + return GPOINTER_TO_UINT (key); + } + + return 0; +} diff --git a/src/presence-cache.h b/src/presence-cache.h index 44af23fb0..0e5283967 100644 --- a/src/presence-cache.h +++ b/src/presence-cache.h @@ -65,6 +65,9 @@ struct _GabbleCapabilityInfo /* array of GabbleDiscoIdentity or NULL */ GPtrArray *identities; + /* array of WockyDataForm or NULL */ + GPtrArray *data_forms; + TpIntSet *guys; guint trust; @@ -107,7 +110,8 @@ void gabble_presence_cache_maybe_remove (GabblePresenceCache *cache, void gabble_presence_cache_add_own_caps (GabblePresenceCache *cache, const gchar *ver, const GabbleCapabilitySet *cap_set, - const GPtrArray *identities); + const GPtrArray *identities, + GPtrArray *data_forms); const GabbleCapabilityInfo *gabble_presence_cache_peek_own_caps ( GabblePresenceCache *cache, const gchar *ver); @@ -140,6 +144,9 @@ GHashTable* gabble_presence_cache_get_location (GabblePresenceCache *cache, gboolean gabble_presence_cache_disco_in_progress (GabblePresenceCache *cache, TpHandle handle, const gchar *resource); +TpHandle gabble_presence_cache_get_handle (GabblePresenceCache *cache, + GabblePresence *presence); + G_END_DECLS #endif /* __GABBLE_PRESENCE_CACHE_H__ */ diff --git a/src/presence.c b/src/presence.c index 41c7c7b7d..9f1a6bfaf 100644 --- a/src/presence.c +++ b/src/presence.c @@ -23,8 +23,10 @@ #include <string.h> #include <telepathy-glib/channel-manager.h> +#include <wocky/wocky-utils.h> +#include <wocky/wocky-xep-0115-capabilities.h> -#include "capabilities.h" +#include "gabble/capabilities.h" #include "conn-presence.h" #include "presence-cache.h" #include "namespaces.h" @@ -35,7 +37,12 @@ #include "debug.h" -G_DEFINE_TYPE (GabblePresence, gabble_presence, G_TYPE_OBJECT); +static void xep_0115_capabilities_iface_init (gpointer, gpointer); + +G_DEFINE_TYPE_WITH_CODE (GabblePresence, gabble_presence, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (WOCKY_TYPE_XEP_0115_CAPABILITIES, + xep_0115_capabilities_iface_init); +) typedef struct _Resource Resource; @@ -43,6 +50,7 @@ struct _Resource { gchar *name; guint client_type; GabbleCapabilitySet *cap_set; + GPtrArray *data_forms; guint caps_serial; GabblePresenceId status; gchar *status_message; @@ -57,6 +65,9 @@ struct _GabblePresencePrivate { /* The aggregated caps of all the contacts' resources. */ GabbleCapabilitySet *cap_set; + /* The aggregated data forms of all the contacts' resources */ + GPtrArray *data_forms; + gchar *no_resource_status_message; GSList *resources; guint olpc_views; @@ -71,6 +82,8 @@ _resource_new (gchar *name) new->name = name; new->client_type = 0; new->cap_set = gabble_capability_set_new (); + new->data_forms = g_ptr_array_new_with_free_func ( + (GDestroyNotify) g_object_unref); new->status = GABBLE_PRESENCE_OFFLINE; new->status_message = NULL; new->priority = 0; @@ -86,6 +99,7 @@ _resource_free (Resource *resource) g_free (resource->name); g_free (resource->status_message); gabble_capability_set_free (resource->cap_set); + g_ptr_array_unref (resource->data_forms); g_slice_free (Resource, resource); } @@ -102,6 +116,7 @@ gabble_presence_finalize (GObject *object) g_slist_free (priv->resources); gabble_capability_set_free (priv->cap_set); + g_ptr_array_unref (priv->data_forms); g_free (presence->nickname); g_free (presence->avatar_sha1); @@ -127,7 +142,11 @@ gabble_presence_init (GabblePresence *self) priv = self->priv; priv->cap_set = gabble_capability_set_new (); + priv->data_forms = g_ptr_array_new_with_free_func ( + (GDestroyNotify) g_object_unref); priv->resources = NULL; + + self->status = GABBLE_PRESENCE_UNKNOWN; } GabblePresence * @@ -195,6 +214,13 @@ gabble_presence_peek_caps (GabblePresence *presence) return presence->priv->cap_set; } +GPtrArray * +gabble_presence_peek_data_forms (GabblePresence *presence) +{ + g_return_val_if_fail (presence != NULL, NULL); + return presence->priv->data_forms; +} + gboolean gabble_presence_has_resources (GabblePresence *self) { @@ -263,10 +289,22 @@ gabble_presence_resource_has_caps (GabblePresence *presence, return FALSE; } +static void +extend_and_dup (GPtrArray *target, + GPtrArray *source) +{ + if (source == NULL) + return; + + g_ptr_array_foreach (source, (GFunc) g_object_ref, NULL); + tp_g_ptr_array_extend (target, source); +} + void gabble_presence_set_capabilities (GabblePresence *presence, const gchar *resource, const GabbleCapabilitySet *cap_set, + const GPtrArray *data_forms, guint serial) { GabblePresencePrivate *priv = presence->priv; @@ -285,11 +323,13 @@ gabble_presence_set_capabilities (GabblePresence *presence, } gabble_capability_set_clear (priv->cap_set); + g_ptr_array_set_size (priv->data_forms, 0); if (resource == NULL) { DEBUG ("Setting capabilities for bare JID"); gabble_capability_set_update (priv->cap_set, cap_set); + extend_and_dup (priv->data_forms, (GPtrArray *) data_forms); return; } @@ -312,6 +352,7 @@ gabble_presence_set_capabilities (GabblePresence *presence, tmp->caps_serial); tmp->caps_serial = serial; gabble_capability_set_clear (tmp->cap_set); + g_ptr_array_set_size (tmp->data_forms, 0); } if (serial >= tmp->caps_serial) @@ -319,11 +360,19 @@ gabble_presence_set_capabilities (GabblePresence *presence, DEBUG ("updating caps for resource %s", resource); gabble_capability_set_update (tmp->cap_set, cap_set); + + /* TODO: deal with duplicates */ + extend_and_dup (tmp->data_forms, (GPtrArray *) data_forms); } } gabble_capability_set_update (priv->cap_set, tmp->cap_set); + + /* TODO: deal with duplicates */ + extend_and_dup (priv->data_forms, tmp->data_forms); } + + g_signal_emit_by_name (presence, "capabilities-changed"); } static Resource * @@ -636,13 +685,19 @@ gabble_presence_dump (GabblePresence *presence) GString *ret = g_string_new (""); gchar *tmp; GabblePresencePrivate *priv = presence->priv; + const gchar *presence_name = wocky_enum_to_nick (GABBLE_TYPE_PRESENCE_ID, + presence->status); + + if (presence_name == NULL) + presence_name = "plugin-specific, not an element of GabblePresenceId"; g_string_append_printf (ret, "nickname: %s\n" - "accumulated status: %d\n" + "accumulated status: %d (%s)\n" "accumulated status msg: %s\n" "kept while unavailable: %d\n", - presence->nickname, presence->status, + presence->nickname, + presence->status, presence_name, presence->status_message, presence->keep_unavailable); @@ -846,3 +901,20 @@ gabble_presence_get_client_types_array (GabblePresence *presence, return (gchar **) g_ptr_array_free (array, FALSE); } + +static const GPtrArray * +gabble_presence_get_data_forms (WockyXep0115Capabilities *caps) +{ + GabblePresence *presence = GABBLE_PRESENCE (caps); + + return presence->priv->data_forms; +} + +static void +xep_0115_capabilities_iface_init (gpointer g_iface, + gpointer iface_data) +{ + WockyXep0115CapabilitiesInterface *iface = g_iface; + + iface->get_data_forms = gabble_presence_get_data_forms; +} diff --git a/src/presence.h b/src/presence.h index 4f9126d09..50d6945a5 100644 --- a/src/presence.h +++ b/src/presence.h @@ -24,7 +24,7 @@ #include <glib-object.h> -#include "capabilities.h" +#include "gabble/capabilities.h" #include "connection.h" #include "types.h" @@ -96,11 +96,13 @@ gboolean gabble_presence_update (GabblePresence *presence, void gabble_presence_set_capabilities (GabblePresence *presence, const gchar *resource, const GabbleCapabilitySet *cap_set, + const GPtrArray *data_forms, guint serial); gboolean gabble_presence_has_cap (GabblePresence *presence, const gchar *ns); GabbleCapabilitySet *gabble_presence_dup_caps (GabblePresence *presence); const GabbleCapabilitySet *gabble_presence_peek_caps (GabblePresence *presence); +GPtrArray *gabble_presence_peek_data_forms (GabblePresence *presence); gboolean gabble_presence_has_resources (GabblePresence *self); diff --git a/src/private-tubes-factory.c b/src/private-tubes-factory.c index 00d05324e..fc8e3ea6f 100644 --- a/src/private-tubes-factory.c +++ b/src/private-tubes-factory.c @@ -36,8 +36,7 @@ #define DEBUG_FLAG GABBLE_DEBUG_TUBES -#include "caps-channel-manager.h" -#include "capabilities.h" +#include "gabble/caps-channel-manager.h" #include "connection.h" #include "debug.h" #include "muc-channel.h" @@ -92,14 +91,14 @@ struct _GabblePrivateTubesFactoryPrivate #define GABBLE_PRIVATE_TUBES_FACTORY_GET_PRIVATE(obj) ((obj)->priv) static const gchar * const tubes_channel_fixed_properties[] = { - TP_IFACE_CHANNEL ".ChannelType", - TP_IFACE_CHANNEL ".TargetHandleType", + TP_PROP_CHANNEL_CHANNEL_TYPE, + TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, NULL }; static const gchar * const old_tubes_channel_allowed_properties[] = { - TP_IFACE_CHANNEL ".TargetHandle", - TP_IFACE_CHANNEL ".TargetID", + TP_PROP_CHANNEL_TARGET_HANDLE, + TP_PROP_CHANNEL_TARGET_ID, NULL }; @@ -373,7 +372,7 @@ gabble_private_tubes_factory_close_all (GabblePrivateTubesFactory *fac) /* Use a temporary variable (the macro does this) because we don't want * tubes_channel_closed_cb to remove the channel from the hash table a * second time */ - tp_clear_pointer (&priv->tubes_channels, g_hash_table_destroy); + tp_clear_pointer (&priv->tubes_channels, g_hash_table_unref); } static void @@ -388,8 +387,8 @@ add_service_to_array (const gchar *service, GValue *target_handle_type_value; gchar *tube_allowed_properties[] = { - TP_IFACE_CHANNEL ".TargetHandle", - TP_IFACE_CHANNEL ".TargetID", + TP_PROP_CHANNEL_TARGET_HANDLE, + TP_PROP_CHANNEL_TARGET_ID, NULL }; @@ -410,23 +409,23 @@ add_service_to_array (const gchar *service, else g_value_set_static_string (channel_type_value, TP_IFACE_CHANNEL_TYPE_DBUS_TUBE); - g_hash_table_insert (fixed_properties, TP_IFACE_CHANNEL ".ChannelType", + g_hash_table_insert (fixed_properties, TP_PROP_CHANNEL_CHANNEL_TYPE, channel_type_value); target_handle_type_value = tp_g_value_slice_new (G_TYPE_UINT); g_value_set_uint (target_handle_type_value, TP_HANDLE_TYPE_CONTACT); g_hash_table_insert (fixed_properties, - TP_IFACE_CHANNEL ".TargetHandleType", target_handle_type_value); + TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, target_handle_type_value); target_handle_type_value = tp_g_value_slice_new (G_TYPE_STRING); g_value_set_string (target_handle_type_value, service); if (type == TP_TUBE_TYPE_STREAM) g_hash_table_insert (fixed_properties, - TP_IFACE_CHANNEL_TYPE_STREAM_TUBE ".Service", + TP_PROP_CHANNEL_TYPE_STREAM_TUBE_SERVICE, target_handle_type_value); else g_hash_table_insert (fixed_properties, - TP_IFACE_CHANNEL_TYPE_DBUS_TUBE ".ServiceName", + TP_PROP_CHANNEL_TYPE_DBUS_TUBE_SERVICE_NAME, target_handle_type_value); dbus_g_type_struct_set (&monster, @@ -434,7 +433,7 @@ add_service_to_array (const gchar *service, 1, tube_allowed_properties, G_MAXUINT); - g_hash_table_destroy (fixed_properties); + g_hash_table_unref (fixed_properties); g_ptr_array_add (arr, g_value_get_boxed (&monster)); } @@ -460,20 +459,20 @@ add_generic_tube_caps (GPtrArray *arr) g_value_set_static_string (channel_type_value, TP_IFACE_CHANNEL_TYPE_STREAM_TUBE); - g_hash_table_insert (fixed_properties, TP_IFACE_CHANNEL ".ChannelType", + g_hash_table_insert (fixed_properties, TP_PROP_CHANNEL_CHANNEL_TYPE, channel_type_value); target_handle_type_value = tp_g_value_slice_new (G_TYPE_UINT); g_value_set_uint (target_handle_type_value, TP_HANDLE_TYPE_CONTACT); g_hash_table_insert (fixed_properties, - TP_IFACE_CHANNEL ".TargetHandleType", target_handle_type_value); + TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, target_handle_type_value); dbus_g_type_struct_set (&monster1, 0, fixed_properties, 1, gabble_tube_stream_channel_get_allowed_properties (), G_MAXUINT); - g_hash_table_destroy (fixed_properties); + g_hash_table_unref (fixed_properties); g_ptr_array_add (arr, g_value_get_boxed (&monster1)); /* DBusTube */ @@ -489,20 +488,20 @@ add_generic_tube_caps (GPtrArray *arr) g_value_set_static_string (channel_type_value, TP_IFACE_CHANNEL_TYPE_DBUS_TUBE); - g_hash_table_insert (fixed_properties, TP_IFACE_CHANNEL ".ChannelType", + g_hash_table_insert (fixed_properties, TP_PROP_CHANNEL_CHANNEL_TYPE, channel_type_value); target_handle_type_value = tp_g_value_slice_new (G_TYPE_UINT); g_value_set_uint (target_handle_type_value, TP_HANDLE_TYPE_CONTACT); g_hash_table_insert (fixed_properties, - TP_IFACE_CHANNEL ".TargetHandleType", target_handle_type_value); + TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, target_handle_type_value); dbus_g_type_struct_set (&monster2, 0, fixed_properties, 1, gabble_tube_dbus_channel_get_allowed_properties (), G_MAXUINT); - g_hash_table_destroy (fixed_properties); + g_hash_table_unref (fixed_properties); g_ptr_array_add (arr, g_value_get_boxed (&monster2)); } @@ -563,8 +562,13 @@ gabble_private_tubes_factory_add_cap (GabbleCapsChannelManager *manager, const gchar *channel_type, *service; gchar *ns = NULL; + /* capabilities mean being able to RECEIVE said kinds of tubes. hence, + * skip Requested=true (locally initiated) channel classes */ + if (tp_asv_get_boolean (cap, TP_PROP_CHANNEL_REQUESTED, FALSE)) + return; + channel_type = tp_asv_get_string (cap, - TP_IFACE_CHANNEL ".ChannelType"); + TP_PROP_CHANNEL_CHANNEL_TYPE); /* this channel is not for this factory */ if (tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_TUBES) && @@ -573,13 +577,13 @@ gabble_private_tubes_factory_add_cap (GabbleCapsChannelManager *manager, return; if (tp_asv_get_uint32 (cap, - TP_IFACE_CHANNEL ".TargetHandleType", NULL) != TP_HANDLE_TYPE_CONTACT) + TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, NULL) != TP_HANDLE_TYPE_CONTACT) return; if (!tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_STREAM_TUBE)) { service = tp_asv_get_string (cap, - TP_IFACE_CHANNEL_TYPE_STREAM_TUBE ".Service"); + TP_PROP_CHANNEL_TYPE_STREAM_TUBE_SERVICE); if (service != NULL) ns = g_strconcat (STREAM_CAP_PREFIX, service, NULL); @@ -587,7 +591,7 @@ gabble_private_tubes_factory_add_cap (GabbleCapsChannelManager *manager, else if (!tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_DBUS_TUBE)) { service = tp_asv_get_string (cap, - TP_IFACE_CHANNEL_TYPE_DBUS_TUBE ".ServiceName"); + TP_PROP_CHANNEL_TYPE_DBUS_TUBE_SERVICE_NAME); if (service != NULL) ns = g_strconcat (DBUS_CAP_PREFIX, service, NULL); @@ -607,7 +611,8 @@ gabble_private_tubes_factory_represent_client ( const gchar *client_name, const GPtrArray *filters, const gchar * const *cap_tokens, - GabbleCapabilitySet *cap_set) + GabbleCapabilitySet *cap_set, + GPtrArray *data_forms) { guint i; @@ -805,17 +810,17 @@ gabble_private_tubes_factory_type_foreach_channel_class (GType type, value = tp_g_value_slice_new (G_TYPE_STRING); g_value_set_static_string (value, TP_IFACE_CHANNEL_TYPE_TUBES); - g_hash_table_insert (table, TP_IFACE_CHANNEL ".ChannelType", + g_hash_table_insert (table, TP_PROP_CHANNEL_CHANNEL_TYPE, value); value = tp_g_value_slice_new (G_TYPE_UINT); g_value_set_uint (value, TP_HANDLE_TYPE_CONTACT); - g_hash_table_insert (table, TP_IFACE_CHANNEL ".TargetHandleType", + g_hash_table_insert (table, TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, value); func (type, table, old_tubes_channel_allowed_properties, user_data); - g_hash_table_destroy (table); + g_hash_table_unref (table); /* 1-1 Channel.Type.StreamTube */ table = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, @@ -823,18 +828,18 @@ gabble_private_tubes_factory_type_foreach_channel_class (GType type, value = tp_g_value_slice_new (G_TYPE_STRING); g_value_set_static_string (value, TP_IFACE_CHANNEL_TYPE_STREAM_TUBE); - g_hash_table_insert (table, TP_IFACE_CHANNEL ".ChannelType", + g_hash_table_insert (table, TP_PROP_CHANNEL_CHANNEL_TYPE, value); value = tp_g_value_slice_new (G_TYPE_UINT); g_value_set_uint (value, TP_HANDLE_TYPE_CONTACT); - g_hash_table_insert (table, TP_IFACE_CHANNEL ".TargetHandleType", + g_hash_table_insert (table, TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, value); func (type, table, gabble_tube_stream_channel_get_allowed_properties (), user_data); - g_hash_table_destroy (table); + g_hash_table_unref (table); /* 1-1 Channel.Type.DBusTube */ table = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, @@ -842,18 +847,18 @@ gabble_private_tubes_factory_type_foreach_channel_class (GType type, value = tp_g_value_slice_new (G_TYPE_STRING); g_value_set_static_string (value, TP_IFACE_CHANNEL_TYPE_DBUS_TUBE); - g_hash_table_insert (table, TP_IFACE_CHANNEL ".ChannelType", + g_hash_table_insert (table, TP_PROP_CHANNEL_CHANNEL_TYPE, value); value = tp_g_value_slice_new (G_TYPE_UINT); g_value_set_uint (value, TP_HANDLE_TYPE_CONTACT); - g_hash_table_insert (table, TP_IFACE_CHANNEL ".TargetHandleType", + g_hash_table_insert (table, TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, value); func (type, table, gabble_tube_dbus_channel_get_allowed_properties (), user_data); - g_hash_table_destroy (table); + g_hash_table_unref (table); } @@ -870,11 +875,11 @@ gabble_private_tubes_factory_requestotron (GabblePrivateTubesFactory *self, GabbleTubesChannel *channel; if (tp_asv_get_uint32 (request_properties, - TP_IFACE_CHANNEL ".TargetHandleType", NULL) != TP_HANDLE_TYPE_CONTACT) + TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, NULL) != TP_HANDLE_TYPE_CONTACT) return FALSE; channel_type = tp_asv_get_string (request_properties, - TP_IFACE_CHANNEL ".ChannelType"); + TP_PROP_CHANNEL_CHANNEL_TYPE); if (tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_TUBES) && tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_STREAM_TUBE) && @@ -901,12 +906,12 @@ gabble_private_tubes_factory_requestotron (GabblePrivateTubesFactory *self, /* "Service" is a mandatory, not-fixed property */ service = tp_asv_get_string (request_properties, - TP_IFACE_CHANNEL_TYPE_STREAM_TUBE ".Service"); + TP_PROP_CHANNEL_TYPE_STREAM_TUBE_SERVICE); if (service == NULL) { g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED, "Request does not contain the mandatory property '%s'", - TP_IFACE_CHANNEL_TYPE_STREAM_TUBE ".Service"); + TP_PROP_CHANNEL_TYPE_STREAM_TUBE_SERVICE); goto error; } } @@ -923,12 +928,12 @@ gabble_private_tubes_factory_requestotron (GabblePrivateTubesFactory *self, /* "ServiceName" is a mandatory, not-fixed property */ service = tp_asv_get_string (request_properties, - TP_IFACE_CHANNEL_TYPE_DBUS_TUBE ".ServiceName"); + TP_PROP_CHANNEL_TYPE_DBUS_TUBE_SERVICE_NAME); if (service == NULL) { g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED, "Request does not contain the mandatory property '%s'", - TP_IFACE_CHANNEL_TYPE_DBUS_TUBE ".ServiceName"); + TP_PROP_CHANNEL_TYPE_DBUS_TUBE_SERVICE_NAME); goto error; } @@ -944,7 +949,7 @@ gabble_private_tubes_factory_requestotron (GabblePrivateTubesFactory *self, /* validity already checked by TpBaseConnection */ handle = tp_asv_get_uint32 (request_properties, - TP_IFACE_CHANNEL ".TargetHandle", NULL); + TP_PROP_CHANNEL_TARGET_HANDLE, NULL); g_assert (handle != 0); /* Don't support opening a channel to our self handle */ @@ -1014,7 +1019,7 @@ gabble_private_tubes_factory_requestotron (GabblePrivateTubesFactory *self, g_hash_table_insert (channels, new_channel, request_tokens); tp_channel_manager_emit_new_channels (self, channels); - g_hash_table_destroy (channels); + g_hash_table_unref (channels); g_slist_free (request_tokens); } else diff --git a/src/protocol.c b/src/protocol.c index a1b98c1d6..a6e343188 100644 --- a/src/protocol.c +++ b/src/protocol.c @@ -19,11 +19,15 @@ #include "protocol.h" +#include <string.h> #include <telepathy-glib/base-connection-manager.h> #include <dbus/dbus-protocol.h> #include <dbus/dbus-glib.h> +#include "extensions/extensions.h" + #include "conn-presence.h" + #include "connection.h" #include "connection-manager.h" #include "im-factory.h" @@ -32,15 +36,19 @@ #include "roomlist-manager.h" #include "search-manager.h" #include "util.h" +#include "addressing-util.h" #define PROTOCOL_NAME "jabber" #define ICON_NAME "im-" PROTOCOL_NAME #define VCARD_FIELD_NAME "x-" PROTOCOL_NAME #define ENGLISH_NAME "Jabber" -G_DEFINE_TYPE (GabbleJabberProtocol, - gabble_jabber_protocol, - TP_TYPE_BASE_PROTOCOL) +static void addressing_iface_init (TpProtocolAddressingInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (GabbleJabberProtocol, gabble_jabber_protocol, + TP_TYPE_BASE_PROTOCOL, + G_IMPLEMENT_INTERFACE (TP_TYPE_PROTOCOL_ADDRESSING, addressing_iface_init); + ) static TpCMParamSpec jabber_params[] = { { "account", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING, @@ -76,7 +84,7 @@ static TpCMParamSpec jabber_params[] = { 0 /* unused */, NULL, NULL }, { "require-encryption", DBUS_TYPE_BOOLEAN_AS_STRING, G_TYPE_BOOLEAN, - TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, GINT_TO_POINTER(FALSE), + TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, GINT_TO_POINTER(TRUE), 0 /* unused */, NULL, NULL }, { "register", DBUS_TYPE_BOOLEAN_AS_STRING, G_TYPE_BOOLEAN, @@ -149,7 +157,9 @@ static TpCMParamSpec jabber_params[] = { { GABBLE_PROP_CONNECTION_INTERFACE_GABBLE_DECLOAK_DECLOAK_AUTOMATICALLY, DBUS_TYPE_BOOLEAN_AS_STRING, G_TYPE_BOOLEAN, - TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, GINT_TO_POINTER (FALSE), + TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT | + TP_CONN_MGR_PARAM_FLAG_DBUS_PROPERTY, + GINT_TO_POINTER (TRUE), 0 /* unused */, NULL, NULL }, { "fallback-servers", "as", 0, @@ -301,6 +311,7 @@ get_interfaces (TpBaseProtocol *self) { const gchar * const interfaces[] = { TP_IFACE_PROTOCOL_INTERFACE_PRESENCE, + TP_IFACE_PROTOCOL_INTERFACE_ADDRESSING, NULL }; return g_strdupv ((GStrv) interfaces); @@ -329,7 +340,9 @@ get_connection_details (TpBaseProtocol *self, if (channel_managers != NULL) { GType types[] = { +#ifdef ENABLE_FILE_TRANSFER GABBLE_TYPE_FT_MANAGER, +#endif GABBLE_TYPE_IM_FACTORY, GABBLE_TYPE_MEDIA_FACTORY, GABBLE_TYPE_MUC_FACTORY, @@ -368,6 +381,59 @@ dup_authentication_types (TpBaseProtocol *self) return g_strdupv ((GStrv) types); } +static GStrv +dup_supported_uri_schemes (TpBaseProtocol *self) +{ + return g_strdupv ((gchar **) gabble_get_addressable_uri_schemes ()); +} + +static GStrv +dup_supported_vcard_fields (TpBaseProtocol *self) +{ + return g_strdupv ((gchar **) gabble_get_addressable_vcard_fields ()); +} + +static gchar * +addressing_normalize_vcard_address (TpBaseProtocol *self, + const gchar *vcard_field, + const gchar *vcard_address, + GError **error) +{ + gchar *normalized_address = gabble_normalize_vcard_address (vcard_field, vcard_address, error); + + if (normalized_address == NULL) + { + /* InvalidHandle makes no sense in Protocol */ + if (error != NULL && g_error_matches (*error, TP_ERROR, TP_ERROR_INVALID_HANDLE)) + { + (*error)->code = TP_ERROR_INVALID_ARGUMENT; + } + } + + return normalized_address; +} + +static gchar * +addressing_normalize_contact_uri (TpBaseProtocol *self, + const gchar *uri, + GError **error) +{ + gchar *normalized_address = NULL; + + normalized_address = gabble_normalize_contact_uri (uri, error); + + if (normalized_address == NULL) + { + /* InvalidHandle makes no sense in Protocol */ + if (error != NULL && g_error_matches (*error, TP_ERROR, TP_ERROR_INVALID_HANDLE)) + { + (*error)->code = TP_ERROR_INVALID_ARGUMENT; + } + } + + return normalized_address; +} + static void gabble_jabber_protocol_class_init (GabbleJabberProtocolClass *klass) { @@ -392,3 +458,11 @@ gabble_jabber_protocol_new (void) NULL); } +static void +addressing_iface_init (TpProtocolAddressingInterface *iface) +{ + iface->dup_supported_vcard_fields = dup_supported_vcard_fields; + iface->dup_supported_uri_schemes = dup_supported_uri_schemes; + iface->normalize_vcard_address = addressing_normalize_vcard_address; + iface->normalize_contact_uri = addressing_normalize_contact_uri; +} diff --git a/src/room-config.c b/src/room-config.c new file mode 100644 index 000000000..6345fc767 --- /dev/null +++ b/src/room-config.c @@ -0,0 +1,101 @@ +/* + * room-config.c - Channel.Interface.RoomConfig1 implementation + * Copyright ©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 "room-config.h" +#include "muc-channel.h" + +#define DEBUG_FLAG GABBLE_DEBUG_MUC +#include "debug.h" + +static void gabble_room_config_update_configuration_async ( + TpBaseRoomConfig *base_config, + GHashTable *validated_properties, + GAsyncReadyCallback callback, + gpointer user_data); + +struct _GabbleRoomConfigPrivate { + gpointer hi_dere; +}; + +G_DEFINE_TYPE (GabbleRoomConfig, gabble_room_config, TP_TYPE_BASE_ROOM_CONFIG) + +static void +gabble_room_config_init (GabbleRoomConfig *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GABBLE_TYPE_ROOM_CONFIG, + GabbleRoomConfigPrivate); +} + +static void +gabble_room_config_class_init (GabbleRoomConfigClass *klass) +{ + TpBaseRoomConfigClass *parent_class = TP_BASE_ROOM_CONFIG_CLASS (klass); + + parent_class->update_async = gabble_room_config_update_configuration_async; + g_type_class_add_private (klass, sizeof (GabbleRoomConfigPrivate)); +} + +GabbleRoomConfig * +gabble_room_config_new ( + TpBaseChannel *channel) +{ + g_return_val_if_fail (TP_IS_BASE_CHANNEL (channel), NULL); + + return g_object_new (GABBLE_TYPE_ROOM_CONFIG, + "channel", channel, + NULL); +} + +static void +updated_configuration_cb ( + GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GabbleMucChannel *channel = GABBLE_MUC_CHANNEL (source); + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (user_data); + GError *error = NULL; + + if (!gabble_muc_channel_update_configuration_finish (channel, result, &error)) + { + g_simple_async_result_set_from_error (simple, error); + g_clear_error (&error); + } + + g_simple_async_result_complete (simple); + g_object_unref (simple); +} + +static void +gabble_room_config_update_configuration_async ( + TpBaseRoomConfig *base_config, + GHashTable *validated_properties, + GAsyncReadyCallback callback, + gpointer user_data) +{ + TpBaseChannel *base_channel = tp_base_room_config_dup_channel (base_config); + GSimpleAsyncResult *simple = g_simple_async_result_new ( + G_OBJECT (base_config), callback, user_data, + gabble_room_config_update_configuration_async); + + gabble_muc_channel_update_configuration_async ( + GABBLE_MUC_CHANNEL (base_channel), validated_properties, + updated_configuration_cb, simple); + g_object_unref (base_channel); +} diff --git a/src/room-config.h b/src/room-config.h new file mode 100644 index 000000000..a2981c186 --- /dev/null +++ b/src/room-config.h @@ -0,0 +1,61 @@ +/* + * room-config.h - header for Channel.I.RoomConfig1 implementation + * Copyright ©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 GABBLE_ROOM_CONFIG_H +#define GABBLE_ROOM_CONFIG_H + +#include <glib-object.h> +#include <telepathy-glib/base-room-config.h> + +typedef struct _GabbleRoomConfig GabbleRoomConfig; +typedef struct _GabbleRoomConfigClass GabbleRoomConfigClass; +typedef struct _GabbleRoomConfigPrivate GabbleRoomConfigPrivate; + +struct _GabbleRoomConfigClass { + TpBaseRoomConfigClass parent_class; +}; + +struct _GabbleRoomConfig { + TpBaseRoomConfig parent; + + GabbleRoomConfigPrivate *priv; +}; + +GabbleRoomConfig *gabble_room_config_new ( + TpBaseChannel *channel); + +/* TYPE MACROS */ +GType gabble_room_config_get_type (void); + +#define GABBLE_TYPE_ROOM_CONFIG \ + (gabble_room_config_get_type ()) +#define GABBLE_ROOM_CONFIG(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), GABBLE_TYPE_ROOM_CONFIG, GabbleRoomConfig)) +#define GABBLE_ROOM_CONFIG_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), GABBLE_TYPE_ROOM_CONFIG,\ + GabbleRoomConfigClass)) +#define GABBLE_IS_ROOM_CONFIG(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), GABBLE_TYPE_ROOM_CONFIG)) +#define GABBLE_IS_ROOM_CONFIG_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), GABBLE_TYPE_ROOM_CONFIG)) +#define GABBLE_ROOM_CONFIG_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), GABBLE_TYPE_ROOM_CONFIG, \ + GabbleRoomConfigClass)) + +#endif /* GABBLE_ROOM_CONFIG_H */ diff --git a/src/roomlist-channel.c b/src/roomlist-channel.c index cc1a29500..642c983d1 100644 --- a/src/roomlist-channel.c +++ b/src/roomlist-channel.c @@ -231,7 +231,7 @@ gabble_roomlist_channel_dispose (GObject *object) g_assert (priv->pending_room_signals != NULL); g_assert (priv->pending_room_signals->len == 0); - g_ptr_array_free (priv->pending_room_signals, TRUE); + g_ptr_array_unref (priv->pending_room_signals); priv->pending_room_signals = NULL; if (G_OBJECT_CLASS (gabble_roomlist_channel_parent_class)->dispose) @@ -429,7 +429,7 @@ room_info_cb (gpointer pipeline, GabbleDiscoItem *item, gpointer user_data) DEBUG ("adding new room signal data to pending: %s", jid); g_ptr_array_add (priv->pending_room_signals, g_value_get_boxed (&room)); - g_hash_table_destroy (keys); + g_hash_table_unref (keys); } static void diff --git a/src/roomlist-manager.c b/src/roomlist-manager.c index 76abee688..e3cb09a4a 100644 --- a/src/roomlist-manager.c +++ b/src/roomlist-manager.c @@ -33,7 +33,7 @@ #define DEBUG_FLAG GABBLE_DEBUG_MUC -#include "caps-channel-manager.h" +#include "gabble/caps-channel-manager.h" #include "connection.h" #include "debug.h" #include "namespaces.h" @@ -113,7 +113,7 @@ gabble_roomlist_manager_close_all (GabbleRoomlistManager *self) g_object_unref (channel); } - g_ptr_array_free (tmp, TRUE); + g_ptr_array_unref (tmp); } } @@ -289,7 +289,7 @@ gabble_roomlist_manager_type_foreach_channel_class (GType type, func (type, table, roomlist_channel_allowed_properties, user_data); - g_hash_table_destroy (table); + g_hash_table_unref (table); } diff --git a/src/roster.c b/src/roster.c index 075957eaf..fec216bdf 100644 --- a/src/roster.c +++ b/src/roster.c @@ -22,8 +22,6 @@ #include "config.h" #include "roster.h" -#define DBUS_API_SUBJECT_TO_CHANGE - #include <string.h> #include <dbus/dbus-glib.h> @@ -37,7 +35,7 @@ #define DEBUG_FLAG GABBLE_DEBUG_ROSTER -#include "caps-channel-manager.h" +#include "gabble/caps-channel-manager.h" #include "conn-aliasing.h" #include "conn-presence.h" #include "conn-util.h" @@ -52,7 +50,7 @@ /* signal enum */ enum { - NICKNAME_UPDATE, + NICKNAMES_UPDATE, LAST_SIGNAL }; @@ -114,8 +112,12 @@ struct _GabbleRosterItemEdit /* if these are ..._INVALID, that means don't edit */ GabbleRosterSubscription new_subscription; GoogleItemType new_google_type; - /* owned by the GabbleRosterItemEdit; if NULL, that means don't edit */ + + /* owned by the GabbleRosterItemEdit. If NULL, that means don't edit... */ gchar *new_name; + /* if TRUE, disregard new_name and remove name='' from the roster item */ + gboolean remove_name; + TpHandleSet *add_to_groups; TpHandleSet *remove_from_groups; gboolean remove_from_all_other_groups; @@ -234,7 +236,7 @@ gabble_roster_finalize (GObject *object) DEBUG ("called with %p", object); g_hash_table_foreach (priv->items, item_handle_unref_foreach, priv); - g_hash_table_destroy (priv->items); + g_hash_table_unref (priv->items); G_OBJECT_CLASS (gabble_roster_parent_class)->finalize (object); } @@ -525,7 +527,8 @@ static GabbleRosterItem * _gabble_roster_item_update (GabbleRoster *roster, TpHandle contact_handle, WockyNode *node, - gboolean google_roster_mode) + gboolean google_roster_mode, + gboolean *nickname_updated) { GabbleRosterPrivate *priv = roster->priv; GabbleRosterItem *item; @@ -572,8 +575,12 @@ _gabble_roster_item_update (GabbleRoster *roster, DEBUG ("name for contact#%u changed to %s", contact_handle, name); - g_signal_emit (G_OBJECT (roster), signals[NICKNAME_UPDATE], 0, - contact_handle); + + *nickname_updated = TRUE; + } + else + { + *nickname_updated = FALSE; } new_groups_handle_set = _parse_item_groups (node, @@ -616,7 +623,7 @@ _gabble_roster_item_update (GabbleRoster *roster, tp_base_contact_list_groups_created ((TpBaseContactList *) roster, (const gchar * const *) strv->pdata, strv->len); - g_ptr_array_free (strv, TRUE); + g_ptr_array_unref (strv); } tp_clear_pointer (&created_groups, tp_intset_destroy); @@ -1085,6 +1092,7 @@ process_roster ( TpBaseConnection *conn = (TpBaseConnection *) priv->conn; TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (conn, TP_HANDLE_TYPE_CONTACT); + GArray *updated_nicknames = g_array_new (FALSE, FALSE, sizeof (TpHandle)); /* asymmetry is because we don't get locally pending subscription * requests via <roster>, we get it via <presence> */ @@ -1109,6 +1117,7 @@ process_roster ( const char *jid; TpHandle handle; GabbleRosterItem *item; + gboolean nickname_updated; handle = validate_roster_item (contact_repo, item_node, &jid); @@ -1120,7 +1129,7 @@ process_roster ( tp_handle_unref (contact_repo, handle); item = _gabble_roster_item_update (roster, handle, item_node, - google_roster); + google_roster, &nickname_updated); #ifdef ENABLE_DEBUG if (DEBUGGING) { @@ -1130,6 +1139,9 @@ process_roster ( } #endif + if (nickname_updated) + g_array_append_val (updated_nicknames, handle); + /* handle publish list changes */ switch (item->subscription) { @@ -1233,12 +1245,17 @@ process_roster ( case GABBLE_ROSTER_SUBSCRIPTION_FROM: case GABBLE_ROSTER_SUBSCRIPTION_BOTH: if (google_roster && - /* Don't hide contacts from stored if they're remote pending. - * This works around Google Talk flickering ask="subscribe" - * when you try to subscribe to someone; see - * test-google-roster.py. + /* Don't hide contacts from stored if they're pending. + * This works around two Google Talk issues: + * - When you try to subscribe to someone, you get a flickering + * ask="subscribe"; + * - When somebody tries to subscribe to you, you get a presence + * with type="subscribe" followed by a roster update with + * subscribe="none". + * See test-google-roster.py for more details. */ item->subscribe != TP_SUBSCRIPTION_STATE_ASK && + item->publish != TP_SUBSCRIPTION_STATE_ASK && !_google_roster_item_should_keep (jid, item)) { tp_handle_set_remove (changed, handle); @@ -1303,6 +1320,9 @@ process_roster ( _gabble_roster_item_maybe_remove (roster, handle); } + if (updated_nicknames->len > 0) + g_signal_emit (roster, signals[NICKNAMES_UPDATE], 0, updated_nicknames); + tp_base_contact_list_contacts_changed ((TpBaseContactList *) roster, changed, removed); @@ -1313,6 +1333,7 @@ process_roster ( tp_handle_set_destroy (blocking_changed); } + g_array_unref (updated_nicknames); tp_handle_set_destroy (changed); tp_handle_set_destroy (removed); tp_handle_set_destroy (referenced_handles); @@ -1359,6 +1380,16 @@ got_roster_iq (GabbleRoster *roster, return FALSE; } + if (sub_type == WOCKY_STANZA_SUB_TYPE_RESULT && priv->received) + { + /* <https://bugs.freedesktop.org/show_bug.cgi?id=42186>: some super-buggy + * XMPP server running on vk.com sends its reply to our roster query twice. + */ + DEBUG ("The server sent replied to our roster query more than once! " + "Ignoring this reply"); + return FALSE; + } + process_roster (roster, query_node); if (sub_type == WOCKY_STANZA_SUB_TYPE_RESULT) @@ -1380,16 +1411,36 @@ got_roster_iq (GabbleRoster *roster, { GabbleRosterItem *item = v; TpHandle contact = GPOINTER_TO_UINT (k); + GabblePresence *presence = gabble_presence_cache_get ( + priv->conn->presence_cache, contact); + + if (item->subscribe == TP_SUBSCRIPTION_STATE_YES && + (presence == NULL || presence->status == GABBLE_PRESENCE_UNKNOWN)) + { + /* The contact might be in the presence cache with UNKNOWN + * presence if we've received a message from them before the + * roster arrived: an item is forcibly added to stash the + * nickname which might have been included in the <message/> in + * the presence cache. (This seems like a rather illogical place + * to stash such nicknames—if anything, they should live in + * GabbleImFactory—but there we go.) + * + * So if this is the case, we flip their status to OFFLINE. We + * don't use gabble_presence_update() because we want to signal + * all the unknown→offline transitions together. + */ + if (presence != NULL) + presence->status = GABBLE_PRESENCE_OFFLINE; - if (item->subscribe == TP_SUBSCRIPTION_STATE_YES) - g_array_append_val (members, contact); + g_array_append_val (members, contact); + } if (item->unsent_edits != NULL) edited_items = g_slist_prepend (edited_items, item); } conn_presence_emit_presence_update (priv->conn, members); - g_array_free (members, TRUE); + g_array_unref (members); /* The roster is now complete and we can emit signals... */ tp_base_contact_list_set_list_received ((TpBaseContactList *) roster); @@ -1708,6 +1759,8 @@ roster_received_cb (GObject *source_object, result, &response, &error)) { got_roster_iq (self, response); + + g_object_unref (response); } else { @@ -2017,7 +2070,13 @@ roster_item_apply_edits (GabbleRoster *roster, } } - if (edits->new_name != NULL && tp_strdiff (item->name, edits->new_name)) + if (edits->remove_name) + { + DEBUG ("Removing name='' (was '%s')", item->name); + altered = TRUE; + edited_item.name = NULL; + } + else if (edits->new_name != NULL && tp_strdiff (item->name, edits->new_name)) { DEBUG ("Changing name from %s to %s", item->name, edits->new_name); altered = TRUE; @@ -2284,7 +2343,6 @@ gabble_roster_handle_set_name (GabbleRoster *roster, g_return_val_if_fail (GABBLE_IS_ROSTER (roster), FALSE); g_return_val_if_fail (tp_handle_is_valid (contact_repo, handle, NULL), FALSE); - g_return_val_if_fail (name != NULL, FALSE); item = _gabble_roster_item_ensure (roster, handle); g_return_val_if_fail (item != NULL, FALSE); @@ -2292,10 +2350,19 @@ gabble_roster_handle_set_name (GabbleRoster *roster, if (item->unsent_edits == NULL) item->unsent_edits = item_edit_new (contact_repo, handle); - DEBUG ("queue edit to contact#%u - change name to \"%s\"", - handle, name); - g_free (item->unsent_edits->new_name); - item->unsent_edits->new_name = g_strdup (name); + tp_clear_pointer (&item->unsent_edits->new_name, g_free); + + if (name == NULL) + { + DEBUG ("queue edit to contact#%u - remove name", handle); + item->unsent_edits->remove_name = TRUE; + } + else + { + DEBUG ("queue edit to contact#%u - set name='%s'", handle, name); + item->unsent_edits->remove_name = FALSE; + item->unsent_edits->new_name = g_strdup (name); + } /* maybe we can apply the edit immediately? */ roster_item_apply_edits (roster, handle, item); @@ -2680,19 +2747,15 @@ gabble_roster_authorize_publication_async (TpBaseContactList *base, TpIntSetFastIter iter; TpHandle contact; GError *error = NULL; -#ifdef ENABLE_DEBUG TpHandleRepoIface *contact_repo = tp_base_connection_get_handles ( (TpBaseConnection *) self->priv->conn, TP_HANDLE_TYPE_CONTACT); -#endif tp_intset_fast_iter_init (&iter, tp_handle_set_peek (contacts)); while (tp_intset_fast_iter_next (&iter, &contact)) { GabbleRosterItem *item = _gabble_roster_item_lookup (self, contact); -#ifdef ENABLE_DEBUG const gchar *contact_id = tp_handle_inspect (contact_repo, contact); -#endif if (item == NULL || item->publish == TP_SUBSCRIPTION_STATE_NO || item->publish == TP_SUBSCRIPTION_STATE_REMOVED_REMOTELY) @@ -3134,7 +3197,7 @@ gabble_roster_set_contact_groups_async (TpBaseContactList *base, (const gchar * const *) groups_created->pdata, groups_created->len); } - g_ptr_array_free (groups_created, TRUE); + g_ptr_array_unref (groups_created); if (item->unsent_edits == NULL) item->unsent_edits = item_edit_new (contact_repo, contact); @@ -3492,13 +3555,13 @@ gabble_roster_class_init (GabbleRosterClass *cls) base_class->dup_states = gabble_roster_dup_states; base_class->dup_contacts = gabble_roster_dup_contacts; - signals[NICKNAME_UPDATE] = g_signal_new ( - "nickname-update", + signals[NICKNAMES_UPDATE] = g_signal_new ( + "nicknames-update", G_TYPE_FROM_CLASS (cls), G_SIGNAL_RUN_LAST, 0, NULL, NULL, - g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT); + g_cclosure_marshal_VOID__BOXED, G_TYPE_NONE, 1, DBUS_TYPE_G_UINT_ARRAY); } gboolean diff --git a/src/search-channel.c b/src/search-channel.c index c7dd1d02f..05915db1e 100644 --- a/src/search-channel.c +++ b/src/search-channel.c @@ -237,7 +237,7 @@ parse_unextended_field_response ( g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE, "server is broken: %s is not a field defined in XEP 0055", field->name); - g_ptr_array_free (search_keys, TRUE); + g_ptr_array_unref (search_keys); return NULL; } } @@ -350,7 +350,7 @@ parse_data_form ( return search_keys; fail: - g_ptr_array_free (search_keys, TRUE); + g_ptr_array_unref (search_keys); return NULL; } @@ -661,7 +661,7 @@ parse_result_item (GabbleSearchChannel *chan, } add_search_result (chan, info); - g_hash_table_destroy (info); + g_hash_table_unref (info); } static void @@ -714,7 +714,7 @@ parse_extended_result_item (GabbleSearchChannel *chan, add_search_result (chan, info); } - g_hash_table_destroy (info); + g_hash_table_unref (info); } static gboolean @@ -1059,7 +1059,7 @@ gabble_search_channel_finalize (GObject *obj) g_free (priv->server); tp_handle_set_destroy (priv->result_handles); - g_hash_table_destroy (chan->priv->tp_to_xmpp); + g_hash_table_unref (chan->priv->tp_to_xmpp); g_free (chan->priv->available_search_keys); @@ -1067,9 +1067,9 @@ gabble_search_channel_finalize (GObject *obj) { g_free (g_ptr_array_index (priv->boolean_keys, i)); } - g_ptr_array_free (priv->boolean_keys, TRUE); + g_ptr_array_unref (priv->boolean_keys); - g_hash_table_destroy (chan->priv->results); + g_hash_table_unref (chan->priv->results); if (G_OBJECT_CLASS (gabble_search_channel_parent_class)->finalize) G_OBJECT_CLASS (gabble_search_channel_parent_class)->finalize (obj); diff --git a/src/search-manager.c b/src/search-manager.c index b1a173b3c..b96bd5e54 100644 --- a/src/search-manager.c +++ b/src/search-manager.c @@ -29,7 +29,7 @@ #define DEBUG_FLAG GABBLE_DEBUG_SEARCH -#include "caps-channel-manager.h" +#include "gabble/caps-channel-manager.h" #include "connection.h" #include "debug.h" #include "disco.h" @@ -230,7 +230,7 @@ gabble_search_manager_finalize (GObject *object) /* close_all removed all the channels from the hash table */ g_assert_cmpuint (g_hash_table_size (priv->channels), ==, 0); - g_hash_table_destroy (priv->channels); + g_hash_table_unref (priv->channels); if (G_OBJECT_CLASS (gabble_search_manager_parent_class)->finalize) G_OBJECT_CLASS (gabble_search_manager_parent_class)->finalize (object); @@ -341,7 +341,7 @@ gabble_search_manager_type_foreach_channel_class (GType type, func (type, table, search_channel_allowed_properties, user_data); - g_hash_table_destroy (table); + g_hash_table_unref (table); } static void diff --git a/src/server-tls-channel.c b/src/server-tls-channel.c index 2d23bf823..e6f8267fc 100644 --- a/src/server-tls-channel.c +++ b/src/server-tls-channel.c @@ -63,7 +63,7 @@ struct _GabbleServerTLSChannelPrivate { GabbleTLSCertificate *server_cert; gchar *server_cert_path; gchar *hostname; - GPtrArray *reference_identities; + GStrv reference_identities; gboolean dispose_has_run; }; @@ -85,7 +85,7 @@ gabble_server_tls_channel_get_property (GObject *object, g_value_set_string (value, self->priv->hostname); break; case PROP_REFERENCE_IDENTITIES: - g_value_set_boxed (value, self->priv->reference_identities->pdata); + g_value_set_boxed (value, self->priv->reference_identities); break; case PROP_TLS_SESSION: g_value_set_object (value, self->priv->tls_session); @@ -112,6 +112,9 @@ gabble_server_tls_channel_set_property (GObject *object, case PROP_HOSTNAME: self->priv->hostname = g_value_dup_string (value); break; + case PROP_REFERENCE_IDENTITIES: + self->priv->reference_identities = g_value_dup_boxed (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -127,7 +130,7 @@ gabble_server_tls_channel_finalize (GObject *object) g_free (self->priv->server_cert_path); g_free (self->priv->hostname); - g_ptr_array_free (self->priv->reference_identities, TRUE); + g_strfreev (self->priv->reference_identities); G_OBJECT_CLASS (gabble_server_tls_channel_parent_class)->finalize (object); } @@ -182,10 +185,6 @@ gabble_server_tls_channel_constructed (GObject *object) const gchar *path; gchar *cert_object_path; GPtrArray *certificates; - gchar *connect_server = NULL; - gchar *explicit_server = NULL; - gchar **extra_certificate_identities = NULL; - gint i; if (chain_up != NULL) chain_up (object); @@ -206,44 +205,6 @@ gabble_server_tls_channel_constructed (GObject *object) NULL); self->priv->server_cert_path = cert_object_path; - /* Build up the identities we can check against */ - self->priv->reference_identities = g_ptr_array_new_with_free_func (g_free); - g_object_get (tp_base_channel_get_connection (TP_BASE_CHANNEL (self)), - "connect-server", &connect_server, - "explicit-server", &explicit_server, - "extra-certificate_identities", &extra_certificate_identities, - NULL); - - /* First the domain part of the JID, which we were initialied with */ - g_ptr_array_add (self->priv->reference_identities, - g_strdup (self->priv->hostname)); - - /* And secondly the an explicitly overridden server (if in use) */ - if (!tp_str_empty (explicit_server) && - !tp_strdiff (connect_server, explicit_server)) - { - g_ptr_array_add (self->priv->reference_identities, - g_strdup (explicit_server)); - } - - /* Lastly extra identities added to the account as a result of user choices */ - if (extra_certificate_identities != NULL) - { - for (i = 0; extra_certificate_identities[i] != NULL; ++i) - { - if (!tp_str_empty (extra_certificate_identities[i])) - g_ptr_array_add (self->priv->reference_identities, - g_strdup (extra_certificate_identities[i])); - } - } - - /* Null terminate, since this is a gchar** */ - g_ptr_array_add (self->priv->reference_identities, NULL); - - g_free (explicit_server); - g_free (connect_server); - g_strfreev (extra_certificate_identities); - DEBUG ("Server TLS channel constructed at %s", path); } @@ -323,7 +284,7 @@ gabble_server_tls_channel_class_init (GabbleServerTLSChannelClass *klass) "The various identities to check the certificate against", "The server certificate identity should match one of these identities.", G_TYPE_STRV, - G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); g_object_class_install_property (oclass, PROP_REFERENCE_IDENTITIES, pspec); pspec = g_param_spec_object ("tls-session", "The WockyTLSSession", diff --git a/src/server-tls-manager.c b/src/server-tls-manager.c index a5cef2295..a82814281 100644 --- a/src/server-tls-manager.c +++ b/src/server-tls-manager.c @@ -25,7 +25,7 @@ #define DEBUG_FLAG GABBLE_DEBUG_TLS #include "debug.h" -#include "caps-channel-manager.h" +#include "gabble/caps-channel-manager.h" #include "connection.h" #include "server-tls-channel.h" #include "util.h" @@ -54,6 +54,7 @@ struct _GabbleServerTLSManagerPrivate { GabbleServerTLSChannel *channel; gchar *peername; + GStrv reference_identities; WockyTLSSession *tls_session; GSimpleAsyncResult *async_result; @@ -146,8 +147,8 @@ server_tls_channel_closed_cb (GabbleServerTLSChannel *channel, WOCKY_TLS_HANDLER_CLASS (gabble_server_tls_manager_parent_class)->verify_async_func ( WOCKY_TLS_HANDLER (self), self->priv->tls_session, - self->priv->peername, self->priv->async_callback, - self->priv->async_data); + self->priv->peername, self->priv->reference_identities, + self->priv->async_callback, self->priv->async_data); } tp_clear_object (&self->priv->async_result); @@ -205,9 +206,73 @@ tls_certificate_rejected_cb (GabbleTLSCertificate *certificate, } static void +extend_string_ptr_array (GPtrArray *array, + GStrv new_elements) +{ + gint i; + + if (new_elements != NULL) + { + for (i = 0; new_elements[i] != NULL; i++) + { + if (!tp_str_empty (new_elements[i])) + g_ptr_array_add (array, g_strdup (new_elements[i])); + } + } +} + +static void +fill_reference_identities (GabbleServerTLSManager *self, + const gchar *peername, + GStrv original_extra_identities) +{ + GPtrArray *identities; + gchar *connect_server = NULL; + gchar *explicit_server = NULL; + GStrv extra_certificate_identities = NULL; + + g_return_if_fail (self->priv->reference_identities == NULL); + + g_object_get (self->priv->connection, + "connect-server", &connect_server, + "explicit-server", &explicit_server, + "extra-certificate-identities", &extra_certificate_identities, + NULL); + + identities = g_ptr_array_new (); + + /* The peer name, i.e, the domain part of the JID */ + g_ptr_array_add (identities, g_strdup (peername)); + + /* The extra identities that the caller of verify_async() passed */ + extend_string_ptr_array (identities, original_extra_identities); + + /* The explicitly overridden server (if in use) */ + if (!tp_str_empty (explicit_server) && + !tp_strdiff (connect_server, explicit_server)) + { + g_ptr_array_add (identities, g_strdup (explicit_server)); + } + + /* Extra identities added to the account as a result of user choices */ + extend_string_ptr_array (identities, extra_certificate_identities); + + /* Null terminate, since this is a gchar** */ + g_ptr_array_add (identities, NULL); + + self->priv->reference_identities = (GStrv) g_ptr_array_free (identities, + FALSE); + + g_strfreev (extra_certificate_identities); + g_free (explicit_server); + g_free (connect_server); +} + +static void gabble_server_tls_manager_verify_async (WockyTLSHandler *handler, WockyTLSSession *tls_session, const gchar *peername, + GStrv extra_identities, GAsyncReadyCallback callback, gpointer user_data) { @@ -222,19 +287,6 @@ gabble_server_tls_manager_verify_async (WockyTLSHandler *handler, self->priv->verify_async_called = TRUE; - if (!self->priv->interactive_tls) - { - DEBUG ("ignore-ssl-errors is set, fallback to non-interactive " - "verification."); - - WOCKY_TLS_HANDLER_CLASS - (gabble_server_tls_manager_parent_class)->verify_async_func ( - WOCKY_TLS_HANDLER (self), tls_session, peername, - callback, user_data); - - return; - } - result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, gabble_server_tls_manager_verify_async); @@ -248,6 +300,23 @@ gabble_server_tls_manager_verify_async (WockyTLSHandler *handler, return; } + fill_reference_identities (self, peername, extra_identities); + + if (!self->priv->interactive_tls) + { + DEBUG ("ignore-ssl-errors is set, fallback to non-interactive " + "verification."); + + g_object_unref (result); + + WOCKY_TLS_HANDLER_CLASS + (gabble_server_tls_manager_parent_class)->verify_async_func ( + WOCKY_TLS_HANDLER (self), tls_session, peername, + self->priv->reference_identities, callback, user_data); + + return; + } + self->priv->async_result = result; self->priv->tls_session = g_object_ref (tls_session); self->priv->peername = g_strdup (peername); @@ -258,6 +327,7 @@ gabble_server_tls_manager_verify_async (WockyTLSHandler *handler, "connection", self->priv->connection, "tls-session", tls_session, "hostname", peername, + "reference-identities", self->priv->reference_identities, NULL); g_signal_connect (self->priv->channel, "closed", @@ -332,6 +402,7 @@ gabble_server_tls_manager_finalize (GObject *object) tp_base_channel_close (TP_BASE_CHANNEL (self->priv->channel)); g_free (self->priv->peername); + g_strfreev (self->priv->reference_identities); G_OBJECT_CLASS (gabble_server_tls_manager_parent_class)->finalize (object); } @@ -390,13 +461,9 @@ gabble_server_tls_manager_foreach_channel (TpChannelManager *manager, { GabbleServerTLSManager *self = GABBLE_SERVER_TLS_MANAGER (manager); - DEBUG ("Foreach channel"); - - if (self->priv->channel == NULL) - return; - /* there's only one channel of this kind */ - func (TP_EXPORTABLE_CHANNEL (self->priv->channel), user_data); + if (self->priv->channel != NULL) + func (TP_EXPORTABLE_CHANNEL (self->priv->channel), user_data); } static void diff --git a/src/tls-certificate.c b/src/tls-certificate.c index 86e40d58d..6d5748568 100644 --- a/src/tls-certificate.c +++ b/src/tls-certificate.c @@ -134,7 +134,7 @@ gabble_tls_certificate_finalize (GObject *object) g_free (self->priv->object_path); g_free (self->priv->cert_type); - g_ptr_array_free (self->priv->cert_data, TRUE); + g_ptr_array_unref (self->priv->cert_data); G_OBJECT_CLASS (gabble_tls_certificate_parent_class)->finalize (object); } diff --git a/src/tube-dbus.c b/src/tube-dbus.c index 173f36a4d..7622b076c 100644 --- a/src/tube-dbus.c +++ b/src/tube-dbus.c @@ -27,7 +27,7 @@ #include <dbus/dbus-glib.h> #include <dbus/dbus-glib-lowlevel.h> #include <loudmouth/loudmouth.h> - +#include <wocky/wocky-utils.h> #include <telepathy-glib/channel-iface.h> #include <telepathy-glib/dbus.h> #include <telepathy-glib/exportable-channel.h> @@ -591,8 +591,8 @@ gabble_tube_dbus_dispose (GObject *object) contact_repo); } - tp_clear_pointer (&priv->dbus_names, g_hash_table_destroy); - tp_clear_pointer (&priv->dbus_name_to_handle, g_hash_table_destroy); + tp_clear_pointer (&priv->dbus_names, g_hash_table_unref); + tp_clear_pointer (&priv->dbus_name_to_handle, g_hash_table_unref); if (priv->reassembly_buffer) g_string_free (priv->reassembly_buffer, TRUE); @@ -612,8 +612,8 @@ gabble_tube_dbus_finalize (GObject *object) g_free (priv->object_path); g_free (priv->stream_id); g_free (priv->service); - g_hash_table_destroy (priv->parameters); - g_array_free (priv->supported_access_controls, TRUE); + g_hash_table_unref (priv->parameters); + g_array_unref (priv->supported_access_controls); if (priv->muc != NULL) { @@ -841,7 +841,7 @@ gabble_tube_dbus_set_property (GObject *object, break; case PROP_PARAMETERS: if (priv->parameters != NULL) - g_hash_table_destroy (priv->parameters); + g_hash_table_unref (priv->parameters); priv->parameters = g_value_dup_boxed (value); break; case PROP_MUC: @@ -904,7 +904,7 @@ gabble_tube_dbus_constructor (GType type, priv->dbus_name_to_handle = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL); - g_assert (gabble_decode_jid ( + g_assert (wocky_decode_jid ( tp_handle_inspect (contact_repo, priv->self_handle), NULL, NULL, &nick)); g_assert (nick != NULL); @@ -1681,7 +1681,7 @@ gabble_tube_dbus_add_name (GabbleTubeDBus *self, const gchar *jid; jid = tp_handle_inspect (contact_repo, handle); - g_assert (gabble_decode_jid (jid, NULL, NULL, &nick)); + g_assert (wocky_decode_jid (jid, NULL, NULL, &nick)); supposed_name = _gabble_generate_dbus_unique_name (nick); g_free (nick); @@ -1712,8 +1712,8 @@ gabble_tube_dbus_add_name (GabbleTubeDBus *self, tp_svc_channel_type_dbus_tube_emit_dbus_names_changed (self, added, removed); - g_hash_table_destroy (added); - g_array_free (removed, TRUE); + g_hash_table_unref (added); + g_array_unref (removed); return TRUE; } @@ -1750,8 +1750,8 @@ gabble_tube_dbus_remove_name (GabbleTubeDBus *self, tp_svc_channel_type_dbus_tube_emit_dbus_names_changed (self, added, removed); - g_hash_table_destroy (added); - g_array_free (removed, TRUE); + g_hash_table_unref (added); + g_array_unref (removed); tp_handle_unref (contact_repo, handle); return TRUE; } diff --git a/src/tube-stream.c b/src/tube-stream.c index 94c4d6fa9..07ff7a5f2 100644 --- a/src/tube-stream.c +++ b/src/tube-stream.c @@ -1035,7 +1035,7 @@ tube_stream_open (GabbleTubeStream *self, priv->address = tp_g_value_slice_new (DBUS_TYPE_G_UCHAR_ARRAY); g_value_set_boxed (priv->address, array); - g_array_free (array, TRUE); + g_array_unref (array); ret = gibber_listener_listen_socket (priv->local_listener, path, FALSE, error); @@ -1207,9 +1207,9 @@ gabble_tube_stream_dispose (GObject *object) g_string_free (path, TRUE); } - tp_clear_pointer (&priv->transport_to_bytestream, g_hash_table_destroy); - tp_clear_pointer (&priv->bytestream_to_transport, g_hash_table_destroy); - tp_clear_pointer (&priv->transport_to_id, g_hash_table_destroy); + tp_clear_pointer (&priv->transport_to_bytestream, g_hash_table_unref); + tp_clear_pointer (&priv->bytestream_to_transport, g_hash_table_unref); + tp_clear_pointer (&priv->transport_to_id, g_hash_table_unref); tp_handle_unref (contact_repo, priv->initiator); @@ -1234,7 +1234,7 @@ gabble_tube_stream_finalize (GObject *object) g_free (priv->object_path); g_free (priv->service); - g_hash_table_destroy (priv->parameters); + g_hash_table_unref (priv->parameters); if (priv->address != NULL) { @@ -1446,7 +1446,7 @@ gabble_tube_stream_set_property (GObject *object, break; case PROP_PARAMETERS: if (priv->parameters != NULL) - g_hash_table_destroy (priv->parameters); + g_hash_table_unref (priv->parameters); priv->parameters = g_value_dup_boxed (value); break; case PROP_ADDRESS_TYPE: @@ -2327,7 +2327,7 @@ static void destroy_socket_control_list (gpointer data) { GArray *tab = data; - g_array_free (tab, TRUE); + g_array_unref (tab); } /** diff --git a/src/tubes-channel.c b/src/tubes-channel.c index a4fa88d6b..ac7011649 100644 --- a/src/tubes-channel.c +++ b/src/tubes-channel.c @@ -353,8 +353,8 @@ d_bus_names_changed_added (GabbleTubesChannel *self, for (i = 0; i < added->len; i++) g_boxed_free (TP_STRUCT_TYPE_DBUS_TUBE_MEMBER, added->pdata[i]); - g_ptr_array_free (added, TRUE); - g_array_free (removed, TRUE); + g_ptr_array_unref (added); + g_array_unref (removed); } static void @@ -377,8 +377,8 @@ d_bus_names_changed_removed (GabbleTubesChannel *self, tp_svc_channel_type_tubes_emit_d_bus_names_changed (self, tube_id, added, removed); - g_ptr_array_free (added, TRUE); - g_array_free (removed, TRUE); + g_ptr_array_unref (added); + g_array_unref (removed); } static void @@ -532,7 +532,7 @@ tube_offered_cb (GabbleTubeIface *tube, update_tubes_presence (self); g_free (service); - g_hash_table_destroy (parameters); + g_hash_table_unref (parameters); } static GabbleTubeIface * @@ -781,10 +781,8 @@ contact_left_muc (GabbleTubesChannel *self, TpHandle contact) { GabbleTubesChannelPrivate *priv = self->priv; -#ifdef ENABLE_DEBUG TpHandleRepoIface *contact_repo = tp_base_connection_get_handles ( (TpBaseConnection *) priv->conn, TP_HANDLE_TYPE_CONTACT); -#endif GHashTable *old_dbus_tubes; struct _add_in_old_dbus_tubes_data add_data; struct _emit_d_bus_names_changed_foreach_data emit_data; @@ -805,7 +803,7 @@ contact_left_muc (GabbleTubesChannel *self, g_hash_table_foreach (old_dbus_tubes, emit_d_bus_names_changed_foreach, &emit_data); - g_hash_table_destroy (old_dbus_tubes); + g_hash_table_unref (old_dbus_tubes); } /* Called when we receive a presence from a contact who is @@ -916,7 +914,7 @@ gabble_tubes_channel_presence_updated (GabbleTubesChannel *self, /* the tube has reffed its initiator, no need to keep a ref */ tp_handle_unref (contact_repo, initiator_handle); - g_hash_table_destroy (parameters); + g_hash_table_unref (parameters); } } else @@ -963,7 +961,7 @@ gabble_tubes_channel_presence_updated (GabbleTubesChannel *self, g_hash_table_foreach (old_dbus_tubes, emit_d_bus_names_changed_foreach, &emit_data); - g_hash_table_destroy (old_dbus_tubes); + g_hash_table_unref (old_dbus_tubes); } static void @@ -1053,7 +1051,7 @@ gabble_tubes_channel_get_available_tube_types (TpSvcChannelTypeTubes *iface, tp_svc_channel_type_tubes_return_from_get_available_tube_types (context, ret); - g_array_free (ret, TRUE); + g_array_unref (ret); } /** @@ -1081,7 +1079,7 @@ gabble_tubes_channel_list_tubes (TpSvcChannelTypeTubes *iface, for (i = 0; i < ret->len; i++) g_boxed_free (TP_STRUCT_TYPE_TUBE_INFO, ret->pdata[i]); - g_ptr_array_free (ret, TRUE); + g_ptr_array_unref (ret); } struct _i_hate_g_hash_table_foreach @@ -1216,7 +1214,7 @@ gabble_tubes_channel_tube_si_offered (GabbleTubesChannel *self, NODE_DEBUG (tube_node, e.message); gabble_bytestream_iface_close (bytestream, &e); - g_hash_table_destroy (parameters); + g_hash_table_unref (parameters); return; } @@ -1237,7 +1235,7 @@ gabble_tubes_channel_tube_si_offered (GabbleTubesChannel *self, tp_channel_manager_emit_new_channel (priv->conn->private_tubes_factory, TP_EXPORTABLE_CHANNEL (tube), NULL); - g_hash_table_destroy (parameters); + g_hash_table_unref (parameters); } /* Called when we receive a SI request, @@ -1415,7 +1413,7 @@ tube_msg_offered (GabbleTubesChannel *self, tp_channel_manager_emit_new_channel (priv->conn->private_tubes_factory, TP_EXPORTABLE_CHANNEL (tube), NULL); - g_hash_table_destroy (parameters); + g_hash_table_unref (parameters); } static void @@ -1550,7 +1548,7 @@ GabbleTubeIface *gabble_tubes_channel_tube_request (GabbleTubesChannel *self, tube = create_new_tube (self, type, priv->self_handle, service, parameters, stream_id, tube_id, NULL, TRUE); g_free (stream_id); - g_hash_table_destroy (parameters); + g_hash_table_unref (parameters); return tube; } @@ -2020,7 +2018,7 @@ gabble_tubes_channel_get_d_bus_names (TpSvcChannelTypeTubes *iface, for (i = 0; i < ret->len; i++) g_boxed_free (TP_STRUCT_TYPE_DBUS_TUBE_MEMBER, ret->pdata[i]); g_hash_table_unref (names); - g_ptr_array_free (ret, TRUE); + g_ptr_array_unref (ret); } /** @@ -2100,7 +2098,7 @@ gabble_tubes_channel_get_available_stream_tube_types (TpSvcChannelTypeTubes *ifa tp_svc_channel_type_tubes_return_from_get_available_stream_tube_types ( context, ret); - g_hash_table_destroy (ret); + g_hash_table_unref (ret); } static void @@ -2133,7 +2131,7 @@ gabble_tubes_channel_close (GabbleTubesChannel *self) priv->closed = TRUE; g_hash_table_foreach (priv->tubes, emit_tube_closed_signal, self); - g_hash_table_destroy (priv->tubes); + g_hash_table_unref (priv->tubes); priv->tubes = NULL; diff --git a/src/util.c b/src/util.c index 891509cee..dc107e0ff 100644 --- a/src/util.c +++ b/src/util.c @@ -349,35 +349,6 @@ lm_message_build_with_sub_type (const gchar *to, LmMessageType type, } /** - * gabble_decode_jid - * - * Parses a JID which may be one of the following forms: - * - * domain - * domain/resource - * node@domain - * node@domain/resource - * - * If the JID is valid, returns TRUE and sets the caller's - * node/domain/resource pointers if they are not NULL. The node and resource - * pointers will be set to NULL if the respective part is not present in the - * JID. The node and domain are lower-cased because the Jabber protocol treats - * them case-insensitively. - * - * XXX: Do nodeprep/resourceprep and length checking. - * - * See RFC 3920 §3. - */ -gboolean -gabble_decode_jid (const gchar *jid, - gchar **node, - gchar **domain, - gchar **resource) -{ - return wocky_decode_jid (jid, node, domain, resource); -} - -/** * gabble_get_room_handle_from_jid: * @room_repo: The %TP_HANDLE_TYPE_ROOM handle repository * @jid: A JID @@ -442,7 +413,7 @@ gabble_normalize_room (TpHandleRepoIface *repo, qualified_name = g_strdup (jid); } - if (!gabble_decode_jid (qualified_name, NULL, NULL, &resource)) + if (!wocky_decode_jid (qualified_name, NULL, NULL, &resource)) { INVALID_HANDLE (error, "room JID %s is invalid", qualified_name); return NULL; @@ -525,7 +496,7 @@ gabble_normalize_contact (TpHandleRepoIface *repo, gchar *username = NULL, *server = NULL, *resource = NULL; gchar *ret = NULL; - if (!gabble_decode_jid (jid, &username, &server, &resource) || !username) + if (!wocky_decode_jid (jid, &username, &server, &resource) || !username) { INVALID_HANDLE (error, "JID %s is invalid or has no node part", jid); diff --git a/src/util.h b/src/util.h index 1ab39657f..b5896cfc2 100644 --- a/src/util.h +++ b/src/util.h @@ -35,8 +35,6 @@ #include "types.h" -#define CHECK_STR_EMPTY(x) ((x) == NULL || (x)[0] == '\0') - typedef GSList * NodeIter; #define node_iter(node) (node->children) #define node_iter_next(i) (g_slist_next (i)) @@ -62,7 +60,6 @@ G_GNUC_NULL_TERMINATED LmMessage * lm_message_build_with_sub_type ( guint spec, ...); G_GNUC_WARN_UNUSED_RESULT -gboolean gabble_decode_jid (const gchar *jid, gchar **a, gchar **b, gchar **c); gchar *gabble_encode_jid (const gchar *node, const gchar *domain, const gchar *resource); diff --git a/src/vcard-manager.c b/src/vcard-manager.c index b78802354..25297fe3b 100644 --- a/src/vcard-manager.c +++ b/src/vcard-manager.c @@ -627,7 +627,7 @@ gabble_vcard_manager_dispose (GObject *object) g_hash_table_foreach (priv->cache, disconnect_entry_foreach, NULL); tp_heap_destroy (priv->timed_cache); - g_hash_table_destroy (priv->cache); + g_hash_table_unref (priv->cache); if (priv->edit_pipeline_item) gabble_request_pipeline_item_cancel (priv->edit_pipeline_item); @@ -884,7 +884,7 @@ observe_vcard (GabbleConnection *conn, { const gchar *fn = fn_node->content; - if (!CHECK_STR_EMPTY(fn)) + if (!tp_str_empty (fn)) { field = "<FN>"; alias = g_strdup (fn); diff --git a/src/write-mgr-file.c b/src/write-mgr-file.c index e37b3cea4..b097316ba 100644 --- a/src/write-mgr-file.c +++ b/src/write-mgr-file.c @@ -27,6 +27,7 @@ #include <dbus/dbus-protocol.h> #include <telepathy-glib/telepathy-glib.h> +#include "extensions/extensions.h" #include "protocol.h" @@ -293,19 +294,33 @@ mgr_file_contents (const char *busname, while (protocols != NULL) { - TpBaseProtocol *protocol = protocols->data; - GHashTable *props = - tp_base_protocol_get_immutable_properties (protocol); - gchar *section_name = g_strdup_printf ("Protocol %s", - tp_base_protocol_get_name (protocol)); - const gchar * const *ifaces = tp_asv_get_strv (props, - TP_PROP_PROTOCOL_INTERFACES); - const gchar * const *c_ifaces = tp_asv_get_strv (props, + GabbleJabberProtocol *protocol = protocols->data; + gchar *section_name; + GHashTable *props; + const gchar * const *ifaces; + const gchar * const *c_ifaces; + const gchar * const *addr_vcard_fields; + const gchar * const *addr_uri_schemes; + const gchar * const *auth_types; + + g_object_get (G_OBJECT (protocol), + "immutable-properties", &props, + NULL); + + section_name = g_strdup_printf ("Protocol %s", + tp_base_protocol_get_name (TP_BASE_PROTOCOL (protocol))); + + ifaces = tp_asv_get_strv (props, TP_PROP_PROTOCOL_INTERFACES); + c_ifaces = tp_asv_get_strv (props, TP_PROP_PROTOCOL_CONNECTION_INTERFACES); - const gchar * const *auth_types = tp_asv_get_strv (props, + auth_types = tp_asv_get_strv (props, TP_PROP_PROTOCOL_AUTHENTICATION_TYPES); + addr_vcard_fields = tp_asv_get_strv (props, + TP_PROP_PROTOCOL_INTERFACE_ADDRESSING_ADDRESSABLE_VCARD_FIELDS); + addr_uri_schemes = tp_asv_get_strv (props, + TP_PROP_PROTOCOL_INTERFACE_ADDRESSING_ADDRESSABLE_URI_SCHEMES); - write_parameters (f, section_name, protocol); + write_parameters (f, section_name, TP_BASE_PROTOCOL (protocol)); write_rccs (f, section_name, props); g_key_file_set_string_list (f, section_name, "Interfaces", @@ -314,13 +329,17 @@ mgr_file_contents (const char *busname, c_ifaces, g_strv_length ((gchar **) c_ifaces)); g_key_file_set_string_list (f, section_name, "AuthenticationTypes", auth_types, g_strv_length ((gchar **) auth_types)); + g_key_file_set_string_list (f, section_name, "AddressableVCardFields", + addr_vcard_fields, g_strv_length ((gchar **) addr_vcard_fields)); + g_key_file_set_string_list (f, section_name, "AddressableURISchemes", + addr_uri_schemes, g_strv_length ((gchar **) addr_uri_schemes)); WRITE_STR (TP_PROP_PROTOCOL_VCARD_FIELD, "VCardField"); WRITE_STR (TP_PROP_PROTOCOL_ENGLISH_NAME, "EnglishName"); WRITE_STR (TP_PROP_PROTOCOL_ICON, "Icon"); g_free (section_name); - g_hash_table_destroy (props); + g_hash_table_unref (props); protocols = protocols->next; } |