summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMikhail Zabaluev <mikhail.zabaluev@nokia.com>2011-01-03 17:45:15 +0200
committerMikhail Zabaluev <mikhail.zabaluev@nokia.com>2011-01-03 17:45:15 +0200
commit248b7a0c9b0efac2653c3e2659cbec1eda01d238 (patch)
tree4a272fbf3aa1ccc8d0c3cf496b0e1ca994692911
parentc89168ecb97280c6e1f732c659c800af832bd7ab (diff)
parent6ef183a45800b000fe1c402255e822963f90dc0c (diff)
Merge branch 'master' into messages-retouch
-rw-r--r--Makefile.am9
-rw-r--r--NEWS12
-rw-r--r--configure.ac6
-rw-r--r--src/Makefile.am2
-rw-r--r--src/media-factory.c11
-rw-r--r--src/protocol.c527
-rw-r--r--src/protocol.h75
-rw-r--r--src/sip-connection-helpers.c28
-rw-r--r--src/sip-connection-helpers.h5
-rw-r--r--src/sip-connection-manager.c428
-rw-r--r--src/sip-connection-private.h3
-rw-r--r--src/sip-connection.c181
-rw-r--r--src/sip-connection.h2
-rw-r--r--src/sip-media-channel.c8
-rw-r--r--src/sip-media-session.c28
-rw-r--r--src/sip-media-stream.c21
-rw-r--r--src/sip-text-channel.c6
-rw-r--r--src/text-factory.c9
-rw-r--r--src/write-mgr-file.c374
-rw-r--r--tests/twisted/Makefile.am3
-rw-r--r--tests/twisted/cm/protocol.py58
-rw-r--r--tests/twisted/constants.py400
-rw-r--r--tests/twisted/servicetest.py560
-rw-r--r--tests/twisted/sofiatest.py12
-rw-r--r--tests/twisted/test-handle-normalisation.py39
-rw-r--r--tests/twisted/test-message.py6
-rw-r--r--tests/twisted/test-register-sasl.py36
-rw-r--r--tests/twisted/test-register.py2
-rw-r--r--tools/Makefile.am3
-rw-r--r--tools/make-release-mail.py76
-rw-r--r--tools/make-version-script.py208
-rw-r--r--tools/telepathy.am65
32 files changed, 2421 insertions, 782 deletions
diff --git a/Makefile.am b/Makefile.am
index 242d97d..aab3064 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -24,14 +24,9 @@ SUBDIRS = \
src \
tests
-dist-hook:
- chmod u+w ${distdir}/ChangeLog
- if test -d ${top_srcdir}/.git; then \
- git --work-tree=${top_srcdir} log --stat > ${distdir}/ChangeLog || \
- git --work-tree=${top_srcdir} log > ${distdir}/ChangeLog; \
- fi
-
include tools/lcov.am
LCOV_CHECK_ARGS = CHECK_TWISTED_SLEEP=6
clean-local: lcov-clean
+
+include tools/telepathy.am
diff --git a/NEWS b/NEWS
index cb24089..8f9f75a 100644
--- a/NEWS
+++ b/NEWS
@@ -7,6 +7,18 @@ recent at the top).
See also ChangeLog.
+telepathy-sofiasip 0.7.0 (2010-11-23)
+-------------------------------------
+
+The "Fermentation Vat No. 78" release.
+
+- Opening the new development series.
+- Implemented o.fd.Tp.Ch.I.Messages (fd.o #29377).
+- Implemented o.fd.Tp.Protocol (fd.o #30538).
+- Uses TpDBusDaemon to export media streams (fd.o #31720).
+- Depends on telepathy-glib 0.12.0 now.
+- Improvements in session state and signalling.
+
Changes in release 0.6.3 (2010-06-21)
-------------------------------------
diff --git a/configure.ac b/configure.ac
index a08d34b..a3a5fb8 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,8 +2,8 @@ AC_PREREQ([2.59])
m4_define([THIS_PACKAGE],[telepathy-sofiasip])
m4_define([VERSION_MAJOR],[0])
-m4_define([VERSION_MINOR],[6])
-m4_define([VERSION_MICRO],[99])
+m4_define([VERSION_MINOR],[7])
+m4_define([VERSION_MICRO],[0])
m4_define([VERSION_NANO],[1])
m4_define([BASE_VERSION],[VERSION_MAJOR.VERSION_MINOR.VERSION_MICRO])
@@ -85,7 +85,7 @@ AC_SUBST(SOFIA_SIP_UA_CFLAGS)
AC_SUBST(SOFIA_SIP_UA_VERSION)
dnl Check for telepathy-glib
-PKG_CHECK_MODULES(TELEPATHY_GLIB, [telepathy-glib >= 0.12])
+PKG_CHECK_MODULES(TELEPATHY_GLIB, [telepathy-glib >= 0.13.9])
AC_SUBST(TELEPATHY_GLIB_CFLAGS)
AC_SUBST(TELEPATHY_GLIB_LIBS)
diff --git a/src/Makefile.am b/src/Makefile.am
index 6684513..ca15a87 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -92,6 +92,8 @@ libtpsip_convenience_la_SOURCES = \
debug.c \
media-factory.h \
media-factory.c \
+ protocol.h \
+ protocol.c \
text-factory.h \
text-factory.c \
sip-connection-helpers.h \
diff --git a/src/media-factory.c b/src/media-factory.c
index 689d773..f4573c5 100644
--- a/src/media-factory.c
+++ b/src/media-factory.c
@@ -451,15 +451,15 @@ static const gchar * const named_channel_allowed_properties[] = {
NULL
};
-/* not advertised in foreach_channel_class - can only be requested with
+/* not advertised in type_foreach_channel_class - can only be requested with
* RequestChannel, not with CreateChannel/EnsureChannel */
static const gchar * const anon_channel_allowed_properties[] = {
NULL
};
static void
-tpsip_media_factory_foreach_channel_class (TpChannelManager *manager,
- TpChannelManagerChannelClassFunc func,
+tpsip_media_factory_type_foreach_channel_class (GType type,
+ TpChannelManagerTypeChannelClassFunc func,
gpointer user_data)
{
GHashTable *table = g_hash_table_new_full (g_str_hash, g_str_equal,
@@ -476,7 +476,7 @@ tpsip_media_factory_foreach_channel_class (TpChannelManager *manager,
handle_type_value);
g_value_set_uint (handle_type_value, TP_HANDLE_TYPE_CONTACT);
- func (manager, table, named_channel_allowed_properties, user_data);
+ func (type, table, named_channel_allowed_properties, user_data);
g_hash_table_destroy (table);
}
@@ -688,7 +688,8 @@ channel_manager_iface_init (gpointer g_iface,
TpChannelManagerIface *iface = g_iface;
iface->foreach_channel = tpsip_media_factory_foreach_channel;
- iface->foreach_channel_class = tpsip_media_factory_foreach_channel_class;
+ iface->type_foreach_channel_class =
+ tpsip_media_factory_type_foreach_channel_class;
iface->request_channel = tpsip_media_factory_request_channel;
iface->create_channel = tpsip_media_factory_create_channel;
iface->ensure_channel = tpsip_media_factory_ensure_channel;
diff --git a/src/protocol.c b/src/protocol.c
new file mode 100644
index 0000000..d9c2383
--- /dev/null
+++ b/src/protocol.c
@@ -0,0 +1,527 @@
+/*
+ * protocol.c - source for TpsipProtocol
+ * Copyright (C) 2007-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 "protocol.h"
+
+#include <string.h>
+
+#include <telepathy-glib/telepathy-glib.h>
+#include <dbus/dbus-protocol.h>
+#include <dbus/dbus-glib.h>
+
+#include <tpsip/sofia-decls.h>
+#include <sofia-sip/su_glib.h>
+
+#define DEBUG_FLAG TPSIP_DEBUG_CONNECTION
+#include "debug.h"
+#include "media-factory.h"
+#include "sip-connection.h"
+#include "sip-connection-helpers.h"
+#include "text-factory.h"
+
+#define PROTOCOL_NAME "sip"
+#define ICON_NAME "im-" PROTOCOL_NAME
+#define VCARD_FIELD_NAME "x-" PROTOCOL_NAME
+#define ENGLISH_NAME "SIP"
+
+G_DEFINE_TYPE (TpsipProtocol,
+ tpsip_protocol,
+ TP_TYPE_BASE_PROTOCOL)
+
+enum {
+ PROP_SOFIA_ROOT = 1,
+};
+
+struct _TpsipProtocolPrivate
+{
+ su_root_t *sofia_root;
+};
+
+/* Used in the otherwise-unused offset field of the TpCMParamSpec. The first
+ * one is nonzero to catch implicit zero-initialization. */
+enum {
+ PARAM_EASY = 1,
+ PARAM_SET_SEPARATELY
+};
+
+static TpCMParamSpec tpsip_params[] = {
+ /* Account (a sip: URI)
+ *
+ * FIXME: validate account SIP URI properly, using appropriate RFCs */
+ { "account", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING,
+ TP_CONN_MGR_PARAM_FLAG_REQUIRED | TP_CONN_MGR_PARAM_FLAG_REGISTER,
+ NULL, PARAM_SET_SEPARATELY, tp_cm_param_filter_string_nonempty, NULL },
+
+ /* Username to register with, if different than in the account URI */
+ { "auth-user", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING,
+ 0, NULL, PARAM_EASY },
+
+ /* Password */
+ { "password", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING,
+ TP_CONN_MGR_PARAM_FLAG_SECRET, NULL, PARAM_EASY },
+
+ /* Display name for self */
+ { "alias", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING, 0, NULL, PARAM_EASY,
+ /* setting a 0-length alias makes no sense */
+ tp_cm_param_filter_string_nonempty, NULL },
+
+ /* Registrar */
+ { "registrar", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING, 0, NULL,
+ PARAM_EASY },
+
+ /* Used to compose proxy URI */
+ { "proxy-host", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING, 0, NULL,
+ PARAM_SET_SEPARATELY },
+ { "port", DBUS_TYPE_UINT16_AS_STRING, G_TYPE_UINT,
+ TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, GUINT_TO_POINTER(SIP_DEFAULT_PORT),
+ PARAM_SET_SEPARATELY, tp_cm_param_filter_uint_nonzero },
+ { "transport", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING,
+ TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, "auto", PARAM_SET_SEPARATELY },
+
+ /* Enables loose routing as per RFC 3261 */
+ { "loose-routing", DBUS_TYPE_BOOLEAN_AS_STRING, G_TYPE_BOOLEAN,
+ TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, GUINT_TO_POINTER(FALSE),
+ PARAM_EASY },
+
+ /* Used to enable proactive NAT traversal techniques */
+ { "discover-binding", DBUS_TYPE_BOOLEAN_AS_STRING, G_TYPE_BOOLEAN,
+ TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, GUINT_TO_POINTER(TRUE),
+ PARAM_EASY },
+
+ /* Mechanism used for connection keepalive maintenance */
+ { "keepalive-mechanism", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING,
+ TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, "auto", PARAM_SET_SEPARATELY },
+
+ /* Keep-alive interval */
+ { "keepalive-interval", DBUS_TYPE_UINT32_AS_STRING, G_TYPE_UINT,
+ TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, GUINT_TO_POINTER(0), PARAM_EASY },
+
+ /* Use SRV DNS lookup to discover STUN server */
+ { "discover-stun", DBUS_TYPE_BOOLEAN_AS_STRING, G_TYPE_BOOLEAN,
+ TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, GUINT_TO_POINTER(TRUE), PARAM_EASY },
+
+ /* STUN server */
+ { "stun-server", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING, 0, NULL,
+ PARAM_EASY },
+
+ /* STUN port */
+ { "stun-port", DBUS_TYPE_UINT16_AS_STRING, G_TYPE_UINT,
+ TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT,
+ GUINT_TO_POINTER(TPSIP_DEFAULT_STUN_PORT), PARAM_EASY,
+ tp_cm_param_filter_uint_nonzero },
+
+ /* If the session content cannot be modified once initially set up */
+ { "immutable-streams", DBUS_TYPE_BOOLEAN_AS_STRING, G_TYPE_BOOLEAN,
+ TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, GUINT_TO_POINTER(FALSE),
+ PARAM_EASY },
+
+ /* Local IP address to use, workaround purposes only */
+ { "local-ip-address", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING,
+ 0, NULL, PARAM_EASY },
+
+ /* Local port for SIP, workaround purposes only */
+ { "local-port", DBUS_TYPE_UINT16_AS_STRING, G_TYPE_UINT, 0, NULL,
+ PARAM_EASY },
+
+ /* Extra-realm authentication */
+ { "extra-auth-user", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING,
+ 0, NULL, PARAM_EASY },
+ { "extra-auth-password", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING,
+ TP_CONN_MGR_PARAM_FLAG_SECRET, NULL, PARAM_EASY },
+
+ { NULL }
+};
+
+static void
+tpsip_protocol_init (TpsipProtocol *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, TPSIP_TYPE_PROTOCOL,
+ TpsipProtocolPrivate);
+}
+
+static const TpCMParamSpec *
+get_parameters (TpBaseProtocol *self G_GNUC_UNUSED)
+{
+ return tpsip_params;
+}
+
+static TpsipConnectionKeepaliveMechanism
+priv_parse_keepalive (const gchar *str)
+{
+ if (str == NULL || strcmp (str, "auto") == 0)
+ return TPSIP_CONNECTION_KEEPALIVE_AUTO;
+ if (strcmp (str, "register") == 0)
+ return TPSIP_CONNECTION_KEEPALIVE_REGISTER;
+ if (strcmp (str, "options") == 0)
+ return TPSIP_CONNECTION_KEEPALIVE_OPTIONS;
+ if (strcmp (str, "stun") == 0)
+ return TPSIP_CONNECTION_KEEPALIVE_STUN;
+ if (strcmp (str, "off") == 0)
+ return TPSIP_CONNECTION_KEEPALIVE_NONE;
+
+ WARNING ("unsupported keepalive-method value \"%s\", falling back to auto", str);
+ return TPSIP_CONNECTION_KEEPALIVE_AUTO;
+}
+
+static gchar *
+priv_compose_proxy_uri (const gchar *host,
+ const gchar *transport,
+ guint port)
+{
+ const gchar *scheme = "sip";
+
+ if (host == NULL)
+ return NULL;
+
+ /* Set scheme to SIPS if transport is TLS */
+
+ if (transport != NULL && !g_ascii_strcasecmp (transport, "tls"))
+ scheme = "sips";
+
+ /* Format the resulting URI */
+
+ if (port)
+ return g_strdup_printf ("%s:%s:%u", scheme, host, port);
+ else
+ return g_strdup_printf ("%s:%s", scheme, host);
+}
+
+/**
+ * Returns a default SIP proxy address based on the public
+ * SIP address 'sip_address' and . For instance
+ * "sip:first.surname@company.com" would result in "sip:company.com".
+ * The SIP stack will further utilize DNS lookups to find the IP address
+ * for the SIP server responsible for the domain "company.com".
+ */
+static gchar *
+priv_compose_default_proxy_uri (const gchar *sip_address,
+ const gchar *transport)
+{
+ char *result = NULL;
+ char *host;
+ char *found;
+
+ g_return_val_if_fail (sip_address != NULL, NULL);
+
+ /* skip sip and sips prefixes, updating transport if necessary */
+ found = strchr (sip_address, ':');
+ if (found != NULL) {
+ if (g_ascii_strncasecmp ("sip:", sip_address, 4) == 0)
+ ;
+ else if (g_ascii_strncasecmp ("sips:", sip_address, 5) == 0)
+ {
+ if (transport == NULL ||
+ strcmp (transport, "auto") == 0)
+ transport = "tls";
+ }
+ else
+ {
+ /* error, unknown URI prefix */
+ return NULL;
+ }
+
+ sip_address = found + 1;
+ }
+
+ /* skip userinfo */
+ found = strchr (sip_address, '@');
+ if (found != NULL)
+ sip_address = found + 1;
+
+ /* copy rest of the string */
+ host = g_strdup (sip_address);
+
+ /* mark end (before uri-parameters defs or headers) */
+ found = strchr (host, ';');
+ if (found == NULL)
+ found = strchr (host, '?');
+ if (found != NULL)
+ *found = '\0';
+
+ result = priv_compose_proxy_uri (host, transport, 0);
+
+ g_free (host);
+
+ return result;
+}
+
+static TpBaseConnection *
+new_connection (TpBaseProtocol *protocol,
+ GHashTable *params,
+ GError **error)
+{
+ TpsipProtocol *self = TPSIP_PROTOCOL (protocol);
+ TpsipConnection *conn;
+ guint i;
+ const gchar *account;
+ const gchar *transport;
+ const gchar *proxy_host;
+ guint16 port;
+ gchar *proxy;
+ TpsipConnectionKeepaliveMechanism keepalive_mechanism;
+
+ account = tp_asv_get_string (params, "account");
+ transport = tp_asv_get_string (params, "transport");
+ port = tp_asv_get_uint32 (params, "port", NULL);
+
+ conn = g_object_new (TPSIP_TYPE_CONNECTION,
+ "protocol", PROTOCOL_NAME,
+ "sofia-root", self->priv->sofia_root,
+ "address", account,
+ NULL);
+
+ proxy_host = tp_asv_get_string (params, "proxy-host");
+
+ if (tp_str_empty (proxy_host))
+ {
+ proxy = priv_compose_default_proxy_uri (account, transport);
+ DEBUG("set outbound proxy address to <%s>, based on <%s>", proxy,
+ account);
+ }
+ else
+ {
+ proxy = priv_compose_proxy_uri (proxy_host, transport, port);
+ }
+
+ g_object_set (conn,
+ "proxy", proxy,
+ NULL);
+ g_free (proxy);
+
+ if (!tp_str_empty (transport) && tp_strdiff (transport, "auto"))
+ g_object_set (conn,
+ "transport", transport,
+ NULL);
+
+ for (i = 0; tpsip_params[i].name != NULL; i++)
+ {
+ if (tpsip_params[i].offset == PARAM_SET_SEPARATELY)
+ {
+ DEBUG ("Parameter %s is handled specially", tpsip_params[i].name);
+ continue;
+ }
+
+ g_assert (tpsip_params[i].offset == PARAM_EASY);
+
+ switch (tpsip_params[i].gtype)
+ {
+ case G_TYPE_STRING:
+ {
+ const gchar *s = tp_asv_get_string (params,
+ tpsip_params[i].name);
+
+ if (!tp_str_empty (s))
+ g_object_set (conn,
+ tpsip_params[i].name, s,
+ NULL);
+ }
+ break;
+
+ case G_TYPE_UINT:
+ {
+ gboolean valid = FALSE;
+ guint u = tp_asv_get_uint32 (params,
+ tpsip_params[i].name, &valid);
+
+ if (valid)
+ g_object_set (conn,
+ tpsip_params[i].name, u,
+ NULL);
+ }
+ break;
+
+ case G_TYPE_BOOLEAN:
+ {
+ gboolean valid = FALSE;
+ gboolean b = tp_asv_get_boolean (params, tpsip_params[i].name,
+ &valid);
+
+ if (valid)
+ g_object_set (conn,
+ tpsip_params[i].name, b,
+ NULL);
+ }
+ break;
+
+ default:
+ /* no other parameters have been written yet */
+ g_assert_not_reached ();
+ }
+ }
+
+ keepalive_mechanism = priv_parse_keepalive (tp_asv_get_string (params,
+ "keepalive-mechanism"));
+ g_object_set (conn,
+ "keepalive-mechanism", keepalive_mechanism,
+ NULL);
+
+ return TP_BASE_CONNECTION (conn);
+}
+
+static gchar *
+normalize_contact (TpBaseProtocol *self G_GNUC_UNUSED,
+ const gchar *contact,
+ GError **error)
+{
+ return tpsip_normalize_contact (contact, NULL, NULL, error);
+}
+
+static gchar *
+identify_account (TpBaseProtocol *self G_GNUC_UNUSED,
+ GHashTable *asv,
+ GError **error)
+{
+ const gchar *account = tp_asv_get_string (asv, "account");
+
+ g_assert (account != NULL);
+ return g_strdup (account);
+}
+
+static GStrv
+get_interfaces (TpBaseProtocol *self)
+{
+ return g_new0 (gchar *, 1);
+}
+
+static void
+get_connection_details (TpBaseProtocol *self,
+ GStrv *connection_interfaces,
+ GType **channel_managers,
+ gchar **icon_name,
+ gchar **english_name,
+ gchar **vcard_field)
+{
+ if (connection_interfaces != NULL)
+ {
+ *connection_interfaces = g_strdupv (
+ (GStrv) tpsip_connection_get_implemented_interfaces ());
+ }
+
+ if (channel_managers != NULL)
+ {
+ GType types[] = {
+ TPSIP_TYPE_TEXT_FACTORY,
+ TPSIP_TYPE_MEDIA_FACTORY,
+ G_TYPE_INVALID };
+
+ *channel_managers = g_memdup (types, sizeof(types));
+ }
+
+ if (icon_name != NULL)
+ {
+ *icon_name = g_strdup (ICON_NAME);
+ }
+
+ if (vcard_field != NULL)
+ {
+ *vcard_field = g_strdup (VCARD_FIELD_NAME);
+ }
+
+ if (english_name != NULL)
+ {
+ *english_name = g_strdup (ENGLISH_NAME);
+ }
+}
+
+static GStrv
+dup_authentication_types (TpBaseProtocol *base)
+{
+ const gchar * const types[] = {
+ TP_IFACE_CHANNEL_INTERFACE_SASL_AUTHENTICATION,
+ NULL
+ };
+
+ return g_strdupv ((GStrv) types);
+}
+
+static void
+tpsip_protocol_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ TpsipProtocol *self = TPSIP_PROTOCOL (object);
+
+ switch (property_id)
+ {
+ case PROP_SOFIA_ROOT:
+ g_value_set_pointer (value, self->priv->sofia_root);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+tpsip_protocol_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ TpsipProtocol *self = TPSIP_PROTOCOL (object);
+
+ switch (property_id)
+ {
+ case PROP_SOFIA_ROOT:
+ self->priv->sofia_root = g_value_get_pointer (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+tpsip_protocol_class_init (TpsipProtocolClass *klass)
+{
+ TpBaseProtocolClass *base_class = (TpBaseProtocolClass *) klass;
+ GObjectClass *object_class = (GObjectClass *) klass;
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (klass, sizeof (TpsipProtocolPrivate));
+
+ base_class->get_parameters = get_parameters;
+ base_class->new_connection = new_connection;
+ base_class->normalize_contact = normalize_contact;
+ base_class->identify_account = identify_account;
+ base_class->get_interfaces = get_interfaces;
+ base_class->get_connection_details = get_connection_details;
+ base_class->dup_authentication_types = dup_authentication_types;
+
+ object_class->get_property = tpsip_protocol_get_property;
+ object_class->set_property = tpsip_protocol_set_property;
+
+ param_spec = g_param_spec_pointer ("sofia-root", "Sofia-SIP root",
+ "the root object for Sofia-SIP",
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_SOFIA_ROOT,
+ param_spec);
+}
+
+TpBaseProtocol *
+tpsip_protocol_new (su_root_t *sofia_root)
+{
+ return g_object_new (TPSIP_TYPE_PROTOCOL,
+ "name", PROTOCOL_NAME,
+ "sofia-root", sofia_root,
+ NULL);
+}
diff --git a/src/protocol.h b/src/protocol.h
new file mode 100644
index 0000000..7470af1
--- /dev/null
+++ b/src/protocol.h
@@ -0,0 +1,75 @@
+/*
+ * protocol.h - header for TpsipProtocol
+ * Copyright (C) 2007-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 TPSIP_PROTOCOL_H
+#define TPSIP_PROTOCOL_H
+
+#include <glib-object.h>
+#include <telepathy-glib/base-protocol.h>
+
+#include <tpsip/sofia-decls.h>
+#include <sofia-sip/su_glib.h>
+
+G_BEGIN_DECLS
+
+typedef struct _TpsipProtocol TpsipProtocol;
+typedef struct _TpsipProtocolPrivate TpsipProtocolPrivate;
+typedef struct _TpsipProtocolClass TpsipProtocolClass;
+typedef struct _TpsipProtocolClassPrivate TpsipProtocolClassPrivate;
+
+struct _TpsipProtocolClass {
+ TpBaseProtocolClass parent_class;
+
+ TpsipProtocolClassPrivate *priv;
+};
+
+struct _TpsipProtocol {
+ TpBaseProtocol parent;
+
+ TpsipProtocolPrivate *priv;
+};
+
+GType tpsip_protocol_get_type (void);
+
+#define TPSIP_TYPE_PROTOCOL \
+ (tpsip_protocol_get_type ())
+#define TPSIP_PROTOCOL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ TPSIP_TYPE_PROTOCOL, \
+ TpsipProtocol))
+#define TPSIP_PROTOCOL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ TPSIP_TYPE_PROTOCOL, \
+ TpsipProtocolClass))
+#define TPSIP_IS_PROTOCOL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+ TPSIP_TYPE_PROTOCOL))
+#define TPSIP_PROTOCOL_GET_CLASS(klass) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ TPSIP_TYPE_PROTOCOL, \
+ TpsipProtocolClass))
+
+gchar *tpsip_protocol_normalize_contact (const gchar *id,
+ GError **error);
+
+TpBaseProtocol *tpsip_protocol_new (su_root_t *sofia_root);
+
+G_END_DECLS
+
+#endif
diff --git a/src/sip-connection-helpers.c b/src/sip-connection-helpers.c
index 0fda819..8ad9b42 100644
--- a/src/sip-connection-helpers.c
+++ b/src/sip-connection-helpers.c
@@ -775,14 +775,11 @@ priv_lowercase_url_part (su_home_t *home, const char *src)
#define TPSIP_RESERVED_CHARS_ALLOWED_IN_USERNAME "!*'()&=+$,;?/"
gchar *
-tpsip_handle_normalize (TpHandleRepoIface *repo,
- const gchar *sipuri,
- gpointer context,
- GError **error)
+tpsip_normalize_contact (const gchar *sipuri,
+ const url_t *base_url,
+ const gchar *transport,
+ GError **error)
{
- TpsipConnection *conn = TPSIP_CONNECTION (context);
- TpsipConnectionPrivate *priv = TPSIP_CONNECTION_GET_PRIVATE (conn);
- const url_t *base_url = priv->account_url;
su_home_t home[1] = { SU_HOME_INIT(home) };
url_t *url;
gchar *retval = NULL;
@@ -827,8 +824,8 @@ tpsip_handle_normalize (TpHandleRepoIface *repo,
{
/* Set the scheme to SIP or SIPS accordingly to the connection's
* transport preference */
- if (priv->transport != NULL
- && g_ascii_strcasecmp (priv->transport, "tls") == 0)
+ if (transport != NULL
+ && g_ascii_strcasecmp (transport, "tls") == 0)
{
url->url_type = url_sips;
url->url_scheme = "sips";
@@ -879,6 +876,19 @@ error:
return retval;
}
+gchar *
+tpsip_handle_normalize (TpHandleRepoIface *repo,
+ const gchar *sipuri,
+ gpointer context,
+ GError **error)
+{
+ TpsipConnection *conn = TPSIP_CONNECTION (context);
+ TpsipConnectionPrivate *priv = TPSIP_CONNECTION_GET_PRIVATE (conn);
+
+ return tpsip_normalize_contact (sipuri, priv->account_url, priv->transport,
+ error);
+}
+
static GQuark
tpsip_handle_url_quark ()
{
diff --git a/src/sip-connection-helpers.h b/src/sip-connection-helpers.h
index 327c13c..4644908 100644
--- a/src/sip-connection-helpers.h
+++ b/src/sip-connection-helpers.h
@@ -66,6 +66,11 @@ gchar * tpsip_handle_normalize (TpHandleRepoIface *repo,
gpointer context,
GError **error);
+gchar *tpsip_normalize_contact (const gchar *sipuri,
+ const url_t *base_url,
+ const gchar *transport,
+ GError **error);
+
const url_t* tpsip_conn_get_contact_url (TpsipConnection *conn,
TpHandle handle);
diff --git a/src/sip-connection-manager.c b/src/sip-connection-manager.c
index 7a33481..ecb882e 100644
--- a/src/sip-connection-manager.c
+++ b/src/sip-connection-manager.c
@@ -38,6 +38,7 @@
#include <tpsip/sofia-decls.h>
#include <sofia-sip/su_glib.h>
+#include "protocol.h"
#include "sip-connection-manager.h"
#include "sip-connection.h"
@@ -48,165 +49,6 @@
G_DEFINE_TYPE(TpsipConnectionManager, tpsip_connection_manager,
TP_TYPE_BASE_CONNECTION_MANAGER)
-/* private structure *//* typedef struct _TpsipConnectionManagerPrivate TpsipConnectionManagerPrivate; */
-
-typedef struct {
- gchar *account;
- gchar *auth_user;
- gchar *password;
- gchar *alias;
- gchar *registrar;
- gchar *proxy_host;
- guint port;
- gchar *transport;
- gboolean loose_routing;
- gboolean discover_binding;
- gchar *keepalive_mechanism;
- guint keepalive_interval;
- gboolean discover_stun;
- gchar *stun_server;
- guint stun_port;
- gboolean immutable_streams;
- gchar *local_ip_address;
- guint local_port;
- gchar *extra_auth_user;
- gchar *extra_auth_password;
-} TpsipConnParams;
-
-static void *
-alloc_params (void)
-{
- return g_slice_new0 (TpsipConnParams);
-}
-
-static void
-free_params (void *p)
-{
- TpsipConnParams *params = (TpsipConnParams *)p;
-
- g_free (params->account);
- g_free (params->auth_user);
- g_free (params->password);
- g_free (params->alias);
- g_free (params->registrar);
- g_free (params->proxy_host);
- g_free (params->transport);
- g_free (params->keepalive_mechanism);
- g_free (params->stun_server);
- g_free (params->local_ip_address);
- g_free (params->extra_auth_user);
- g_free (params->extra_auth_password);
-
- g_slice_free (TpsipConnParams, params);
-}
-
-enum {
- TPSIP_CONN_PARAM_ACCOUNT = 0,
- TPSIP_CONN_PARAM_AUTH_USER,
- TPSIP_CONN_PARAM_PASSWORD,
- TPSIP_CONN_PARAM_ALIAS,
- TPSIP_CONN_PARAM_REGISTRAR,
- TPSIP_CONN_PARAM_PROXY_HOST,
- TPSIP_CONN_PARAM_PORT,
- TPSIP_CONN_PARAM_TRANSPORT,
- TPSIP_CONN_PARAM_LOOSE_ROUTING,
- TPSIP_CONN_PARAM_DISCOVER_BINDING,
- TPSIP_CONN_PARAM_KEEPALIVE_MECHANISM,
- TPSIP_CONN_PARAM_KEEPALIVE_INTERVAL,
- TPSIP_CONN_PARAM_DISCOVER_STUN,
- TPSIP_CONN_PARAM_STUN_SERVER,
- TPSIP_CONN_PARAM_STUN_PORT,
- TPSIP_CONN_PARAM_IMMUTABLE_STREAMS,
- TPSIP_CONN_PARAM_LOCAL_IP_ADDRESS,
- TPSIP_CONN_PARAM_LOCAL_PORT,
- TPSIP_CONN_PARAM_EXTRA_AUTH_USER,
- TPSIP_CONN_PARAM_EXTRA_AUTH_PASSWORD,
- N_TPSIP_CONN_PARAMS
-};
-
-static const TpCMParamSpec tpsip_params[] = {
- /* Account (a sip: URI) */
- { "account", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING,
- TP_CONN_MGR_PARAM_FLAG_REQUIRED | TP_CONN_MGR_PARAM_FLAG_REGISTER,
- NULL, G_STRUCT_OFFSET (TpsipConnParams, account) },
- /* Username to register with, if different than in the account URI */
- { "auth-user", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING,
- 0, NULL, G_STRUCT_OFFSET (TpsipConnParams, auth_user) },
- /* Password */
- { "password", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING,
- TP_CONN_MGR_PARAM_FLAG_SECRET,
- NULL, G_STRUCT_OFFSET (TpsipConnParams, password) },
- /* Display name for self */
- { "alias", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING, 0, NULL,
- G_STRUCT_OFFSET(TpsipConnParams, alias),
- /* setting a 0-length alias makes no sense */
- tp_cm_param_filter_string_nonempty, NULL },
- /* Registrar */
- { "registrar", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING,
- 0, NULL, G_STRUCT_OFFSET (TpsipConnParams, registrar) },
- /* Used to compose proxy URI */
- { "proxy-host", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING,
- 0, NULL, G_STRUCT_OFFSET (TpsipConnParams, proxy_host) },
- /* Used to compose proxy URI */
- { "port", DBUS_TYPE_UINT16_AS_STRING, G_TYPE_UINT,
- TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, GUINT_TO_POINTER(SIP_DEFAULT_PORT),
- G_STRUCT_OFFSET (TpsipConnParams, port) },
- /* Used to compose proxy URI */
- { "transport", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING,
- TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, "auto",
- G_STRUCT_OFFSET (TpsipConnParams, transport) },
- /* Enables loose routing as per RFC 3261 */
- { "loose-routing", DBUS_TYPE_BOOLEAN_AS_STRING, G_TYPE_BOOLEAN,
- TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, GUINT_TO_POINTER(FALSE),
- G_STRUCT_OFFSET (TpsipConnParams, loose_routing) },
- /* Used to enable proactive NAT traversal techniques */
- { "discover-binding", DBUS_TYPE_BOOLEAN_AS_STRING, G_TYPE_BOOLEAN,
- TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, GUINT_TO_POINTER(TRUE),
- G_STRUCT_OFFSET (TpsipConnParams, discover_binding) },
- /* Mechanism used for connection keepalive maintenance */
- { "keepalive-mechanism", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING,
- TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, "auto",
- G_STRUCT_OFFSET (TpsipConnParams, keepalive_mechanism) },
- /* Keep-alive interval */
- { "keepalive-interval", DBUS_TYPE_UINT32_AS_STRING, G_TYPE_UINT,
- TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, GUINT_TO_POINTER(0),
- G_STRUCT_OFFSET (TpsipConnParams, keepalive_interval) },
- /* Use SRV DNS lookup to discover STUN server */
- { "discover-stun", DBUS_TYPE_BOOLEAN_AS_STRING, G_TYPE_BOOLEAN,
- TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, GUINT_TO_POINTER(TRUE),
- G_STRUCT_OFFSET (TpsipConnParams, discover_stun) },
- /* STUN server */
- { "stun-server", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING,
- 0, NULL, G_STRUCT_OFFSET (TpsipConnParams, stun_server) },
- /* STUN port */
- { "stun-port", DBUS_TYPE_UINT16_AS_STRING, G_TYPE_UINT,
- TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT,
- GUINT_TO_POINTER(TPSIP_DEFAULT_STUN_PORT),
- G_STRUCT_OFFSET (TpsipConnParams, stun_port) },
- /* If the session content cannot be modified once initially set up */
- { "immutable-streams", DBUS_TYPE_BOOLEAN_AS_STRING, G_TYPE_BOOLEAN,
- TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, GUINT_TO_POINTER(FALSE),
- G_STRUCT_OFFSET (TpsipConnParams, immutable_streams) },
- /* Local IP address to use, workaround purposes only */
- { "local-ip-address", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING,
- 0, NULL, G_STRUCT_OFFSET (TpsipConnParams, local_ip_address) },
- /* Local port for SIP, workaround purposes only */
- { "local-port", DBUS_TYPE_UINT16_AS_STRING, G_TYPE_UINT,
- 0, NULL, G_STRUCT_OFFSET (TpsipConnParams, local_port) },
- /* Extra-realm authentication */
- { "extra-auth-user", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING,
- 0, NULL, G_STRUCT_OFFSET (TpsipConnParams, extra_auth_user) },
- { "extra-auth-password", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING,
- TP_CONN_MGR_PARAM_FLAG_SECRET,
- NULL, G_STRUCT_OFFSET (TpsipConnParams, extra_auth_password) },
- { NULL, NULL, 0, 0, NULL, 0 }
-};
-
-const TpCMProtocolSpec tpsip_protocols[] = {
- { "sip", tpsip_params, alloc_params, free_params },
- { NULL, NULL }
-};
-
struct _TpsipConnectionManagerPrivate
{
su_root_t *sofia_root;
@@ -237,10 +79,24 @@ tpsip_connection_manager_init (TpsipConnectionManager *obj)
#endif
}
+static void
+tpsip_connection_manager_constructed (GObject *object)
+{
+ TpsipConnectionManager *self = TPSIP_CONNECTION_MANAGER (object);
+ TpBaseConnectionManager *base = (TpBaseConnectionManager *) self;
+ void (*constructed) (GObject *) =
+ ((GObjectClass *) tpsip_connection_manager_parent_class)->constructed;
+ TpBaseProtocol *protocol;
+
+ if (constructed != NULL)
+ constructed (object);
+
+ protocol = tpsip_protocol_new (self->priv->sofia_root);
+ tp_base_connection_manager_add_protocol (base, protocol);
+ g_object_unref (protocol);
+}
+
static void tpsip_connection_manager_finalize (GObject *object);
-static TpBaseConnection *tpsip_connection_manager_new_connection (
- TpBaseConnectionManager *base, const gchar *proto,
- TpIntSet *params_present, void *parsed_params, GError **error);
static void
tpsip_connection_manager_class_init (TpsipConnectionManagerClass *klass)
@@ -251,11 +107,10 @@ tpsip_connection_manager_class_init (TpsipConnectionManagerClass *klass)
g_type_class_add_private (klass, sizeof (TpsipConnectionManagerPrivate));
+ object_class->constructed = tpsip_connection_manager_constructed;
object_class->finalize = tpsip_connection_manager_finalize;
- base_class->new_connection = tpsip_connection_manager_new_connection;
base_class->cm_dbus_name = "sofiasip";
- base_class->protocol_params = tpsip_protocols;
}
void
@@ -279,248 +134,3 @@ tpsip_connection_manager_finalize (GObject *object)
G_OBJECT_CLASS (tpsip_connection_manager_parent_class)->finalize (object);
}
-
-static gchar *
-priv_compose_proxy_uri (const gchar *host,
- const gchar *transport,
- guint port)
-{
- const gchar *scheme = "sip";
-
- if (host == NULL)
- return NULL;
-
- /* Set scheme to SIPS if transport is TLS */
-
- if (transport != NULL && !g_ascii_strcasecmp (transport, "tls"))
- scheme = "sips";
-
- /* Format the resulting URI */
-
- if (port)
- return g_strdup_printf ("%s:%s:%u", scheme, host, port);
- else
- return g_strdup_printf ("%s:%s", scheme, host);
-}
-
-/**
- * Returns a default SIP proxy address based on the public
- * SIP address 'sip_address' and . For instance
- * "sip:first.surname@company.com" would result in "sip:company.com".
- * The SIP stack will further utilize DNS lookups to find the IP address
- * for the SIP server responsible for the domain "company.com".
- */
-static gchar *
-priv_compose_default_proxy_uri (const gchar *sip_address,
- const gchar *transport)
-{
- char *result = NULL;
- char *host;
- char *found;
-
- g_return_val_if_fail (sip_address != NULL, NULL);
-
- /* skip sip and sips prefixes, updating transport if necessary */
- found = strchr (sip_address, ':');
- if (found != NULL) {
- if (g_ascii_strncasecmp ("sip:", sip_address, 4) == 0)
- ;
- else if (g_ascii_strncasecmp ("sips:", sip_address, 5) == 0)
- {
- if (transport == NULL ||
- strcmp (transport, "auto") == 0)
- transport = "tls";
- }
- else
- {
- /* error, unknown URI prefix */
- return NULL;
- }
-
- sip_address = found + 1;
- }
-
- /* skip userinfo */
- found = strchr (sip_address, '@');
- if (found != NULL)
- sip_address = found + 1;
-
- /* copy rest of the string */
- host = g_strdup (sip_address);
-
- /* mark end (before uri-parameters defs or headers) */
- found = strchr (host, ';');
- if (found == NULL)
- found = strchr (host, '?');
- if (found != NULL)
- *found = '\0';
-
- result = priv_compose_proxy_uri (host, transport, 0);
-
- g_free (host);
-
- return result;
-}
-
-static TpsipConnectionKeepaliveMechanism
-priv_parse_keepalive (const gchar *str)
-{
- if (str == NULL || strcmp (str, "auto") == 0)
- return TPSIP_CONNECTION_KEEPALIVE_AUTO;
- if (strcmp (str, "register") == 0)
- return TPSIP_CONNECTION_KEEPALIVE_REGISTER;
- if (strcmp (str, "options") == 0)
- return TPSIP_CONNECTION_KEEPALIVE_OPTIONS;
- if (strcmp (str, "stun") == 0)
- return TPSIP_CONNECTION_KEEPALIVE_STUN;
- if (strcmp (str, "off") == 0)
- return TPSIP_CONNECTION_KEEPALIVE_NONE;
-
- WARNING ("unsupported keepalive-method value \"%s\", falling back to auto", str);
- return TPSIP_CONNECTION_KEEPALIVE_AUTO;
-}
-
-#define SET_PROPERTY_IF_PARAM_SET(prop, param, member) \
- if (tp_intset_is_member (params_present, param)) \
- { \
- g_object_set (connection, prop, member, NULL); \
- }
-
-#define NULLIFY_IF_EMPTY(param) \
- if ((param) != NULL && (param)[0] == '\0') \
- param = NULL;
-
-static gboolean
-check_not_empty_if_present (const gchar *name,
- const gchar *value,
- GError **error)
-{
- if (value != NULL && value[0] == '\0')
- {
- g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
- "If supplied, '%s' account parameter may not be empty", name);
- return FALSE;
- }
- return TRUE;
-}
-
-static TpBaseConnection *
-tpsip_connection_manager_new_connection (TpBaseConnectionManager *base,
- const gchar *proto,
- TpIntSet *params_present,
- void *parsed_params,
- GError **error)
-{
- TpsipConnectionManager *self = TPSIP_CONNECTION_MANAGER (base);
- TpsipConnectionManagerPrivate *priv = TPSIP_CONNECTION_MANAGER_GET_PRIVATE (self);
- TpBaseConnection *connection = NULL;
- TpsipConnParams *params = (TpsipConnParams *)parsed_params;
- gchar *proxy = NULL;
- TpsipConnectionKeepaliveMechanism keepalive_mechanism;
-
- if (strcmp (proto, "sip")) {
- g_set_error (error, TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED,
- "This connection manager only implements protocol 'sip', not '%s'",
- proto);
- return NULL;
- }
-
- /* TpBaseConnectionManager code has already checked that required params
- * are present (but not that they are non-empty, if we're using >= 0.5.8)
- */
- g_assert (params->account);
-
- /* FIXME: validate account SIP URI properly, using appropriate RFCs */
- if (!check_not_empty_if_present ("account", params->account, error))
- return FALSE;
- NULLIFY_IF_EMPTY (params->auth_user);
- NULLIFY_IF_EMPTY (params->password);
- /* FIXME: validate registrar SIP URI properly, using appropriate RFCs */
- NULLIFY_IF_EMPTY (params->registrar);
- /* FIXME: validate proxy host properly */
- NULLIFY_IF_EMPTY (params->proxy_host);
- /* FIXME: check against the list (which presumably exists) of valid
- * transports */
- NULLIFY_IF_EMPTY (params->transport);
- /* FIXME: check against the list (which presumably exists) of valid
- * KA mechanisms */
- NULLIFY_IF_EMPTY (params->keepalive_mechanism);
- /* FIXME: validate STUN server properly */
- NULLIFY_IF_EMPTY (params->stun_server);
- /* FIXME: validate local IP address properly */
- NULLIFY_IF_EMPTY (params->local_ip_address);
- NULLIFY_IF_EMPTY (params->extra_auth_user);
- NULLIFY_IF_EMPTY (params->extra_auth_password);
-
- DEBUG("New SIP connection to %s", params->account);
-
- connection = (TpBaseConnection *)g_object_new(TPSIP_TYPE_CONNECTION,
- "protocol", "sip",
- "sofia-root", priv->sofia_root,
- "address", params->account,
- NULL);
-
- if (params->proxy_host == NULL) {
- proxy = priv_compose_default_proxy_uri (params->account,
- params->transport);
- DEBUG("set outbound proxy address to <%s>, based on <%s>", proxy, params->account);
- } else
- proxy = priv_compose_proxy_uri (params->proxy_host,
- params->transport,
- params->port);
-
- g_object_set (connection, "proxy", proxy, NULL);
- g_free (proxy);
-
- if (params->transport != NULL && strcmp (params->transport, "auto") != 0)
- g_object_set (connection, "transport", params->transport, NULL);
-
- SET_PROPERTY_IF_PARAM_SET ("auth-user", TPSIP_CONN_PARAM_AUTH_USER,
- params->auth_user);
-
- SET_PROPERTY_IF_PARAM_SET ("password", TPSIP_CONN_PARAM_PASSWORD,
- params->password);
-
- SET_PROPERTY_IF_PARAM_SET ("alias", TPSIP_CONN_PARAM_ALIAS,
- params->alias);
-
- SET_PROPERTY_IF_PARAM_SET ("registrar", TPSIP_CONN_PARAM_REGISTRAR,
- params->registrar);
-
- SET_PROPERTY_IF_PARAM_SET ("loose-routing", TPSIP_CONN_PARAM_LOOSE_ROUTING,
- params->loose_routing);
-
- SET_PROPERTY_IF_PARAM_SET ("discover-binding", TPSIP_CONN_PARAM_DISCOVER_BINDING,
- params->discover_binding);
-
- SET_PROPERTY_IF_PARAM_SET ("discover-stun", TPSIP_CONN_PARAM_DISCOVER_STUN,
- params->discover_stun);
-
- SET_PROPERTY_IF_PARAM_SET ("stun-server", TPSIP_CONN_PARAM_STUN_SERVER,
- params->stun_server);
-
- SET_PROPERTY_IF_PARAM_SET ("stun-port", TPSIP_CONN_PARAM_STUN_PORT,
- params->stun_port);
-
- SET_PROPERTY_IF_PARAM_SET ("immutable-streams", TPSIP_CONN_PARAM_IMMUTABLE_STREAMS,
- params->immutable_streams);
-
- SET_PROPERTY_IF_PARAM_SET ("keepalive-interval",
- TPSIP_CONN_PARAM_KEEPALIVE_INTERVAL, params->keepalive_interval);
-
- keepalive_mechanism = priv_parse_keepalive (params->keepalive_mechanism);
- g_object_set (connection, "keepalive-mechanism", keepalive_mechanism, NULL);
-
- SET_PROPERTY_IF_PARAM_SET ("local-ip-address", TPSIP_CONN_PARAM_LOCAL_IP_ADDRESS,
- params->local_ip_address);
-
- SET_PROPERTY_IF_PARAM_SET ("local-port", TPSIP_CONN_PARAM_LOCAL_PORT,
- params->local_port);
-
- SET_PROPERTY_IF_PARAM_SET ("extra-auth-user", TPSIP_CONN_PARAM_EXTRA_AUTH_USER,
- params->extra_auth_user);
- SET_PROPERTY_IF_PARAM_SET ("extra-auth-password", TPSIP_CONN_PARAM_EXTRA_AUTH_PASSWORD,
- params->extra_auth_password);
-
- return connection;
-}
diff --git a/src/sip-connection-private.h b/src/sip-connection-private.h
index 6a7efcf..2250694 100644
--- a/src/sip-connection-private.h
+++ b/src/sip-connection-private.h
@@ -27,6 +27,8 @@
#include <tpsip/sofia-decls.h>
#include <sofia-sip/sresolv.h>
+#include <telepathy-glib/simple-password-manager.h>
+
#ifdef HAVE_LIBIPHB
#include <libiphb.h>
#endif
@@ -51,6 +53,7 @@ struct _TpsipConnectionPrivate
gchar *registrar_realm;
TpsipMediaFactory *media_factory;
+ TpSimplePasswordManager *password_manager;
gchar *address;
gchar *auth_user;
diff --git a/src/sip-connection.c b/src/sip-connection.c
index bc175cf..b515699 100644
--- a/src/sip-connection.c
+++ b/src/sip-connection.c
@@ -160,6 +160,10 @@ tpsip_connection_create_channel_managers (TpBaseConnection *conn)
"connection", self, NULL);
g_ptr_array_add (channel_managers, priv->media_factory);
+ priv->password_manager = tp_simple_password_manager_new (
+ conn);
+ g_ptr_array_add (channel_managers, priv->password_manager);
+
return channel_managers;
}
@@ -422,15 +426,22 @@ static void tpsip_connection_shut_down (TpBaseConnection *base);
static gboolean tpsip_connection_start_connecting (TpBaseConnection *base,
GError **error);
+static const gchar *interfaces_always_present[] = {
+ TP_IFACE_CONNECTION_INTERFACE_REQUESTS,
+ TP_IFACE_CONNECTION_INTERFACE_CONTACTS,
+ TP_IFACE_CONNECTION_INTERFACE_ALIASING,
+ NULL };
+
+const gchar **
+tpsip_connection_get_implemented_interfaces (void)
+{
+ /* we don't have any conditionally-implemented interfaces */
+ return interfaces_always_present;
+}
+
static void
tpsip_connection_class_init (TpsipConnectionClass *klass)
{
- static const gchar *interfaces_always_present[] = {
- TP_IFACE_CONNECTION_INTERFACE_REQUESTS,
- TP_IFACE_CONNECTION_INTERFACE_CONTACTS,
- TP_IFACE_CONNECTION_INTERFACE_ALIASING,
- NULL };
-
GObjectClass *object_class = G_OBJECT_CLASS (klass);
TpBaseConnectionClass *base_class =
(TpBaseConnectionClass *)klass;
@@ -593,6 +604,54 @@ tpsip_connection_class_init (TpsipConnectionClass *klass)
G_STRUCT_OFFSET (TpsipConnectionClass, contacts_class));
}
+typedef struct {
+ TpsipConnection* self;
+ nua_handle_t *nh;
+ gchar *method;
+ gchar *realm;
+ gchar *user;
+} PrivHandleAuthData;
+
+static PrivHandleAuthData *
+priv_handle_auth_data_new (TpsipConnection* self,
+ nua_handle_t *nh,
+ const gchar *method,
+ const gchar *realm,
+ const gchar *user)
+{
+ PrivHandleAuthData *data = g_slice_new (PrivHandleAuthData);
+
+ data->self = g_object_ref (self);
+ data->nh = nua_handle_ref (nh);
+ data->method = g_strdup (method);
+ data->realm = g_strdup (realm);
+ data->user = g_strdup (user);
+
+ return data;
+}
+
+static void
+priv_handle_auth_data_free (PrivHandleAuthData *data)
+{
+ g_object_unref (data->self);
+ nua_handle_unref (data->nh);
+ g_free (data->method);
+ g_free (data->realm);
+ g_free (data->user);
+
+ g_slice_free (PrivHandleAuthData, data);
+}
+
+static void priv_password_manager_prompt_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data);
+static void priv_handle_auth_continue (TpsipConnection* self,
+ nua_handle_t *nh,
+ const gchar *method,
+ const gchar *realm,
+ const gchar *user,
+ const gchar *password);
+
static gboolean
priv_handle_auth (TpsipConnection* self,
int status,
@@ -607,7 +666,6 @@ priv_handle_auth (TpsipConnection* self,
const char *realm = NULL;
const char *user = NULL;
const char *password = NULL;
- gchar *auth = NULL;
if (status != 401 && status != 407)
return FALSE;
@@ -635,6 +693,12 @@ priv_handle_auth (TpsipConnection* self,
return FALSE;
}
+ if (method == NULL)
+ {
+ WARNING ("no method presented for authentication");
+ return FALSE;
+ }
+
/* step: determine which set of credentials to use */
if (home_realm)
{
@@ -668,6 +732,9 @@ priv_handle_auth (TpsipConnection* self,
/* fall back to the main username */
user = priv->auth_user;
password = priv->extra_auth_password;
+ if (password == NULL)
+ /* note that this prevents asking the user for a password */
+ password = "";
DEBUG("using the extra auth credentials");
}
@@ -678,37 +745,101 @@ priv_handle_auth (TpsipConnection* self,
if (sipfrom && sipfrom->a_url[0].url_user)
/* or use the userpart in "From" header */
user = sipfrom->a_url[0].url_user;
+ else
+ return FALSE;
}
if (password == NULL)
- password = "";
+ {
+ PrivHandleAuthData *data = NULL;
+
+ DEBUG("asking the user for a password.");
+ data = priv_handle_auth_data_new (self, nh, method, realm,
+ user);
+ tp_simple_password_manager_prompt_async (priv->password_manager,
+ priv_password_manager_prompt_cb, data);
+ /* Promise that we'll handle it eventually, even if we end up just
+ * handling it with a blank password. */
+ return TRUE;
+ }
- /* step: if all info is available, create an authorization response */
- g_assert (realm != NULL);
- if (user && method) {
- if (realm[0] == '"')
- auth = g_strdup_printf ("%s:%s:%s:%s",
- method, realm, user, password);
- else
- auth = g_strdup_printf ("%s:\"%s\":%s:%s",
- method, realm, user, password);
+ priv_handle_auth_continue (self, nh, method, realm,
+ user, password);
+ return TRUE;
+}
- DEBUG("%s authenticating user='%s' realm=%s",
- wa ? "server" : "proxy", user, realm);
- }
+static void
+priv_password_manager_prompt_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ PrivHandleAuthData *data = user_data;
+ GError *error = NULL;
+ const GString *password_string;
+ const gchar *password;
+
+ password_string = tp_simple_password_manager_prompt_finish (
+ TP_SIMPLE_PASSWORD_MANAGER (source_object), result, &error);
- if (auth == NULL)
+ if (error != NULL)
{
- WARNING ("authentication data are incomplete");
- return FALSE;
+ /* we promised to handle the auth challenge in priv_handle_auth() by
+ * returning TRUE, so we need to handle it anyway, even if it means
+ * doing it with a blank password.
+ */
+ DEBUG ("Auth channel failed: %s. Using blank password.", error->message);
+
+ password = "";
+
+ g_error_free (error);
}
+ else
+ {
+ TpsipConnectionPrivate *priv =
+ TPSIP_CONNECTION_GET_PRIVATE (data->self);
+
+ password = password_string->str;
+ /* also save it for later. */
+ g_free (priv->password);
+ priv->password = g_strdup (password);
+ }
+
+ priv_handle_auth_continue (data->self, data->nh, data->method, data->realm,
+ data->user, password);
+
+ priv_handle_auth_data_free (data);
+}
+
+static void
+priv_handle_auth_continue (TpsipConnection* self,
+ nua_handle_t *nh,
+ const gchar *method,
+ const gchar *realm,
+ const gchar *user,
+ const gchar *password)
+{
+ gchar *auth = NULL;
+
+ /* step: if all info is available, create an authorization response */
+ g_assert (realm != NULL);
+ g_assert (method != NULL);
+ g_assert (user != NULL);
+ g_assert (password != NULL);
+
+ if (realm[0] == '"')
+ auth = g_strdup_printf ("%s:%s:%s:%s",
+ method, realm, user, password);
+ else
+ auth = g_strdup_printf ("%s:\"%s\":%s:%s",
+ method, realm, user, password);
+
+ DEBUG ("%s-authenticating user='%s' realm=%s",
+ method, user, realm);
/* step: authenticate */
nua_authenticate(nh, NUTAG_AUTH(auth), TAG_END());
g_free (auth);
-
- return TRUE;
}
static gboolean
diff --git a/src/sip-connection.h b/src/sip-connection.h
index ac025ca..2280be9 100644
--- a/src/sip-connection.h
+++ b/src/sip-connection.h
@@ -80,6 +80,8 @@ GType tpsip_connection_get_type (void) G_GNUC_CONST;
void tpsip_connection_connect_auth_handler (TpsipConnection *self,
TpsipEventTarget *target);
+const gchar **tpsip_connection_get_implemented_interfaces (void);
+
G_END_DECLS
#endif /* #ifndef __TPSIP_CONNECTION_H__*/
diff --git a/src/sip-media-channel.c b/src/sip-media-channel.c
index c6d6df5..8f896ed 100644
--- a/src/sip-media-channel.c
+++ b/src/sip-media-channel.c
@@ -189,7 +189,7 @@ tpsip_media_channel_constructed (GObject *obj)
TpBaseConnection *conn = (TpBaseConnection *)(priv->conn);
GObjectClass *parent_object_class =
G_OBJECT_CLASS (tpsip_media_channel_parent_class);
- DBusGConnection *bus;
+ TpDBusDaemon *bus;
TpHandleRepoIface *contact_repo;
TpIntSet *set;
@@ -203,10 +203,10 @@ tpsip_media_channel_constructed (GObject *obj)
tp_handle_ref (contact_repo, priv->handle);
/* register object on the bus */
- bus = tp_get_bus ();
+ bus = tp_base_connection_get_dbus_daemon (conn);
DEBUG("registering object to dbus path=%s", priv->object_path);
- dbus_g_connection_register_g_object (bus, priv->object_path, obj);
+ tp_dbus_daemon_register_object (bus, priv->object_path, obj);
/* initialize group mixin */
tp_group_mixin_init (obj,
@@ -1622,6 +1622,8 @@ priv_create_session (TpsipMediaChannel *channel,
NULL);
session = g_object_new (TPSIP_TYPE_MEDIA_SESSION,
+ "dbus-daemon",
+ tp_base_connection_get_dbus_daemon (conn),
"media-channel", channel,
"object-path", object_path,
"nua-handle", nh,
diff --git a/src/sip-media-session.c b/src/sip-media-session.c
index 73b7482..8272200 100644
--- a/src/sip-media-session.c
+++ b/src/sip-media-session.c
@@ -72,6 +72,7 @@ enum
enum
{
PROP_MEDIA_CHANNEL = 1,
+ PROP_DBUS_DAEMON,
PROP_OBJECT_PATH,
PROP_NUA_OP,
PROP_PEER,
@@ -122,6 +123,7 @@ typedef struct _TpsipMediaSessionPrivate TpsipMediaSessionPrivate;
struct _TpsipMediaSessionPrivate
{
+ TpDBusDaemon *dbus_daemon;
TpsipMediaChannel *channel; /* see gobj. prop. 'media-channel' */
gchar *object_path; /* see gobj. prop. 'object-path' */
nua_handle_t *nua_op; /* see gobj. prop. 'nua-handle' */
@@ -192,14 +194,13 @@ tpsip_media_session_constructor (GType type, guint n_props,
{
GObject *obj;
TpsipMediaSessionPrivate *priv;
- DBusGConnection *bus;
obj = G_OBJECT_CLASS (tpsip_media_session_parent_class)->
constructor (type, n_props, props);
priv = TPSIP_MEDIA_SESSION_GET_PRIVATE (TPSIP_MEDIA_SESSION (obj));
- bus = tp_get_bus ();
- dbus_g_connection_register_g_object (bus, priv->object_path, obj);
+ g_assert (TP_IS_DBUS_DAEMON (priv->dbus_daemon));
+ tp_dbus_daemon_register_object (priv->dbus_daemon, priv->object_path, obj);
return obj;
}
@@ -212,7 +213,11 @@ static void tpsip_media_session_get_property (GObject *object,
TpsipMediaSession *session = TPSIP_MEDIA_SESSION (object);
TpsipMediaSessionPrivate *priv = TPSIP_MEDIA_SESSION_GET_PRIVATE (session);
- switch (property_id) {
+ switch (property_id)
+ {
+ case PROP_DBUS_DAEMON:
+ g_value_set_object (value, priv->dbus_daemon);
+ break;
case PROP_MEDIA_CHANNEL:
g_value_set_object (value, priv->channel);
break;
@@ -296,7 +301,12 @@ static void tpsip_media_session_set_property (GObject *object,
TpsipMediaSession *session = TPSIP_MEDIA_SESSION (object);
TpsipMediaSessionPrivate *priv = TPSIP_MEDIA_SESSION_GET_PRIVATE (session);
- switch (property_id) {
+ switch (property_id)
+ {
+ case PROP_DBUS_DAEMON:
+ g_assert (priv->dbus_daemon == NULL); /* construct-only */
+ priv->dbus_daemon = g_value_dup_object (value);
+ break;
case PROP_MEDIA_CHANNEL:
priv->channel = TPSIP_MEDIA_CHANNEL (g_value_get_object (value));
break;
@@ -343,6 +353,11 @@ tpsip_media_session_class_init (TpsipMediaSessionClass *klass)
object_class->dispose = tpsip_media_session_dispose;
object_class->finalize = tpsip_media_session_finalize;
+ param_spec = g_param_spec_object ("dbus-daemon", "TpDBusDaemon",
+ "Connection to D-Bus.", TP_TYPE_DBUS_DAEMON,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_DBUS_DAEMON, param_spec);
+
param_spec = g_param_spec_object ("media-channel", "TpsipMediaChannel object",
"SIP media channel object that owns this media session object"
" (not reference counted).",
@@ -442,6 +457,8 @@ tpsip_media_session_dispose (GObject *object)
if (priv->glare_timer_id)
g_source_remove (priv->glare_timer_id);
+ tp_clear_object (&priv->dbus_daemon);
+
if (G_OBJECT_CLASS (tpsip_media_session_parent_class)->dispose)
G_OBJECT_CLASS (tpsip_media_session_parent_class)->dispose (object);
@@ -2118,6 +2135,7 @@ tpsip_media_session_add_stream (TpsipMediaSession *self,
direction &= ~TP_MEDIA_STREAM_DIRECTION_RECEIVE;
stream = g_object_new (TPSIP_TYPE_MEDIA_STREAM,
+ "dbus-daemon", priv->dbus_daemon,
"media-session", self,
"media-type", media_type,
"object-path", object_path,
diff --git a/src/sip-media-stream.c b/src/sip-media-stream.c
index 37e14df..d3826bb 100644
--- a/src/sip-media-stream.c
+++ b/src/sip-media-stream.c
@@ -84,6 +84,7 @@ static guint signals[SIG_LAST_SIGNAL] = {0};
enum
{
PROP_MEDIA_SESSION = 1,
+ PROP_DBUS_DAEMON,
PROP_OBJECT_PATH,
PROP_ID,
PROP_MEDIA_TYPE,
@@ -105,6 +106,7 @@ typedef struct _TpsipMediaStreamPrivate TpsipMediaStreamPrivate;
struct _TpsipMediaStreamPrivate
{
+ TpDBusDaemon *dbus_daemon;
TpsipMediaSession *session; /* see gobj. prop. 'media-session' */
gchar *object_path; /* see gobj. prop. 'object-path' */
guint id; /* see gobj. prop. 'id' */
@@ -200,7 +202,6 @@ tpsip_media_stream_constructed (GObject *obj)
TPSIP_MEDIA_STREAM (obj));
GObjectClass *parent_object_class =
G_OBJECT_CLASS (tpsip_media_stream_parent_class);
- DBusGConnection *bus;
/* call base class method */
if (parent_object_class->constructed != NULL)
@@ -220,8 +221,8 @@ tpsip_media_stream_constructed (GObject *obj)
}
/* go for the bus */
- bus = tp_get_bus ();
- dbus_g_connection_register_g_object (bus, priv->object_path, obj);
+ g_assert (TP_IS_DBUS_DAEMON (priv->dbus_daemon));
+ tp_dbus_daemon_register_object (priv->dbus_daemon, priv->object_path, obj);
}
static void
@@ -235,6 +236,9 @@ tpsip_media_stream_get_property (GObject *object,
switch (property_id)
{
+ case PROP_DBUS_DAEMON:
+ g_value_set_object (value, priv->dbus_daemon);
+ break;
case PROP_MEDIA_SESSION:
g_value_set_object (value, priv->session);
break;
@@ -288,6 +292,10 @@ tpsip_media_stream_set_property (GObject *object,
switch (property_id)
{
+ case PROP_DBUS_DAEMON:
+ g_assert (priv->dbus_daemon == NULL); /* construct-only */
+ priv->dbus_daemon = g_value_dup_object (value);
+ break;
case PROP_MEDIA_SESSION:
priv->session = g_value_get_object (value);
break;
@@ -358,6 +366,11 @@ tpsip_media_stream_class_init (TpsipMediaStreamClass *klass)
object_class->dispose = tpsip_media_stream_dispose;
object_class->finalize = tpsip_media_stream_finalize;
+ param_spec = g_param_spec_object ("dbus-daemon", "TpDBusDaemon",
+ "Connection to D-Bus.", TP_TYPE_DBUS_DAEMON,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_DBUS_DAEMON, param_spec);
+
param_spec = g_param_spec_object ("media-session", "TpsipMediaSession object",
"SIP media session object that owns this media stream object.",
TPSIP_TYPE_MEDIA_SESSION,
@@ -516,7 +529,7 @@ tpsip_media_stream_dispose (GObject *object)
priv->dispose_has_run = TRUE;
- /* release any references held by the object here */
+ tp_clear_object (&priv->dbus_daemon);
if (G_OBJECT_CLASS (tpsip_media_stream_parent_class)->dispose)
G_OBJECT_CLASS (tpsip_media_stream_parent_class)->dispose (object);
diff --git a/src/sip-text-channel.c b/src/sip-text-channel.c
index ec0ed32..aeb0209 100644
--- a/src/sip-text-channel.c
+++ b/src/sip-text-channel.c
@@ -162,7 +162,7 @@ tpsip_text_channel_constructed (GObject *obj)
TpsipTextChannelPrivate *priv;
TpBaseConnection *base_conn;
TpHandleRepoIface *contact_handles;
- DBusGConnection *bus;
+ TpDBusDaemon *bus;
TpChannelTextMessageType types[] = {
TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL,
};
@@ -202,8 +202,8 @@ tpsip_text_channel_constructed (GObject *obj)
TP_DELIVERY_REPORTING_SUPPORT_FLAG_RECEIVE_SUCCESSES,
supported_content_types);
- bus = tp_get_bus();
- dbus_g_connection_register_g_object(bus, priv->object_path, obj);
+ bus = tp_base_connection_get_dbus_daemon (base_conn);
+ tp_dbus_daemon_register_object (bus, priv->object_path, obj);
}
diff --git a/src/text-factory.c b/src/text-factory.c
index 36730de..d62b2b6 100644
--- a/src/text-factory.c
+++ b/src/text-factory.c
@@ -328,8 +328,8 @@ static const gchar * const text_channel_allowed_properties[] = {
};
static void
-tpsip_text_factory_foreach_channel_class (TpChannelManager *manager,
- TpChannelManagerChannelClassFunc func,
+tpsip_text_factory_type_foreach_channel_class (GType type,
+ TpChannelManagerTypeChannelClassFunc func,
gpointer user_data)
{
GHashTable *table = g_hash_table_new_full (g_str_hash, g_str_equal,
@@ -346,7 +346,7 @@ tpsip_text_factory_foreach_channel_class (TpChannelManager *manager,
g_hash_table_insert (table, (gchar *) text_channel_fixed_properties[1],
value);
- func (manager, table, text_channel_allowed_properties, user_data);
+ func (type, table, text_channel_allowed_properties, user_data);
g_hash_table_destroy (table);
}
@@ -635,7 +635,8 @@ channel_manager_iface_init (gpointer g_iface, gpointer iface_data)
TpChannelManagerIface *iface = g_iface;
iface->foreach_channel = tpsip_text_factory_foreach_channel;
- iface->foreach_channel_class = tpsip_text_factory_foreach_channel_class;
+ iface->type_foreach_channel_class =
+ tpsip_text_factory_type_foreach_channel_class;
iface->create_channel = tpsip_text_factory_create_channel;
iface->request_channel = tpsip_text_factory_request_channel;
iface->ensure_channel = tpsip_text_factory_ensure_channel;
diff --git a/src/write-mgr-file.c b/src/write-mgr-file.c
index e718d91..402e89e 100644
--- a/src/write-mgr-file.c
+++ b/src/write-mgr-file.c
@@ -1,113 +1,357 @@
/*
- * write_mgr_file.c - utility to produce .manager files.
- * Copyright (C) 2006-2007 Collabora Ltd.
- * Copyright (C) 2006-2007 Nokia Corporation
+ * write_mgr_file.c - utility to produce manager files. Derived from Gabble.
+ * Copyright (C) 2006-2010 Collabora Ltd.
+ * Copyright (C) 2006-2010 Nokia Corporation
*
- * This work is free software; you can redistribute it and/or
+ * 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 work is distributed in the hope that it will be useful,
+ * 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 work; if not, write to the Free Software
+ * 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 <stdio.h>
+#include <string.h>
#include <dbus/dbus-glib.h>
#include <dbus/dbus-protocol.h>
+#include <telepathy-glib/telepathy-glib.h>
-#include <telepathy-glib/enums.h>
-#include "sip-connection-manager.h"
+#include "protocol.h"
-/* Cloned straight from Gabble - this might eventually go in a library */
-static gchar *
-mgr_file_contents (const char *busname,
- const char *objpath,
- const TpCMProtocolSpec protocols[],
- GError **error)
+
+#define WRITE_STR(prop, key) \
+ { \
+ const gchar *val = tp_asv_get_string (props, prop); \
+ g_assert (!tp_str_empty (val)); \
+ g_key_file_set_string (f, section_name, key, val); \
+ }
+
+static void
+write_parameters (GKeyFile *f, gchar *section_name, TpBaseProtocol *protocol)
{
- GKeyFile *f = g_key_file_new();
- const TpCMProtocolSpec *protocol;
+ const TpCMParamSpec *parameters =
+ tp_base_protocol_get_parameters (protocol);
const TpCMParamSpec *row;
- g_key_file_set_string(f, "ConnectionManager", "BusName", busname);
- g_key_file_set_string(f, "ConnectionManager", "ObjectPath", objpath);
+ for (row = parameters; row->name; row++)
+ {
+ gchar *param_name = g_strdup_printf ("param-%s", row->name);
+ gchar *param_value = g_strdup_printf ("%s%s%s%s", row->dtype,
+ (row->flags & TP_CONN_MGR_PARAM_FLAG_REQUIRED ? " required" : ""),
+ (row->flags & TP_CONN_MGR_PARAM_FLAG_REGISTER ? " register" : ""),
+ (row->flags & TP_CONN_MGR_PARAM_FLAG_SECRET ? " secret" : ""));
+ g_key_file_set_string (f, section_name, param_name, param_value);
+ g_free (param_value);
+ g_free (param_name);
+ }
- for (protocol = protocols; protocol->name; protocol++)
+ for (row = parameters; row->name; row++)
{
- gchar *section_name = g_strdup_printf("Protocol %s", protocol->name);
+ if (row->flags & TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT)
+ {
+ gchar *default_name = g_strdup_printf ("default-%s", row->name);
- for (row = protocol->parameters; row->name; row++)
+ switch (row->gtype)
+ {
+ case G_TYPE_STRING:
+ g_key_file_set_string (f, section_name, default_name,
+ row->def);
+ break;
+ case G_TYPE_INT:
+ case G_TYPE_UINT:
+ g_key_file_set_integer (f, section_name, default_name,
+ GPOINTER_TO_INT(row->def));
+ break;
+ case G_TYPE_BOOLEAN:
+ g_key_file_set_boolean (f, section_name, default_name,
+ GPOINTER_TO_INT(row->def) ? 1 : 0);
+ break;
+ default:
+ /* can't be in the case because G_TYPE_STRV is actually a
+ * function */
+ if (row->gtype == G_TYPE_STRV)
+ {
+ g_key_file_set_string_list (f, section_name, default_name,
+ (const gchar **) row->def,
+ g_strv_length ((gchar **) row->def));
+ }
+ }
+ g_free (default_name);
+ }
+ }
+}
+
+static void
+write_rcc_property (GKeyFile *keyfile,
+ const gchar *group_name,
+ const gchar *key,
+ GValue *val)
+{
+ switch (G_VALUE_TYPE (val))
+ {
+ case G_TYPE_BOOLEAN:
{
- gchar *param_name = g_strdup_printf("param-%s", row->name);
- gchar *param_value = g_strdup_printf("%s%s%s%s", row->dtype,
- (row->flags & TP_CONN_MGR_PARAM_FLAG_REQUIRED ? " required" : ""),
- (row->flags & TP_CONN_MGR_PARAM_FLAG_REGISTER ? " register" : ""),
- (row->flags & TP_CONN_MGR_PARAM_FLAG_SECRET ? " secret" : ""));
- g_key_file_set_string(f, section_name, param_name, param_value);
- g_free(param_value);
- g_free(param_name);
+ gchar *kf_key = g_strconcat (key,
+ " " DBUS_TYPE_BOOLEAN_AS_STRING, NULL);
+ g_key_file_set_boolean (keyfile, group_name, kf_key,
+ g_value_get_boolean (val));
+ g_free (kf_key);
+ break;
}
- for (row = protocol->parameters; row->name; row++)
+ case G_TYPE_STRING:
{
- if (row->flags & TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT)
- {
- gchar *default_name = g_strdup_printf("default-%s", row->name);
+ gchar *kf_key = g_strconcat (key,
+ " " DBUS_TYPE_STRING_AS_STRING, NULL);
+ g_key_file_set_string (keyfile, group_name, kf_key,
+ g_value_get_string (val));
+ g_free (kf_key);
+ break;
+ }
- switch (row->gtype)
- {
- case G_TYPE_STRING:
- g_key_file_set_string(f, section_name, default_name,
- row->def);
- break;
- case G_TYPE_INT:
- case G_TYPE_UINT:
- g_key_file_set_integer(f, section_name, default_name,
- GPOINTER_TO_INT(row->def));
- break;
- case G_TYPE_BOOLEAN:
- g_key_file_set_boolean(f, section_name, default_name,
- GPOINTER_TO_INT(row->def) ? 1 : 0);
- }
- g_free(default_name);
+ case G_TYPE_UINT:
+ {
+ gchar *kf_key = g_strconcat (key,
+ " " DBUS_TYPE_UINT32_AS_STRING, NULL);
+ gchar *kf_val = g_strdup_printf ("%u", g_value_get_uint (val));
+ g_key_file_set_value (keyfile, group_name, kf_key, kf_val);
+ g_free (kf_key);
+ g_free (kf_val);
+ break;
+ }
+
+ /* FIXME: when we depend on Glib 2.26, we can use
+ * g_key_file_set_[u]int64 (g.o #614864). */
+ case G_TYPE_UINT64:
+ {
+ gchar *kf_key = g_strconcat (key,
+ " " DBUS_TYPE_UINT64_AS_STRING, NULL);
+ gchar *kf_val = g_strdup_printf ("%" G_GUINT64_FORMAT,
+ g_value_get_uint64 (val));
+ g_key_file_set_value (keyfile, group_name, kf_key, kf_val);
+ g_free (kf_key);
+ g_free (kf_val);
+ break;
+ }
+
+ case G_TYPE_INT:
+ {
+ gchar *kf_key = g_strconcat (key,
+ " " DBUS_TYPE_INT32_AS_STRING, NULL);
+ g_key_file_set_integer (keyfile, group_name, kf_key,
+ g_value_get_int (val));
+ g_free (kf_key);
+ break;
+ }
+
+ case G_TYPE_INT64:
+ {
+ gchar *kf_key = g_strconcat (key,
+ " " DBUS_TYPE_UINT64_AS_STRING, NULL);
+ gchar *kf_val = g_strdup_printf ("%" G_GINT64_FORMAT,
+ g_value_get_int64 (val));
+ g_key_file_set_value (keyfile, group_name, kf_key, kf_val);
+ g_free (kf_key);
+ g_free (kf_val);
+ break;
+ }
+
+ default:
+ {
+ if (G_VALUE_TYPE (val) == G_TYPE_STRV)
+ {
+ gchar **list = g_value_get_boxed (val);
+ gchar *kf_key = g_strconcat (key, " "
+ DBUS_TYPE_ARRAY_AS_STRING DBUS_TYPE_STRING_AS_STRING, NULL);
+ g_key_file_set_string_list (keyfile, group_name,
+ kf_key, (const gchar **) list, g_strv_length (list));
+ g_free (kf_key);
+ break;
}
+
+ /* we'd rather crash than forget to write required rcc property */
+ g_assert_not_reached ();
}
- g_free(section_name);
}
- return g_key_file_to_data(f, NULL, error);
}
-/* defined by telepathy-glib if your version is new enough - included here
- * so telepathy-glib 0.5.4 and 0.5.5 will still work
- */
-#ifndef TP_CM_BUS_NAME_BASE
-# define TP_CM_BUS_NAME_BASE "org.freedesktop.Telepathy.ConnectionManager."
-# define TP_CM_OBJECT_PATH_BASE "/org/freedesktop/Telepathy/ConnectionManager/"
-#endif
+static gchar *
+generate_group_name (GHashTable *props)
+{
+ static guint counter = 0;
+ gchar *retval;
+ gchar *chan_type = g_ascii_strdown (tp_asv_get_string (props,
+ TP_PROP_CHANNEL_CHANNEL_TYPE), -1);
+ gchar *chan_type_suffix;
+ gchar *handle_type_name;
+ guint handle_type = tp_asv_get_uint32 (props,
+ TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, NULL);
+
+ g_assert (chan_type != NULL);
+ chan_type_suffix = strrchr (chan_type, '.');
+ g_assert (chan_type_suffix != NULL);
+ chan_type_suffix++;
+
+ switch (handle_type)
+ {
+ case TP_HANDLE_TYPE_CONTACT:
+ handle_type_name = "-1on1";
+ break;
+
+ case TP_HANDLE_TYPE_ROOM:
+ handle_type_name = "-multi";
+ break;
+
+ case TP_HANDLE_TYPE_GROUP:
+ handle_type_name = "-group";
+ break;
+
+ case TP_HANDLE_TYPE_LIST:
+ handle_type_name = "-list";
+ break;
+
+ default:
+ handle_type_name = "";
+ }
+
+ retval = g_strdup_printf ("%s%s-%d", chan_type_suffix, handle_type_name,
+ ++counter);
+
+ g_free (chan_type);
+ return retval;
+}
+
+static void
+write_rccs (GKeyFile *f, const gchar *section_name, GHashTable *props)
+{
+ GPtrArray *rcc_list = tp_asv_get_boxed (props,
+ TP_PROP_PROTOCOL_REQUESTABLE_CHANNEL_CLASSES,
+ TP_ARRAY_TYPE_REQUESTABLE_CHANNEL_CLASS_LIST);
+ guint i;
+ gchar **group_names = g_new0 (gchar *, rcc_list->len + 1);
+
+ for (i = 0; i < rcc_list->len; i++)
+ {
+ gchar **allowed;
+ gchar *group_name;
+ GHashTable *fixed;
+ GHashTableIter iter;
+ gpointer k, v;
+
+ tp_value_array_unpack (g_ptr_array_index (rcc_list, i), 2,
+ &fixed, &allowed);
+
+ group_name = generate_group_name (fixed);
+
+ g_hash_table_iter_init (&iter, fixed);
+ while (g_hash_table_iter_next (&iter, &k, &v))
+ {
+ const gchar *key = k;
+ GValue *val = v;
+
+ write_rcc_property (f, group_name, key, val);
+ }
+
+ /* takes ownership */
+ group_names[i] = group_name;
+
+ g_key_file_set_string_list (f, group_name, "allowed",
+ (const gchar **) allowed, g_strv_length (allowed));
+ }
+
+ g_key_file_set_string_list (f, section_name, "RequestableChannelClasses",
+ (const gchar **) group_names, rcc_list->len);
+
+ g_strfreev (group_names);
+}
+
+static gchar *
+mgr_file_contents (const char *busname,
+ const char *objpath,
+ GSList *protocols,
+ GError **error)
+{
+ GKeyFile *f = g_key_file_new ();
+ gchar *file_data;
+
+ g_key_file_set_string (f, "ConnectionManager", "BusName", busname);
+ g_key_file_set_string (f, "ConnectionManager", "ObjectPath", objpath);
+
+ /* there are no CM interfaces defined yet, so we cheat */
+ g_key_file_set_string (f, "ConnectionManager", "Interfaces", "");
+
+ 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,
+ TP_PROP_PROTOCOL_CONNECTION_INTERFACES);
+
+ write_parameters (f, section_name, protocol);
+ write_rccs (f, section_name, props);
+
+ g_key_file_set_string_list (f, section_name, "Interfaces",
+ ifaces, g_strv_length ((gchar **) ifaces));
+ g_key_file_set_string_list (f, section_name, "ConnectionInterfaces",
+ c_ifaces, g_strv_length ((gchar **) c_ifaces));
+
+ 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);
+ protocols = protocols->next;
+ }
+
+ file_data = g_key_file_to_data (f, NULL, error);
+ g_key_file_free (f);
+ return file_data;
+}
int
main (void)
{
GError *error = NULL;
+ gchar *s;
+ GSList *protocols = NULL;
+
+ g_type_init ();
+ dbus_g_type_specialized_init ();
+
+ protocols = g_slist_prepend (protocols,
+ tpsip_protocol_new (NULL));
+
+ s = mgr_file_contents (TP_CM_BUS_NAME_BASE "sofiasip",
+ TP_CM_OBJECT_PATH_BASE "sofiasip",
+ protocols, &error);
+
+ g_object_unref (protocols->data);
+ g_slist_free (protocols);
- gchar *s = mgr_file_contents(TP_CM_BUS_NAME_BASE "sofiasip",
- TP_CM_OBJECT_PATH_BASE "sofiasip",
- tpsip_protocols, &error);
if (!s)
{
- fprintf(stderr, "%s\n", error->message);
- g_error_free(error);
+ fprintf (stderr, "%s", error->message);
+ g_error_free (error);
return 1;
}
- printf("%s", s);
- g_free(s);
+ printf ("%s", s);
+ g_free (s);
return 0;
}
diff --git a/tests/twisted/Makefile.am b/tests/twisted/Makefile.am
index 3b5e483..086e437 100644
--- a/tests/twisted/Makefile.am
+++ b/tests/twisted/Makefile.am
@@ -1,6 +1,8 @@
TWISTED_TESTS = \
+ cm/protocol.py \
test-register.py \
test-register-fail.py \
+ test-register-sasl.py \
test-handle-normalisation.py \
test-message.py \
test-self-alias.py \
@@ -27,6 +29,7 @@ check-twisted:
EXTRA_DIST = \
$(TWISTED_TESTS) \
+ constants.py \
sofiatest.py \
servicetest.py
diff --git a/tests/twisted/cm/protocol.py b/tests/twisted/cm/protocol.py
new file mode 100644
index 0000000..f0b0c40
--- /dev/null
+++ b/tests/twisted/cm/protocol.py
@@ -0,0 +1,58 @@
+"""
+Test tpsip's o.fd.T.Protocol implementation
+"""
+
+import dbus
+from servicetest import (unwrap, tp_path_prefix, assertEquals, assertContains,
+ call_async)
+from sofiatest import exec_test
+import constants as cs
+
+def test(q, bus, conn, sip):
+ cm = bus.get_object(cs.CM + '.sofiasip',
+ tp_path_prefix + '/ConnectionManager/sofiasip')
+ cm_iface = dbus.Interface(cm, cs.CM)
+ cm_prop_iface = dbus.Interface(cm, cs.PROPERTIES_IFACE)
+
+ protocols = unwrap(cm_prop_iface.Get(cs.CM, 'Protocols'))
+ assertEquals(set(['sip']), set(protocols.keys()))
+
+ protocol_names = unwrap(cm_iface.ListProtocols())
+ assertEquals(set(['sip']), set(protocol_names))
+
+ cm_params = cm_iface.GetParameters('sip')
+ local_props = protocols['sip']
+ local_params = local_props[cs.PROTOCOL + '.Parameters']
+ assertEquals(cm_params, local_params)
+
+ proto = bus.get_object(cm.bus_name, cm.object_path + '/sip')
+ proto_iface = dbus.Interface(proto, cs.PROTOCOL)
+ proto_prop_iface = dbus.Interface(proto, cs.PROPERTIES_IFACE)
+ proto_props = unwrap(proto_prop_iface.GetAll(cs.PROTOCOL))
+
+ for key in ['Parameters', 'Interfaces', 'ConnectionInterfaces',
+ 'RequestableChannelClasses', u'VCardField', u'EnglishName', u'Icon']:
+ a = local_props[cs.PROTOCOL + '.' + key]
+ b = proto_props[key]
+ assertEquals(a, b)
+
+ assertEquals('x-sip', proto_props['VCardField'])
+ assertEquals('SIP', proto_props['EnglishName'])
+ assertEquals('im-sip', proto_props['Icon'])
+
+ assertContains(cs.CONN_IFACE_ALIASING, proto_props['ConnectionInterfaces'])
+ assertContains(cs.CONN_IFACE_CONTACTS, proto_props['ConnectionInterfaces'])
+ assertContains(cs.CONN_IFACE_REQUESTS, proto_props['ConnectionInterfaces'])
+
+ assertEquals('sip:example@mit.edu',
+ unwrap(proto_iface.NormalizeContact('example@MIT.Edu')))
+
+ # Only account is mandatory
+ call_async(q, proto_iface, 'IdentifyAccount', {})
+ q.expect('dbus-error', method='IdentifyAccount', name=cs.INVALID_ARGUMENT)
+ test_params = {'account': 'smcv@example.com'}
+ acc_name = unwrap(proto_iface.IdentifyAccount(test_params))
+ assertEquals('smcv@example.com', acc_name)
+
+if __name__ == '__main__':
+ exec_test(test)
diff --git a/tests/twisted/constants.py b/tests/twisted/constants.py
new file mode 100644
index 0000000..1ffa4d8
--- /dev/null
+++ b/tests/twisted/constants.py
@@ -0,0 +1,400 @@
+"""
+Some handy constants for other tests to share and enjoy.
+"""
+
+from dbus import PROPERTIES_IFACE
+
+CM = "org.freedesktop.Telepathy.ConnectionManager"
+
+HT_NONE = 0
+HT_CONTACT = 1
+HT_ROOM = 2
+HT_LIST = 3
+HT_GROUP = 4
+
+CHANNEL = "org.freedesktop.Telepathy.Channel"
+
+CHANNEL_IFACE_CALL_STATE = CHANNEL + ".Interface.CallState"
+CHANNEL_IFACE_CHAT_STATE = CHANNEL + '.Interface.ChatState'
+CHANNEL_IFACE_DESTROYABLE = CHANNEL + ".Interface.Destroyable"
+CHANNEL_IFACE_GROUP = CHANNEL + ".Interface.Group"
+CHANNEL_IFACE_HOLD = CHANNEL + ".Interface.Hold"
+CHANNEL_IFACE_MEDIA_SIGNALLING = CHANNEL + ".Interface.MediaSignalling"
+CHANNEL_IFACE_MESSAGES = CHANNEL + ".Interface.Messages"
+CHANNEL_IFACE_PASSWORD = CHANNEL + ".Interface.Password"
+CHANNEL_IFACE_TUBE = CHANNEL + ".Interface.Tube"
+CHANNEL_IFACE_SASL_AUTH = CHANNEL + ".Interface.SaslAuthentication.DRAFT"
+CHANNEL_IFACE_CONFERENCE = CHANNEL + '.Interface.Conference'
+
+CHANNEL_TYPE_CALL = CHANNEL + ".Type.Call.DRAFT"
+CHANNEL_TYPE_CONTACT_LIST = CHANNEL + ".Type.ContactList"
+CHANNEL_TYPE_CONTACT_SEARCH = CHANNEL + ".Type.ContactSearch"
+CHANNEL_TYPE_TEXT = CHANNEL + ".Type.Text"
+CHANNEL_TYPE_TUBES = CHANNEL + ".Type.Tubes"
+CHANNEL_TYPE_STREAM_TUBE = CHANNEL + ".Type.StreamTube"
+CHANNEL_TYPE_DBUS_TUBE = CHANNEL + ".Type.DBusTube"
+CHANNEL_TYPE_STREAMED_MEDIA = CHANNEL + ".Type.StreamedMedia"
+CHANNEL_TYPE_TEXT = CHANNEL + ".Type.Text"
+CHANNEL_TYPE_FILE_TRANSFER = CHANNEL + ".Type.FileTransfer"
+CHANNEL_TYPE_SERVER_AUTHENTICATION = \
+ CHANNEL + ".Type.ServerAuthentication.DRAFT"
+CHANNEL_TYPE_SERVER_TLS_CONNECTION = \
+ CHANNEL + ".Type.ServerTLSConnection"
+
+TP_AWKWARD_PROPERTIES = "org.freedesktop.Telepathy.Properties"
+PROPERTY_FLAG_READ = 1
+PROPERTY_FLAG_WRITE = 2
+PROPERTY_FLAGS_RW = PROPERTY_FLAG_READ | PROPERTY_FLAG_WRITE
+
+CHANNEL_TYPE = CHANNEL + '.ChannelType'
+TARGET_HANDLE_TYPE = CHANNEL + '.TargetHandleType'
+TARGET_HANDLE = CHANNEL + '.TargetHandle'
+TARGET_ID = CHANNEL + '.TargetID'
+REQUESTED = CHANNEL + '.Requested'
+INITIATOR_HANDLE = CHANNEL + '.InitiatorHandle'
+INITIATOR_ID = CHANNEL + '.InitiatorID'
+INTERFACES = CHANNEL + '.Interfaces'
+
+INITIAL_AUDIO = CHANNEL_TYPE_STREAMED_MEDIA + '.InitialAudio'
+INITIAL_VIDEO = CHANNEL_TYPE_STREAMED_MEDIA + '.InitialVideo'
+IMMUTABLE_STREAMS = CHANNEL_TYPE_STREAMED_MEDIA + '.ImmutableStreams'
+
+CALL_INITIAL_AUDIO = CHANNEL_TYPE_CALL + '.InitialAudio'
+CALL_INITIAL_VIDEO = CHANNEL_TYPE_CALL + '.InitialVideo'
+CALL_MUTABLE_CONTENTS = CHANNEL_TYPE_CALL + '.MutableContents'
+
+CALL_CONTENT = 'org.freedesktop.Telepathy.Call.Content.DRAFT'
+CALL_CONTENT_IFACE_MEDIA = \
+ 'org.freedesktop.Telepathy.Call.Content.Interface.Media.DRAFT'
+
+CALL_CONTENT_CODECOFFER = \
+ 'org.freedesktop.Telepathy.Call.Content.CodecOffer.DRAFT'
+
+CALL_STREAM = 'org.freedesktop.Telepathy.Call.Stream.DRAFT'
+CALL_STREAM_IFACE_MEDIA = \
+ 'org.freedesktop.Telepathy.Call.Stream.Interface.Media.DRAFT'
+
+CALL_STREAM_ENDPOINT = 'org.freedesktop.Telepathy.Call.Stream.Endpoint.DRAFT'
+
+CALL_MEDIA_TYPE_AUDIO = 0
+CALL_MEDIA_TYPE_VIDEO = 1
+
+CALL_STREAM_TRANSPORT_RAW_UDP = 0
+CALL_STREAM_TRANSPORT_ICE = 1
+CALL_STREAM_TRANSPORT_GOOGLE = 2
+
+CALL_STATE_UNKNOWN = 0
+CALL_STATE_PENDING_INITIATOR = 1
+CALL_STATE_PENDING_RECEIVER = 2
+CALL_STATE_ACCEPTED = 3
+CALL_STATE_ENDED = 4
+
+CALL_MEMBER_FLAG_RINGING = 1
+CALL_MEMBER_FLAG_HELD = 2
+
+CALL_DISPOSITION_NONE = 0
+CALL_DISPOSITION_EARLY_MEDIA = 1
+CALL_DISPOSITION_INITIAL = 2
+
+CALL_SENDING_STATE_NONE = 0
+CALL_SENDING_STATE_PENDING_SEND = 1
+CALL_SENDING_STATE_SENDING = 2
+
+SUBSCRIPTION_STATE_UNKNOWN = 0
+SUBSCRIPTION_STATE_NO = 1
+SUBSCRIPTION_STATE_REMOVED_REMOTELY = 2
+SUBSCRIPTION_STATE_ASK = 3
+SUBSCRIPTION_STATE_YES = 4
+
+CONTACT_LIST_STATE_NONE = 0
+CONTACT_LIST_STATE_WAITING = 1
+CONTACT_LIST_STATE_FAILURE = 2
+CONTACT_LIST_STATE_SUCCESS = 3
+
+CONN = "org.freedesktop.Telepathy.Connection"
+CONN_IFACE_AVATARS = CONN + '.Interface.Avatars'
+CONN_IFACE_ALIASING = CONN + '.Interface.Aliasing'
+CONN_IFACE_CAPS = CONN + '.Interface.Capabilities'
+CONN_IFACE_CONTACTS = CONN + '.Interface.Contacts'
+CONN_IFACE_CONTACT_CAPS = CONN + '.Interface.ContactCapabilities.DRAFT'
+CONN_IFACE_CONTACT_INFO = CONN + ".Interface.ContactInfo"
+CONN_IFACE_PRESENCE = CONN + '.Interface.Presence'
+CONN_IFACE_SIMPLE_PRESENCE = CONN + '.Interface.SimplePresence'
+CONN_IFACE_REQUESTS = CONN + '.Interface.Requests'
+CONN_IFACE_LOCATION = CONN + '.Interface.Location'
+CONN_IFACE_GABBLE_DECLOAK = CONN + '.Interface.Gabble.Decloak'
+CONN_IFACE_MAIL_NOTIFICATION = CONN + '.Interface.MailNotification.DRAFT'
+CONN_IFACE_CONTACT_LIST = CONN + '.Interface.ContactList'
+CONN_IFACE_CONTACT_GROUPS = CONN + '.Interface.ContactGroups'
+
+ATTR_CONTACT_CAPABILITIES = CONN_IFACE_CONTACT_CAPS + '/capabilities'
+
+STREAM_HANDLER = 'org.freedesktop.Telepathy.Media.StreamHandler'
+
+ERROR = 'org.freedesktop.Telepathy.Error'
+INVALID_ARGUMENT = ERROR + '.InvalidArgument'
+NOT_IMPLEMENTED = ERROR + '.NotImplemented'
+NOT_AVAILABLE = ERROR + '.NotAvailable'
+PERMISSION_DENIED = ERROR + '.PermissionDenied'
+OFFLINE = ERROR + '.Offline'
+NOT_CAPABLE = ERROR + '.NotCapable'
+CONNECTION_REFUSED = ERROR + '.ConnectionRefused'
+CONNECTION_FAILED = ERROR + '.ConnectionFailed'
+CONNECTION_LOST = ERROR + '.ConnectionLost'
+CANCELLED = ERROR + '.Cancelled'
+DISCONNECTED = ERROR + '.Disconnected'
+REGISTRATION_EXISTS = ERROR + '.RegistrationExists'
+AUTHENTICATION_FAILED = ERROR + '.AuthenticationFailed'
+CONNECTION_REPLACED = ERROR + '.ConnectionReplaced'
+ALREADY_CONNECTED = ERROR + '.AlreadyConnected'
+NETWORK_ERROR = ERROR + '.NetworkError'
+NOT_YET = ERROR + '.NotYet'
+INVALID_HANDLE = ERROR + '.InvalidHandle'
+CERT_UNTRUSTED = ERROR + '.Cert.Untrusted'
+
+UNKNOWN_METHOD = 'org.freedesktop.DBus.Error.UnknownMethod'
+
+TUBE_PARAMETERS = CHANNEL_IFACE_TUBE + '.Parameters'
+TUBE_STATE = CHANNEL_IFACE_TUBE + '.State'
+STREAM_TUBE_SERVICE = CHANNEL_TYPE_STREAM_TUBE + '.Service'
+DBUS_TUBE_SERVICE_NAME = CHANNEL_TYPE_DBUS_TUBE + '.ServiceName'
+DBUS_TUBE_DBUS_NAMES = CHANNEL_TYPE_DBUS_TUBE + '.DBusNames'
+DBUS_TUBE_SUPPORTED_ACCESS_CONTROLS = CHANNEL_TYPE_DBUS_TUBE + '.SupportedAccessControls'
+STREAM_TUBE_SUPPORTED_SOCKET_TYPES = CHANNEL_TYPE_STREAM_TUBE + '.SupportedSocketTypes'
+
+CONFERENCE_INITIAL_CHANNELS = CHANNEL_IFACE_CONFERENCE + '.InitialChannels'
+CONFERENCE_INITIAL_INVITEE_HANDLES = CHANNEL_IFACE_CONFERENCE + '.InitialInviteeHandles'
+CONFERENCE_INITIAL_INVITEE_IDS = CHANNEL_IFACE_CONFERENCE + '.InitialInviteeIDs'
+
+CONTACT_SEARCH_ASK = CHANNEL_TYPE_CONTACT_SEARCH + '.AvailableSearchKeys'
+CONTACT_SEARCH_SERVER = CHANNEL_TYPE_CONTACT_SEARCH + '.Server'
+CONTACT_SEARCH_STATE = CHANNEL_TYPE_CONTACT_SEARCH + '.SearchState'
+
+SEARCH_NOT_STARTED = 0
+SEARCH_IN_PROGRESS = 1
+SEARCH_MORE_AVAILABLE = 2
+SEARCH_COMPLETED = 3
+SEARCH_FAILED = 4
+
+TUBE_CHANNEL_STATE_LOCAL_PENDING = 0
+TUBE_CHANNEL_STATE_REMOTE_PENDING = 1
+TUBE_CHANNEL_STATE_OPEN = 2
+TUBE_CHANNEL_STATE_NOT_OFFERED = 3
+
+MEDIA_STREAM_TYPE_AUDIO = 0
+MEDIA_STREAM_TYPE_VIDEO = 1
+
+SOCKET_ADDRESS_TYPE_UNIX = 0
+SOCKET_ADDRESS_TYPE_ABSTRACT_UNIX = 1
+SOCKET_ADDRESS_TYPE_IPV4 = 2
+SOCKET_ADDRESS_TYPE_IPV6 = 3
+
+SOCKET_ACCESS_CONTROL_LOCALHOST = 0
+SOCKET_ACCESS_CONTROL_PORT = 1
+SOCKET_ACCESS_CONTROL_NETMASK = 2
+SOCKET_ACCESS_CONTROL_CREDENTIALS = 3
+
+TUBE_STATE_LOCAL_PENDING = 0
+TUBE_STATE_REMOTE_PENDING = 1
+TUBE_STATE_OPEN = 2
+TUBE_STATE_NOT_OFFERED = 3
+
+TUBE_TYPE_DBUS = 0
+TUBE_TYPE_STREAM = 1
+
+MEDIA_STREAM_DIRECTION_NONE = 0
+MEDIA_STREAM_DIRECTION_SEND = 1
+MEDIA_STREAM_DIRECTION_RECEIVE = 2
+MEDIA_STREAM_DIRECTION_BIDIRECTIONAL = 3
+
+MEDIA_STREAM_PENDING_LOCAL_SEND = 1
+MEDIA_STREAM_PENDING_REMOTE_SEND = 2
+
+MEDIA_STREAM_TYPE_AUDIO = 0
+MEDIA_STREAM_TYPE_VIDEO = 1
+
+MEDIA_STREAM_STATE_DISCONNECTED = 0
+MEDIA_STREAM_STATE_CONNECTING = 1
+MEDIA_STREAM_STATE_CONNECTED = 2
+
+MEDIA_STREAM_DIRECTION_NONE = 0
+MEDIA_STREAM_DIRECTION_SEND = 1
+MEDIA_STREAM_DIRECTION_RECEIVE = 2
+MEDIA_STREAM_DIRECTION_BIDIRECTIONAL = 3
+
+FT_STATE_NONE = 0
+FT_STATE_PENDING = 1
+FT_STATE_ACCEPTED = 2
+FT_STATE_OPEN = 3
+FT_STATE_COMPLETED = 4
+FT_STATE_CANCELLED = 5
+
+FT_STATE_CHANGE_REASON_NONE = 0
+FT_STATE_CHANGE_REASON_REQUESTED = 1
+FT_STATE_CHANGE_REASON_LOCAL_STOPPED = 2
+FT_STATE_CHANGE_REASON_REMOTE_STOPPED = 3
+FT_STATE_CHANGE_REASON_LOCAL_ERROR = 4
+FT_STATE_CHANGE_REASON_REMOTE_ERROR = 5
+
+FILE_HASH_TYPE_NONE = 0
+FILE_HASH_TYPE_MD5 = 1
+FILE_HASH_TYPE_SHA1 = 2
+FILE_HASH_TYPE_SHA256 = 3
+
+FT_STATE = CHANNEL_TYPE_FILE_TRANSFER + '.State'
+FT_CONTENT_TYPE = CHANNEL_TYPE_FILE_TRANSFER + '.ContentType'
+FT_FILENAME = CHANNEL_TYPE_FILE_TRANSFER + '.Filename'
+FT_SIZE = CHANNEL_TYPE_FILE_TRANSFER + '.Size'
+FT_CONTENT_HASH_TYPE = CHANNEL_TYPE_FILE_TRANSFER + '.ContentHashType'
+FT_CONTENT_HASH = CHANNEL_TYPE_FILE_TRANSFER + '.ContentHash'
+FT_DESCRIPTION = CHANNEL_TYPE_FILE_TRANSFER + '.Description'
+FT_DATE = CHANNEL_TYPE_FILE_TRANSFER + '.Date'
+FT_AVAILABLE_SOCKET_TYPES = CHANNEL_TYPE_FILE_TRANSFER + '.AvailableSocketTypes'
+FT_TRANSFERRED_BYTES = CHANNEL_TYPE_FILE_TRANSFER + '.TransferredBytes'
+FT_INITIAL_OFFSET = CHANNEL_TYPE_FILE_TRANSFER + '.InitialOffset'
+FT_FILE_COLLECTION = CHANNEL_TYPE_FILE_TRANSFER + '.FUTURE.FileCollection'
+
+GF_CAN_ADD = 1
+GF_CAN_REMOVE = 2
+GF_CAN_RESCIND = 4
+GF_MESSAGE_ADD = 8
+GF_MESSAGE_REMOVE = 16
+GF_MESSAGE_ACCEPT = 32
+GF_MESSAGE_REJECT = 64
+GF_MESSAGE_RESCIND = 128
+GF_CHANNEL_SPECIFIC_HANDLES = 256
+GF_ONLY_ONE_GROUP = 512
+GF_HANDLE_OWNERS_NOT_AVAILABLE = 1024
+GF_PROPERTIES = 2048
+GF_MEMBERS_CHANGED_DETAILED = 4096
+
+GC_REASON_NONE = 0
+GC_REASON_OFFLINE = 1
+GC_REASON_KICKED = 2
+GC_REASON_BUSY = 3
+GC_REASON_INVITED = 4
+GC_REASON_BANNED = 5
+GC_REASON_ERROR = 6
+GC_REASON_INVALID_CONTACT = 7
+GC_REASON_NO_ANSWER = 8
+GC_REASON_RENAMED = 9
+GC_REASON_PERMISSION_DENIED = 10
+GC_REASON_SEPARATED = 11
+
+HS_UNHELD = 0
+HS_HELD = 1
+HS_PENDING_HOLD = 2
+HS_PENDING_UNHOLD = 3
+
+HSR_NONE = 0
+HSR_REQUESTED = 1
+HSR_RESOURCE_NOT_AVAILABLE = 2
+
+CALL_STATE_RINGING = 1
+CALL_STATE_QUEUED = 2
+CALL_STATE_HELD = 4
+CALL_STATE_FORWARDED = 8
+
+CONN_STATUS_CONNECTED = 0
+CONN_STATUS_CONNECTING = 1
+CONN_STATUS_DISCONNECTED = 2
+
+CSR_NONE_SPECIFIED = 0
+CSR_REQUESTED = 1
+CSR_NETWORK_ERROR = 2
+CSR_AUTHENTICATION_FAILED = 3
+CSR_ENCRYPTION_ERROR = 4
+CSR_NAME_IN_USE = 5
+CSR_CERT_NOT_PROVIDED = 6
+CSR_CERT_UNTRUSTED = 7
+CSR_CERT_EXPIRED = 8
+CSR_CERT_NOT_ACTIVATED = 9
+CSR_CERT_HOSTNAME_MISMATCH = 10
+CSR_CERT_FINGERPRINT_MISMATCH = 11
+CSR_CERT_SELF_SIGNED = 12
+CSR_CERT_OTHER_ERROR = 13
+
+BUDDY_INFO = 'org.laptop.Telepathy.BuddyInfo'
+ACTIVITY_PROPERTIES = 'org.laptop.Telepathy.ActivityProperties'
+
+CHAT_STATE_GONE = 0
+CHAT_STATE_INACTIVE = 1
+CHAT_STATE_ACTIVE = 2
+CHAT_STATE_PAUSED = 3
+CHAT_STATE_COMPOSING = 4
+
+# Channel_Media_Capabilities
+MEDIA_CAP_AUDIO = 1
+MEDIA_CAP_VIDEO = 2
+MEDIA_CAP_STUN = 4
+MEDIA_CAP_GTALKP2P = 8
+MEDIA_CAP_ICEUDP = 16
+MEDIA_CAP_IMMUTABLE_STREAMS = 32
+
+CLIENT = 'org.freedesktop.Telepathy.Client'
+
+PRESENCE_OFFLINE = 1
+PRESENCE_AVAILABLE = 2
+PRESENCE_AWAY = 3
+PRESENCE_EXTENDED_AWAY = 4
+PRESENCE_HIDDEN = 5
+PRESENCE_BUSY = 6
+PRESENCE_UNKNOWN = 7
+PRESENCE_ERROR = 8
+
+CONTACT_INFO_FLAG_CAN_SET = 1
+CONTACT_INFO_FLAG_PUSH = 2
+CONTACT_INFO_FIELD_FLAG_PARAMETERS_MANDATORY = 1
+
+# Channel_Type_ServerAuthentication
+AUTH_TYPE_SASL = 0
+AUTH_TYPE_CAPTCHA = 1
+
+# Channel_Interface_SaslAuthentication
+SASL_STATUS_NOT_STARTED = 0
+SASL_STATUS_IN_PROGRESS = 1
+SASL_STATUS_SERVER_SUCCEEDED = 2
+SASL_STATUS_CLIENT_ACCEPTED = 3
+SASL_STATUS_SUCCEEDED = 4
+SASL_STATUS_SERVER_FAILED = 5
+SASL_STATUS_CLIENT_FAILED = 6
+
+SASL_ABORT_REASON_INVALID_CHALLENGE = 0
+SASL_ABORT_REASON_USER_ABORT = 1
+
+AUTH_METHOD = CHANNEL_TYPE_SERVER_AUTHENTICATION + ".AuthenticationMethod"
+AUTH_INFO = CHANNEL_TYPE_SERVER_AUTHENTICATION + ".AuthenticationInformation"
+SASL_AVAILABLE_MECHANISMS = CHANNEL_IFACE_SASL_AUTH + ".AvailableMechanisms"
+
+# Channel_Type_ServerTLSConnection
+TLS_CERT_PATH = CHANNEL_TYPE_SERVER_TLS_CONNECTION + ".ServerCertificate"
+TLS_HOSTNAME = CHANNEL_TYPE_SERVER_TLS_CONNECTION + ".Hostname"
+
+# Connection.Interface.Location
+
+LOCATION_FEATURE_CAN_SET = 1
+
+# Channel.Type.Text
+
+MT_NORMAL = 0
+MT_ACTION = 1
+MT_NOTICE = 2
+MT_AUTO_REPLY = 3
+MT_DELIVERY_REPORT = 4
+
+PROTOCOL = 'org.freedesktop.Telepathy.Protocol'
+PARAM_REQUIRED = 1
+PARAM_REGISTER = 2
+PARAM_HAS_DEFAULT = 4
+PARAM_SECRET = 8
+PARAM_DBUS_PROPERTY = 16
+
+AUTHENTICATION = 'org.freedesktop.Telepathy.Authentication'
+AUTH_TLS_CERT = AUTHENTICATION + ".TLSCertificate"
+
+TLS_CERT_STATE_PENDING = 0
+TLS_CERT_STATE_ACCEPTED = 1
+TLS_CERT_STATE_REJECTED = 2
+
+TLS_REJECT_REASON_UNKNOWN = 0
+TLS_REJECT_REASON_UNTRUSTED = 1
diff --git a/tests/twisted/servicetest.py b/tests/twisted/servicetest.py
index ecb44f6..5685eec 100644
--- a/tests/twisted/servicetest.py
+++ b/tests/twisted/servicetest.py
@@ -6,54 +6,26 @@ Infrastructure code for testing connection managers.
from twisted.internet import glib2reactor
from twisted.internet.protocol import Protocol, Factory, ClientFactory
glib2reactor.install()
+import sys
+import time
import pprint
-import traceback
import unittest
import dbus.glib
from twisted.internet import reactor
+import constants as cs
+
tp_name_prefix = 'org.freedesktop.Telepathy'
tp_path_prefix = '/org/freedesktop/Telepathy'
-class TryNextHandler(Exception):
- pass
-
-def lazy(func):
- def handler(event, data):
- if func(event, data):
- return True
- else:
- raise TryNextHandler()
- handler.__name__ = func.__name__
- return handler
-
-def match(type, **kw):
- def decorate(func):
- def handler(event, data, *extra, **extra_kw):
- if event.type != type:
- return False
-
- for key, value in kw.iteritems():
- if not hasattr(event, key):
- return False
-
- if getattr(event, key) != value:
- return False
-
- return func(event, data, *extra, **extra_kw)
-
- handler.__name__ = func.__name__
- return handler
-
- return decorate
-
class Event:
def __init__(self, type, **kw):
self.__dict__.update(kw)
self.type = type
+ (self.subqueue, self.subtype) = type.split ("-", 1)
def format_event(event):
ret = ['- type %s' % event.type]
@@ -68,113 +40,24 @@ def format_event(event):
return ret
-class EventTest:
- """Somewhat odd event dispatcher for asynchronous tests.
-
- Callbacks are kept in a queue. Incoming events are passed to the first
- callback. If the callback returns True, the callback is removed. If the
- callback raises AssertionError, the test fails. If there are no more
- callbacks, the test passes. The reactor is stopped when the test passes.
- """
-
- def __init__(self):
- self.queue = []
- self.data = {'test': self}
- self.timeout_delayed_call = reactor.callLater(5, self.timeout_cb)
- #self.verbose = True
- self.verbose = False
- # ugh
- self.stopping = False
-
- def timeout_cb(self):
- print 'timed out waiting for events'
- print self.queue[0]
- self.fail()
-
- def fail(self):
- # ugh; better way to stop the reactor and exit(1)?
- import os
- os._exit(1)
-
- def expect(self, f):
- self.queue.append(f)
-
- def log(self, s):
- if self.verbose:
- print s
-
- def try_stop(self):
- if self.stopping:
- return True
-
- if not self.queue:
- self.log('no handlers left; stopping')
- self.stopping = True
- reactor.stop()
- return True
-
- return False
-
- def call_handlers(self, event):
- self.log('trying %r' % self.queue[0])
- handler = self.queue.pop(0)
-
- try:
- ret = handler(event, self.data)
- if not ret:
- self.queue.insert(0, handler)
- except TryNextHandler, e:
- if self.queue:
- ret = self.call_handlers(event)
- else:
- ret = False
- self.queue.insert(0, handler)
-
- return ret
-
- def handle_event(self, event):
- if self.try_stop():
- return
-
- self.log('got event:')
- self.log('- type: %s' % event.type)
- map(self.log, format_event(event))
-
- try:
- ret = self.call_handlers(event)
- except SystemExit, e:
- if e.code:
- print "Unsuccessful exit:", e
- self.fail()
- else:
- self.queue[:] = []
- ret = True
- except AssertionError, e:
- print 'test failed:'
- traceback.print_exc()
- self.fail()
- except (Exception, KeyboardInterrupt), e:
- print 'error in handler:'
- traceback.print_exc()
- self.fail()
-
- if ret not in (True, False):
- print ("warning: %s() returned something other than True or False"
- % self.queue[0].__name__)
-
- if ret:
- self.timeout_delayed_call.reset(5)
- self.log('event handled')
- else:
- self.log('event not handled')
-
- self.log('')
- self.try_stop()
-
class EventPattern:
def __init__(self, type, **properties):
self.type = type
+ self.predicate = None
+ if 'predicate' in properties:
+ self.predicate = properties['predicate']
+ del properties['predicate']
self.properties = properties
+ (self.subqueue, self.subtype) = type.split ("-", 1)
+
+ def __repr__(self):
+ properties = dict(self.properties)
+
+ if self.predicate is not None:
+ properties['predicate'] = self.predicate
+
+ return '%s(%r, **%r)' % (
+ self.__class__.__name__, self.type, properties)
def match(self, event):
if event.type != self.type:
@@ -187,7 +70,11 @@ class EventPattern:
except AttributeError:
return False
- return True
+ if self.predicate is None or self.predicate(event):
+ return True
+
+ return False
+
class TimeoutError(Exception):
pass
@@ -200,6 +87,8 @@ class BaseEventQueue:
def __init__(self, timeout=None):
self.verbose = False
+ self.forbidden_events = set()
+ self.event_queues = {}
if timeout is None:
self.timeout = 5
@@ -210,16 +99,56 @@ class BaseEventQueue:
if self.verbose:
print s
+ def log_queues(self, queues):
+ self.log ("Waiting for event on: %s" % ", ".join(queues))
+
+ def log_event(self, event):
+ self.log('got event:')
+
+ if self.verbose:
+ map(self.log, format_event(event))
+
+ def forbid_events(self, patterns):
+ """
+ Add patterns (an iterable of EventPattern) to the set of forbidden
+ events. If a forbidden event occurs during an expect or expect_many,
+ the test will fail.
+ """
+ self.forbidden_events.update(set(patterns))
+
+ def unforbid_events(self, patterns):
+ """
+ Remove 'patterns' (an iterable of EventPattern) from the set of
+ forbidden events. These must be the same EventPattern pointers that
+ were passed to forbid_events.
+ """
+ self.forbidden_events.difference_update(set(patterns))
+
+ def _check_forbidden(self, event):
+ for e in self.forbidden_events:
+ if e.match(event):
+ print "forbidden event occurred:"
+ for x in format_event(event):
+ print x
+ assert False
+
def expect(self, type, **kw):
+ """
+ Waits for an event matching the supplied pattern to occur, and returns
+ it. For example, to await a D-Bus signal with particular arguments:
+
+ e = q.expect('dbus-signal', signal='Badgers', args=["foo", 42])
+ """
pattern = EventPattern(type, **kw)
+ t = time.time()
while True:
- event = self.wait()
- self.log('got event:')
- map(self.log, format_event(event))
+ event = self.wait([pattern.subqueue])
+ self._check_forbidden(event)
if pattern.match(event):
- self.log('handled')
+ self.log('handled, took %0.3f ms'
+ % ((time.time() - t) * 1000.0) )
self.log('')
return event
@@ -227,16 +156,57 @@ class BaseEventQueue:
self.log('')
def expect_many(self, *patterns):
+ """
+ Waits for events matching all of the supplied EventPattern instances to
+ return, and returns a list of events in the same order as the patterns
+ they matched. After a pattern is successfully matched, it is not
+ considered for future events; if more than one unsatisfied pattern
+ matches an event, the first "wins".
+
+ Note that the expected events may occur in any order. If you're
+ expecting a series of events in a particular order, use repeated calls
+ to expect() instead.
+
+ This method is useful when you're awaiting a number of events which may
+ happen in any order. For instance, in telepathy-gabble, calling a D-Bus
+ method often causes a value to be returned immediately, as well as a
+ query to be sent to the server. Since these events may reach the test
+ in either order, the following is incorrect and will fail if the IQ
+ happens to reach the test first:
+
+ ret = q.expect('dbus-return', method='Foo')
+ query = q.expect('stream-iq', query_ns=ns.FOO)
+
+ The following would be correct:
+
+ ret, query = q.expect_many(
+ EventPattern('dbus-return', method='Foo'),
+ EventPattern('stream-iq', query_ns=ns.FOO),
+ )
+ """
ret = [None] * len(patterns)
+ t = time.time()
while None in ret:
- event = self.wait()
- self.log('got event:')
- map(self.log, format_event(event))
+ try:
+ queues = set()
+ for i, pattern in enumerate(patterns):
+ if ret[i] is None:
+ queues.add(pattern.subqueue)
+ event = self.wait(queues)
+ except TimeoutError:
+ self.log('timeout')
+ self.log('still expecting:')
+ for i, pattern in enumerate(patterns):
+ if ret[i] is None:
+ self.log(' - %r' % pattern)
+ raise
+ self._check_forbidden(event)
for i, pattern in enumerate(patterns):
- if pattern.match(event):
- self.log('handled')
+ if ret[i] is None and pattern.match(event):
+ self.log('handled, took %0.3f ms'
+ % ((time.time() - t) * 1000.0) )
self.log('')
ret[i] = event
break
@@ -249,9 +219,7 @@ class BaseEventQueue:
def demand(self, type, **kw):
pattern = EventPattern(type, **kw)
- event = self.wait()
- self.log('got event:')
- map(self.log, format_event(event))
+ event = self.wait([pattern.subqueue])
if pattern.match(event):
self.log('handled')
@@ -261,14 +229,34 @@ class BaseEventQueue:
self.log('not handled')
raise RuntimeError('expected %r, got %r' % (pattern, event))
+ def queues_available(self, queues):
+ if queues == None:
+ return self.event_queues.keys()
+ else:
+ available = self.event_queues.keys()
+ return filter(lambda x: x in available, queues)
+
+
+ def pop_next(self, queue):
+ events = self.event_queues[queue]
+ e = events.pop(0)
+ if not events:
+ self.event_queues.pop (queue)
+ return e
+
+ def append(self, event):
+ self.log ("Adding to queue")
+ self.log_event (event)
+ self.event_queues[event.subqueue] = \
+ self.event_queues.get(event.subqueue, []) + [event]
+
class IteratingEventQueue(BaseEventQueue):
"""Event queue that works by iterating the Twisted reactor."""
def __init__(self, timeout=None):
BaseEventQueue.__init__(self, timeout)
- self.events = []
- def wait(self):
+ def wait(self, queues=None):
stop = [False]
def later():
@@ -276,58 +264,92 @@ class IteratingEventQueue(BaseEventQueue):
delayed_call = reactor.callLater(self.timeout, later)
- while (not self.events) and (not stop[0]):
- reactor.iterate(0.1)
+ self.log_queues(queues)
+
+ qa = self.queues_available(queues)
+ while not qa and (not stop[0]):
+ reactor.iterate(0.01)
+ qa = self.queues_available(queues)
- if self.events:
+ if qa:
delayed_call.cancel()
- return self.events.pop(0)
+ e = self.pop_next (qa[0])
+ self.log_event (e)
+ return e
else:
raise TimeoutError
- def append(self, event):
- self.events.append(event)
-
- # compatibility
- handle_event = append
-
class TestEventQueue(BaseEventQueue):
def __init__(self, events):
BaseEventQueue.__init__(self)
- self.events = events
+ for e in events:
+ self.append (e)
- def wait(self):
- if self.events:
- return self.events.pop(0)
+ def wait(self, queues = None):
+ qa = self.queues_available(queues)
+
+ if qa:
+ return self.pop_next (qa[0])
else:
raise TimeoutError
class EventQueueTest(unittest.TestCase):
def test_expect(self):
- queue = TestEventQueue([Event('foo'), Event('bar')])
- assert queue.expect('foo').type == 'foo'
- assert queue.expect('bar').type == 'bar'
+ queue = TestEventQueue([Event('test-foo'), Event('test-bar')])
+ assert queue.expect('test-foo').type == 'test-foo'
+ assert queue.expect('test-bar').type == 'test-bar'
def test_expect_many(self):
- queue = TestEventQueue([Event('foo'), Event('bar')])
+ queue = TestEventQueue([Event('test-foo'),
+ Event('test-bar')])
bar, foo = queue.expect_many(
- EventPattern('bar'),
- EventPattern('foo'))
- assert bar.type == 'bar'
- assert foo.type == 'foo'
+ EventPattern('test-bar'),
+ EventPattern('test-foo'))
+ assert bar.type == 'test-bar'
+ assert foo.type == 'test-foo'
+
+ def test_expect_many2(self):
+ # Test that events are only matched against patterns that haven't yet
+ # been matched. This tests a regression.
+ queue = TestEventQueue([Event('test-foo', x=1), Event('test-foo', x=2)])
+ foo1, foo2 = queue.expect_many(
+ EventPattern('test-foo'),
+ EventPattern('test-foo'))
+ assert foo1.type == 'test-foo' and foo1.x == 1
+ assert foo2.type == 'test-foo' and foo2.x == 2
+
+ def test_expect_queueing(self):
+ queue = TestEventQueue([Event('foo-test', x=1),
+ Event('foo-test', x=2)])
+
+ queue.append(Event('bar-test', x=1))
+ queue.append(Event('bar-test', x=2))
+
+ queue.append(Event('baz-test', x=1))
+ queue.append(Event('baz-test', x=2))
+
+ for x in xrange(1,2):
+ e = queue.expect ('baz-test')
+ assertEquals (x, e.x)
+
+ e = queue.expect ('bar-test')
+ assertEquals (x, e.x)
+
+ e = queue.expect ('foo-test')
+ assertEquals (x, e.x)
def test_timeout(self):
queue = TestEventQueue([])
- self.assertRaises(TimeoutError, queue.expect, 'foo')
+ self.assertRaises(TimeoutError, queue.expect, 'test-foo')
def test_demand(self):
- queue = TestEventQueue([Event('foo'), Event('bar')])
- foo = queue.demand('foo')
- assert foo.type == 'foo'
+ queue = TestEventQueue([Event('test-foo'), Event('test-bar')])
+ foo = queue.demand('test-foo')
+ assert foo.type == 'test-foo'
def test_demand_fail(self):
- queue = TestEventQueue([Event('foo'), Event('bar')])
- self.assertRaises(RuntimeError, queue.demand, 'bar')
+ queue = TestEventQueue([Event('test-foo'), Event('test-bar')])
+ self.assertRaises(RuntimeError, queue.demand, 'test-bar')
def unwrap(x):
"""Hack to unwrap D-Bus values, so that they're easier to read when
@@ -342,7 +364,10 @@ def unwrap(x):
if isinstance(x, dict):
return dict([(unwrap(k), unwrap(v)) for k, v in x.iteritems()])
- for t in [unicode, str, long, int, float, bool]:
+ if isinstance(x, dbus.Boolean):
+ return bool(x)
+
+ for t in [unicode, str, long, int, float]:
if isinstance(x, t):
return t(x)
@@ -353,21 +378,33 @@ def call_async(test, proxy, method, *args, **kw):
resulting method return/error."""
def reply_func(*ret):
- test.handle_event(Event('dbus-return', method=method,
+ test.append(Event('dbus-return', method=method,
value=unwrap(ret)))
def error_func(err):
- test.handle_event(Event('dbus-error', method=method, error=err))
+ test.append(Event('dbus-error', method=method, error=err,
+ name=err.get_dbus_name(), message=str(err)))
method_proxy = getattr(proxy, method)
kw.update({'reply_handler': reply_func, 'error_handler': error_func})
method_proxy(*args, **kw)
+def sync_dbus(bus, q, conn):
+ # Dummy D-Bus method call
+ # This won't do the right thing unless the proxy has a unique name.
+ assert conn.object.bus_name.startswith(':')
+ root_object = bus.get_object(conn.object.bus_name, '/')
+ call_async(
+ q, dbus.Interface(root_object, 'org.freedesktop.DBus.Peer'), 'Ping')
+ q.expect('dbus-return', method='Ping')
class ProxyWrapper:
def __init__(self, object, default, others):
self.object = object
self.default_interface = dbus.Interface(object, default)
+ self.Properties = dbus.Interface(object, dbus.PROPERTIES_IFACE)
+ self.TpProperties = \
+ dbus.Interface(object, tp_name_prefix + '.Properties')
self.interfaces = dict([
(name, dbus.Interface(object, iface))
for name, iface in others.iteritems()])
@@ -381,8 +418,36 @@ class ProxyWrapper:
return getattr(self.default_interface, name)
-def prepare_test(event_func, name, proto, params):
- bus = dbus.SessionBus()
+def wrap_connection(conn):
+ return ProxyWrapper(conn, tp_name_prefix + '.Connection',
+ dict([
+ (name, tp_name_prefix + '.Connection.Interface.' + name)
+ for name in ['Aliasing', 'Avatars', 'Capabilities', 'Contacts',
+ 'Presence', 'SimplePresence', 'Requests']] +
+ [('Peer', 'org.freedesktop.DBus.Peer'),
+ ('ContactCapabilities', cs.CONN_IFACE_CONTACT_CAPS),
+ ('ContactInfo', cs.CONN_IFACE_CONTACT_INFO),
+ ('Location', cs.CONN_IFACE_LOCATION),
+ ('Future', tp_name_prefix + '.Connection.FUTURE'),
+ ('MailNotification', cs.CONN_IFACE_MAIL_NOTIFICATION),
+ ('ContactList', cs.CONN_IFACE_CONTACT_LIST),
+ ('ContactGroups', cs.CONN_IFACE_CONTACT_GROUPS),
+ ]))
+
+def wrap_channel(chan, type_, extra=None):
+ interfaces = {
+ type_: tp_name_prefix + '.Channel.Type.' + type_,
+ 'Group': tp_name_prefix + '.Channel.Interface.Group',
+ }
+
+ if extra:
+ interfaces.update(dict([
+ (name, tp_name_prefix + '.Channel.Interface.' + name)
+ for name in extra]))
+
+ return ProxyWrapper(chan, tp_name_prefix + '.Channel', interfaces)
+
+def make_connection(bus, event_func, name, proto, params):
cm = bus.get_object(
tp_name_prefix + '.ConnectionManager.%s' % name,
tp_path_prefix + '/ConnectionManager/%s' % name)
@@ -390,28 +455,25 @@ def prepare_test(event_func, name, proto, params):
connection_name, connection_path = cm_iface.RequestConnection(
proto, params)
- conn = bus.get_object(connection_name, connection_path)
- conn = ProxyWrapper(conn, tp_name_prefix + '.Connection',
- dict([
- (name, tp_name_prefix + '.Connection.Interface.' + name)
- for name in ['Aliasing', 'Avatars', 'Capabilities', 'Presence']] +
- [('Peer', 'org.freedesktop.DBus.Peer')]))
+ conn = wrap_connection(bus.get_object(connection_name, connection_path))
bus.add_signal_receiver(
lambda *args, **kw:
event_func(
Event('dbus-signal',
- path=unwrap(kw['path'])[len(tp_path_prefix):],
- signal=kw['member'], args=map(unwrap, args))),
+ path=unwrap(kw['path']),
+ signal=kw['member'], args=map(unwrap, args),
+ interface=kw['interface'])),
None, # signal name
None, # interface
cm._named_service,
path_keyword='path',
member_keyword='member',
+ interface_keyword='interface',
byte_arrays=True
)
- return bus, conn
+ return conn
def make_channel_proxy(conn, path, iface):
bus = dbus.SessionBus()
@@ -419,36 +481,39 @@ def make_channel_proxy(conn, path, iface):
chan = dbus.Interface(chan, tp_name_prefix + '.' + iface)
return chan
-def load_event_handlers():
- path, _, _, _ = traceback.extract_stack()[0]
- import compiler
- import __main__
- ast = compiler.parseFile(path)
- return [
- getattr(__main__, node.name)
- for node in ast.node.asList()
- if node.__class__ == compiler.ast.Function and
- node.name.startswith('expect_')]
-
+# block_reading can be used if the test want to choose when we start to read
+# data from the socket.
class EventProtocol(Protocol):
- def __init__(self, queue=None):
+ def __init__(self, queue=None, block_reading=False):
self.queue = queue
def dataReceived(self, data):
if self.queue is not None:
- self.queue.handle_event(Event('socket-data', protocol=self,
+ self.queue.append(Event('socket-data', protocol=self,
data=data))
def sendData(self, data):
self.transport.write(data)
+ def connectionMade(self):
+ if self.block_reading:
+ self.transport.stopReading()
+
+ def connectionLost(self, reason=None):
+ if self.queue is not None:
+ self.queue.append(Event('socket-disconnected', protocol=self))
+
class EventProtocolFactory(Factory):
- def __init__(self, queue):
+ def __init__(self, queue, block_reading=False):
self.queue = queue
+ self.block_reading = block_reading
+
+ def _create_protocol(self):
+ return EventProtocol(self.queue, self.block_reading)
def buildProtocol(self, addr):
- proto = EventProtocol(self.queue)
- self.queue.handle_event(Event('socket-connected', protocol=proto))
+ proto = self._create_protocol()
+ self.queue.append(Event('socket-connected', protocol=proto))
return proto
class EventProtocolClientFactory(EventProtocolFactory, ClientFactory):
@@ -456,7 +521,7 @@ class EventProtocolClientFactory(EventProtocolFactory, ClientFactory):
def watch_tube_signals(q, tube):
def got_signal_cb(*args, **kwargs):
- q.handle_event(Event('tube-signal',
+ q.append(Event('tube-signal',
path=kwargs['path'],
signal=kwargs['member'],
args=map(unwrap, args),
@@ -466,6 +531,81 @@ def watch_tube_signals(q, tube):
path_keyword='path', member_keyword='member',
byte_arrays=True)
+def pretty(x):
+ return pprint.pformat(unwrap(x))
+
+def assertEquals(expected, value):
+ if expected != value:
+ raise AssertionError(
+ "expected:\n%s\ngot:\n%s" % (pretty(expected), pretty(value)))
+
+def assertSameSets(expected, value):
+ exp_set = set(expected)
+ val_set = set(value)
+
+ if exp_set != val_set:
+ raise AssertionError(
+ "expected contents:\n%s\ngot:\n%s" % (
+ pretty(exp_set), pretty(val_set)))
+
+def assertNotEquals(expected, value):
+ if expected == value:
+ raise AssertionError(
+ "expected something other than:\n%s" % pretty(value))
+
+def assertContains(element, value):
+ if element not in value:
+ raise AssertionError(
+ "expected:\n%s\nin:\n%s" % (pretty(element), pretty(value)))
+
+def assertDoesNotContain(element, value):
+ if element in value:
+ raise AssertionError(
+ "expected:\n%s\nnot in:\n%s" % (pretty(element), pretty(value)))
+
+def assertLength(length, value):
+ if len(value) != length:
+ raise AssertionError("expected: length %d, got length %d:\n%s" % (
+ length, len(value), pretty(value)))
+
+def assertFlagsSet(flags, value):
+ masked = value & flags
+ if masked != flags:
+ raise AssertionError(
+ "expected flags %u, of which only %u are set in %u" % (
+ flags, masked, value))
+
+def assertFlagsUnset(flags, value):
+ masked = value & flags
+ if masked != 0:
+ raise AssertionError(
+ "expected none of flags %u, but %u are set in %u" % (
+ flags, masked, value))
+
+def install_colourer():
+ def red(s):
+ return '\x1b[31m%s\x1b[0m' % s
+
+ def green(s):
+ return '\x1b[32m%s\x1b[0m' % s
+
+ patterns = {
+ 'handled': green,
+ 'not handled': red,
+ }
+
+ class Colourer:
+ def __init__(self, fh, patterns):
+ self.fh = fh
+ self.patterns = patterns
+
+ def write(self, s):
+ f = self.patterns.get(s, lambda x: x)
+ self.fh.write(f(s))
+
+ sys.stdout = Colourer(sys.stdout, patterns)
+ return sys.stdout
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/twisted/sofiatest.py b/tests/twisted/sofiatest.py
index 149cacb..94263b9 100644
--- a/tests/twisted/sofiatest.py
+++ b/tests/twisted/sofiatest.py
@@ -20,6 +20,9 @@ class SipProxy(sip.RegisterProxy):
def register(self, message, host, port):
if hasattr(self, 'registrar_handler'):
+ self.event_func(servicetest.Event('sip-register',
+ uri=str(message.uri), headers=message.headers, body=message.body,
+ sip_message=message, host=host, port=port))
if self.registrar_handler(message, host, port):
sip.RegisterProxy.register(self, message, host, port)
else:
@@ -48,9 +51,14 @@ def prepare_test(event_func, register_cb, params=None):
}
if params is not None:
- actual_params.update(params)
+ for k, v in params.items():
+ if v is None:
+ actual_params.pop(k, None)
+ else:
+ actual_params[k] = v
- bus, conn = servicetest.prepare_test(event_func,
+ bus = dbus.SessionBus()
+ conn = servicetest.make_connection(bus, event_func,
'sofiasip', 'sip', actual_params)
port = int(actual_params['port'])
diff --git a/tests/twisted/test-handle-normalisation.py b/tests/twisted/test-handle-normalisation.py
index cf741eb..710ed72 100644
--- a/tests/twisted/test-handle-normalisation.py
+++ b/tests/twisted/test-handle-normalisation.py
@@ -1,14 +1,16 @@
-from servicetest import unwrap, match
-from sofiatest import go
+from servicetest import assertEquals
+from sofiatest import exec_test
import dbus
+import constants as cs
-@match('dbus-signal', signal='StatusChanged', args=[1, 1])
-def expect_connecting(event, data):
- return True
+def test(q, bus, conn, sip):
+ conn.Connect()
+ q.expect('dbus-signal', signal='StatusChanged',
+ args=[cs.CONN_STATUS_CONNECTING, cs.CSR_REQUESTED])
+ q.expect('dbus-signal', signal='StatusChanged',
+ args=[cs.CONN_STATUS_CONNECTED, cs.CSR_REQUESTED])
-@match('dbus-signal', signal='StatusChanged', args=[0, 1])
-def expect_connected(event, data):
tests = [ ('sip:test@localhost', 'sip:test@localhost'),
('test@localhost', 'sip:test@localhost'),
('test', 'sip:test@127.0.0.1'),
@@ -29,22 +31,15 @@ def expect_connected(event, data):
orig = [ x[0] for x in tests ]
expect = [ x[1] for x in tests ]
- handles = data['conn_iface'].RequestHandles(1, orig)
- names = data['conn_iface'].InspectHandles(1, handles)
+ handles = conn.RequestHandles(1, orig)
+ names = conn.InspectHandles(1, handles)
- for a,b in zip(expect, map(lambda x: str(unwrap(x)), names)):
- if a != b:
- print a, b
- raise Exception("test failed")
+ for a,b in zip(expect, names):
+ assertEquals(a, b)
- data['conn_iface'].Disconnect()
- return True
-
-@match('dbus-signal', signal='StatusChanged', args=[2, 1])
-def expect_disconnected(event, data):
- return True
+ conn.Disconnect()
+ q.expect('dbus-signal', signal='StatusChanged',
+ args=[cs.CONN_STATUS_DISCONNECTED, cs.CSR_REQUESTED])
if __name__ == '__main__':
- go()
-
-
+ exec_test(test)
diff --git a/tests/twisted/test-message.py b/tests/twisted/test-message.py
index 913519f..055cccf 100644
--- a/tests/twisted/test-message.py
+++ b/tests/twisted/test-message.py
@@ -141,8 +141,7 @@ def test(q, bus, conn, sip):
send_message(sip, ua_via, 'How are you doing now, old pal?',
sender=contact)
- event = q.expect('dbus-signal', signal='Received')
- assert tp_path_prefix + event.path == chan, (event.path, chan)
+ event = q.expect('dbus-signal', signal='Received', path=chan)
assert event.args[5] == 'How are you doing now, old pal?'
pending_msgs.append(tuple(event.args))
@@ -156,8 +155,7 @@ def test(q, bus, conn, sip):
dbus.Interface(requested_obj, CHANNEL).Close()
del requested_obj
- event = q.expect('dbus-signal', signal='Closed')
- assert tp_path_prefix + event.path == chan, (event.path, chan)
+ event = q.expect('dbus-signal', signal='Closed', path=chan)
requested_obj, handle = test_new_channel (q, bus, conn,
target_uri=contact,
diff --git a/tests/twisted/test-register-sasl.py b/tests/twisted/test-register-sasl.py
new file mode 100644
index 0000000..f4ef924
--- /dev/null
+++ b/tests/twisted/test-register-sasl.py
@@ -0,0 +1,36 @@
+"""
+Test SIP registration failure.
+"""
+
+import dbus
+
+from sofiatest import exec_test
+
+def test(q, bus, conn, sip):
+ conn.Connect()
+ q.expect('dbus-signal', signal='StatusChanged', args=[1, 1])
+
+ q.expect('sip-register')
+
+ nc = q.expect('dbus-signal', signal='NewChannels')
+ (((path, props),),) = nc.args
+ assert props['org.freedesktop.Telepathy.Channel.ChannelType'] == \
+ 'org.freedesktop.Telepathy.Channel.Type.ServerAuthentication'
+ assert props['org.freedesktop.Telepathy.Channel.Interface.SASLAuthentication.AvailableMechanisms'] == \
+ ['X-TELEPATHY-PASSWORD']
+
+ chan = dbus.Interface(bus.get_object(conn._named_service, path),
+ "org.freedesktop.Telepathy.Channel.Interface.SASLAuthentication")
+
+ chan.StartMechanismWithData('X-TELEPATHY-PASSWORD', 'wrong password')
+ chan.AcceptSASL()
+
+ q.expect('sip-register')
+
+ q.expect('dbus-signal', signal='StatusChanged', args=[2, 3])
+ return True
+
+if __name__ == '__main__':
+ exec_test(test, register_cb=lambda *args: False,
+ params={"password": None})
+
diff --git a/tests/twisted/test-register.py b/tests/twisted/test-register.py
index b44b93d..9a292e7 100644
--- a/tests/twisted/test-register.py
+++ b/tests/twisted/test-register.py
@@ -13,5 +13,5 @@ def test(q, bus, conn, sip):
return True
if __name__ == '__main__':
- exec_test(test)
+ exec_test(test, params={"password": None})
diff --git a/tools/Makefile.am b/tools/Makefile.am
index b5b2331..f9d9e29 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -17,7 +17,8 @@ EXTRA_DIST = \
glib-signals-marshal-gen.py \
identity.xsl \
lcov.am \
- libglibcodegen.py
+ libglibcodegen.py \
+ telepathy.am
CLEANFILES = libglibcodegen.pyc libglibcodegen.pyo $(noinst_SCRIPTS)
diff --git a/tools/make-release-mail.py b/tools/make-release-mail.py
new file mode 100644
index 0000000..2bd7c2b
--- /dev/null
+++ b/tools/make-release-mail.py
@@ -0,0 +1,76 @@
+#!/usr/bin/env python
+# vim: set fileencoding=utf-8 :
+#
+# Hello. This is make-release-mail.py from the Telepathy project. It's
+# designed to turn an item from a NEWS file into a mail suitable for sending
+# to <telepathy@lists.freedesktop.org>. I hope that you enjoy your stay.
+
+import sys
+
+def extract_description(package, version, news_path):
+ release_name = []
+ details = []
+
+ with open(news_path) as f:
+ lines = (line for line in f.readlines())
+ for line in lines:
+ # Find the 'telepathy-foo 0.1.2' header
+ if line.startswith("%s %s" % (package, version)):
+ break
+
+ # Skip the ====== line, and the first blank line
+ lines.next()
+ lines.next()
+
+ got_release_name = False
+
+ for line in lines:
+ line = line.rstrip()
+ # If we hit the next version header, we're done
+ if line.startswith(package):
+ break
+ # Else, if we hit a blank line and we're still reading the release
+ # name, we're done with the release name.
+ elif not got_release_name and line == '':
+ got_release_name = True
+ # Otherwise, append this to the relevant list
+ elif not got_release_name:
+ release_name.append(line)
+ else:
+ details.append(line)
+
+ assert got_release_name, (release_name, details)
+
+ # We rstrip details because it picks up a trailing blank line
+ return ('\n'.join(release_name), '\n'.join(details).rstrip())
+
+BASE_URL = 'http://telepathy.freedesktop.org/releases'
+
+def main(package, version, news_path):
+ release_name, details = extract_description(package, version, news_path)
+
+ print """
+%(release_name)s
+
+tarball: %(base_url)s/%(package)s/%(package)s-%(version)s.tar.gz
+signature: %(base_url)s/%(package)s/%(package)s-%(version)s.tar.gz.asc
+
+%(details)s""".strip().rstrip() % {
+ 'base_url': BASE_URL,
+ 'package': package,
+ 'version': version,
+ 'release_name': release_name,
+ 'details': details,
+ }
+
+if __name__ == '__main__':
+ try:
+ package, version, news_path = sys.argv[1:]
+
+ main(package, version, news_path)
+ except ValueError, e:
+ sys.stderr.write(
+ 'Usage: %s package-name package.version.number path/to/NEWS\n' %
+ sys.argv[0])
+ sys.stderr.flush()
+ sys.exit(1)
diff --git a/tools/make-version-script.py b/tools/make-version-script.py
new file mode 100644
index 0000000..0d30aa3
--- /dev/null
+++ b/tools/make-version-script.py
@@ -0,0 +1,208 @@
+#!/usr/bin/python
+
+"""Construct a GNU ld or Debian dpkg version-script from a set of
+RFC822-style symbol lists.
+
+Usage:
+ make-version-script.py [--symbols SYMBOLS] [--unreleased-version VER]
+ [--dpkg "LIBRARY.so.0 LIBRARY0 #MINVER#"]
+ [--dpkg-build-depends-package LIBRARY-dev]
+ [FILES...]
+
+Each FILE starts with RFC822-style headers "Version:" (the name of the
+symbol version, e.g. FOO_1.2.3) and "Extends:" (either the previous
+version, or "-" if this is the first version). Next there is a blank
+line, then a list of C symbols one per line.
+
+Comments (lines starting with whitespace + "#") are allowed and ignored.
+
+If --symbols is given, SYMBOLS lists the symbols actually exported by
+the library (one per line). If --unreleased-version is given, any symbols
+in SYMBOLS but not in FILES are assigned to that version; otherwise, any
+such symbols cause an error.
+
+If --dpkg is given, produce a Debian dpkg-gensymbols file instead of a
+GNU ld version-script. The argument to --dpkg is the first line of the
+resulting symbols file, and --dpkg-build-depends-package can optionally
+be used to set the Build-Depends-Package field.
+
+This script originates in telepathy-glib <http://telepathy.freedesktop.org/> -
+please send us any changes that are needed.
+"""
+
+# Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+# Copyright (C) 2008 Nokia Corporation
+#
+# Copying and distribution of this file, with or without modification,
+# are permitted in any medium without royalty provided the copyright
+# notice and this notice are preserved.
+
+import sys
+from getopt import gnu_getopt
+
+
+def e(format, *args):
+ sys.stderr.write((format + '\n') % args)
+
+
+def main(abifiles, symbols=None, unreleased_version=None,
+ dpkg=False, dpkg_first_line=None, dpkg_build_depends_package=None):
+
+ gnuld = not dpkg
+ symbol_set = None
+
+ if symbols is not None:
+ symbol_set = open(symbols, 'r').readlines()
+ symbol_set = map(str.strip, symbol_set)
+ symbol_set = set(symbol_set)
+
+ versioned_symbols = set()
+
+ dpkg_symbols = []
+ dpkg_versions = []
+
+ if dpkg:
+ assert dpkg_first_line is not None
+ print dpkg_first_line
+ if dpkg_build_depends_package is not None:
+ print "* Build-Depends-Package: %s" % dpkg_build_depends_package
+
+ for filename in abifiles:
+ lines = open(filename, 'r').readlines()
+
+ version = None
+ extends = None
+ release = None
+
+ for i, line in enumerate(lines):
+ line = line.strip()
+
+ if line.startswith('#'):
+ continue
+ elif not line:
+ # the transition betwen headers and symbols
+ cut = i + 1
+ break
+ elif line.lower().startswith('version:'):
+ line = line[8:].strip()
+ version = line
+ continue
+ elif line.lower().startswith('extends:'):
+ line = line[8:].strip()
+ extends = line
+ continue
+ elif line.lower().startswith('release:'):
+ release = line[8:].strip()
+ continue
+ else:
+ e('Could not understand line in %s header: %s', filename, line)
+ raise SystemExit(1)
+
+ else:
+ e('No symbols in %s', filename)
+ raise SystemExit(1)
+
+ if version is None:
+ e('No Versions: header in %s', filename)
+ raise SystemExit(1)
+
+ if extends is None:
+ e('No Extends: header in %s', filename)
+ raise SystemExit(1)
+
+ if release is None and dpkg:
+ e('No Release: header in %s', filename)
+ raise SystemExit(1)
+
+ if dpkg:
+ dpkg_versions.append('%s@%s %s' % (version, version, release))
+
+ lines = lines[cut:]
+
+ if gnuld:
+ print "%s {" % version
+ print " global:"
+
+ for symbol in lines:
+ symbol = symbol.strip()
+
+ if symbol.startswith('#'):
+ continue
+
+ if gnuld:
+ print " %s;" % symbol
+ elif dpkg:
+ dpkg_symbols.append('%s@%s %s' % (symbol, version, release))
+
+ if symbol in versioned_symbols:
+ raise AssertionError('Symbol %s is in version %s and an '
+ 'earlier version' % (symbol, version))
+
+ versioned_symbols.add(symbol)
+
+ if gnuld:
+ if extends == '-':
+ print " local:"
+ print " *;"
+ print "};"
+ else:
+ print "} %s;" % extends
+ print
+
+ if dpkg:
+ dpkg_symbols.sort()
+ dpkg_versions.sort()
+
+ for x in dpkg_versions:
+ print " %s" % x
+
+ for x in dpkg_symbols:
+ print " %s" % x
+
+ if symbol_set is not None:
+ missing = versioned_symbols - symbol_set
+
+ if missing:
+ e('These symbols have disappeared:')
+
+ for symbol in missing:
+ e(' %s', symbol)
+
+ raise SystemExit(1)
+
+ unreleased = symbol_set - versioned_symbols
+
+ if unreleased:
+ if unreleased_version is None:
+ e('Unversioned symbols are not allowed in releases:')
+
+ for symbol in unreleased:
+ e(' %s', symbol)
+
+ raise SystemExit(1)
+
+ if gnuld:
+ print "%s {" % unreleased_version
+ print " global:"
+
+ for symbol in unreleased:
+ print " %s;" % symbol
+
+ print "} %s;" % version
+
+
+if __name__ == '__main__':
+ options, argv = gnu_getopt (sys.argv[1:], '',
+ ['symbols=', 'unreleased-version=',
+ 'dpkg=', 'dpkg-build-depends-package='])
+
+ opts = {'dpkg': False}
+
+ for option, value in options:
+ if option == '--dpkg':
+ opts['dpkg'] = True
+ opts['dpkg_first_line'] = value
+ else:
+ opts[option.lstrip('-').replace('-', '_')] = value
+
+ main(argv, **opts)
diff --git a/tools/telepathy.am b/tools/telepathy.am
new file mode 100644
index 0000000..45baa77
--- /dev/null
+++ b/tools/telepathy.am
@@ -0,0 +1,65 @@
+## Useful top-level Makefile.am snippets for Telepathy projects.
+
+dist-hook:
+ chmod u+w ${distdir}/ChangeLog
+ if test -d ${top_srcdir}/.git; then \
+ git log --date=iso $(CHANGELOG_RANGE) > ${distdir}/ChangeLog; \
+ fi
+
+distcheck-hook:
+ @test "z$(CHECK_FOR_UNRELEASED)" = z || \
+ case @VERSION@ in \
+ *.*.*.*|*+) ;; \
+ *) \
+ if grep -r UNRELEASED $(CHECK_FOR_UNRELEASED); \
+ then \
+ echo "^^^ This is meant to be a release, but some files say UNRELEASED" >&2; \
+ exit 2; \
+ fi \
+ ;; \
+ esac
+
+_is-release-check:
+ @case @VERSION@ in \
+ (*.*.*.*|*+) \
+ echo "Hey! @VERSION@ is not a release!" >&2; \
+ exit 2; \
+ ;; \
+ esac
+ @if ! git diff --no-ext-diff --quiet --exit-code; then \
+ echo "Hey! Your tree is dirty! No release for you." >&2; \
+ exit 2; \
+ fi
+
+%.tar.gz.asc: %.tar.gz
+ $(AM_V_GEN)gpg --detach-sign --armor $@
+
+@PACKAGE@-@VERSION@.tar.gz: _is-release-check check distcheck
+
+maintainer-prepare-release: _is-release-check check distcheck release-mail
+ git tag -s @PACKAGE@-@VERSION@ -m @PACKAGE@' '@VERSION@
+ gpg --detach-sign --armor @PACKAGE@-@VERSION@.tar.gz
+
+release-mail: NEWS
+ $(AM_V_GEN)(python $(top_srcdir)/tools/make-release-mail.py \
+ @PACKAGE@ @VERSION@ $(top_srcdir)/NEWS > $@.tmp && \
+ mv $@.tmp $@)
+
+maintainer-upload-release: _maintainer-upload-release
+
+_maintainer-upload-release-check: _is-release-check
+ test -f @PACKAGE@-@VERSION@.tar.gz
+ test -f @PACKAGE@-@VERSION@.tar.gz.asc
+ gpg --verify @PACKAGE@-@VERSION@.tar.gz.asc
+
+_maintainer-upload-release: _maintainer-upload-release-check
+ rsync -vzP @PACKAGE@-@VERSION@.tar.gz telepathy.freedesktop.org:/srv/telepathy.freedesktop.org/www/releases/@PACKAGE@/@PACKAGE@-@VERSION@.tar.gz
+ rsync -vzP @PACKAGE@-@VERSION@.tar.gz.asc telepathy.freedesktop.org:/srv/telepathy.freedesktop.org/www/releases/@PACKAGE@/@PACKAGE@-@VERSION@.tar.gz.asc
+
+maintainer-make-release: maintainer-prepare-release maintainer-upload-release
+ @echo "Now:"
+ @echo " • bump the nano-version;"
+ @echo " • push the branch and tags upstream; and"
+ @echo " • send release-mail to <telepathy@lists.freedesktop.org>."
+
+## vim:set ft=automake: