From: Philip Withnall Date: Thu, 10 Jun 2010 14:19:50 +0000 (-0700) Subject: Add the framework for a test suite X-Git-Tag: FOLKS_0_1_17~26 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=2dc9aee089f3550f8eda1da00a70bf3b911fc25b;p=platform%2Fupstream%2Ffolks.git Add the framework for a test suite Add the framework for a test suite for the Telepathy backend, including a dummy account manager, account and connection, to allow complete control over the personas created in libfolks. This comes with a test case framework which wraps the GLib test framework, used by a test case which tests that all expected individuals are exposed by the individual aggregator. Heavily based on work by Travis Reitter . --- diff --git a/Makefile.am b/Makefile.am index 88b7654..9f96506 100644 --- a/Makefile.am +++ b/Makefile.am @@ -3,6 +3,7 @@ ACLOCAL_AMFLAGS = -I m4 SUBDIRS = \ folks \ backends \ + tests \ $(NULL) if ENABLE_DOCS diff --git a/configure.ac b/configure.ac index d2755db..b3fddc4 100644 --- a/configure.ac +++ b/configure.ac @@ -60,6 +60,9 @@ AC_SUBST(LDFLAGS) # ----------------------------------------------------------- GLIB_REQUIRED=2.24.0 TP_GLIB_REQUIRED=0.11.11 +# FIXME: remove the machinery using this once it's safe to require this version +# (be sure to remove the HAVE_TP_GLIB_FOR_TESTS conditionals in Makefile.am files) +TP_GLIB_TESTS_REQUIRED=0.11.14.1 VALA_REQUIRED=0.9.6 AM_PROG_VALAC([$VALA_REQUIRED]) @@ -132,6 +135,15 @@ BACKEND_DIR='$(libdir)/folks/$(FOLKS_MODULE_VERSION)/backends' AC_SUBST(BACKEND_DIR) # ----------------------------------------------------------- +# Tests +# ----------------------------------------------------------- +PKG_CHECK_MODULES(TP_GLIB_FOR_TESTS, + telepathy-glib >= $TP_GLIB_TESTS_REQUIRED, + [have_tp_glib_for_tests=true], + [have_tp_glib_for_tests=false]) +AM_CONDITIONAL([HAVE_TP_GLIB_FOR_TESTS], [$have_tp_glib_for_tests]) + +# ----------------------------------------------------------- # Documentation # ----------------------------------------------------------- AC_ARG_ENABLE(docs, @@ -204,6 +216,12 @@ AC_CONFIG_FILES([ backends/telepathy/lib/Makefile folks/Makefile docs/Makefile + tests/Makefile + tests/telepathy/Makefile + tests/lib/Makefile + tests/lib/telepathy/Makefile + tests/lib/telepathy/contactlist/Makefile + tests/lib/telepathy/contactlist/session.conf ]) AC_OUTPUT @@ -216,6 +234,7 @@ Configure summary: Prefix......................: ${prefix} Bugreporting URL............: ${PACKAGE_BUGREPORT} Documentation...............: ${enable_docs} + Tests.......................: ${have_tp_glib_for_tests} " diff --git a/tests/Makefile.am b/tests/Makefile.am new file mode 100644 index 0000000..8f7ccdf --- /dev/null +++ b/tests/Makefile.am @@ -0,0 +1,16 @@ +SUBDIRS = \ + lib \ + $(NULL) + +if HAVE_TP_GLIB_FOR_TESTS +SUBDIRS += telepathy +endif + +TESTS_ENVIRONMENT = \ + abs_top_builddir=@abs_top_builddir@ \ + abs_top_srcdir=@abs_top_srcdir@ \ + G_SLICE=debug-blocks \ + G_DEBUG=fatal_warnings,fatal_criticals \ + PYTHONPATH=@abs_top_srcdir@/tools + +-include $(top_srcdir)/git.mk diff --git a/tests/lib/Makefile.am b/tests/lib/Makefile.am new file mode 100644 index 0000000..61c997e --- /dev/null +++ b/tests/lib/Makefile.am @@ -0,0 +1,7 @@ +SUBDIRS = + +if HAVE_TP_GLIB_FOR_TESTS +SUBDIRS += telepathy +endif + +-include $(top_srcdir)/git.mk diff --git a/tests/lib/telepathy/Makefile.am b/tests/lib/telepathy/Makefile.am new file mode 100644 index 0000000..6a855c1 --- /dev/null +++ b/tests/lib/telepathy/Makefile.am @@ -0,0 +1,5 @@ +SUBDIRS = \ + contactlist \ + $(NULL) + +-include $(top_srcdir)/git.mk diff --git a/tests/lib/telepathy/contactlist/Makefile.am b/tests/lib/telepathy/contactlist/Makefile.am new file mode 100644 index 0000000..84eb4ca --- /dev/null +++ b/tests/lib/telepathy/contactlist/Makefile.am @@ -0,0 +1,84 @@ +# Taken from telepathy-glib. The only change is to remove the option to install # the data files. +# +# PLEASE DO NOT MODIFY THIS CONNECTION MANAGER. Either subclass it, +# copy-and-modify (moving it to a better namespace), or make changes in the +# copy in telepathy-glib first. + +VALAFLAGS += \ + --vapidir=. \ + --pkg gobject-2.0 \ + --pkg gio-2.0 \ + --pkg gee-1.0 \ + --pkg gmodule-2.0 \ + --pkg dbus-glib-1 \ + --pkg telepathy-glib \ + $(NULL) + +noinst_LTLIBRARIES = libtp-test-contactlist.la + +libtp_test_contactlist_la_SOURCES = \ + _gen/param-spec-struct.h \ + account.c \ + account.h \ + account-manager.c \ + account-manager.h \ + conn.c \ + conn.h \ + contact-list.c \ + contact-list.h \ + contact-list-manager.c \ + contact-list-manager.h \ + $(NULL) + +libtp_test_contactlist_la_CFLAGS = $(TP_GLIB_CFLAGS) +libtp_test_contactlist_la_LIBADD = $(TP_GLIB_LIBS) + +_gen/tp_test_contact_list.manager _gen/param-spec-struct.h: \ + manager-file.py $(top_srcdir)/tools/manager-file.py + $(AM_V_at)$(mkdir_p) _gen + $(AM_V_GEN)$(PYTHON) $(top_srcdir)/tools/manager-file.py \ + $(srcdir)/manager-file.py _gen + +DISTCHECK_CONFIGURE_FLAGS = --enable-introspection + +-include $(INTROSPECTION_MAKEFILE) +INTROSPECTION_GIRS = +INTROSPECTION_SCANNER_ARGS = --add-include-path=$(srcdir) +INTROSPECTION_COMPILER_ARGS = --includedir=$(srcdir) + +tp-test-contactlist.gir: libtp-test-contactlist.la +tp_test_contactlist_gir_INCLUDES = GObject-2.0 TelepathyGLib-0.12 +tp_test_contactlist_gir_CFLAGS = $(TP_GLIB_CFLAGS) +tp_test_contactlist_gir_LIBS = libtp-test-contactlist.la +tp_test_contactlist_gir_FILES = $(libtp_test_contactlist_la_SOURCES) +tp_test_contactlist_gir_NAMESPACE = TpTest +INTROSPECTION_GIRS += tp-test-contactlist.gir + +tp-test-contactlist.vapi: tp-test-contactlist.gir + $(AM_V_GEN)$(VAPIGEN) $(VALAFLAGS) --library tp-test-contactlist \ + tp-test-contactlist.gir + +BUILT_SOURCES = \ + tp-test-contactlist.vapi \ + $(NULL) + +CLEANFILES = \ + $(BUILT_SOURCES) \ + _gen/param-spec-struct.h \ + _gen/tp_test_contact_list.manager \ + $(INTROSPECTION_GIRS) \ + tp-test-contactlist.vapi \ + session.conf \ + $(gir_DATA) \ + $(typelib_DATA) \ + $(NULL) + +EXTRA_DIST = \ + manager-file.py \ + tp-test-contactlist.h \ + $(NULL) + +clean-local: + rm -rf _gen + +-include $(top_srcdir)/git.mk diff --git a/tests/lib/telepathy/contactlist/account-manager.c b/tests/lib/telepathy/contactlist/account-manager.c new file mode 100644 index 0000000..1eebc1e --- /dev/null +++ b/tests/lib/telepathy/contactlist/account-manager.c @@ -0,0 +1,188 @@ +/* + * account-manager.c - a simple account manager service. + * + * Copyright (C) 2007-2009 Collabora Ltd. + * Copyright (C) 2007-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. + * + * Copied from telepathy-glib/tests/lib/simple-account-manager.c. + */ + +#include "account-manager.h" + +#include +#include +#include +#include + +static void account_manager_iface_init (gpointer, gpointer); + +G_DEFINE_TYPE_WITH_CODE (TpTestAccountManager, + tp_test_account_manager, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_ACCOUNT_MANAGER, + account_manager_iface_init); + G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES, + tp_dbus_properties_mixin_iface_init) + ) + + +/* TP_IFACE_ACCOUNT_MANAGER is implied */ +static const char *ACCOUNT_MANAGER_INTERFACES[] = { NULL }; + +static gchar *VALID_ACCOUNTS[] = { + "/org/freedesktop/Telepathy/Account/cm/protocol/account", + NULL }; + +static gchar *INVALID_ACCOUNTS[] = { + "/org/freedesktop/Telepathy/Account/fakecm/fakeproto/invalidaccount", + NULL }; + +enum +{ + PROP_0, + PROP_INTERFACES, + PROP_VALID_ACCOUNTS, + PROP_INVALID_ACCOUNTS, +}; + +struct _TpTestAccountManagerPrivate +{ + int dummy; +}; + +static void +tp_test_account_manager_create_account (TpSvcAccountManager *self, + const gchar *in_Connection_Manager, + const gchar *in_Protocol, + const gchar *in_Display_Name, + GHashTable *in_Parameters, + GHashTable *in_Properties, + DBusGMethodInvocation *context) +{ + const gchar *out_Account = "/some/fake/account/i/think"; + + tp_svc_account_manager_return_from_create_account (context, out_Account); +} + +static void +account_manager_iface_init (gpointer klass, + gpointer unused G_GNUC_UNUSED) +{ +#define IMPLEMENT(x) tp_svc_account_manager_implement_##x (\ + klass, tp_test_account_manager_##x) + IMPLEMENT (create_account); +#undef IMPLEMENT +} + + +static void +tp_test_account_manager_init (TpTestAccountManager *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + TP_TEST_TYPE_ACCOUNT_MANAGER, TpTestAccountManagerPrivate); +} + +static void +tp_test_account_manager_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *spec) +{ + GPtrArray *accounts; + guint i = 0; + + switch (property_id) { + case PROP_INTERFACES: + g_value_set_boxed (value, ACCOUNT_MANAGER_INTERFACES); + break; + + case PROP_VALID_ACCOUNTS: + accounts = g_ptr_array_new (); + + for (i=0; VALID_ACCOUNTS[i] != NULL; i++) + g_ptr_array_add (accounts, g_strdup (VALID_ACCOUNTS[i])); + + g_value_take_boxed (value, accounts); + break; + + case PROP_INVALID_ACCOUNTS: + accounts = g_ptr_array_new (); + + for (i=0; INVALID_ACCOUNTS[i] != NULL; i++) + g_ptr_array_add (accounts, g_strdup (INVALID_ACCOUNTS[i])); + + g_value_take_boxed (value, accounts); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, spec); + break; + } +} + +/** + * This class currently only provides the minimum for + * tp_account_manager_prepare to succeed. This turns out to be only a working + * Properties.GetAll(). If we wanted later to check the case where + * tp_account_prepare succeeds, we would need to implement an account object + * too. + */ +static void +tp_test_account_manager_class_init ( + TpTestAccountManagerClass *klass) +{ + GObjectClass *object_class = (GObjectClass *) klass; + GParamSpec *param_spec; + + static TpDBusPropertiesMixinPropImpl am_props[] = { + { "Interfaces", "interfaces", NULL }, + { "ValidAccounts", "valid-accounts", NULL }, + { "InvalidAccounts", "invalid-accounts", NULL }, + /* + { "SupportedAccountProperties", "supported-account-properties", NULL }, + */ + { NULL } + }; + + static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = { + { TP_IFACE_ACCOUNT_MANAGER, + tp_dbus_properties_mixin_getter_gobject_properties, + NULL, + am_props + }, + { NULL }, + }; + + g_type_class_add_private (klass, sizeof (TpTestAccountManagerPrivate)); + object_class->get_property = tp_test_account_manager_get_property; + + param_spec = g_param_spec_boxed ("interfaces", "Extra D-Bus interfaces", + "In this case we only implement AccountManager, so none.", + G_TYPE_STRV, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_INTERFACES, param_spec); + param_spec = g_param_spec_boxed ("valid-accounts", "Valid accounts", + "The accounts which are valid on this account. This may be a lie.", + TP_ARRAY_TYPE_OBJECT_PATH_LIST, + G_PARAM_READABLE); + g_object_class_install_property (object_class, PROP_VALID_ACCOUNTS, param_spec); + param_spec = g_param_spec_boxed ("invalid-accounts", "Invalid accounts", + "The accounts which are invalid on this account. This may be a lie.", + TP_ARRAY_TYPE_OBJECT_PATH_LIST, + G_PARAM_READABLE); + g_object_class_install_property (object_class, PROP_INVALID_ACCOUNTS, param_spec); + + klass->dbus_props_class.interfaces = prop_interfaces; + tp_dbus_properties_mixin_class_init (object_class, + G_STRUCT_OFFSET (TpTestAccountManagerClass, dbus_props_class)); +} + +TpTestAccountManager * +tp_test_account_manager_new (void) +{ + return g_object_new (TP_TEST_TYPE_ACCOUNT_MANAGER, NULL); +} diff --git a/tests/lib/telepathy/contactlist/account-manager.h b/tests/lib/telepathy/contactlist/account-manager.h new file mode 100644 index 0000000..c35edb5 --- /dev/null +++ b/tests/lib/telepathy/contactlist/account-manager.h @@ -0,0 +1,61 @@ +/* + * account-manager.h - header for a simple account manager service. + * + * Copyright (C) 2007-2009 Collabora Ltd. + * Copyright (C) 2007-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. + * + * Copied from telepathy-glib/tests/lib/simple-account-manager.h. + */ + +#ifndef __TP_TEST_ACCOUNT_MANAGER_H__ +#define __TP_TEST_ACCOUNT_MANAGER_H__ + +#include +#include + + +G_BEGIN_DECLS + +typedef struct _TpTestAccountManager TpTestAccountManager; +typedef struct _TpTestAccountManagerClass TpTestAccountManagerClass; +typedef struct _TpTestAccountManagerPrivate TpTestAccountManagerPrivate; + +struct _TpTestAccountManagerClass { + GObjectClass parent_class; + TpDBusPropertiesMixinClass dbus_props_class; +}; + +struct _TpTestAccountManager { + GObject parent; + + TpTestAccountManagerPrivate *priv; +}; + +GType tp_test_account_manager_get_type (void); + +/* TYPE MACROS */ +#define TP_TEST_TYPE_ACCOUNT_MANAGER \ + (tp_test_account_manager_get_type ()) +#define TP_TEST_ACCOUNT_MANAGER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), TP_TEST_TYPE_ACCOUNT_MANAGER, \ + TpTestAccountManager)) +#define TP_TEST_ACCOUNT_MANAGER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), TP_TEST_TYPE_ACCOUNT_MANAGER, \ + TpTestAccountManagerClass)) +#define IS_TP_TEST_ACCOUNT_MANAGER(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), TP_TEST_TYPE_ACCOUNT_MANAGER)) +#define TP_TEST_IS_ACCOUNT_MANAGER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), TP_TEST_TYPE_ACCOUNT_MANAGER)) +#define TP_TEST_ACCOUNT_MANAGER_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), TP_TEST_TYPE_ACCOUNT_MANAGER, \ + TpTestAccountManagerClass)) + +TpTestAccountManager *tp_test_account_manager_new (void); + +G_END_DECLS + +#endif /* #ifndef __TP_TEST_ACCOUNT_MANAGER_H__ */ diff --git a/tests/lib/telepathy/contactlist/account.c b/tests/lib/telepathy/contactlist/account.c new file mode 100644 index 0000000..6ada9ef --- /dev/null +++ b/tests/lib/telepathy/contactlist/account.c @@ -0,0 +1,341 @@ +/* + * account.c - a simple account service. + * + * Copyright (C) 2010 Collabora Ltd. + * + * 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. + * + * Copied from telepathy-glib/tests/lib/simple-account.c. + */ + +#include "account.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +static void account_iface_init (gpointer, gpointer); + +G_DEFINE_TYPE_WITH_CODE (TpTestAccount, + tp_test_account, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_ACCOUNT, + account_iface_init); + G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES, + tp_dbus_properties_mixin_iface_init) + ) + +/* TP_IFACE_ACCOUNT is implied */ +static const char *ACCOUNT_INTERFACES[] = { NULL }; + +enum +{ + PROP_0, + PROP_INTERFACES, + PROP_DISPLAY_NAME, + PROP_ICON, + PROP_VALID, + PROP_ENABLED, + PROP_NICKNAME, + PROP_PARAMETERS, + PROP_AUTOMATIC_PRESENCE, + PROP_CONNECT_AUTO, + PROP_CONNECTION, + PROP_CONNECTION_STATUS, + PROP_CONNECTION_STATUS_REASON, + PROP_CURRENT_PRESENCE, + PROP_REQUESTED_PRESENCE, + PROP_NORMALIZED_NAME, + PROP_HAS_BEEN_ONLINE, +}; + +struct _TpTestAccountPrivate +{ + gchar *connection_path; +}; + +static void +account_iface_init (gpointer klass, + gpointer unused G_GNUC_UNUSED) +{ +#define IMPLEMENT(x) tp_svc_account_implement_##x (\ + klass, tp_test_account_##x) + /* TODO */ +#undef IMPLEMENT +} + + +static void +tp_test_account_init (TpTestAccount *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, TP_TEST_TYPE_ACCOUNT, + TpTestAccountPrivate); +} + +static void +tp_test_account_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *spec) +{ + GValueArray *presence; + TpTestAccountPrivate *priv = TP_TEST_ACCOUNT (object)->priv; + + presence = tp_value_array_build (3, + G_TYPE_UINT, TP_CONNECTION_PRESENCE_TYPE_AVAILABLE, + G_TYPE_STRING, "available", + G_TYPE_STRING, "", + G_TYPE_INVALID); + + switch (property_id) { + case PROP_INTERFACES: + g_value_set_boxed (value, ACCOUNT_INTERFACES); + break; + case PROP_DISPLAY_NAME: + g_value_set_string (value, "Fake Account"); + break; + case PROP_ICON: + g_value_set_string (value, ""); + break; + case PROP_VALID: + g_value_set_boolean (value, TRUE); + break; + case PROP_ENABLED: + g_value_set_boolean (value, TRUE); + break; + case PROP_NICKNAME: + g_value_set_string (value, "badger"); + break; + case PROP_PARAMETERS: + g_value_take_boxed (value, g_hash_table_new (NULL, NULL)); + break; + case PROP_AUTOMATIC_PRESENCE: + g_value_set_boxed (value, presence); + break; + case PROP_CONNECT_AUTO: + g_value_set_boolean (value, FALSE); + break; + case PROP_CONNECTION: + g_value_set_boxed (value, priv->connection_path); + break; + case PROP_CONNECTION_STATUS: + g_value_set_uint (value, TP_CONNECTION_STATUS_CONNECTED); + break; + case PROP_CONNECTION_STATUS_REASON: + g_value_set_uint (value, TP_CONNECTION_STATUS_REASON_REQUESTED); + break; + case PROP_CURRENT_PRESENCE: + g_value_set_boxed (value, presence); + break; + case PROP_REQUESTED_PRESENCE: + g_value_set_boxed (value, presence); + break; + case PROP_NORMALIZED_NAME: + g_value_set_string (value, ""); + break; + case PROP_HAS_BEEN_ONLINE: + g_value_set_boolean (value, TRUE); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, spec); + break; + } + + g_boxed_free (TP_STRUCT_TYPE_SIMPLE_PRESENCE, presence); +} + +static void +tp_test_account_set_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *spec) +{ + TpTestAccountPrivate *priv = TP_TEST_ACCOUNT (object)->priv; + + switch (property_id) { + case PROP_CONNECTION: + priv->connection_path = g_strdup (g_value_get_boxed (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, spec); + break; + } +} + +static void +tp_test_account_finalize (GObject *object) +{ + TpTestAccountPrivate *priv = TP_TEST_ACCOUNT (object)->priv; + + g_free (priv->connection_path); + + G_OBJECT_CLASS (tp_test_account_parent_class)->finalize (object); +} + +/** + * This class currently only provides the minimum for + * tp_account_prepare to succeed. This turns out to be only a working + * Properties.GetAll(). + */ +static void +tp_test_account_class_init (TpTestAccountClass *klass) +{ + GObjectClass *object_class = (GObjectClass *) klass; + GParamSpec *param_spec; + + static TpDBusPropertiesMixinPropImpl a_props[] = { + { "Interfaces", "interfaces", NULL }, + { "DisplayName", "display-name", NULL }, + { "Icon", "icon", NULL }, + { "Valid", "valid", NULL }, + { "Enabled", "enabled", NULL }, + { "Nickname", "nickname", NULL }, + { "Parameters", "parameters", NULL }, + { "AutomaticPresence", "automatic-presence", NULL }, + { "ConnectAutomatically", "connect-automatically", NULL }, + { "Connection", "connection", NULL }, + { "ConnectionStatus", "connection-status", NULL }, + { "ConnectionStatusReason", "connection-status-reason", NULL }, + { "CurrentPresence", "current-presence", NULL }, + { "RequestedPresence", "requested-presence", NULL }, + { "NormalizedName", "normalized-name", NULL }, + { "HasBeenOnline", "has-been-online", NULL }, + { NULL } + }; + + static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = { + { TP_IFACE_ACCOUNT, + tp_dbus_properties_mixin_getter_gobject_properties, + NULL, + a_props + }, + { NULL }, + }; + + g_type_class_add_private (klass, sizeof (TpTestAccountPrivate)); + object_class->get_property = tp_test_account_get_property; + object_class->set_property = tp_test_account_set_property; + object_class->finalize = tp_test_account_finalize; + + param_spec = g_param_spec_boxed ("interfaces", "Extra D-Bus interfaces", + "In this case we only implement Account, so none.", + G_TYPE_STRV, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_INTERFACES, param_spec); + + param_spec = g_param_spec_string ("display-name", "display name", + "DisplayName property", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_DISPLAY_NAME, param_spec); + + param_spec = g_param_spec_string ("icon", "icon", + "Icon property", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_ICON, param_spec); + + param_spec = g_param_spec_boolean ("valid", "valid", + "Valid property", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_VALID, param_spec); + + param_spec = g_param_spec_boolean ("enabled", "enabled", + "Enabled property", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_ENABLED, param_spec); + + param_spec = g_param_spec_string ("nickname", "nickname", + "Nickname property", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_NICKNAME, param_spec); + + param_spec = g_param_spec_boxed ("parameters", "parameters", + "Parameters property", + TP_HASH_TYPE_STRING_VARIANT_MAP, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_PARAMETERS, param_spec); + + param_spec = g_param_spec_boxed ("automatic-presence", "automatic presence", + "AutomaticPresence property", + TP_STRUCT_TYPE_SIMPLE_PRESENCE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_AUTOMATIC_PRESENCE, + param_spec); + + param_spec = g_param_spec_boolean ("connect-automatically", + "connect automatically", "ConnectAutomatically property", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_CONNECT_AUTO, param_spec); + + param_spec = g_param_spec_boxed ("connection", "connection", + "Connection property", + DBUS_TYPE_G_OBJECT_PATH, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_CONNECTION, param_spec); + + param_spec = g_param_spec_uint ("connection-status", "connection status", + "ConnectionStatus property", + 0, NUM_TP_CONNECTION_STATUSES, TP_CONNECTION_STATUS_DISCONNECTED, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_CONNECTION_STATUS, + param_spec); + + param_spec = g_param_spec_uint ("connection-status-reason", + "connection status reason", "ConnectionStatusReason property", + 0, NUM_TP_CONNECTION_STATUS_REASONS, + TP_CONNECTION_STATUS_REASON_NONE_SPECIFIED, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_CONNECTION_STATUS_REASON, + param_spec); + + param_spec = g_param_spec_boxed ("current-presence", "current presence", + "CurrentPresence property", + TP_STRUCT_TYPE_SIMPLE_PRESENCE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_CURRENT_PRESENCE, + param_spec); + + param_spec = g_param_spec_boxed ("requested-presence", "requested presence", + "RequestedPresence property", + TP_STRUCT_TYPE_SIMPLE_PRESENCE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_REQUESTED_PRESENCE, + param_spec); + + param_spec = g_param_spec_string ("normalized-name", "normalized name", + "NormalizedName property", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_NORMALIZED_NAME, + param_spec); + + param_spec = g_param_spec_boolean ("has-been-online", "has been online", + "HasBeenOnline property", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_HAS_BEEN_ONLINE, + param_spec); + + klass->dbus_props_class.interfaces = prop_interfaces; + tp_dbus_properties_mixin_class_init (object_class, + G_STRUCT_OFFSET (TpTestAccountClass, dbus_props_class)); +} + +TpTestAccount * +tp_test_account_new (const gchar *connection_path) +{ + return g_object_new (TP_TEST_TYPE_ACCOUNT, + "connection", connection_path, NULL); +} diff --git a/tests/lib/telepathy/contactlist/account.h b/tests/lib/telepathy/contactlist/account.h new file mode 100644 index 0000000..271cbab --- /dev/null +++ b/tests/lib/telepathy/contactlist/account.h @@ -0,0 +1,60 @@ +/* + * account.h - header for a simple account service. + * + * Copyright (C) 2010 Collabora Ltd. + * + * 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. + * + * Copied from telepathy-glib/tests/lib/simple-account.h. + */ + +#ifndef __TP_TEST_ACCOUNT_H__ +#define __TP_TEST_ACCOUNT_H__ + +#include +#include + + +G_BEGIN_DECLS + +typedef struct _TpTestAccount TpTestAccount; +typedef struct _TpTestAccountClass TpTestAccountClass; +typedef struct _TpTestAccountPrivate TpTestAccountPrivate; + +struct _TpTestAccountClass { + GObjectClass parent_class; + TpDBusPropertiesMixinClass dbus_props_class; +}; + +struct _TpTestAccount { + GObject parent; + + TpTestAccountPrivate *priv; +}; + +GType tp_test_account_get_type (void); + +/* TYPE MACROS */ +#define TP_TEST_TYPE_ACCOUNT \ + (tp_test_account_get_type ()) +#define TP_TEST_ACCOUNT(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), TP_TEST_TYPE_ACCOUNT, \ + TpTestAccount)) +#define TP_TEST_ACCOUNT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), TP_TEST_TYPE_ACCOUNT, \ + TpTestAccountClass)) +#define TP_TEST_IS_ACCOUNT(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), TP_TEST_TYPE_ACCOUNT)) +#define TP_TEST_IS_ACCOUNT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), TP_TEST_TYPE_ACCOUNT)) +#define TP_TEST_ACCOUNT_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), TP_TEST_TYPE_ACCOUNT, \ + TpTestAccountClass)) + +TpTestAccount *tp_test_account_new (const gchar *connection_path); + +G_END_DECLS + +#endif /* #ifndef __TP_TEST_ACCOUNT_H__ */ diff --git a/tests/lib/telepathy/contactlist/conn.c b/tests/lib/telepathy/contactlist/conn.c new file mode 100644 index 0000000..2e0677a --- /dev/null +++ b/tests/lib/telepathy/contactlist/conn.c @@ -0,0 +1,614 @@ +/* + * conn.c - an tp_test connection + * + * Copyright © 2007-2009 Collabora Ltd. + * Copyright © 2007-2009 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. + */ + +#include "conn.h" + +#include + +#include + +#include +#include +#include + +#include "contact-list-manager.h" + +static void init_aliasing (gpointer, gpointer); + +G_DEFINE_TYPE_WITH_CODE (TpTestContactListConnection, + tp_test_contact_list_connection, + TP_TYPE_BASE_CONNECTION, + G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_ALIASING, + init_aliasing); + G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACTS, + tp_contacts_mixin_iface_init); + G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_PRESENCE, + tp_presence_mixin_iface_init); + G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_SIMPLE_PRESENCE, + tp_presence_mixin_simple_presence_iface_init)) + +enum +{ + PROP_ACCOUNT = 1, + PROP_SIMULATION_DELAY, + N_PROPS +}; + +struct _TpTestContactListConnectionPrivate +{ + gchar *account; + guint simulation_delay; + TpTestContactListManager *list_manager; + gboolean away; +}; + +static void +tp_test_contact_list_connection_init (TpTestContactListConnection *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + TP_TEST_TYPE_CONTACT_LIST_CONNECTION, + TpTestContactListConnectionPrivate); +} + +static void +get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *spec) +{ + TpTestContactListConnection *self = + TP_TEST_CONTACT_LIST_CONNECTION (object); + + switch (property_id) + { + case PROP_ACCOUNT: + g_value_set_string (value, self->priv->account); + break; + + case PROP_SIMULATION_DELAY: + g_value_set_uint (value, self->priv->simulation_delay); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, spec); + } +} + +static void +set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *spec) +{ + TpTestContactListConnection *self = + TP_TEST_CONTACT_LIST_CONNECTION (object); + + switch (property_id) + { + case PROP_ACCOUNT: + g_free (self->priv->account); + self->priv->account = g_value_dup_string (value); + break; + + case PROP_SIMULATION_DELAY: + self->priv->simulation_delay = g_value_get_uint (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, spec); + } +} + +static void +finalize (GObject *object) +{ + TpTestContactListConnection *self = + TP_TEST_CONTACT_LIST_CONNECTION (object); + + tp_contacts_mixin_finalize (object); + g_free (self->priv->account); + + G_OBJECT_CLASS (tp_test_contact_list_connection_parent_class)->finalize ( + object); +} + +static gchar * +get_unique_connection_name (TpBaseConnection *conn) +{ + TpTestContactListConnection *self = TP_TEST_CONTACT_LIST_CONNECTION (conn); + + return g_strdup_printf ("%s@%p", self->priv->account, self); +} + +gchar * +tp_test_contact_list_normalize_contact (TpHandleRepoIface *repo, + const gchar *id, + gpointer context, + GError **error) +{ + if (id[0] == '\0') + { + g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_HANDLE, + "Contact ID must not be empty"); + return NULL; + } + + return g_utf8_normalize (id, -1, G_NORMALIZE_ALL_COMPOSE); +} + +static gchar * +tp_test_contact_list_normalize_group (TpHandleRepoIface *repo, + const gchar *id, + gpointer context, + GError **error) +{ + if (id[0] == '\0') + { + g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_HANDLE, + "Contact group name cannot be empty"); + return NULL; + } + + return g_utf8_normalize (id, -1, G_NORMALIZE_ALL_COMPOSE); +} + +static void +create_handle_repos (TpBaseConnection *conn, + TpHandleRepoIface *repos[NUM_TP_HANDLE_TYPES]) +{ + repos[TP_HANDLE_TYPE_CONTACT] = tp_dynamic_handle_repo_new + (TP_HANDLE_TYPE_CONTACT, tp_test_contact_list_normalize_contact, NULL); + + repos[TP_HANDLE_TYPE_LIST] = tp_static_handle_repo_new + (TP_HANDLE_TYPE_LIST, tp_test_contact_lists ()); + + repos[TP_HANDLE_TYPE_GROUP] = tp_dynamic_handle_repo_new + (TP_HANDLE_TYPE_GROUP, tp_test_contact_list_normalize_group, NULL); +} + +static void +alias_updated_cb (TpTestContactListManager *manager, + TpHandle contact, + TpTestContactListConnection *self) +{ + GPtrArray *aliases; + GValueArray *pair; + + pair = g_value_array_new (2); + g_value_array_append (pair, NULL); + g_value_array_append (pair, NULL); + g_value_init (pair->values + 0, G_TYPE_UINT); + g_value_init (pair->values + 1, G_TYPE_STRING); + g_value_set_uint (pair->values + 0, contact); + g_value_set_string (pair->values + 1, + tp_test_contact_list_manager_get_alias (manager, contact)); + + aliases = g_ptr_array_sized_new (1); + g_ptr_array_add (aliases, pair); + + tp_svc_connection_interface_aliasing_emit_aliases_changed (self, aliases); + + g_ptr_array_free (aliases, TRUE); + g_value_array_free (pair); +} + +static void +presence_updated_cb (TpTestContactListManager *manager, + TpHandle contact, + TpTestContactListConnection *self) +{ + TpBaseConnection *base = (TpBaseConnection *) self; + TpPresenceStatus *status; + + /* we ignore the presence indicated by the contact list for our own handle */ + if (contact == base->self_handle) + return; + + status = tp_presence_status_new ( + tp_test_contact_list_manager_get_presence (manager, contact), + NULL); + tp_presence_mixin_emit_one_presence_update ((GObject *) self, + contact, status); + tp_presence_status_free (status); +} + +static GPtrArray * +create_channel_managers (TpBaseConnection *conn) +{ + TpTestContactListConnection *self = + TP_TEST_CONTACT_LIST_CONNECTION (conn); + GPtrArray *ret = g_ptr_array_sized_new (1); + + self->priv->list_manager = + TP_TEST_CONTACT_LIST_MANAGER (g_object_new ( + TP_TEST_TYPE_CONTACT_LIST_MANAGER, + "connection", conn, + "simulation-delay", self->priv->simulation_delay, + NULL)); + + g_signal_connect (self->priv->list_manager, "alias-updated", + G_CALLBACK (alias_updated_cb), self); + g_signal_connect (self->priv->list_manager, "presence-updated", + G_CALLBACK (presence_updated_cb), self); + + g_ptr_array_add (ret, self->priv->list_manager); + + return ret; +} + +static gboolean +start_connecting (TpBaseConnection *conn, + GError **error) +{ + TpTestContactListConnection *self = TP_TEST_CONTACT_LIST_CONNECTION (conn); + TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (conn, + TP_HANDLE_TYPE_CONTACT); + + /* In a real connection manager we'd ask the underlying implementation to + * start connecting, then go to state CONNECTED when finished, but here + * we can do it immediately. */ + + conn->self_handle = tp_handle_ensure (contact_repo, self->priv->account, + NULL, error); + + if (conn->self_handle == 0) + return FALSE; + + tp_base_connection_change_status (conn, TP_CONNECTION_STATUS_CONNECTED, + TP_CONNECTION_STATUS_REASON_REQUESTED); + + return TRUE; +} + +static void +shut_down (TpBaseConnection *conn) +{ + /* In a real connection manager we'd ask the underlying implementation to + * start shutting down, then call this function when finished, but here + * we can do it immediately. */ + tp_base_connection_finish_shutdown (conn); +} + +static void +aliasing_fill_contact_attributes (GObject *object, + const GArray *contacts, + GHashTable *attributes) +{ + TpTestContactListConnection *self = + TP_TEST_CONTACT_LIST_CONNECTION (object); + guint i; + + for (i = 0; i < contacts->len; i++) + { + TpHandle contact = g_array_index (contacts, guint, i); + + tp_contacts_mixin_set_contact_attribute (attributes, contact, + TP_TOKEN_CONNECTION_INTERFACE_ALIASING_ALIAS, + tp_g_value_slice_new_string ( + tp_test_contact_list_manager_get_alias (self->priv->list_manager, + contact))); + } +} + +static void +constructed (GObject *object) +{ + TpBaseConnection *base = TP_BASE_CONNECTION (object); + void (*chain_up) (GObject *) = + G_OBJECT_CLASS (tp_test_contact_list_connection_parent_class)->constructed; + + if (chain_up != NULL) + chain_up (object); + + tp_contacts_mixin_init (object, + G_STRUCT_OFFSET (TpTestContactListConnection, contacts_mixin)); + tp_base_connection_register_with_contacts_mixin (base); + tp_contacts_mixin_add_contact_attributes_iface (object, + TP_IFACE_CONNECTION_INTERFACE_ALIASING, + aliasing_fill_contact_attributes); + + tp_presence_mixin_init (object, + G_STRUCT_OFFSET (TpTestContactListConnection, presence_mixin)); + tp_presence_mixin_simple_presence_register_with_contacts_mixin (object); +} + +static gboolean +status_available (GObject *object, + guint index_) +{ + TpBaseConnection *base = TP_BASE_CONNECTION (object); + + if (base->status != TP_CONNECTION_STATUS_CONNECTED) + return FALSE; + + return TRUE; +} + +static GHashTable * +get_contact_statuses (GObject *object, + const GArray *contacts, + GError **error) +{ + TpTestContactListConnection *self = + TP_TEST_CONTACT_LIST_CONNECTION (object); + TpBaseConnection *base = TP_BASE_CONNECTION (object); + guint i; + GHashTable *result = g_hash_table_new_full (g_direct_hash, g_direct_equal, + NULL, (GDestroyNotify) tp_presence_status_free); + + for (i = 0; i < contacts->len; i++) + { + TpHandle contact = g_array_index (contacts, guint, i); + TpTestContactListPresence presence; + GHashTable *parameters; + + /* we get our own status from the connection, and everyone else's status + * from the contact lists */ + if (contact == base->self_handle) + { + presence = (self->priv->away ? TP_TEST_CONTACT_LIST_PRESENCE_AWAY + : TP_TEST_CONTACT_LIST_PRESENCE_AVAILABLE); + } + else + { + presence = tp_test_contact_list_manager_get_presence ( + self->priv->list_manager, contact); + } + + parameters = g_hash_table_new_full (g_str_hash, + g_str_equal, NULL, (GDestroyNotify) tp_g_value_slice_free); + g_hash_table_insert (result, GUINT_TO_POINTER (contact), + tp_presence_status_new (presence, parameters)); + g_hash_table_destroy (parameters); + } + + return result; +} + +static gboolean +set_own_status (GObject *object, + const TpPresenceStatus *status, + GError **error) +{ + TpTestContactListConnection *self = + TP_TEST_CONTACT_LIST_CONNECTION (object); + TpBaseConnection *base = TP_BASE_CONNECTION (object); + GHashTable *presences; + + if (status->index == TP_TEST_CONTACT_LIST_PRESENCE_AWAY) + { + if (self->priv->away) + return TRUE; + + self->priv->away = TRUE; + } + else + { + if (!self->priv->away) + return TRUE; + + self->priv->away = FALSE; + } + + presences = g_hash_table_new_full (g_direct_hash, g_direct_equal, + NULL, NULL); + g_hash_table_insert (presences, GUINT_TO_POINTER (base->self_handle), + (gpointer) status); + tp_presence_mixin_emit_presence_update (object, presences); + g_hash_table_destroy (presences); + return TRUE; +} + +static void +tp_test_contact_list_connection_class_init ( + TpTestContactListConnectionClass *klass) +{ + static const gchar *interfaces_always_present[] = { + TP_IFACE_CONNECTION_INTERFACE_ALIASING, + TP_IFACE_CONNECTION_INTERFACE_CONTACTS, + TP_IFACE_CONNECTION_INTERFACE_PRESENCE, + TP_IFACE_CONNECTION_INTERFACE_REQUESTS, + TP_IFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE, + NULL }; + TpBaseConnectionClass *base_class = (TpBaseConnectionClass *) klass; + GObjectClass *object_class = (GObjectClass *) klass; + GParamSpec *param_spec; + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->constructed = constructed; + object_class->finalize = finalize; + g_type_class_add_private (klass, + sizeof (TpTestContactListConnectionPrivate)); + + base_class->create_handle_repos = create_handle_repos; + base_class->get_unique_connection_name = get_unique_connection_name; + base_class->create_channel_managers = create_channel_managers; + base_class->start_connecting = start_connecting; + base_class->shut_down = shut_down; + base_class->interfaces_always_present = interfaces_always_present; + + param_spec = g_param_spec_string ("account", "Account name", + "The username of this user", NULL, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_ACCOUNT, param_spec); + + param_spec = g_param_spec_uint ("simulation-delay", "Simulation delay", + "Delay between simulated network events", + 0, G_MAXUINT32, 1000, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_SIMULATION_DELAY, + param_spec); + + tp_contacts_mixin_class_init (object_class, + G_STRUCT_OFFSET (TpTestContactListConnectionClass, contacts_mixin)); + tp_presence_mixin_class_init (object_class, + G_STRUCT_OFFSET (TpTestContactListConnectionClass, presence_mixin), + status_available, get_contact_statuses, set_own_status, + tp_test_contact_list_presence_statuses ()); + tp_presence_mixin_simple_presence_init_dbus_properties (object_class); +} + +static void +get_alias_flags (TpSvcConnectionInterfaceAliasing *aliasing, + DBusGMethodInvocation *context) +{ + TpBaseConnection *base = TP_BASE_CONNECTION (aliasing); + + TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context); + tp_svc_connection_interface_aliasing_return_from_get_alias_flags (context, + TP_CONNECTION_ALIAS_FLAG_USER_SET); +} + +static void +get_aliases (TpSvcConnectionInterfaceAliasing *aliasing, + const GArray *contacts, + DBusGMethodInvocation *context) +{ + TpTestContactListConnection *self = + TP_TEST_CONTACT_LIST_CONNECTION (aliasing); + TpBaseConnection *base = TP_BASE_CONNECTION (aliasing); + TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base, + TP_HANDLE_TYPE_CONTACT); + GHashTable *result; + GError *error = NULL; + guint i; + + TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context); + + if (!tp_handles_are_valid (contact_repo, contacts, FALSE, &error)) + { + dbus_g_method_return_error (context, error); + g_error_free (error); + return; + } + + result = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL); + + for (i = 0; i < contacts->len; i++) + { + TpHandle contact = g_array_index (contacts, TpHandle, i); + const gchar *alias = tp_test_contact_list_manager_get_alias ( + self->priv->list_manager, contact); + + g_hash_table_insert (result, GUINT_TO_POINTER (contact), + (gchar *) alias); + } + + tp_svc_connection_interface_aliasing_return_from_get_aliases (context, + result); + g_hash_table_destroy (result); +} + +static void +request_aliases (TpSvcConnectionInterfaceAliasing *aliasing, + const GArray *contacts, + DBusGMethodInvocation *context) +{ + TpTestContactListConnection *self = + TP_TEST_CONTACT_LIST_CONNECTION (aliasing); + TpBaseConnection *base = TP_BASE_CONNECTION (aliasing); + TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base, + TP_HANDLE_TYPE_CONTACT); + GPtrArray *result; + gchar **strings; + GError *error = NULL; + guint i; + + TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context); + + if (!tp_handles_are_valid (contact_repo, contacts, FALSE, &error)) + { + dbus_g_method_return_error (context, error); + g_error_free (error); + return; + } + + result = g_ptr_array_sized_new (contacts->len + 1); + + for (i = 0; i < contacts->len; i++) + { + TpHandle contact = g_array_index (contacts, TpHandle, i); + const gchar *alias = tp_test_contact_list_manager_get_alias ( + self->priv->list_manager, contact); + + g_ptr_array_add (result, (gchar *) alias); + } + + g_ptr_array_add (result, NULL); + strings = (gchar **) g_ptr_array_free (result, FALSE); + tp_svc_connection_interface_aliasing_return_from_request_aliases (context, + (const gchar **) strings); + g_free (strings); +} + +static void +set_aliases (TpSvcConnectionInterfaceAliasing *aliasing, + GHashTable *aliases, + DBusGMethodInvocation *context) +{ + TpTestContactListConnection *self = + TP_TEST_CONTACT_LIST_CONNECTION (aliasing); + TpBaseConnection *base = TP_BASE_CONNECTION (aliasing); + TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base, + TP_HANDLE_TYPE_CONTACT); + GHashTableIter iter; + gpointer key, value; + + g_hash_table_iter_init (&iter, aliases); + + while (g_hash_table_iter_next (&iter, &key, &value)) + { + GError *error = NULL; + + if (!tp_handle_is_valid (contact_repo, GPOINTER_TO_UINT (key), + &error)) + { + dbus_g_method_return_error (context, error); + g_error_free (error); + return; + } + } + + g_hash_table_iter_init (&iter, aliases); + + while (g_hash_table_iter_next (&iter, &key, &value)) + { + tp_test_contact_list_manager_set_alias (self->priv->list_manager, + GPOINTER_TO_UINT (key), value); + } + + tp_svc_connection_interface_aliasing_return_from_set_aliases (context); +} + +static void +init_aliasing (gpointer iface, + gpointer iface_data G_GNUC_UNUSED) +{ + TpSvcConnectionInterfaceAliasingClass *klass = iface; + +#define IMPLEMENT(x) tp_svc_connection_interface_aliasing_implement_##x (\ + klass, x) + IMPLEMENT(get_alias_flags); + IMPLEMENT(request_aliases); + IMPLEMENT(get_aliases); + IMPLEMENT(set_aliases); +#undef IMPLEMENT +} + +TpTestContactListConnection * +tp_test_contact_list_connection_new (const gchar *account, + const gchar *protocol) +{ + return g_object_new (TP_TEST_TYPE_CONTACT_LIST_CONNECTION, "account", account, + "protocol", protocol, NULL); +} diff --git a/tests/lib/telepathy/contactlist/conn.h b/tests/lib/telepathy/contactlist/conn.h new file mode 100644 index 0000000..0d46d67 --- /dev/null +++ b/tests/lib/telepathy/contactlist/conn.h @@ -0,0 +1,68 @@ +/* + * conn.h - header for an example connection + * + * Copyright © 2007-2009 Collabora Ltd. + * Copyright © 2007-2009 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. + */ + +#ifndef __TP_TEST_CONTACT_LIST_CONN_H__ +#define __TP_TEST_CONTACT_LIST_CONN_H__ + +#include +#include +#include +#include + +G_BEGIN_DECLS + +typedef struct _TpTestContactListConnection TpTestContactListConnection; +typedef struct _TpTestContactListConnectionClass + TpTestContactListConnectionClass; +typedef struct _TpTestContactListConnectionPrivate + TpTestContactListConnectionPrivate; + +struct _TpTestContactListConnectionClass { + TpBaseConnectionClass parent_class; + TpPresenceMixinClass presence_mixin; + TpContactsMixinClass contacts_mixin; +}; + +struct _TpTestContactListConnection { + TpBaseConnection parent; + TpPresenceMixin presence_mixin; + TpContactsMixin contacts_mixin; + + TpTestContactListConnectionPrivate *priv; +}; + +GType tp_test_contact_list_connection_get_type (void); + +#define TP_TEST_TYPE_CONTACT_LIST_CONNECTION \ + (tp_test_contact_list_connection_get_type ()) +#define TP_TEST_CONTACT_LIST_CONNECTION(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), TP_TEST_TYPE_CONTACT_LIST_CONNECTION, \ + TpTestContactListConnection)) +#define TP_TEST_CONTACT_LIST_CONNECTION_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), TP_TEST_TYPE_CONTACT_LIST_CONNECTION, \ + TpTestContactListConnectionClass)) +#define TP_TEST_IS_CONTACT_LIST_CONNECTION(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), TP_TEST_TYPE_CONTACT_LIST_CONNECTION)) +#define TP_TEST_IS_CONTACT_LIST_CONNECTION_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), TP_TEST_TYPE_CONTACT_LIST_CONNECTION)) +#define TP_TEST_CONTACT_LIST_CONNECTION_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), TP_TEST_TYPE_CONTACT_LIST_CONNECTION, \ + TpTestContactListConnectionClass)) + +gchar *tp_test_contact_list_normalize_contact (TpHandleRepoIface *repo, + const gchar *id, gpointer context, GError **error); + +TpTestContactListConnection *tp_test_contact_list_connection_new ( + const gchar *account, const gchar *protocol); + +G_END_DECLS + +#endif diff --git a/tests/lib/telepathy/contactlist/contact-list-manager.c b/tests/lib/telepathy/contactlist/contact-list-manager.c new file mode 100644 index 0000000..317dfcd --- /dev/null +++ b/tests/lib/telepathy/contactlist/contact-list-manager.c @@ -0,0 +1,1649 @@ +/* + * Example channel manager for contact lists + * + * Copyright © 2007-2009 Collabora Ltd. + * Copyright © 2007-2009 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. + */ + +#include "contact-list-manager.h" + +#include + +#include + +#include + +#include "contact-list.h" + +/* elements 0, 1... of this array must be kept in sync with elements 1, 2... + * of the enum TpTestContactList in contact-list-manager.h */ +static const gchar *_contact_lists[NUM_TP_TEST_CONTACT_LISTS + 1] = { + "subscribe", + "publish", + "stored", + NULL +}; + +const gchar ** +tp_test_contact_lists (void) +{ + return _contact_lists; +} + +/* this array must be kept in sync with the enum + * TpTestContactListPresence in contact-list-manager.h */ +static const TpPresenceStatusSpec _statuses[] = { + { "offline", TP_CONNECTION_PRESENCE_TYPE_OFFLINE, FALSE, NULL }, + { "unknown", TP_CONNECTION_PRESENCE_TYPE_UNKNOWN, FALSE, NULL }, + { "error", TP_CONNECTION_PRESENCE_TYPE_ERROR, FALSE, NULL }, + { "away", TP_CONNECTION_PRESENCE_TYPE_AWAY, TRUE, NULL }, + { "available", TP_CONNECTION_PRESENCE_TYPE_AVAILABLE, TRUE, NULL }, + { NULL } +}; + +const TpPresenceStatusSpec * +tp_test_contact_list_presence_statuses (void) +{ + return _statuses; +} + +typedef struct { + gchar *alias; + + guint subscribe:1; + guint publish:1; + guint subscribe_requested:1; + guint publish_requested:1; + + TpHandleSet *tags; + +} TpTestContactDetails; + +static TpTestContactDetails * +tp_test_contact_details_new (void) +{ + return g_slice_new0 (TpTestContactDetails); +} + +static void +tp_test_contact_details_destroy (gpointer p) +{ + TpTestContactDetails *d = p; + + if (d->tags != NULL) + tp_handle_set_destroy (d->tags); + + g_free (d->alias); + g_slice_free (TpTestContactDetails, d); +} + +static void channel_manager_iface_init (gpointer, gpointer); + +G_DEFINE_TYPE_WITH_CODE (TpTestContactListManager, + tp_test_contact_list_manager, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_MANAGER, + channel_manager_iface_init)) + +enum +{ + ALIAS_UPDATED, + PRESENCE_UPDATED, + N_SIGNALS +}; + +static guint signals[N_SIGNALS] = { 0 }; + +enum +{ + PROP_CONNECTION = 1, + PROP_SIMULATION_DELAY, + N_PROPS +}; + +struct _TpTestContactListManagerPrivate +{ + TpBaseConnection *conn; + guint simulation_delay; + TpHandleRepoIface *contact_repo; + TpHandleRepoIface *group_repo; + + TpHandleSet *contacts; + /* GUINT_TO_POINTER (handle borrowed from contacts) + * => TpTestContactDetails */ + GHashTable *contact_details; + + TpTestContactList *lists[NUM_TP_TEST_CONTACT_LISTS]; + + /* GUINT_TO_POINTER (handle borrowed from channel) => TpTestContactGroup */ + GHashTable *groups; + + /* borrowed TpExportableChannel => GSList of gpointer (request tokens) that + * will be satisfied by that channel when the contact list has been + * downloaded. The requests are in reverse chronological order */ + GHashTable *queued_requests; + + gulong status_changed_id; +}; + +static void +tp_test_contact_list_manager_init (TpTestContactListManager *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + TP_TEST_TYPE_CONTACT_LIST_MANAGER, TpTestContactListManagerPrivate); + + self->priv->contact_details = g_hash_table_new_full (g_direct_hash, + g_direct_equal, NULL, tp_test_contact_details_destroy); + self->priv->groups = g_hash_table_new_full (g_direct_hash, g_direct_equal, + NULL, g_object_unref); + self->priv->queued_requests = g_hash_table_new_full (g_direct_hash, + g_direct_equal, NULL, NULL); + + /* initialized properly in constructed() */ + self->priv->contact_repo = NULL; + self->priv->group_repo = NULL; + self->priv->contacts = NULL; +} + +static void +tp_test_contact_list_manager_close_all (TpTestContactListManager *self) +{ + guint i; + + if (self->priv->queued_requests != NULL) + { + GHashTable *tmp = self->priv->queued_requests; + GHashTableIter iter; + gpointer key, value; + + self->priv->queued_requests = NULL; + g_hash_table_iter_init (&iter, tmp); + + while (g_hash_table_iter_next (&iter, &key, &value)) + { + GSList *requests = value; + GSList *l; + + requests = g_slist_reverse (requests); + + for (l = requests; l != NULL; l = l->next) + { + tp_channel_manager_emit_request_failed (self, + l->data, TP_ERRORS, TP_ERROR_DISCONNECTED, + "Unable to complete channel request due to disconnection"); + } + + g_slist_free (requests); + g_hash_table_iter_steal (&iter); + } + + g_hash_table_destroy (tmp); + } + + if (self->priv->contacts != NULL) + { + tp_handle_set_destroy (self->priv->contacts); + self->priv->contacts = NULL; + } + + if (self->priv->contact_details != NULL) + { + GHashTable *tmp = self->priv->contact_details; + + self->priv->contact_details = NULL; + g_hash_table_destroy (tmp); + } + + if (self->priv->groups != NULL) + { + GHashTable *tmp = self->priv->groups; + + self->priv->groups = NULL; + g_hash_table_destroy (tmp); + } + + for (i = 0; i < NUM_TP_TEST_CONTACT_LISTS; i++) + { + if (self->priv->lists[i] != NULL) + { + TpTestContactList *list = self->priv->lists[i]; + + /* set self->priv->lists[i] to NULL here so list_closed_cb does + * not try to delete the list again */ + self->priv->lists[i] = NULL; + g_object_unref (list); + } + } + + if (self->priv->status_changed_id != 0) + { + g_signal_handler_disconnect (self->priv->conn, + self->priv->status_changed_id); + self->priv->status_changed_id = 0; + } +} + +static void +dispose (GObject *object) +{ + TpTestContactListManager *self = TP_TEST_CONTACT_LIST_MANAGER (object); + + tp_test_contact_list_manager_close_all (self); + g_assert (self->priv->groups == NULL); + g_assert (self->priv->lists[0] == NULL); + g_assert (self->priv->queued_requests == NULL); + + ((GObjectClass *) tp_test_contact_list_manager_parent_class)->dispose ( + object); +} + +static void +get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + TpTestContactListManager *self = TP_TEST_CONTACT_LIST_MANAGER (object); + + switch (property_id) + { + case PROP_CONNECTION: + g_value_set_object (value, self->priv->conn); + break; + + case PROP_SIMULATION_DELAY: + g_value_set_uint (value, self->priv->simulation_delay); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + TpTestContactListManager *self = TP_TEST_CONTACT_LIST_MANAGER (object); + + switch (property_id) + { + case PROP_CONNECTION: + /* We don't ref the connection, because it owns a reference to the + * manager, and it guarantees that the manager's lifetime is + * less than its lifetime */ + self->priv->conn = g_value_get_object (value); + break; + + case PROP_SIMULATION_DELAY: + self->priv->simulation_delay = g_value_get_uint (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +satisfy_queued_requests (TpExportableChannel *channel, + gpointer user_data) +{ + TpTestContactListManager *self = TP_TEST_CONTACT_LIST_MANAGER (user_data); + GSList *requests = g_hash_table_lookup (self->priv->queued_requests, + channel); + + /* this is all fine even if requests is NULL */ + g_hash_table_steal (self->priv->queued_requests, channel); + requests = g_slist_reverse (requests); + tp_channel_manager_emit_new_channel (self, channel, requests); + g_slist_free (requests); +} + +static TpTestContactDetails * +lookup_contact (TpTestContactListManager *self, + TpHandle contact) +{ + return g_hash_table_lookup (self->priv->contact_details, + GUINT_TO_POINTER (contact)); +} + +static TpTestContactDetails * +ensure_contact (TpTestContactListManager *self, + TpHandle contact, + gboolean *created) +{ + TpTestContactDetails *ret = lookup_contact (self, contact); + + if (ret == NULL) + { + tp_handle_set_add (self->priv->contacts, contact); + + ret = tp_test_contact_details_new (); + ret->alias = g_strdup (tp_handle_inspect (self->priv->contact_repo, + contact)); + + g_hash_table_insert (self->priv->contact_details, + GUINT_TO_POINTER (contact), ret); + + if (created != NULL) + *created = TRUE; + } + else if (created != NULL) + { + *created = FALSE; + } + + return ret; +} + +static void +tp_test_contact_list_manager_foreach_channel (TpChannelManager *manager, + TpExportableChannelFunc callback, + gpointer user_data) +{ + TpTestContactListManager *self = TP_TEST_CONTACT_LIST_MANAGER (manager); + GHashTableIter iter; + gpointer handle, channel; + guint i; + + for (i = 0; i < NUM_TP_TEST_CONTACT_LISTS; i++) + { + if (self->priv->lists[i] != NULL) + callback (TP_EXPORTABLE_CHANNEL (self->priv->lists[i]), user_data); + } + + g_hash_table_iter_init (&iter, self->priv->groups); + + while (g_hash_table_iter_next (&iter, &handle, &channel)) + { + callback (TP_EXPORTABLE_CHANNEL (channel), user_data); + } +} + +static TpTestContactGroup *ensure_group (TpTestContactListManager *self, + TpHandle handle); + +static TpTestContactList *ensure_list (TpTestContactListManager *self, + TpTestContactListHandle handle); + +static gboolean +receive_contact_lists (gpointer p) +{ + TpTestContactListManager *self = p; + TpHandle handle, cambridge, montreal, francophones; + TpTestContactDetails *d; + TpIntSet *set, *cam_set, *mtl_set, *fr_set; + TpIntSetFastIter iter; + TpTestContactList *subscribe, *publish, *stored; + TpTestContactGroup *cambridge_group, *montreal_group, + *francophones_group; + + if (self->priv->groups == NULL) + { + /* connection already disconnected, so don't process the + * "data from the server" */ + return FALSE; + } + + /* In a real CM we'd have received a contact list from the server at this + * point. But this isn't a real CM, so we have to make one up... */ + + g_message ("Receiving roster from server"); + + subscribe = ensure_list (self, TP_TEST_CONTACT_LIST_SUBSCRIBE); + publish = ensure_list (self, TP_TEST_CONTACT_LIST_PUBLISH); + stored = ensure_list (self, TP_TEST_CONTACT_LIST_STORED); + + cambridge = tp_handle_ensure (self->priv->group_repo, "Cambridge", NULL, + NULL); + montreal = tp_handle_ensure (self->priv->group_repo, "Montreal", NULL, + NULL); + francophones = tp_handle_ensure (self->priv->group_repo, "Francophones", + NULL, NULL); + + cambridge_group = ensure_group (self, cambridge); + montreal_group = ensure_group (self, montreal); + francophones_group = ensure_group (self, francophones); + + /* Add various people who are already subscribing and publishing */ + + set = tp_intset_new (); + cam_set = tp_intset_new (); + mtl_set = tp_intset_new (); + fr_set = tp_intset_new (); + + handle = tp_handle_ensure (self->priv->contact_repo, "sjoerd@example.com", + NULL, NULL); + tp_intset_add (set, handle); + tp_intset_add (cam_set, handle); + d = ensure_contact (self, handle, NULL); + g_free (d->alias); + d->alias = g_strdup ("Sjoerd"); + d->subscribe = TRUE; + d->publish = TRUE; + d->tags = tp_handle_set_new (self->priv->group_repo); + tp_handle_set_add (d->tags, cambridge); + tp_handle_unref (self->priv->contact_repo, handle); + + handle = tp_handle_ensure (self->priv->contact_repo, "guillaume@example.com", + NULL, NULL); + tp_intset_add (set, handle); + tp_intset_add (cam_set, handle); + tp_intset_add (fr_set, handle); + d = ensure_contact (self, handle, NULL); + g_free (d->alias); + d->alias = g_strdup ("Guillaume"); + d->subscribe = TRUE; + d->publish = TRUE; + d->tags = tp_handle_set_new (self->priv->group_repo); + tp_handle_set_add (d->tags, cambridge); + tp_handle_set_add (d->tags, francophones); + tp_handle_unref (self->priv->contact_repo, handle); + + handle = tp_handle_ensure (self->priv->contact_repo, "olivier@example.com", + NULL, NULL); + tp_intset_add (set, handle); + tp_intset_add (mtl_set, handle); + tp_intset_add (fr_set, handle); + d = ensure_contact (self, handle, NULL); + g_free (d->alias); + d->alias = g_strdup ("Olivier"); + d->subscribe = TRUE; + d->publish = TRUE; + d->tags = tp_handle_set_new (self->priv->group_repo); + tp_handle_set_add (d->tags, montreal); + tp_handle_set_add (d->tags, francophones); + tp_handle_unref (self->priv->contact_repo, handle); + + handle = tp_handle_ensure (self->priv->contact_repo, "travis@example.com", + NULL, NULL); + tp_intset_add (set, handle); + d = ensure_contact (self, handle, NULL); + g_free (d->alias); + d->alias = g_strdup ("Travis"); + d->subscribe = TRUE; + d->publish = TRUE; + tp_handle_unref (self->priv->contact_repo, handle); + + tp_group_mixin_change_members ((GObject *) subscribe, "", + set, NULL, NULL, NULL, + 0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE); + tp_group_mixin_change_members ((GObject *) publish, "", + set, NULL, NULL, NULL, + 0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE); + tp_group_mixin_change_members ((GObject *) stored, "", + set, NULL, NULL, NULL, + 0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE); + + tp_intset_fast_iter_init (&iter, set); + + while (tp_intset_fast_iter_next (&iter, &handle)) + { + g_signal_emit (self, signals[ALIAS_UPDATED], 0, handle); + g_signal_emit (self, signals[PRESENCE_UPDATED], 0, handle); + } + + tp_intset_destroy (set); + + /* Add a couple of people whose presence we've requested. They are + * remote-pending in subscribe */ + + set = tp_intset_new (); + + handle = tp_handle_ensure (self->priv->contact_repo, "geraldine@example.com", + NULL, NULL); + tp_intset_add (set, handle); + tp_intset_add (cam_set, handle); + tp_intset_add (fr_set, handle); + d = ensure_contact (self, handle, NULL); + g_free (d->alias); + d->alias = g_strdup ("Géraldine"); + d->subscribe_requested = TRUE; + d->tags = tp_handle_set_new (self->priv->group_repo); + tp_handle_set_add (d->tags, cambridge); + tp_handle_set_add (d->tags, francophones); + tp_handle_unref (self->priv->contact_repo, handle); + + handle = tp_handle_ensure (self->priv->contact_repo, "helen@example.com", + NULL, NULL); + tp_intset_add (set, handle); + tp_intset_add (cam_set, handle); + d = ensure_contact (self, handle, NULL); + g_free (d->alias); + d->alias = g_strdup ("Helen"); + d->subscribe_requested = TRUE; + d->tags = tp_handle_set_new (self->priv->group_repo); + tp_handle_set_add (d->tags, cambridge); + tp_handle_unref (self->priv->contact_repo, handle); + + tp_group_mixin_change_members ((GObject *) subscribe, "", + NULL, NULL, NULL, set, + 0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE); + tp_group_mixin_change_members ((GObject *) stored, "", + set, NULL, NULL, NULL, + 0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE); + + tp_intset_fast_iter_init (&iter, set); + + while (tp_intset_fast_iter_next (&iter, &handle)) + { + g_signal_emit (self, signals[ALIAS_UPDATED], 0, handle); + g_signal_emit (self, signals[PRESENCE_UPDATED], 0, handle); + } + + tp_intset_destroy (set); + + /* Receive a couple of authorization requests too. These people are + * local-pending in publish */ + + handle = tp_handle_ensure (self->priv->contact_repo, "wim@example.com", + NULL, NULL); + d = ensure_contact (self, handle, NULL); + g_free (d->alias); + d->alias = g_strdup ("Wim"); + d->publish_requested = TRUE; + tp_handle_unref (self->priv->contact_repo, handle); + + set = tp_intset_new_containing (handle); + tp_group_mixin_change_members ((GObject *) publish, + "I'm more metal than you!", + NULL, NULL, set, NULL, + handle, TP_CHANNEL_GROUP_CHANGE_REASON_NONE); + tp_group_mixin_change_members ((GObject *) stored, "", + set, NULL, NULL, NULL, + handle, TP_CHANNEL_GROUP_CHANGE_REASON_NONE); + tp_intset_destroy (set); + g_signal_emit (self, signals[ALIAS_UPDATED], 0, handle); + g_signal_emit (self, signals[PRESENCE_UPDATED], 0, handle); + + handle = tp_handle_ensure (self->priv->contact_repo, "christian@example.com", + NULL, NULL); + d = ensure_contact (self, handle, NULL); + g_free (d->alias); + d->alias = g_strdup ("Christian"); + d->publish_requested = TRUE; + tp_handle_unref (self->priv->contact_repo, handle); + + set = tp_intset_new_containing (handle); + tp_group_mixin_change_members ((GObject *) publish, + "I have some fermented herring for you", + NULL, NULL, set, NULL, + handle, TP_CHANNEL_GROUP_CHANGE_REASON_NONE); + tp_group_mixin_change_members ((GObject *) stored, "", + set, NULL, NULL, NULL, + handle, TP_CHANNEL_GROUP_CHANGE_REASON_NONE); + tp_intset_destroy (set); + g_signal_emit (self, signals[ALIAS_UPDATED], 0, handle); + g_signal_emit (self, signals[PRESENCE_UPDATED], 0, handle); + + tp_group_mixin_change_members ((GObject *) cambridge_group, "", + cam_set, NULL, NULL, NULL, + 0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE); + tp_group_mixin_change_members ((GObject *) montreal_group, "", + mtl_set, NULL, NULL, NULL, + 0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE); + tp_group_mixin_change_members ((GObject *) francophones_group, "", + fr_set, NULL, NULL, NULL, + 0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE); + + tp_intset_destroy (fr_set); + tp_intset_destroy (cam_set); + tp_intset_destroy (mtl_set); + + tp_handle_unref (self->priv->group_repo, cambridge); + tp_handle_unref (self->priv->group_repo, montreal); + tp_handle_unref (self->priv->group_repo, francophones); + + /* Now we've received the roster, we can satisfy all the queued requests */ + + tp_test_contact_list_manager_foreach_channel ((TpChannelManager *) self, + satisfy_queued_requests, self); + + g_assert (g_hash_table_size (self->priv->queued_requests) == 0); + g_hash_table_destroy (self->priv->queued_requests); + self->priv->queued_requests = NULL; + + return FALSE; +} + +static void +status_changed_cb (TpBaseConnection *conn, + guint status, + guint reason, + TpTestContactListManager *self) +{ + switch (status) + { + case TP_CONNECTION_STATUS_CONNECTED: + { + /* Do network I/O to get the contact list. This connection manager + * doesn't really have a server, so simulate a small network delay + * then invent a contact list */ + g_timeout_add_full (G_PRIORITY_DEFAULT, + 2 * self->priv->simulation_delay, receive_contact_lists, + g_object_ref (self), g_object_unref); + } + break; + + case TP_CONNECTION_STATUS_DISCONNECTED: + { + tp_test_contact_list_manager_close_all (self); + } + break; + } +} + +static void +constructed (GObject *object) +{ + TpTestContactListManager *self = TP_TEST_CONTACT_LIST_MANAGER (object); + void (*chain_up) (GObject *) = + ((GObjectClass *) tp_test_contact_list_manager_parent_class)->constructed; + + if (chain_up != NULL) + { + chain_up (object); + } + + self->priv->contact_repo = tp_base_connection_get_handles (self->priv->conn, + TP_HANDLE_TYPE_CONTACT); + self->priv->group_repo = tp_base_connection_get_handles (self->priv->conn, + TP_HANDLE_TYPE_GROUP); + self->priv->contacts = tp_handle_set_new (self->priv->contact_repo); + + self->priv->status_changed_id = g_signal_connect (self->priv->conn, + "status-changed", (GCallback) status_changed_cb, self); +} + +static void +tp_test_contact_list_manager_class_init (TpTestContactListManagerClass *klass) +{ + GParamSpec *param_spec; + GObjectClass *object_class = (GObjectClass *) klass; + + object_class->constructed = constructed; + object_class->dispose = dispose; + object_class->get_property = get_property; + object_class->set_property = set_property; + + param_spec = g_param_spec_object ("connection", "Connection object", + "The connection that owns this channel manager", + TP_TYPE_BASE_CONNECTION, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_CONNECTION, param_spec); + + param_spec = g_param_spec_uint ("simulation-delay", "Simulation delay", + "Delay between simulated network events", + 0, G_MAXUINT32, 1000, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_SIMULATION_DELAY, + param_spec); + + g_type_class_add_private (klass, sizeof (TpTestContactListManagerPrivate)); + + signals[ALIAS_UPDATED] = g_signal_new ("alias-updated", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT); + + signals[PRESENCE_UPDATED] = g_signal_new ("presence-updated", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT); +} + +static void +list_closed_cb (TpTestContactList *chan, + TpTestContactListManager *self) +{ + TpHandle handle; + + tp_channel_manager_emit_channel_closed_for_object (self, + TP_EXPORTABLE_CHANNEL (chan)); + + g_object_get (chan, + "handle", &handle, + NULL); + + if (self->priv->lists[handle] == NULL) + return; + + g_assert (chan == self->priv->lists[handle]); + g_object_unref (self->priv->lists[handle]); + self->priv->lists[handle] = NULL; +} + +static void +group_closed_cb (TpTestContactGroup *chan, + TpTestContactListManager *self) +{ + tp_channel_manager_emit_channel_closed_for_object (self, + TP_EXPORTABLE_CHANNEL (chan)); + + if (self->priv->groups != NULL) + { + TpHandle handle; + + g_object_get (chan, + "handle", &handle, + NULL); + + g_hash_table_remove (self->priv->groups, GUINT_TO_POINTER (handle)); + } +} + +static TpTestContactListBase * +new_channel (TpTestContactListManager *self, + TpHandleType handle_type, + TpHandle handle, + gpointer request_token) +{ + TpTestContactListBase *chan; + gchar *object_path; + GType type; + GSList *requests = NULL; + + if (handle_type == TP_HANDLE_TYPE_LIST) + { + /* Some Telepathy clients wrongly assume that contact lists of type LIST + * have object paths ending with "/subscribe", "/publish" etc. - + * telepathy-spec has no such guarantee, so in this tp_test we break + * those clients. Please read the spec when implementing it :-) */ + object_path = g_strdup_printf ("%s/%sContactList", + self->priv->conn->object_path, _contact_lists[handle - 1]); + type = TP_TEST_TYPE_CONTACT_LIST; + } + else + { + /* Using Group%u (with handle as the value of %u) would be OK here too, + * but we'll encode the group name into the object path to be kind + * to people reading debug logs. */ + gchar *id = tp_escape_as_identifier (tp_handle_inspect ( + self->priv->group_repo, handle)); + + g_assert (handle_type == TP_HANDLE_TYPE_GROUP); + object_path = g_strdup_printf ("%s/Group/%s", + self->priv->conn->object_path, id); + type = TP_TEST_TYPE_CONTACT_GROUP; + + g_free (id); + } + + chan = g_object_new (type, + "connection", self->priv->conn, + "manager", self, + "object-path", object_path, + "handle-type", handle_type, + "handle", handle, + NULL); + + g_free (object_path); + + if (handle_type == TP_HANDLE_TYPE_LIST) + { + g_signal_connect (chan, "closed", (GCallback) list_closed_cb, self); + g_assert (self->priv->lists[handle] == NULL); + self->priv->lists[handle] = TP_TEST_CONTACT_LIST (chan); + } + else + { + g_signal_connect (chan, "closed", (GCallback) group_closed_cb, self); + + g_assert (g_hash_table_lookup (self->priv->groups, + GUINT_TO_POINTER (handle)) == NULL); + g_hash_table_insert (self->priv->groups, GUINT_TO_POINTER (handle), + TP_TEST_CONTACT_GROUP (chan)); + } + + if (self->priv->queued_requests == NULL) + { + if (request_token != NULL) + requests = g_slist_prepend (requests, request_token); + + tp_channel_manager_emit_new_channel (self, TP_EXPORTABLE_CHANNEL (chan), + requests); + g_slist_free (requests); + } + else if (request_token != NULL) + { + /* initial contact list not received yet, so we have to wait for it */ + requests = g_hash_table_lookup (self->priv->queued_requests, chan); + g_hash_table_steal (self->priv->queued_requests, chan); + requests = g_slist_prepend (requests, request_token); + g_hash_table_insert (self->priv->queued_requests, chan, requests); + } + + return chan; +} + +static TpTestContactList * +ensure_list (TpTestContactListManager *self, + TpTestContactListHandle handle) +{ + if (self->priv->lists[handle] == NULL) + { + new_channel (self, TP_HANDLE_TYPE_LIST, handle, NULL); + g_assert (self->priv->lists[handle] != NULL); + } + + return self->priv->lists[handle]; +} + +static TpTestContactGroup * +ensure_group (TpTestContactListManager *self, + TpHandle handle) +{ + TpTestContactGroup *group = g_hash_table_lookup (self->priv->groups, + GUINT_TO_POINTER (handle)); + + if (group == NULL) + { + group = TP_TEST_CONTACT_GROUP (new_channel (self, TP_HANDLE_TYPE_GROUP, + handle, NULL)); + } + + return group; +} + +static const gchar * const fixed_properties[] = { + TP_PROP_CHANNEL_CHANNEL_TYPE, + TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, + NULL +}; + +static const gchar * const allowed_properties[] = { + TP_PROP_CHANNEL_TARGET_HANDLE, + TP_PROP_CHANNEL_TARGET_ID, + NULL +}; + +static void +tp_test_contact_list_manager_foreach_channel_class (TpChannelManager *manager, + TpChannelManagerChannelClassFunc func, + gpointer user_data) +{ + GHashTable *table = tp_asv_new ( + TP_PROP_CHANNEL_CHANNEL_TYPE, + G_TYPE_STRING, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST, + TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, TP_HANDLE_TYPE_LIST, + NULL); + + func (manager, table, allowed_properties, user_data); + + g_hash_table_insert (table, TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, + tp_g_value_slice_new_uint (TP_HANDLE_TYPE_GROUP)); + func (manager, table, allowed_properties, user_data); + + g_hash_table_destroy (table); +} + +static gboolean +tp_test_contact_list_manager_request (TpTestContactListManager *self, + gpointer request_token, + GHashTable *request_properties, + gboolean require_new) +{ + TpHandleType handle_type; + TpHandle handle; + TpTestContactListBase *chan; + GError *error = NULL; + + if (tp_strdiff (tp_asv_get_string (request_properties, + TP_PROP_CHANNEL_CHANNEL_TYPE), + TP_IFACE_CHANNEL_TYPE_CONTACT_LIST)) + { + return FALSE; + } + + handle_type = tp_asv_get_uint32 (request_properties, + TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, NULL); + + if (handle_type != TP_HANDLE_TYPE_LIST && + handle_type != TP_HANDLE_TYPE_GROUP) + { + return FALSE; + } + + handle = tp_asv_get_uint32 (request_properties, + TP_PROP_CHANNEL_TARGET_HANDLE, NULL); + g_assert (handle != 0); + + if (tp_channel_manager_asv_has_unknown_properties (request_properties, + fixed_properties, allowed_properties, &error)) + { + goto error; + } + + if (handle_type == TP_HANDLE_TYPE_LIST) + { + /* telepathy-glib has already checked that the handle is valid */ + g_assert (handle < NUM_TP_TEST_CONTACT_LISTS); + + chan = TP_TEST_CONTACT_LIST_BASE (self->priv->lists[handle]); + } + else + { + chan = g_hash_table_lookup (self->priv->groups, + GUINT_TO_POINTER (handle)); + } + + if (chan == NULL) + { + new_channel (self, handle_type, handle, request_token); + } + else if (require_new) + { + g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE, + "A ContactList channel for type #%u, handle #%u already exists", + handle_type, handle); + goto error; + } + else + { + tp_channel_manager_emit_request_already_satisfied (self, + request_token, TP_EXPORTABLE_CHANNEL (chan)); + } + + return TRUE; + +error: + tp_channel_manager_emit_request_failed (self, request_token, + error->domain, error->code, error->message); + g_error_free (error); + return TRUE; +} + +static gboolean +tp_test_contact_list_manager_create_channel (TpChannelManager *manager, + gpointer request_token, + GHashTable *request_properties) +{ + return tp_test_contact_list_manager_request ( + TP_TEST_CONTACT_LIST_MANAGER (manager), request_token, + request_properties, TRUE); +} + +static gboolean +tp_test_contact_list_manager_ensure_channel (TpChannelManager *manager, + gpointer request_token, + GHashTable *request_properties) +{ + return tp_test_contact_list_manager_request ( + TP_TEST_CONTACT_LIST_MANAGER (manager), request_token, + request_properties, FALSE); +} + +static void +channel_manager_iface_init (gpointer g_iface, + gpointer data G_GNUC_UNUSED) +{ + TpChannelManagerIface *iface = g_iface; + + iface->foreach_channel = tp_test_contact_list_manager_foreach_channel; + iface->foreach_channel_class = + tp_test_contact_list_manager_foreach_channel_class; + iface->create_channel = tp_test_contact_list_manager_create_channel; + iface->ensure_channel = tp_test_contact_list_manager_ensure_channel; + /* In this channel manager, Request has the same semantics as Ensure */ + iface->request_channel = tp_test_contact_list_manager_ensure_channel; +} + +static void +send_updated_roster (TpTestContactListManager *self, + TpHandle contact) +{ + TpTestContactDetails *d = g_hash_table_lookup (self->priv->contact_details, + GUINT_TO_POINTER (contact)); + const gchar *identifier = tp_handle_inspect (self->priv->contact_repo, + contact); + + /* In a real connection manager, we'd transmit these new details to the + * server, rather than just printing messages. */ + + if (d == NULL) + { + g_message ("Deleting contact %s from server", identifier); + } + else + { + g_message ("Transmitting new state of contact %s to server", identifier); + g_message ("\talias = %s", d->alias); + g_message ("\tcan see our presence = %s", + d->publish ? "yes" : + (d->publish_requested ? "no, but has requested it" : "no")); + g_message ("\tsends us presence = %s", + d->subscribe ? "yes" : + (d->subscribe_requested ? "no, but we have requested it" : "no")); + + if (d->tags == NULL || tp_handle_set_size (d->tags) == 0) + { + g_message ("\tnot in any groups"); + } + else + { + TpIntSet *set = tp_handle_set_peek (d->tags); + TpIntSetFastIter iter; + TpHandle member; + + tp_intset_fast_iter_init (&iter, set); + + while (tp_intset_fast_iter_next (&iter, &member)) + { + g_message ("\tin group: %s", + tp_handle_inspect (self->priv->group_repo, member)); + } + } + } +} + +gboolean +tp_test_contact_list_manager_add_to_group (TpTestContactListManager *self, + GObject *channel, + TpHandle group, + TpHandle member, + const gchar *message, + GError **error) +{ + gboolean updated; + TpTestContactDetails *d = ensure_contact (self, member, &updated); + TpTestContactList *stored = self->priv->lists[ + TP_TEST_CONTACT_LIST_STORED]; + + if (d->tags == NULL) + d->tags = tp_handle_set_new (self->priv->group_repo); + + if (!tp_handle_set_is_member (d->tags, group)) + { + tp_handle_set_add (d->tags, group); + updated = TRUE; + } + + if (updated) + { + TpIntSet *added = tp_intset_new_containing (member); + + send_updated_roster (self, member); + tp_group_mixin_change_members (channel, "", added, NULL, NULL, NULL, + self->priv->conn->self_handle, TP_CHANNEL_GROUP_CHANGE_REASON_NONE); + tp_group_mixin_change_members ((GObject *) stored, "", + added, NULL, NULL, NULL, + self->priv->conn->self_handle, TP_CHANNEL_GROUP_CHANGE_REASON_NONE); + tp_intset_destroy (added); + } + + return TRUE; +} + +gboolean +tp_test_contact_list_manager_remove_from_group ( + TpTestContactListManager *self, + GObject *channel, + TpHandle group, + TpHandle member, + const gchar *message, + GError **error) +{ + TpTestContactDetails *d = lookup_contact (self, member); + + /* If not on the roster or not in any groups, we have nothing to do */ + if (d == NULL || d->tags == NULL) + return TRUE; + + if (tp_handle_set_remove (d->tags, group)) + { + TpIntSet *removed = tp_intset_new_containing (member); + + send_updated_roster (self, member); + tp_group_mixin_change_members (channel, "", NULL, removed, NULL, NULL, + self->priv->conn->self_handle, TP_CHANNEL_GROUP_CHANGE_REASON_NONE); + tp_intset_destroy (removed); + } + + return TRUE; +} + +typedef struct { + TpTestContactListManager *self; + TpHandle contact; +} SelfAndContact; + +static SelfAndContact * +self_and_contact_new (TpTestContactListManager *self, + TpHandle contact) +{ + SelfAndContact *ret = g_slice_new0 (SelfAndContact); + + ret->self = g_object_ref (self); + ret->contact = contact; + tp_handle_ref (self->priv->contact_repo, contact); + return ret; +} + +static void +self_and_contact_destroy (gpointer p) +{ + SelfAndContact *s = p; + + tp_handle_unref (s->self->priv->contact_repo, s->contact); + g_object_unref (s->self); + g_slice_free (SelfAndContact, s); +} + +static void +receive_auth_request (TpTestContactListManager *self, + TpHandle contact) +{ + TpTestContactDetails *d; + TpIntSet *set; + TpTestContactList *publish = self->priv->lists[ + TP_TEST_CONTACT_LIST_PUBLISH]; + TpTestContactList *stored = self->priv->lists[ + TP_TEST_CONTACT_LIST_STORED]; + + /* if shutting down, do nothing */ + if (publish == NULL) + return; + + /* A remote contact has asked to see our presence. + * + * In a real connection manager this would be the result of incoming + * data from the server. */ + + g_message ("From server: %s has sent us a publish request", + tp_handle_inspect (self->priv->contact_repo, contact)); + + d = ensure_contact (self, contact, NULL); + + if (d->publish) + return; + + d->publish_requested = TRUE; + + set = tp_intset_new_containing (contact); + tp_group_mixin_change_members ((GObject *) publish, + "May I see your presence, please?", + NULL, NULL, set, NULL, + contact, TP_CHANNEL_GROUP_CHANGE_REASON_NONE); + tp_group_mixin_change_members ((GObject *) stored, "", + set, NULL, NULL, NULL, + contact, TP_CHANNEL_GROUP_CHANGE_REASON_NONE); + tp_intset_destroy (set); +} + +static gboolean +receive_authorized (gpointer p) +{ + SelfAndContact *s = p; + TpTestContactDetails *d; + TpIntSet *set; + TpTestContactList *subscribe = s->self->priv->lists[ + TP_TEST_CONTACT_LIST_SUBSCRIBE]; + TpTestContactList *stored = s->self->priv->lists[ + TP_TEST_CONTACT_LIST_STORED]; + + /* A remote contact has accepted our request to see their presence. + * + * In a real connection manager this would be the result of incoming + * data from the server. */ + + g_message ("From server: %s has accepted our subscribe request", + tp_handle_inspect (s->self->priv->contact_repo, s->contact)); + + d = ensure_contact (s->self, s->contact, NULL); + + /* if we were already subscribed to them, then nothing really happened */ + if (d->subscribe) + return FALSE; + + d->subscribe_requested = FALSE; + d->subscribe = TRUE; + + set = tp_intset_new_containing (s->contact); + tp_group_mixin_change_members ((GObject *) subscribe, "", + set, NULL, NULL, NULL, + s->contact, TP_CHANNEL_GROUP_CHANGE_REASON_NONE); + tp_group_mixin_change_members ((GObject *) stored, "", + set, NULL, NULL, NULL, + s->contact, TP_CHANNEL_GROUP_CHANGE_REASON_NONE); + tp_intset_destroy (set); + + /* their presence changes to something other than UNKNOWN */ + g_signal_emit (s->self, signals[PRESENCE_UPDATED], 0, s->contact); + + /* if we're not publishing to them, also pretend they have asked us to + * do so */ + if (!d->publish) + { + receive_auth_request (s->self, s->contact); + } + + return FALSE; +} + +static gboolean +receive_unauthorized (gpointer p) +{ + SelfAndContact *s = p; + TpTestContactDetails *d; + TpIntSet *set; + TpTestContactList *subscribe = s->self->priv->lists[ + TP_TEST_CONTACT_LIST_SUBSCRIBE]; + + /* if shutting down, do nothing */ + if (subscribe == NULL) + return FALSE; + + /* A remote contact has rejected our request to see their presence. + * + * In a real connection manager this would be the result of incoming + * data from the server. */ + + g_message ("From server: %s has rejected our subscribe request", + tp_handle_inspect (s->self->priv->contact_repo, s->contact)); + + d = ensure_contact (s->self, s->contact, NULL); + + if (!d->subscribe && !d->subscribe_requested) + return FALSE; + + d->subscribe_requested = FALSE; + d->subscribe = FALSE; + + set = tp_intset_new_containing (s->contact); + tp_group_mixin_change_members ((GObject *) subscribe, "Say 'please'!", + NULL, set, NULL, NULL, + s->contact, TP_CHANNEL_GROUP_CHANGE_REASON_NONE); + tp_intset_destroy (set); + + /* their presence changes to UNKNOWN */ + g_signal_emit (s->self, signals[PRESENCE_UPDATED], 0, s->contact); + + return FALSE; +} + +gboolean +tp_test_contact_list_manager_add_to_list (TpTestContactListManager *self, + GObject *channel, + TpTestContactListHandle list, + TpHandle member, + const gchar *message, + GError **error) +{ + TpIntSet *set; + TpTestContactList *stored = self->priv->lists[TP_TEST_CONTACT_LIST_STORED]; + + switch (list) + { + case TP_TEST_CONTACT_LIST_SUBSCRIBE: + /* we would like to see member's presence */ + { + gboolean created; + TpTestContactDetails *d = ensure_contact (self, member, &created); + gchar *message_lc; + + /* if they already authorized us, it's a no-op */ + if (d->subscribe) + return TRUE; + + /* In a real connection manager we'd start a network request here */ + g_message ("Transmitting authorization request to %s: %s", + tp_handle_inspect (self->priv->contact_repo, member), + message); + + if (created || !d->subscribe_requested) + { + d->subscribe_requested = TRUE; + send_updated_roster (self, member); + } + + set = tp_intset_new_containing (member); + tp_group_mixin_change_members (channel, message, + NULL, NULL, NULL, set, + self->priv->conn->self_handle, + TP_CHANNEL_GROUP_CHANGE_REASON_NONE); + /* subscribing to someone implicitly puts them on Stored, too */ + tp_group_mixin_change_members ((GObject *) stored, "", + set, NULL, NULL, NULL, + self->priv->conn->self_handle, + TP_CHANNEL_GROUP_CHANGE_REASON_NONE); + tp_intset_destroy (set); + + /* Pretend that after a delay, the contact notices the request + * and allows or rejects it. In this tp_test connection manager, + * empty requests are allowed, as are requests that contain "please" + * case-insensitively. All other requests are denied. */ + message_lc = g_ascii_strdown (message, -1); + + if (message[0] == '\0' || strstr (message_lc, "please") != NULL) + { + g_timeout_add_full (G_PRIORITY_DEFAULT, + self->priv->simulation_delay, receive_authorized, + self_and_contact_new (self, member), + self_and_contact_destroy); + } + else + { + g_timeout_add_full (G_PRIORITY_DEFAULT, + self->priv->simulation_delay, + receive_unauthorized, + self_and_contact_new (self, member), + self_and_contact_destroy); + } + + g_free (message_lc); + } + return TRUE; + + case TP_TEST_CONTACT_LIST_PUBLISH: + /* We would like member to see our presence. This is meaningless, + * unless they have asked for it. */ + { + TpTestContactDetails *d = lookup_contact (self, member); + + if (d == NULL || !d->publish_requested) + { + /* the group mixin won't actually allow this to be reached, + * because of the flags we set */ + g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE, + "Can't unilaterally send presence to %s", + tp_handle_inspect (self->priv->contact_repo, member)); + return FALSE; + } + + if (!d->publish) + { + d->publish = TRUE; + d->publish_requested = FALSE; + send_updated_roster (self, member); + + set = tp_intset_new_containing (member); + tp_group_mixin_change_members (channel, "", + set, NULL, NULL, NULL, + self->priv->conn->self_handle, + TP_CHANNEL_GROUP_CHANGE_REASON_NONE); + tp_group_mixin_change_members ((GObject *) stored, "", + set, NULL, NULL, NULL, + self->priv->conn->self_handle, + TP_CHANNEL_GROUP_CHANGE_REASON_NONE); + tp_intset_destroy (set); + } + } + return TRUE; + + case TP_TEST_CONTACT_LIST_STORED: + /* we would like member to be on the roster */ + { + gboolean created; + + ensure_contact (self, member, &created); + + if (created) + send_updated_roster (self, member); + + set = tp_intset_new_containing (member); + tp_group_mixin_change_members (channel, "", + set, NULL, NULL, NULL, self->priv->conn->self_handle, + TP_CHANNEL_GROUP_CHANGE_REASON_NONE); + tp_intset_destroy (set); + } + return TRUE; + + default: + g_return_val_if_reached (FALSE); + } +} + +static gboolean +auth_request_cb (gpointer p) +{ + SelfAndContact *s = p; + + receive_auth_request (s->self, s->contact); + + return FALSE; +} + +gboolean +tp_test_contact_list_manager_remove_from_list (TpTestContactListManager *self, + GObject *channel, + TpTestContactListHandle list, + TpHandle member, + const gchar *message, + GError **error) +{ + TpIntSet *set; + + switch (list) + { + case TP_TEST_CONTACT_LIST_PUBLISH: + /* we would like member not to see our presence any more, or we + * would like to reject a request from them to see our presence */ + { + TpTestContactDetails *d = lookup_contact (self, member); + + if (d != NULL) + { + if (d->publish_requested) + { + g_message ("Rejecting authorization request from %s", + tp_handle_inspect (self->priv->contact_repo, member)); + d->publish_requested = FALSE; + + set = tp_intset_new_containing (member); + tp_group_mixin_change_members (channel, "", + NULL, set, NULL, NULL, + self->priv->conn->self_handle, + TP_CHANNEL_GROUP_CHANGE_REASON_NONE); + tp_intset_destroy (set); + } + else if (d->publish) + { + g_message ("Removing authorization from %s", + tp_handle_inspect (self->priv->contact_repo, member)); + d->publish = FALSE; + + set = tp_intset_new_containing (member); + tp_group_mixin_change_members (channel, "", + NULL, set, NULL, NULL, + self->priv->conn->self_handle, + TP_CHANNEL_GROUP_CHANGE_REASON_NONE); + tp_intset_destroy (set); + + /* Pretend that after a delay, the contact notices the change + * and asks for our presence again */ + g_timeout_add_full (G_PRIORITY_DEFAULT, + self->priv->simulation_delay, auth_request_cb, + self_and_contact_new (self, member), + self_and_contact_destroy); + } + else + { + /* nothing to do, avoid "updating the roster" */ + return TRUE; + } + + send_updated_roster (self, member); + } + } + return TRUE; + + case TP_TEST_CONTACT_LIST_SUBSCRIBE: + /* we would like to avoid receiving member's presence any more, + * or we would like to cancel an outstanding request for their + * presence */ + { + TpTestContactDetails *d = lookup_contact (self, member); + + if (d != NULL) + { + if (d->subscribe_requested) + { + g_message ("Cancelling our authorization request to %s", + tp_handle_inspect (self->priv->contact_repo, member)); + d->subscribe_requested = FALSE; + + set = tp_intset_new_containing (member); + tp_group_mixin_change_members (channel, "", + NULL, set, NULL, NULL, + self->priv->conn->self_handle, + TP_CHANNEL_GROUP_CHANGE_REASON_NONE); + tp_intset_destroy (set); + } + else if (d->subscribe) + { + g_message ("We no longer want presence from %s", + tp_handle_inspect (self->priv->contact_repo, member)); + d->subscribe = FALSE; + + set = tp_intset_new_containing (member); + tp_group_mixin_change_members (channel, "", + NULL, set, NULL, NULL, + self->priv->conn->self_handle, + TP_CHANNEL_GROUP_CHANGE_REASON_NONE); + tp_intset_destroy (set); + + /* since they're no longer on the subscribe list, we can't + * see their presence, so emit a signal changing it to + * UNKNOWN */ + g_signal_emit (self, signals[PRESENCE_UPDATED], 0, member); + } + else + { + /* nothing to do, avoid "updating the roster" */ + return TRUE; + } + + send_updated_roster (self, member); + } + } + return TRUE; + + case TP_TEST_CONTACT_LIST_STORED: + /* we would like to remove member from the roster altogether */ + { + TpTestContactDetails *d = lookup_contact (self, member); + + if (d != NULL) + { + g_hash_table_remove (self->priv->contact_details, + GUINT_TO_POINTER (member)); + send_updated_roster (self, member); + + set = tp_intset_new_containing (member); + tp_group_mixin_change_members (channel, "", + NULL, set, NULL, NULL, + self->priv->conn->self_handle, + TP_CHANNEL_GROUP_CHANGE_REASON_NONE); + tp_group_mixin_change_members ( + (GObject *) self->priv->lists[TP_TEST_CONTACT_LIST_SUBSCRIBE], + "", NULL, set, NULL, NULL, + self->priv->conn->self_handle, + TP_CHANNEL_GROUP_CHANGE_REASON_NONE); + tp_group_mixin_change_members ( + (GObject *) self->priv->lists[TP_TEST_CONTACT_LIST_PUBLISH], + "", NULL, set, NULL, NULL, + self->priv->conn->self_handle, + TP_CHANNEL_GROUP_CHANGE_REASON_NONE); + tp_intset_destroy (set); + + tp_handle_set_remove (self->priv->contacts, member); + + /* since they're no longer on the subscribe list, we can't + * see their presence, so emit a signal changing it to + * UNKNOWN */ + g_signal_emit (self, signals[PRESENCE_UPDATED], 0, member); + + } + } + return TRUE; + + default: + g_return_val_if_reached (FALSE); + } +} + +TpTestContactListPresence +tp_test_contact_list_manager_get_presence (TpTestContactListManager *self, + TpHandle contact) +{ + TpTestContactDetails *d = lookup_contact (self, contact); + const gchar *id; + + if (d == NULL || !d->subscribe) + { + /* we don't know the presence of people not on the subscribe list, + * by definition */ + return TP_TEST_CONTACT_LIST_PRESENCE_UNKNOWN; + } + + id = tp_handle_inspect (self->priv->contact_repo, contact); + + /* In this tp_test CM, we fake contacts' presence based on their name: + * contacts in the first half of the alphabet are available, the rest + * (including non-alphabetic and non-ASCII initial letters) are away. */ + if ((id[0] >= 'A' && id[0] <= 'M') || (id[0] >= 'a' && id[0] <= 'm')) + { + return TP_TEST_CONTACT_LIST_PRESENCE_AVAILABLE; + } + + return TP_TEST_CONTACT_LIST_PRESENCE_AWAY; +} + +const gchar * +tp_test_contact_list_manager_get_alias (TpTestContactListManager *self, + TpHandle contact) +{ + TpTestContactDetails *d = lookup_contact (self, contact); + + if (d == NULL) + { + /* we don't have a user-defined alias for people not on the roster */ + return tp_handle_inspect (self->priv->contact_repo, contact); + } + + return d->alias; +} + +void +tp_test_contact_list_manager_set_alias (TpTestContactListManager *self, + TpHandle contact, + const gchar *alias) +{ + gboolean created; + TpTestContactDetails *d = ensure_contact (self, contact, &created); + TpTestContactList *stored = self->priv->lists[ + TP_TEST_CONTACT_LIST_STORED]; + gchar *old = d->alias; + TpIntSet *set; + + /* FIXME: if stored list hasn't been retrieved yet, queue the change for + * later */ + + /* if shutting down, do nothing */ + if (stored == NULL) + return; + + d->alias = g_strdup (alias); + + if (created || tp_strdiff (old, alias)) + send_updated_roster (self, contact); + + g_free (old); + + set = tp_intset_new_containing (contact); + tp_group_mixin_change_members ((GObject *) stored, "", + set, NULL, NULL, NULL, self->priv->conn->self_handle, + TP_CHANNEL_GROUP_CHANGE_REASON_NONE); + tp_intset_destroy (set); +} diff --git a/tests/lib/telepathy/contactlist/contact-list-manager.h b/tests/lib/telepathy/contactlist/contact-list-manager.h new file mode 100644 index 0000000..7dd6c16 --- /dev/null +++ b/tests/lib/telepathy/contactlist/contact-list-manager.h @@ -0,0 +1,107 @@ +/* + * Example channel manager for contact lists + * + * Copyright © 2007-2009 Collabora Ltd. + * Copyright © 2007-2009 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. + */ + +#ifndef __TP_TEST_CONTACT_LIST_MANAGER_H__ +#define __TP_TEST_CONTACT_LIST_MANAGER_H__ + +#include + +#include +#include +#include + +G_BEGIN_DECLS + +typedef struct _TpTestContactListManager TpTestContactListManager; +typedef struct _TpTestContactListManagerClass TpTestContactListManagerClass; +typedef struct _TpTestContactListManagerPrivate TpTestContactListManagerPrivate; + +struct _TpTestContactListManagerClass { + GObjectClass parent_class; +}; + +struct _TpTestContactListManager { + GObject parent; + + TpTestContactListManagerPrivate *priv; +}; + +GType tp_test_contact_list_manager_get_type (void); + +#define TP_TEST_TYPE_CONTACT_LIST_MANAGER \ + (tp_test_contact_list_manager_get_type ()) +#define TP_TEST_CONTACT_LIST_MANAGER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), TP_TEST_TYPE_CONTACT_LIST_MANAGER, \ + TpTestContactListManager)) +#define TP_TEST_CONTACT_LIST_MANAGER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), TP_TEST_TYPE_CONTACT_LIST_MANAGER, \ + TpTestContactListManagerClass)) +#define TP_TEST_IS_CONTACT_LIST_MANAGER(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), TP_TEST_TYPE_CONTACT_LIST_MANAGER)) +#define TP_TEST_IS_CONTACT_LIST_MANAGER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), TP_TEST_TYPE_CONTACT_LIST_MANAGER)) +#define TP_TEST_CONTACT_LIST_MANAGER_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), TP_TEST_TYPE_CONTACT_LIST_MANAGER, \ + TpTestContactListManagerClass)) + +gboolean tp_test_contact_list_manager_add_to_group ( + TpTestContactListManager *self, GObject *channel, + TpHandle group, TpHandle member, const gchar *message, GError **error); + +gboolean tp_test_contact_list_manager_remove_from_group ( + TpTestContactListManager *self, GObject *channel, + TpHandle group, TpHandle member, const gchar *message, GError **error); + +/* elements 1, 2... of this enum must be kept in sync with elements 0, 1... + * of the array _contact_lists in contact-list-manager.h */ +typedef enum { + INVALID_TP_TEST_CONTACT_LIST, + TP_TEST_CONTACT_LIST_SUBSCRIBE = 1, + TP_TEST_CONTACT_LIST_PUBLISH, + TP_TEST_CONTACT_LIST_STORED, + NUM_TP_TEST_CONTACT_LISTS +} TpTestContactListHandle; + +/* this enum must be kept in sync with the array _statuses in + * contact-list-manager.c */ +typedef enum { + TP_TEST_CONTACT_LIST_PRESENCE_OFFLINE = 0, + TP_TEST_CONTACT_LIST_PRESENCE_UNKNOWN, + TP_TEST_CONTACT_LIST_PRESENCE_ERROR, + TP_TEST_CONTACT_LIST_PRESENCE_AWAY, + TP_TEST_CONTACT_LIST_PRESENCE_AVAILABLE +} TpTestContactListPresence; + +const TpPresenceStatusSpec *tp_test_contact_list_presence_statuses ( + void); + +gboolean tp_test_contact_list_manager_add_to_list ( + TpTestContactListManager *self, GObject *channel, + TpTestContactListHandle list, TpHandle member, const gchar *message, + GError **error); + +gboolean tp_test_contact_list_manager_remove_from_list ( + TpTestContactListManager *self, GObject *channel, + TpTestContactListHandle list, TpHandle member, const gchar *message, + GError **error); + +const gchar **tp_test_contact_lists (void); + +TpTestContactListPresence tp_test_contact_list_manager_get_presence ( + TpTestContactListManager *self, TpHandle contact); +const gchar *tp_test_contact_list_manager_get_alias ( + TpTestContactListManager *self, TpHandle contact); +void tp_test_contact_list_manager_set_alias ( + TpTestContactListManager *self, TpHandle contact, const gchar *alias); + +G_END_DECLS + +#endif diff --git a/tests/lib/telepathy/contactlist/contact-list.c b/tests/lib/telepathy/contactlist/contact-list.c new file mode 100644 index 0000000..0f6eaea --- /dev/null +++ b/tests/lib/telepathy/contactlist/contact-list.c @@ -0,0 +1,632 @@ +/* + * An example ContactList channel with handle type LIST or GROUP + * + * Copyright © 2009 Collabora Ltd. + * Copyright © 2009 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. + */ + +#include "contact-list.h" + +#include +#include +#include +#include + +#include "contact-list-manager.h" + +static void channel_iface_init (gpointer iface, gpointer data); +static void list_channel_iface_init (gpointer iface, gpointer data); +static void group_channel_iface_init (gpointer iface, gpointer data); + +/* Abstract base class */ +G_DEFINE_TYPE_WITH_CODE (TpTestContactListBase, tp_test_contact_list_base, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL, channel_iface_init); + G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_TYPE_CONTACT_LIST, NULL); + G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_GROUP, + tp_group_mixin_iface_init); + G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES, + tp_dbus_properties_mixin_iface_init); + G_IMPLEMENT_INTERFACE (TP_TYPE_EXPORTABLE_CHANNEL, NULL); + G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_IFACE, NULL)) + +/* Subclass for handle type LIST */ +G_DEFINE_TYPE_WITH_CODE (TpTestContactList, tp_test_contact_list, + TP_TEST_TYPE_CONTACT_LIST_BASE, + G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL, list_channel_iface_init)) + +/* Subclass for handle type GROUP */ +G_DEFINE_TYPE_WITH_CODE (TpTestContactGroup, tp_test_contact_group, + TP_TEST_TYPE_CONTACT_LIST_BASE, + G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL, group_channel_iface_init)) + +static const gchar *contact_list_interfaces[] = { + TP_IFACE_CHANNEL_INTERFACE_GROUP, + NULL +}; + +enum +{ + PROP_OBJECT_PATH = 1, + PROP_CHANNEL_TYPE, + PROP_HANDLE_TYPE, + PROP_HANDLE, + PROP_TARGET_ID, + PROP_REQUESTED, + PROP_INITIATOR_HANDLE, + PROP_INITIATOR_ID, + PROP_CONNECTION, + PROP_MANAGER, + PROP_INTERFACES, + PROP_CHANNEL_DESTROYED, + PROP_CHANNEL_PROPERTIES, + N_PROPS +}; + +struct _TpTestContactListBasePrivate +{ + TpBaseConnection *conn; + TpTestContactListManager *manager; + gchar *object_path; + TpHandleType handle_type; + TpHandle handle; + + /* These are really booleans, but gboolean is signed. Thanks, GLib */ + unsigned closed:1; + unsigned disposed:1; +}; + +struct _TpTestContactListPrivate +{ + int dummy:1; +}; + +struct _TpTestContactGroupPrivate +{ + int dummy:1; +}; + +static void +tp_test_contact_list_base_init (TpTestContactListBase *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + TP_TEST_TYPE_CONTACT_LIST_BASE, TpTestContactListBasePrivate); +} + +static void +tp_test_contact_list_init (TpTestContactList *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, TP_TEST_TYPE_CONTACT_LIST, + TpTestContactListPrivate); +} + +static void +tp_test_contact_group_init (TpTestContactGroup *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, TP_TEST_TYPE_CONTACT_GROUP, + TpTestContactGroupPrivate); +} + +static void +constructed (GObject *object) +{ + TpTestContactListBase *self = TP_TEST_CONTACT_LIST_BASE (object); + void (*chain_up) (GObject *) = + ((GObjectClass *) tp_test_contact_list_base_parent_class)->constructed; + TpHandleRepoIface *contact_repo = tp_base_connection_get_handles + (self->priv->conn, TP_HANDLE_TYPE_CONTACT); + TpHandle self_handle = self->priv->conn->self_handle; + TpHandleRepoIface *handle_repo = tp_base_connection_get_handles + (self->priv->conn, self->priv->handle_type); + + if (chain_up != NULL) + chain_up (object); + + g_assert (TP_IS_BASE_CONNECTION (self->priv->conn)); + g_assert (TP_TEST_IS_CONTACT_LIST_MANAGER (self->priv->manager)); + + tp_dbus_daemon_register_object ( + tp_base_connection_get_dbus_daemon (self->priv->conn), + self->priv->object_path, self); + + tp_handle_ref (handle_repo, self->priv->handle); + tp_group_mixin_init (object, G_STRUCT_OFFSET (TpTestContactListBase, group), + contact_repo, self_handle); + /* Both the subclasses have full support for telepathy-spec 0.17.6. */ + tp_group_mixin_change_flags (object, + TP_CHANNEL_GROUP_FLAG_PROPERTIES, 0); +} + +static void +list_constructed (GObject *object) +{ + TpTestContactList *self = TP_TEST_CONTACT_LIST (object); + void (*chain_up) (GObject *) = + ((GObjectClass *) tp_test_contact_list_parent_class)->constructed; + + if (chain_up != NULL) + chain_up (object); + + g_assert (self->parent.priv->handle_type == TP_HANDLE_TYPE_LIST); + + switch (self->parent.priv->handle) + { + case TP_TEST_CONTACT_LIST_PUBLISH: + /* We can stop publishing presence to people, but we can't + * start sending people our presence unless they ask for it. + * + * (We can accept people's requests to see our presence - but that's + * always allowed, so there's no flag.) + */ + tp_group_mixin_change_flags (object, + TP_CHANNEL_GROUP_FLAG_CAN_REMOVE, 0); + break; + case TP_TEST_CONTACT_LIST_STORED: + /* We can add people to our roster (not that that's very useful without + * also adding them to subscribe), and we can remove them altogether + * (which implicitly removes them from subscribe, publish, and all + * user-defined groups). + */ + tp_group_mixin_change_flags (object, + TP_CHANNEL_GROUP_FLAG_CAN_ADD | TP_CHANNEL_GROUP_FLAG_CAN_REMOVE, 0); + break; + case TP_TEST_CONTACT_LIST_SUBSCRIBE: + /* We can ask people to show us their presence, attaching a message. + * We can also cancel (rescind) requests that they haven't replied to, + * and stop receiving their presence after they allow it. + */ + tp_group_mixin_change_flags (object, + TP_CHANNEL_GROUP_FLAG_CAN_ADD | TP_CHANNEL_GROUP_FLAG_MESSAGE_ADD | + TP_CHANNEL_GROUP_FLAG_CAN_REMOVE | + TP_CHANNEL_GROUP_FLAG_CAN_RESCIND, + 0); + break; + default: + g_assert_not_reached (); + } +} + +static void +group_constructed (GObject *object) +{ + TpTestContactGroup *self = TP_TEST_CONTACT_GROUP (object); + void (*chain_up) (GObject *) = + ((GObjectClass *) tp_test_contact_group_parent_class)->constructed; + + if (chain_up != NULL) + chain_up (object); + + g_assert (self->parent.priv->handle_type == TP_HANDLE_TYPE_GROUP); + + /* We can add people to user-defined groups, and also remove them. */ + tp_group_mixin_change_flags (object, + TP_CHANNEL_GROUP_FLAG_CAN_ADD | TP_CHANNEL_GROUP_FLAG_CAN_REMOVE, 0); +} + + +static void +get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + TpTestContactListBase *self = TP_TEST_CONTACT_LIST_BASE (object); + + switch (property_id) + { + case PROP_OBJECT_PATH: + g_value_set_string (value, self->priv->object_path); + break; + case PROP_CHANNEL_TYPE: + g_value_set_static_string (value, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST); + break; + case PROP_HANDLE_TYPE: + g_value_set_uint (value, self->priv->handle_type); + break; + case PROP_HANDLE: + g_value_set_uint (value, self->priv->handle); + break; + case PROP_TARGET_ID: + { + TpHandleRepoIface *handle_repo = tp_base_connection_get_handles ( + self->priv->conn, self->priv->handle_type); + + g_value_set_string (value, + tp_handle_inspect (handle_repo, self->priv->handle)); + } + break; + case PROP_REQUESTED: + g_value_set_boolean (value, FALSE); + break; + case PROP_INITIATOR_HANDLE: + g_value_set_uint (value, 0); + break; + case PROP_INITIATOR_ID: + g_value_set_static_string (value, ""); + break; + case PROP_CONNECTION: + g_value_set_object (value, self->priv->conn); + break; + case PROP_MANAGER: + g_value_set_object (value, self->priv->manager); + break; + case PROP_INTERFACES: + g_value_set_boxed (value, contact_list_interfaces); + break; + case PROP_CHANNEL_DESTROYED: + g_value_set_boolean (value, self->priv->closed); + break; + case PROP_CHANNEL_PROPERTIES: + g_value_take_boxed (value, + tp_dbus_properties_mixin_make_properties_hash (object, + TP_IFACE_CHANNEL, "ChannelType", + TP_IFACE_CHANNEL, "TargetHandleType", + TP_IFACE_CHANNEL, "TargetHandle", + TP_IFACE_CHANNEL, "TargetID", + TP_IFACE_CHANNEL, "InitiatorHandle", + TP_IFACE_CHANNEL, "InitiatorID", + TP_IFACE_CHANNEL, "Requested", + TP_IFACE_CHANNEL, "Interfaces", + NULL)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + TpTestContactListBase *self = TP_TEST_CONTACT_LIST_BASE (object); + + switch (property_id) + { + case PROP_OBJECT_PATH: + g_free (self->priv->object_path); + self->priv->object_path = g_value_dup_string (value); + break; + case PROP_HANDLE: + /* we don't ref it here because we don't necessarily have access to the + * repository (or even type) yet - instead we ref it in the constructor. + */ + self->priv->handle = g_value_get_uint (value); + break; + case PROP_HANDLE_TYPE: + self->priv->handle_type = g_value_get_uint (value); + break; + case PROP_CHANNEL_TYPE: + /* this property is writable in the interface, but not actually + * meaningfully changable on this channel, so we do nothing */ + break; + case PROP_CONNECTION: + self->priv->conn = g_value_get_object (value); + break; + case PROP_MANAGER: + self->priv->manager = g_value_get_object (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +dispose (GObject *object) +{ + TpTestContactListBase *self = TP_TEST_CONTACT_LIST_BASE (object); + + if (self->priv->disposed) + return; + + self->priv->disposed = TRUE; + + if (!self->priv->closed) + { + self->priv->closed = TRUE; + tp_svc_channel_emit_closed (self); + } + + ((GObjectClass *) tp_test_contact_list_base_parent_class)->dispose (object); +} + +static void +finalize (GObject *object) +{ + TpTestContactListBase *self = TP_TEST_CONTACT_LIST_BASE (object); + TpHandleRepoIface *handle_repo = tp_base_connection_get_handles + (self->priv->conn, self->priv->handle_type); + + tp_handle_unref (handle_repo, self->priv->handle); + g_free (self->priv->object_path); + tp_group_mixin_finalize (object); + + ((GObjectClass *) tp_test_contact_list_base_parent_class)->finalize (object); +} + +static gboolean +group_add_member (GObject *object, + TpHandle handle, + const gchar *message, + GError **error) +{ + TpTestContactListBase *self = TP_TEST_CONTACT_LIST_BASE (object); + + return tp_test_contact_list_manager_add_to_group (self->priv->manager, + object, self->priv->handle, handle, message, error); +} + +static gboolean +group_remove_member (GObject *object, + TpHandle handle, + const gchar *message, + GError **error) +{ + TpTestContactListBase *self = TP_TEST_CONTACT_LIST_BASE (object); + + return tp_test_contact_list_manager_remove_from_group (self->priv->manager, + object, self->priv->handle, handle, message, error); +} + +static gboolean +list_add_member (GObject *object, + TpHandle handle, + const gchar *message, + GError **error) +{ + TpTestContactListBase *self = TP_TEST_CONTACT_LIST_BASE (object); + + return tp_test_contact_list_manager_add_to_list (self->priv->manager, + object, self->priv->handle, handle, message, error); +} + +static gboolean +list_remove_member (GObject *object, + TpHandle handle, + const gchar *message, + GError **error) +{ + TpTestContactListBase *self = TP_TEST_CONTACT_LIST_BASE (object); + + return tp_test_contact_list_manager_remove_from_list (self->priv->manager, + object, self->priv->handle, handle, message, error); +} + +static void +tp_test_contact_list_base_class_init (TpTestContactListBaseClass *klass) +{ + static TpDBusPropertiesMixinPropImpl channel_props[] = { + { "TargetHandleType", "handle-type", NULL }, + { "TargetHandle", "handle", NULL }, + { "ChannelType", "channel-type", NULL }, + { "Interfaces", "interfaces", NULL }, + { "TargetID", "target-id", NULL }, + { "Requested", "requested", NULL }, + { "InitiatorHandle", "initiator-handle", NULL }, + { "InitiatorID", "initiator-id", NULL }, + { NULL } + }; + static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = { + { TP_IFACE_CHANNEL, + tp_dbus_properties_mixin_getter_gobject_properties, + NULL, + channel_props, + }, + { NULL } + }; + GObjectClass *object_class = (GObjectClass *) klass; + GParamSpec *param_spec; + + g_type_class_add_private (klass, sizeof (TpTestContactListBasePrivate)); + + object_class->constructed = constructed; + object_class->set_property = set_property; + object_class->get_property = get_property; + object_class->dispose = dispose; + object_class->finalize = finalize; + + g_object_class_override_property (object_class, PROP_OBJECT_PATH, + "object-path"); + g_object_class_override_property (object_class, PROP_CHANNEL_TYPE, + "channel-type"); + g_object_class_override_property (object_class, PROP_HANDLE_TYPE, + "handle-type"); + g_object_class_override_property (object_class, PROP_HANDLE, "handle"); + + g_object_class_override_property (object_class, PROP_CHANNEL_DESTROYED, + "channel-destroyed"); + g_object_class_override_property (object_class, PROP_CHANNEL_PROPERTIES, + "channel-properties"); + + param_spec = g_param_spec_object ("connection", "TpBaseConnection object", + "Connection object that owns this channel", + TP_TYPE_BASE_CONNECTION, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_CONNECTION, param_spec); + + param_spec = g_param_spec_object ("manager", "TpTestContactListManager", + "TpTestContactListManager object that owns this channel", + TP_TEST_TYPE_CONTACT_LIST_MANAGER, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_MANAGER, param_spec); + + param_spec = g_param_spec_boxed ("interfaces", "Extra D-Bus interfaces", + "Additional Channel.Interface.* interfaces", + G_TYPE_STRV, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_INTERFACES, param_spec); + + param_spec = g_param_spec_string ("target-id", "Chatroom's ID", + "The string obtained by inspecting the MUC's handle", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_TARGET_ID, param_spec); + + param_spec = g_param_spec_uint ("initiator-handle", "Initiator's handle", + "The contact who initiated the channel", + 0, G_MAXUINT32, 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_INITIATOR_HANDLE, + param_spec); + + param_spec = g_param_spec_string ("initiator-id", "Initiator's ID", + "The string obtained by inspecting the initiator-handle", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_INITIATOR_ID, + param_spec); + + param_spec = g_param_spec_boolean ("requested", "Requested?", + "True if this channel was requested by the local user", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_REQUESTED, param_spec); + + klass->dbus_properties_class.interfaces = prop_interfaces; + tp_dbus_properties_mixin_class_init (object_class, + G_STRUCT_OFFSET (TpTestContactListBaseClass, dbus_properties_class)); + + /* Group mixin is initialized separately for each subclass - they have + * different callbacks */ +} + +static void +tp_test_contact_list_class_init (TpTestContactListClass *klass) +{ + GObjectClass *object_class = (GObjectClass *) klass; + + g_type_class_add_private (klass, sizeof (TpTestContactListPrivate)); + + object_class->constructed = list_constructed; + + tp_group_mixin_class_init (object_class, + G_STRUCT_OFFSET (TpTestContactListBaseClass, group_class), + list_add_member, + list_remove_member); + tp_group_mixin_init_dbus_properties (object_class); +} + +static void +tp_test_contact_group_class_init (TpTestContactGroupClass *klass) +{ + GObjectClass *object_class = (GObjectClass *) klass; + + g_type_class_add_private (klass, sizeof (TpTestContactGroupPrivate)); + + object_class->constructed = group_constructed; + + tp_group_mixin_class_init (object_class, + G_STRUCT_OFFSET (TpTestContactListBaseClass, group_class), + group_add_member, + group_remove_member); + tp_group_mixin_init_dbus_properties (object_class); +} + +static void +list_channel_close (TpSvcChannel *iface G_GNUC_UNUSED, + DBusGMethodInvocation *context) +{ + GError e = { TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED, + "ContactList channels with handle type LIST may not be closed" }; + + dbus_g_method_return_error (context, &e); +} + +static void +group_channel_close (TpSvcChannel *iface, + DBusGMethodInvocation *context) +{ + TpTestContactGroup *self = TP_TEST_CONTACT_GROUP (iface); + TpTestContactListBase *base = TP_TEST_CONTACT_LIST_BASE (iface); + + if (tp_handle_set_size (base->group.members) > 0) + { + GError e = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE, + "Non-empty groups may not be deleted (closed)" }; + + dbus_g_method_return_error (context, &e); + return; + } + + if (!base->priv->closed) + { + /* If this was a real connection manager we'd delete the group here, + * if such a concept existed in the protocol (in XMPP, it doesn't). + * + * Afterwards, close the channel: + */ + base->priv->closed = TRUE; + tp_svc_channel_emit_closed (self); + } + + tp_svc_channel_return_from_close (context); +} + +static void +channel_get_channel_type (TpSvcChannel *iface G_GNUC_UNUSED, + DBusGMethodInvocation *context) +{ + tp_svc_channel_return_from_get_channel_type (context, + TP_IFACE_CHANNEL_TYPE_CONTACT_LIST); +} + +static void +channel_get_handle (TpSvcChannel *iface, + DBusGMethodInvocation *context) +{ + TpTestContactListBase *self = TP_TEST_CONTACT_LIST_BASE (iface); + + tp_svc_channel_return_from_get_handle (context, self->priv->handle_type, + self->priv->handle); +} + +static void +channel_get_interfaces (TpSvcChannel *iface G_GNUC_UNUSED, + DBusGMethodInvocation *context) +{ + tp_svc_channel_return_from_get_interfaces (context, + contact_list_interfaces); +} + +static void +channel_iface_init (gpointer iface, + gpointer data) +{ + TpSvcChannelClass *klass = iface; + +#define IMPLEMENT(x) tp_svc_channel_implement_##x (klass, channel_##x) + /* close is implemented in subclasses, so don't IMPLEMENT (close); */ + IMPLEMENT (get_channel_type); + IMPLEMENT (get_handle); + IMPLEMENT (get_interfaces); +#undef IMPLEMENT +} + +static void +list_channel_iface_init (gpointer iface, + gpointer data G_GNUC_UNUSED) +{ + TpSvcChannelClass *klass = iface; + +#define IMPLEMENT(x) tp_svc_channel_implement_##x (klass, list_channel_##x) + IMPLEMENT (close); +#undef IMPLEMENT +} + +static void +group_channel_iface_init (gpointer iface, + gpointer data G_GNUC_UNUSED) +{ + TpSvcChannelClass *klass = iface; + +#define IMPLEMENT(x) tp_svc_channel_implement_##x (klass, group_channel_##x) + IMPLEMENT (close); +#undef IMPLEMENT +} diff --git a/tests/lib/telepathy/contactlist/contact-list.h b/tests/lib/telepathy/contactlist/contact-list.h new file mode 100644 index 0000000..2d7d26b --- /dev/null +++ b/tests/lib/telepathy/contactlist/contact-list.h @@ -0,0 +1,118 @@ +/* + * TpTest ContactList channels with handle type LIST or GROUP + * + * Copyright © 2009 Collabora Ltd. + * Copyright © 2009 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. + */ + +#ifndef TP_TEST_CONTACT_LIST_H +#define TP_TEST_CONTACT_LIST_H + +#include + +#include +#include + +G_BEGIN_DECLS + +typedef struct _TpTestContactListBase TpTestContactListBase; +typedef struct _TpTestContactListBaseClass TpTestContactListBaseClass; +typedef struct _TpTestContactListBasePrivate TpTestContactListBasePrivate; + +typedef struct _TpTestContactList TpTestContactList; +typedef struct _TpTestContactListClass TpTestContactListClass; +typedef struct _TpTestContactListPrivate TpTestContactListPrivate; + +typedef struct _TpTestContactGroup TpTestContactGroup; +typedef struct _TpTestContactGroupClass TpTestContactGroupClass; +typedef struct _TpTestContactGroupPrivate TpTestContactGroupPrivate; + +GType tp_test_contact_list_base_get_type (void); +GType tp_test_contact_list_get_type (void); +GType tp_test_contact_group_get_type (void); + +#define TP_TEST_TYPE_CONTACT_LIST_BASE \ + (tp_test_contact_list_base_get_type ()) +#define TP_TEST_CONTACT_LIST_BASE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), TP_TEST_TYPE_CONTACT_LIST_BASE, \ + TpTestContactListBase)) +#define TP_TEST_CONTACT_LIST_BASE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), TP_TEST_TYPE_CONTACT_LIST_BASE, \ + TpTestContactListBaseClass)) +#define TP_TEST_IS_CONTACT_LIST_BASE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TP_TEST_TYPE_CONTACT_LIST_BASE)) +#define TP_TEST_IS_CONTACT_LIST_BASE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), TP_TEST_TYPE_CONTACT_LIST_BASE)) +#define TP_TEST_CONTACT_LIST_BASE_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), TP_TEST_TYPE_CONTACT_LIST_BASE, \ + TpTestContactListBaseClass)) + +#define TP_TEST_TYPE_CONTACT_LIST \ + (tp_test_contact_list_get_type ()) +#define TP_TEST_CONTACT_LIST(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), TP_TEST_TYPE_CONTACT_LIST, \ + TpTestContactList)) +#define TP_TEST_CONTACT_LIST_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), TP_TEST_TYPE_CONTACT_LIST, \ + TpTestContactListClass)) +#define TP_TEST_IS_CONTACT_LIST(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TP_TEST_TYPE_CONTACT_LIST)) +#define TP_TEST_IS_CONTACT_LIST_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), TP_TEST_TYPE_CONTACT_LIST)) +#define TP_TEST_CONTACT_LIST_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), TP_TEST_TYPE_CONTACT_LIST, \ + TpTestContactListClass)) + +#define TP_TEST_TYPE_CONTACT_GROUP \ + (tp_test_contact_group_get_type ()) +#define TP_TEST_CONTACT_GROUP(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), TP_TEST_TYPE_CONTACT_GROUP, \ + TpTestContactGroup)) +#define TP_TEST_CONTACT_GROUP_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), TP_TEST_TYPE_CONTACT_GROUP, \ + TpTestContactGroupClass)) +#define TP_TEST_IS_CONTACT_GROUP(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TP_TEST_TYPE_CONTACT_GROUP)) +#define TP_TEST_IS_CONTACT_GROUP_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), TP_TEST_TYPE_CONTACT_GROUP)) +#define TP_TEST_CONTACT_GROUP_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), TP_TEST_TYPE_CONTACT_GROUP, \ + TpTestContactGroupClass)) + +struct _TpTestContactListBaseClass { + GObjectClass parent_class; + TpGroupMixinClass group_class; + TpDBusPropertiesMixinClass dbus_properties_class; +}; + +struct _TpTestContactListClass { + TpTestContactListBaseClass parent_class; +}; + +struct _TpTestContactGroupClass { + TpTestContactListBaseClass parent_class; +}; + +struct _TpTestContactListBase { + GObject parent; + TpGroupMixin group; + TpTestContactListBasePrivate *priv; +}; + +struct _TpTestContactList { + TpTestContactListBase parent; + TpTestContactListPrivate *priv; +}; + +struct _TpTestContactGroup { + TpTestContactListBase parent; + TpTestContactGroupPrivate *priv; +}; + +G_END_DECLS + +#endif diff --git a/tests/lib/telepathy/contactlist/manager-file.py b/tests/lib/telepathy/contactlist/manager-file.py new file mode 100755 index 0000000..c1d3680 --- /dev/null +++ b/tests/lib/telepathy/contactlist/manager-file.py @@ -0,0 +1,23 @@ +# Input for tools/manager-file.py + +MANAGER = 'tp_test_contact_list' +PARAMS = { + 'tp_test' : { + 'account': { + 'dtype': 's', + 'flags': 'required register', + 'filter': 'account_param_filter', + # 'filter_data': 'NULL', + # 'default': ..., + # 'struct_field': '...', + # 'setter_data': 'NULL', + }, + 'simulation-delay': { + 'dtype': 'u', + 'default': 1000, + }, + }, + } +STRUCTS = { + 'tp_test': 'TpTestParams' + } diff --git a/tests/lib/telepathy/contactlist/session.conf.in b/tests/lib/telepathy/contactlist/session.conf.in new file mode 100644 index 0000000..0388c32 --- /dev/null +++ b/tests/lib/telepathy/contactlist/session.conf.in @@ -0,0 +1,54 @@ + + + + session + + + + + unix:tmpdir=/tmp + + + @abs_top_srcdir@/tests/lib/backends/telepathy/contactlist/_gen + + + + + + + + + + + + 60000 + + contexts/dbus_contexts + + + + + 1000000000 + 1000000000 + 1000000000 + 120000 + 240000 + 100000 + 10000 + 100000 + 10000 + 50000 + 50000 + 50000 + + diff --git a/tests/lib/telepathy/contactlist/tp-test-contactlist.deps b/tests/lib/telepathy/contactlist/tp-test-contactlist.deps new file mode 100644 index 0000000..0d850c2 --- /dev/null +++ b/tests/lib/telepathy/contactlist/tp-test-contactlist.deps @@ -0,0 +1,5 @@ +gio-2.0 +dbus-glib-1 +gobject-2.0 +gio-2.0 +telepathy-glib diff --git a/tests/lib/telepathy/contactlist/tp-test-contactlist.h b/tests/lib/telepathy/contactlist/tp-test-contactlist.h new file mode 100644 index 0000000..19088b9 --- /dev/null +++ b/tests/lib/telepathy/contactlist/tp-test-contactlist.h @@ -0,0 +1,10 @@ +#ifndef __EXAMPLE_CONTACTLIST_H__ +#define __EXAMPLE_CONTACTLIST_H__ + +#include +#include +#include +#include +#include + +#endif /* __EXAMPLE_CONTACTLIST_H__ */ diff --git a/tests/telepathy/Makefile.am b/tests/telepathy/Makefile.am new file mode 100644 index 0000000..39b6896 --- /dev/null +++ b/tests/telepathy/Makefile.am @@ -0,0 +1,63 @@ +AM_CPPFLAGS = \ + $(GLIB_CFLAGS) \ + $(GEE_CFLAGS) \ + $(TP_GLIB_CFLAGS) \ + -I$(top_srcdir)/folks \ + -I$(top_srcdir)/backends/telepathy \ + -I$(top_srcdir)/tests/lib/telepathy/contactlist \ + -include $(CONFIG_HEADER) \ + $(NULL) + +LDADD = \ + $(GLIB_LIBS) \ + $(GEE_LIBS) \ + $(TP_GLIB_LIBS) \ + $(NULL) + +RUN_WITH_PRIVATE_BUS = $(top_srcdir)/tests/tools/with-session-bus.sh + +VALAFLAGS += \ + --vapidir=$(top_builddir)/tests/lib/telepathy/contactlist/ \ + --vapidir=. \ + --vapidir=$(top_srcdir)/folks \ + --vapidir=$(top_srcdir)/backends/telepathy/lib \ + --pkg gobject-2.0 \ + --pkg gio-2.0 \ + --pkg gee-1.0 \ + --pkg gmodule-2.0 \ + --pkg dbus-glib-1 \ + --pkg telepathy-glib \ + --pkg folks \ + --pkg folks-telepathy \ + --pkg tp-test-contactlist \ + $(NULL) + +TESTS = \ + test-contact-retrieval \ + $(NULL) + +noinst_PROGRAMS = \ + contact-retrieval \ + $(NULL) + +contact_retrieval_SOURCES = \ + test-case.vala \ + contact-retrieval.vala \ + $(NULL) + +contact_retrieval_LDADD = \ + $(top_builddir)/tests/lib/telepathy/contactlist/libtp-test-contactlist.la \ + $(top_builddir)/folks/libfolks.la + +CLEANFILES = \ + $(TESTS) \ + $(NULL) + +test-contact-retrieval: contact-retrieval + { echo "#!/bin/sh" && \ + echo -n "$(RUN_WITH_PRIVATE_BUS) " && \ + echo "--config-file=$(top_srcdir)/tests/lib/telepathy/contactlist/session.conf -- ./$<"; } \ + > $@ + chmod +x $@ + +-include $(top_srcdir)/git.mk diff --git a/tests/telepathy/contact-retrieval.vala b/tests/telepathy/contact-retrieval.vala new file mode 100644 index 0000000..61d68bf --- /dev/null +++ b/tests/telepathy/contact-retrieval.vala @@ -0,0 +1,191 @@ +using DBus; +using TelepathyGLib; +using TpTest; +using Tpf; +using Folks; +using Gee; + +public class ContactRetrievalTests : Folks.TestCase +{ + private DBusDaemon daemon; + private TpTest.Account account; + private TpTest.AccountManager account_manager; + private TpTest.ContactListConnection conn; + private MainLoop main_loop; + private string bus_name; + private string object_path; + + public ContactRetrievalTests () + { + base ("ContactRetrieval"); + + this.add_test ("aggregator", this.test_aggregator); + } + + public override void set_up () + { + this.main_loop = new GLib.MainLoop (null, false); + + try + { + this.daemon = DBusDaemon.dup (); + } + catch (GLib.Error e) + { + error ("Couldn't get D-Bus daemon: %s", e.message); + } + + /* Set up a contact list connection */ + this.conn = new TpTest.ContactListConnection ("me@example.com", + "protocol"); + + try + { + this.conn.register ("cm", out this.bus_name, out this.object_path); + } + catch (GLib.Error e) + { + error ("Failed to register connection %p.", this.conn); + } + + var handle_repo = this.conn.get_handles (HandleType.CONTACT); + Handle self_handle = 0; + try + { + self_handle = TelepathyGLib.handle_ensure (handle_repo, + "me@example.com", null); + } + catch (GLib.Error e) + { + error ("Couldn't ensure self handle '%s': %s", "me@example.com", + e.message); + } + + this.conn.set_self_handle (self_handle); + this.conn.change_status (ConnectionStatus.CONNECTED, + ConnectionStatusReason.REQUESTED); + + /* Create an account */ + this.account = new TpTest.Account (this.object_path); + this.daemon.register_object ( + TelepathyGLib.ACCOUNT_OBJECT_PATH_BASE + "cm/protocol/account", + this.account); + + /* Create an account manager */ + try + { + this.daemon.request_name (TelepathyGLib.ACCOUNT_MANAGER_BUS_NAME, + false); + } + catch (GLib.Error e) + { + error ("Couldn't request account manager bus name '%s': %s", + TelepathyGLib.ACCOUNT_MANAGER_BUS_NAME, e.message); + } + + this.account_manager = new TpTest.AccountManager (); + this.daemon.register_object (TelepathyGLib.ACCOUNT_MANAGER_OBJECT_PATH, + this.account_manager); + } + + public override void tear_down () + { + this.conn.change_status (ConnectionStatus.DISCONNECTED, + ConnectionStatusReason.REQUESTED); + + this.daemon.unregister_object (this.account_manager); + this.account_manager = null; + + try + { + this.daemon.release_name (TelepathyGLib.ACCOUNT_MANAGER_BUS_NAME); + } + catch (GLib.Error e) + { + error ("Couldn't release account manager bus name '%s': %s", + TelepathyGLib.ACCOUNT_MANAGER_BUS_NAME, e.message); + } + + this.daemon.unregister_object (this.account); + this.account = null; + + this.conn = null; + this.daemon = null; + this.bus_name = null; + this.object_path = null; + + Timeout.add_seconds (5, () => + { + this.main_loop.quit (); + this.main_loop = null; + return false; + }); + + /* Run the main loop to process the carnage and destruction */ + this.main_loop.run (); + } + + public void test_aggregator () + { + var main_loop = new GLib.MainLoop (null, false); + + /* Ignore the error caused by not running the logger */ + Test.log_set_fatal_handler ((d, l, m) => + { + return !m.has_suffix ("couldn't get list of favourite contacts: " + + "The name org.freedesktop.Telepathy.Logger was not provided by " + + "any .service files"); + }); + + /* Create a set of the individuals we expect to see */ + HashSet expected_individuals = new HashSet (str_hash, + str_equal); + + string prefix = "telepathy:protocol:"; + expected_individuals.add (prefix + "travis@example.com"); + expected_individuals.add (prefix + "olivier@example.com"); + expected_individuals.add (prefix + "guillaume@example.com"); + expected_individuals.add (prefix + "sjoerd@example.com"); + expected_individuals.add (prefix + "christian@example.com"); + expected_individuals.add (prefix + "wim@example.com"); + expected_individuals.add (prefix + "helen@example.com"); + expected_individuals.add (prefix + "geraldine@example.com"); + + /* Set up the aggregator */ + var aggregator = new IndividualAggregator (); + aggregator.individuals_changed.connect ((added, removed, m, a, r) => + { + foreach (Individual i in added) + expected_individuals.remove (i.id); + + assert (removed == null); + }); + aggregator.prepare (); + + /* Kill the main loop after a few seconds. If there are still individuals + * in the set of expected individuals, the aggregator has either failed + * or been too slow (which we can consider to be failure). */ + Timeout.add_seconds (3, () => + { + main_loop.quit (); + return false; + }); + + main_loop.run (); + + /* We should have enumerated exactly the individuals in the set */ + assert (expected_individuals.size == 0); + } +} + +public int main (string[] args) +{ + Test.init (ref args); + + TestSuite root = TestSuite.get_root (); + root.add_suite (new ContactRetrievalTests ().get_suite ()); + + Test.run (); + + return 0; +} diff --git a/tests/telepathy/test-case.vala b/tests/telepathy/test-case.vala new file mode 100644 index 0000000..0015ef4 --- /dev/null +++ b/tests/telepathy/test-case.vala @@ -0,0 +1,82 @@ +/* testcase.vala + * + * Copyright (C) 2009 Julien Peeters + * + * 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 Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Author: + * Julien Peeters + * + * Copied from libgee/tests/testcase.vala. + */ + +public abstract class Folks.TestCase : Object { + + private GLib.TestSuite suite; + private Adaptor[] adaptors = new Adaptor[0]; + + public delegate void TestMethod (); + + public TestCase (string name) { + this.suite = new GLib.TestSuite (name); + } + + public void add_test (string name, TestMethod test) { + var adaptor = new Adaptor (name, test, this); + this.adaptors += adaptor; + + this.suite.add (new GLib.TestCase (adaptor.name, + adaptor.set_up, + adaptor.run, + adaptor.tear_down )); + } + + public virtual void set_up () { + } + + public virtual void tear_down () { + } + + public GLib.TestSuite get_suite () { + return this.suite; + } + + private class Adaptor { + + public string name { get; private set; } + private TestMethod test; + private TestCase test_case; + + public Adaptor (string name, + TestMethod test, + TestCase test_case) { + this.name = name; + this.test = test; + this.test_case = test_case; + } + + public void set_up (void* fixture) { + this.test_case.set_up (); + } + + public void run (void* fixture) { + this.test (); + } + + public void tear_down (void* fixture) { + this.test_case.tear_down (); + } + } +} diff --git a/tests/tools/with-session-bus.sh b/tests/tools/with-session-bus.sh new file mode 100755 index 0000000..063bd7e --- /dev/null +++ b/tests/tools/with-session-bus.sh @@ -0,0 +1,94 @@ +#!/bin/sh +# with-session-bus.sh - run a program with a temporary D-Bus session daemon +# +# The canonical location of this program is the telepathy-glib tools/ +# directory, please synchronize any changes with that copy. +# +# Copyright (C) 2007-2008 Collabora Ltd. +# +# 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. + +set -e + +me=with-session-bus + +dbus_daemon_args="--print-address=5 --print-pid=6 --fork" +sleep=0 + +usage () +{ + echo "usage: $me [options] -- program [program_options]" >&2 + echo "Requires write access to the current directory." >&2 + echo "" >&2 + echo "If \$WITH_SESSION_BUS_FORK_DBUS_MONITOR is set, fork dbus-monitor" >&2 + echo "with the arguments in \$WITH_SESSION_BUS_FORK_DBUS_MONITOR_OPT." >&2 + echo "The output of dbus-monitor is saved in $me-.dbus-monitor-logs" >&2 + exit 2 +} + +while test "z$1" != "z--"; do + case "$1" in + --sleep=*) + sleep="$1" + sleep="${sleep#--sleep=}" + shift + ;; + --session) + dbus_daemon_args="$dbus_daemon_args --session" + shift + ;; + --config-file=*) + # FIXME: assumes config file doesn't contain any special characters + dbus_daemon_args="$dbus_daemon_args $1" + shift + ;; + *) + usage + ;; + esac +done +shift +if test "z$1" = "z"; then usage; fi + +exec 5> $me-$$.address +exec 6> $me-$$.pid + +cleanup () +{ + pid=`head -n1 $me-$$.pid` + if test -n "$pid" ; then + echo "Killing temporary bus daemon: $pid" >&2 + kill -INT "$pid" + fi + rm -f $me-$$.address + rm -f $me-$$.pid +} + +trap cleanup INT HUP TERM +dbus-daemon $dbus_daemon_args + +{ echo -n "Temporary bus daemon is "; cat $me-$$.address; } >&2 +{ echo -n "Temporary bus daemon PID is "; head -n1 $me-$$.pid; } >&2 + +e=0 +DBUS_SESSION_BUS_ADDRESS="`cat $me-$$.address`" +export DBUS_SESSION_BUS_ADDRESS + +if [ -n "$WITH_SESSION_BUS_FORK_DBUS_MONITOR" ] ; then + echo -n "Forking dbus-monitor $WITH_SESSION_BUS_FORK_DBUS_MONITOR_OPT" >&2 + dbus-monitor $WITH_SESSION_BUS_FORK_DBUS_MONITOR_OPT \ + > $me-$$.dbus-monitor-logs 2>&1 & +fi + +"$@" || e=$? + +if test $sleep != 0; then + sleep $sleep +fi + +trap - INT HUP TERM +cleanup + +exit $e diff --git a/tools/manager-file.py b/tools/manager-file.py new file mode 100755 index 0000000..45f6404 --- /dev/null +++ b/tools/manager-file.py @@ -0,0 +1,175 @@ +#!/usr/bin/python + +# manager-file.py: generate .manager files and TpCMParamSpec arrays from the +# same data (should be suitable for all connection managers that don't have +# plugins) +# +# The master copy of this program is in the telepathy-glib repository - +# please make any changes there. +# +# Copyright (c) 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 + +import re +import sys + +_NOT_C_STR = re.compile(r'[^A-Za-z0-9_-]') + +def c_string(x): + # whitelist-based brute force and ignorance - escape nearly all punctuation + return '"' + _NOT_C_STR.sub(lambda c: r'\x%02x' % ord(c), x) + '"' + +def desktop_string(x): + return x.replace(' ', r'\s').replace('\n', r'\n').replace('\r', r'\r').replace('\t', r'\t') + +supported = list('sbuiqn') + +fdefaultencoders = { + 's': desktop_string, + 'b': (lambda b: b and '1' or '0'), + 'u': (lambda n: '%u' % n), + 'i': (lambda n: '%d' % n), + 'q': (lambda n: '%u' % n), + 'n': (lambda n: '%d' % n), + } +for x in supported: assert x in fdefaultencoders + +gtypes = { + 's': 'G_TYPE_STRING', + 'b': 'G_TYPE_BOOLEAN', + 'u': 'G_TYPE_UINT', + 'i': 'G_TYPE_INT', + 'q': 'G_TYPE_UINT', + 'n': 'G_TYPE_INT', +} +for x in supported: assert x in gtypes + +gdefaultencoders = { + 's': c_string, + 'b': (lambda b: b and 'GINT_TO_POINTER (TRUE)' or 'GINT_TO_POINTER (FALSE)'), + 'u': (lambda n: 'GUINT_TO_POINTER (%u)' % n), + 'i': (lambda n: 'GINT_TO_POINTER (%d)' % n), + 'q': (lambda n: 'GUINT_TO_POINTER (%u)' % n), + 'n': (lambda n: 'GINT_TO_POINTER (%d)' % n), + } +for x in supported: assert x in gdefaultencoders + +gdefaultdefaults = { + 's': 'NULL', + 'b': 'GINT_TO_POINTER (FALSE)', + 'u': 'GUINT_TO_POINTER (0)', + 'i': 'GINT_TO_POINTER (0)', + 'q': 'GUINT_TO_POINTER (0)', + 'n': 'GINT_TO_POINTER (0)', + } +for x in supported: assert x in gdefaultdefaults + +gflags = { + 'has-default': 'TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT', + 'register': 'TP_CONN_MGR_PARAM_FLAG_REGISTER', + 'required': 'TP_CONN_MGR_PARAM_FLAG_REQUIRED', + 'secret': 'TP_CONN_MGR_PARAM_FLAG_SECRET', + 'dbus-property': 'TP_CONN_MGR_PARAM_FLAG_DBUS_PROPERTY', +} + +def write_manager(f, manager, protos): + # pointless backwards compat section + print >> f, '[ConnectionManager]' + print >> f, 'BusName=org.freedesktop.Telepathy.ConnectionManager.' + manager + print >> f, 'ObjectPath=/org/freedesktop/Telepathy/ConnectionManager/' + manager + + # protocols + for proto, params in protos.iteritems(): + print >> f + print >> f, '[Protocol %s]' % proto + + defaults = {} + + for param, info in params.iteritems(): + dtype = info['dtype'] + flags = info.get('flags', '').split() + struct_field = info.get('struct_field', param.replace('-', '_')) + filter = info.get('filter', 'NULL') + filter_data = info.get('filter_data', 'NULL') + setter_data = 'NULL' + + if 'default' in info: + default = fdefaultencoders[dtype](info['default']) + defaults[param] = default + + if flags: + flags = ' ' + ' '.join(flags) + else: + flags = '' + + print >> f, 'param-%s=%s%s' % (param, desktop_string(dtype), flags) + + for param, default in defaults.iteritems(): + print >> f, 'default-%s=%s' % (param, default) + +def write_c_params(f, manager, proto, struct, params): + print >> f, "static const TpCMParamSpec %s_%s_params[] = {" % (manager, proto) + + for param, info in params.iteritems(): + dtype = info['dtype'] + flags = info.get('flags', '').split() + struct_field = info.get('struct_field', param.replace('-', '_')) + filter = info.get('filter', 'NULL') + filter_data = info.get('filter_data', 'NULL') + setter_data = 'NULL' + + if 'default' in info: + default = gdefaultencoders[dtype](info['default']) + else: + default = gdefaultdefaults[dtype] + + if flags: + flags = ' | '.join([gflags[flag] for flag in flags]) + else: + flags = '0' + + if struct is None or struct_field is None: + struct_offset = '0' + else: + struct_offset = 'G_STRUCT_OFFSET (%s, %s)' % (struct, struct_field) + + print >> f, (''' { %s, %s, %s, + %s, + %s, /* default */ + %s, /* struct offset */ + %s, /* filter */ + %s, /* filter data */ + %s /* setter data */ },''' % + (c_string(param), c_string(dtype), gtypes[dtype], flags, + default, struct_offset, filter, filter_data, setter_data)) + + print >> f, " { NULL }" + print >> f, "};" + +if __name__ == '__main__': + environment = {} + execfile(sys.argv[1], environment) + + f = open('%s/%s.manager' % (sys.argv[2], environment['MANAGER']), 'w') + write_manager(f, environment['MANAGER'], environment['PARAMS']) + f.close() + + f = open('%s/param-spec-struct.h' % sys.argv[2], 'w') + for protocol in environment['PARAMS']: + write_c_params(f, environment['MANAGER'], protocol, + environment['STRUCTS'][protocol], + environment['PARAMS'][protocol]) + f.close()