[Telepathy-commits] [telepathy-qt4/master] Added channelspecific example from tp-glib (support channel with type Room).

Andre Moreira Magalhaes (andrunko) andre.magalhaes at collabora.co.uk
Fri Feb 6 13:03:16 PST 2009


---
 configure.ac                 |    1 +
 tests/lib/Makefile.am        |    2 +-
 tests/lib/csh/Makefile.am    |   22 ++
 tests/lib/csh/conn.c         |  273 ++++++++++++++++++
 tests/lib/csh/conn.h         |   58 ++++
 tests/lib/csh/room-manager.c |  376 ++++++++++++++++++++++++
 tests/lib/csh/room-manager.h |   55 ++++
 tests/lib/csh/room.c         |  651 ++++++++++++++++++++++++++++++++++++++++++
 tests/lib/csh/room.h         |   64 ++++
 9 files changed, 1501 insertions(+), 1 deletions(-)
 create mode 100644 tests/lib/csh/Makefile.am
 create mode 100644 tests/lib/csh/conn.c
 create mode 100644 tests/lib/csh/conn.h
 create mode 100644 tests/lib/csh/room-manager.c
 create mode 100644 tests/lib/csh/room-manager.h
 create mode 100644 tests/lib/csh/room.c
 create mode 100644 tests/lib/csh/room.h

diff --git a/configure.ac b/configure.ac
index c90ac8a..3e1c9ab 100644
--- a/configure.ac
+++ b/configure.ac
@@ -247,6 +247,7 @@ AC_OUTPUT([
     tests/dbus/Makefile
     tests/dbus-1/services/account-manager.service
     tests/lib/Makefile
+    tests/lib/csh/Makefile
     tests/lib/echo2/Makefile
     tests/pinocchio/Makefile
     tests/prototype/Makefile
diff --git a/tests/lib/Makefile.am b/tests/lib/Makefile.am
index 31d11b2..dcee207 100644
--- a/tests/lib/Makefile.am
+++ b/tests/lib/Makefile.am
@@ -1,4 +1,4 @@
-SUBDIRS = echo2
+SUBDIRS = . csh echo2
 
 AM_CFLAGS = $(ERROR_CFLAGS)
 
diff --git a/tests/lib/csh/Makefile.am b/tests/lib/csh/Makefile.am
new file mode 100644
index 0000000..64b6cff
--- /dev/null
+++ b/tests/lib/csh/Makefile.am
@@ -0,0 +1,22 @@
+AM_CFLAGS = $(ERROR_CFLAGS)
+AM_CXXFLAGS = $(ERROR_CXXFLAGS)
+
+if ENABLE_TP_GLIB_TESTS
+
+AM_CFLAGS += $(TP_GLIB_CFLAGS)
+AM_CXXFLAGS += $(TP_GLIB_CFLAGS)
+
+noinst_LTLIBRARIES = libtp-glib-csh-tests.la
+
+libtp_glib_csh_tests_la_SOURCES = \
+    conn.c \
+    conn.h \
+    room.c \
+    room.h \
+    room-manager.c \
+    room-manager.h
+
+libtp_glib_csh_tests_la_LIBADD = \
+    $(TP_GLIB_LIBS)
+
+endif
diff --git a/tests/lib/csh/conn.c b/tests/lib/csh/conn.c
new file mode 100644
index 0000000..aab088f
--- /dev/null
+++ b/tests/lib/csh/conn.c
@@ -0,0 +1,273 @@
+/*
+ * conn.c - an example connection
+ *
+ * Copyright (C) 2007-2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * 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.
+ */
+
+#include "conn.h"
+
+#include <string.h>
+
+#include <dbus/dbus-glib.h>
+
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/errors.h>
+#include <telepathy-glib/handle-repo-dynamic.h>
+#include <telepathy-glib/interfaces.h>
+
+#include "room-manager.h"
+
+G_DEFINE_TYPE (ExampleCSHConnection,
+    example_csh_connection,
+    TP_TYPE_BASE_CONNECTION)
+
+/* type definition stuff */
+
+enum
+{
+  PROP_ACCOUNT = 1,
+  N_PROPS
+};
+
+struct _ExampleCSHConnectionPrivate
+{
+  gchar *account;
+};
+
+static void
+example_csh_connection_init (ExampleCSHConnection *self)
+{
+  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+      EXAMPLE_TYPE_CSH_CONNECTION, ExampleCSHConnectionPrivate);
+}
+
+static void
+get_property (GObject *object,
+              guint property_id,
+              GValue *value,
+              GParamSpec *spec)
+{
+  ExampleCSHConnection *self = EXAMPLE_CSH_CONNECTION (object);
+
+  switch (property_id) {
+    case PROP_ACCOUNT:
+      g_value_set_string (value, self->priv->account);
+      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)
+{
+  ExampleCSHConnection *self = EXAMPLE_CSH_CONNECTION (object);
+
+  switch (property_id) {
+    case PROP_ACCOUNT:
+      g_free (self->priv->account);
+      self->priv->account = g_utf8_strdown (g_value_get_string (value), -1);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, spec);
+  }
+}
+
+static void
+finalize (GObject *object)
+{
+  ExampleCSHConnection *self = EXAMPLE_CSH_CONNECTION (object);
+
+  g_free (self->priv->account);
+
+  G_OBJECT_CLASS (example_csh_connection_parent_class)->finalize (object);
+}
+
+static gchar *
+get_unique_connection_name (TpBaseConnection *conn)
+{
+  ExampleCSHConnection *self = EXAMPLE_CSH_CONNECTION (conn);
+
+  return g_strdup (self->priv->account);
+}
+
+gchar *
+example_csh_normalize_contact (TpHandleRepoIface *repo,
+                               const gchar *id,
+                               gpointer context,
+                               GError **error)
+{
+  const gchar *at;
+  /* For this example, we imagine that global handles look like
+   * username at realm and channel-specific handles look like nickname@#chatroom,
+   * where username and nickname contain any UTF-8 except "@", and realm
+   * and chatroom contain any UTF-8 except "@" and "#".
+   *
+   * Additionally, we imagine that everything is case-sensitive but is
+   * required to be in NFKC.
+   */
+
+  if (id[0] == '\0')
+    {
+      g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_HANDLE,
+          "ID must not be empty");
+      return NULL;
+    }
+
+  at = strchr (id, '@');
+
+  if (at == NULL || at == id || at[1] == '\0')
+    {
+      g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_HANDLE,
+          "ID must look like aaa at bbb");
+      return NULL;
+    }
+
+  if (strchr (at + 1, '@') != NULL)
+    {
+      g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_HANDLE,
+          "ID cannot contain more than one '@'");
+      return NULL;
+    }
+
+  if (at[1] == '#' && at[2] == '\0')
+    {
+      g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_HANDLE,
+          "chatroom name cannot be empty");
+      return NULL;
+    }
+
+  if (strchr (at + 2, '#') != NULL)
+    {
+      g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_HANDLE,
+          "realm/chatroom cannot contain '#' except at the beginning");
+      return NULL;
+    }
+
+  return g_utf8_normalize (id, -1, G_NORMALIZE_ALL_COMPOSE);
+}
+
+static gchar *
+example_csh_normalize_room (TpHandleRepoIface *repo,
+                            const gchar *id,
+                            gpointer context,
+                            GError **error)
+{
+  /* See example_csh_normalize_contact(). */
+
+  if (id[0] != '#')
+    {
+      g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_HANDLE,
+          "Chatroom names in this protocol start with #");
+    }
+
+  if (id[1] == '\0')
+    {
+      g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_HANDLE,
+          "Chatroom name cannot be empty");
+      return NULL;
+    }
+
+  if (strchr (id, '@') != NULL)
+    {
+      g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_HANDLE,
+          "Chatroom names in this protocol cannot contain '@'");
+      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, example_csh_normalize_contact, NULL);
+
+  repos[TP_HANDLE_TYPE_ROOM] = tp_dynamic_handle_repo_new
+      (TP_HANDLE_TYPE_ROOM, example_csh_normalize_room, NULL);
+}
+
+static GPtrArray *
+create_channel_managers (TpBaseConnection *conn)
+{
+  GPtrArray *ret = g_ptr_array_sized_new (1);
+
+  g_ptr_array_add (ret, g_object_new (EXAMPLE_TYPE_CSH_ROOM_MANAGER,
+        "connection", conn,
+        NULL));
+
+  return ret;
+}
+
+static gboolean
+start_connecting (TpBaseConnection *conn,
+                  GError **error)
+{
+  ExampleCSHConnection *self = EXAMPLE_CSH_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
+example_csh_connection_class_init (ExampleCSHConnectionClass *klass)
+{
+  static const gchar *interfaces_always_present[] = {
+      TP_IFACE_CONNECTION_INTERFACE_REQUESTS,
+      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->finalize = finalize;
+  g_type_class_add_private (klass, sizeof (ExampleCSHConnectionPrivate));
+
+  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);
+}
diff --git a/tests/lib/csh/conn.h b/tests/lib/csh/conn.h
new file mode 100644
index 0000000..00b44bf
--- /dev/null
+++ b/tests/lib/csh/conn.h
@@ -0,0 +1,58 @@
+/*
+ * conn.h - header for an example connection
+ *
+ * Copyright (C) 2007-2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * 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.
+ */
+
+#ifndef __EXAMPLE_CSH_CONN_H__
+#define __EXAMPLE_CSH_CONN_H__
+
+#include <glib-object.h>
+#include <telepathy-glib/base-connection.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ExampleCSHConnection ExampleCSHConnection;
+typedef struct _ExampleCSHConnectionClass ExampleCSHConnectionClass;
+typedef struct _ExampleCSHConnectionPrivate ExampleCSHConnectionPrivate;
+
+struct _ExampleCSHConnectionClass {
+    TpBaseConnectionClass parent_class;
+};
+
+struct _ExampleCSHConnection {
+    TpBaseConnection parent;
+
+    ExampleCSHConnectionPrivate *priv;
+};
+
+GType example_csh_connection_get_type (void);
+
+/* TYPE MACROS */
+#define EXAMPLE_TYPE_CSH_CONNECTION \
+  (example_csh_connection_get_type ())
+#define EXAMPLE_CSH_CONNECTION(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj), EXAMPLE_TYPE_CSH_CONNECTION, \
+                              ExampleCSHConnection))
+#define EXAMPLE_CSH_CONNECTION_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass), EXAMPLE_TYPE_CSH_CONNECTION, \
+                           ExampleCSHConnectionClass))
+#define EXAMPLE_IS_CSH_CONNECTION(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj), EXAMPLE_TYPE_CSH_CONNECTION))
+#define EXAMPLE_IS_CSH_CONNECTION_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass), EXAMPLE_TYPE_CSH_CONNECTION))
+#define EXAMPLE_CSH_CONNECTION_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_CSH_CONNECTION, \
+                              ExampleCSHConnectionClass))
+
+gchar *example_csh_normalize_contact (TpHandleRepoIface *repo,
+    const gchar *id, gpointer context, GError **error);
+
+G_END_DECLS
+
+#endif
diff --git a/tests/lib/csh/room-manager.c b/tests/lib/csh/room-manager.c
new file mode 100644
index 0000000..7666927
--- /dev/null
+++ b/tests/lib/csh/room-manager.c
@@ -0,0 +1,376 @@
+/*
+ * room-manager.c: example channel manager for chatrooms
+ *
+ * Copyright (C) 2007-2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2007 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 "room-manager.h"
+
+#include <dbus/dbus-glib.h>
+
+#include <telepathy-glib/base-connection.h>
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/errors.h>
+#include <telepathy-glib/interfaces.h>
+
+#include "room.h"
+
+static void channel_manager_iface_init (gpointer, gpointer);
+
+G_DEFINE_TYPE_WITH_CODE (ExampleCSHRoomManager,
+    example_csh_room_manager,
+    G_TYPE_OBJECT,
+    G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_MANAGER,
+      channel_manager_iface_init))
+
+/* type definition stuff */
+
+enum
+{
+  PROP_CONNECTION = 1,
+  N_PROPS
+};
+
+struct _ExampleCSHRoomManagerPrivate
+{
+  TpBaseConnection *conn;
+
+  /* GUINT_TO_POINTER (room handle) => ExampleCSHRoomChannel */
+  GHashTable *channels;
+  gulong status_changed_id;
+};
+
+static void
+example_csh_room_manager_init (ExampleCSHRoomManager *self)
+{
+  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+      EXAMPLE_TYPE_CSH_ROOM_MANAGER, ExampleCSHRoomManagerPrivate);
+
+  self->priv->channels = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+      NULL, g_object_unref);
+}
+
+static void example_csh_room_manager_close_all (ExampleCSHRoomManager *self);
+
+static void
+dispose (GObject *object)
+{
+  ExampleCSHRoomManager *self = EXAMPLE_CSH_ROOM_MANAGER (object);
+
+  example_csh_room_manager_close_all (self);
+  g_assert (self->priv->channels == NULL);
+
+  ((GObjectClass *) example_csh_room_manager_parent_class)->dispose (object);
+}
+
+static void
+get_property (GObject *object,
+              guint property_id,
+              GValue *value,
+              GParamSpec *pspec)
+{
+  ExampleCSHRoomManager *self = EXAMPLE_CSH_ROOM_MANAGER (object);
+
+  switch (property_id)
+    {
+    case PROP_CONNECTION:
+      g_value_set_object (value, self->priv->conn);
+      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)
+{
+  ExampleCSHRoomManager *self = EXAMPLE_CSH_ROOM_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;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+    }
+}
+
+static void
+status_changed_cb (TpBaseConnection *conn,
+                   guint status,
+                   guint reason,
+                   ExampleCSHRoomManager *self)
+{
+  if (status == TP_CONNECTION_STATUS_DISCONNECTED)
+    example_csh_room_manager_close_all (self);
+}
+
+static void
+constructed (GObject *object)
+{
+  ExampleCSHRoomManager *self = EXAMPLE_CSH_ROOM_MANAGER (object);
+  void (*chain_up) (GObject *) =
+      ((GObjectClass *) example_csh_room_manager_parent_class)->constructed;
+
+  if (chain_up != NULL)
+    {
+      chain_up (object);
+    }
+
+  self->priv->status_changed_id = g_signal_connect (self->priv->conn,
+      "status-changed", (GCallback) status_changed_cb, self);
+}
+
+static void
+example_csh_room_manager_class_init (ExampleCSHRoomManagerClass *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);
+
+  g_type_class_add_private (klass, sizeof (ExampleCSHRoomManagerPrivate));
+}
+
+static void
+example_csh_room_manager_close_all (ExampleCSHRoomManager *self)
+{
+  if (self->priv->channels != NULL)
+    {
+      GHashTable *tmp = self->priv->channels;
+
+      self->priv->channels = NULL;
+      g_hash_table_destroy (tmp);
+    }
+
+  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
+example_csh_room_manager_foreach_channel (TpChannelManager *manager,
+                                          TpExportableChannelFunc callback,
+                                          gpointer user_data)
+{
+  ExampleCSHRoomManager *self = EXAMPLE_CSH_ROOM_MANAGER (manager);
+  GHashTableIter iter;
+  gpointer handle, channel;
+
+  g_hash_table_iter_init (&iter, self->priv->channels);
+
+  while (g_hash_table_iter_next (&iter, &handle, &channel))
+    {
+      callback (TP_EXPORTABLE_CHANNEL (channel), user_data);
+    }
+}
+
+static void
+channel_closed_cb (ExampleCSHRoomChannel *chan,
+                   ExampleCSHRoomManager *self)
+{
+  tp_channel_manager_emit_channel_closed_for_object (self,
+      TP_EXPORTABLE_CHANNEL (chan));
+
+  if (self->priv->channels != NULL)
+    {
+      TpHandle handle;
+
+      g_object_get (chan,
+          "handle", &handle,
+          NULL);
+
+      g_hash_table_remove (self->priv->channels, GUINT_TO_POINTER (handle));
+    }
+}
+
+static ExampleCSHRoomChannel *
+new_channel (ExampleCSHRoomManager *self,
+             TpHandle handle,
+             TpHandle initiator,
+             gpointer request_token)
+{
+  ExampleCSHRoomChannel *chan;
+  gchar *object_path;
+  GSList *requests = NULL;
+
+  object_path = g_strdup_printf ("%s/CSHRoomChannel%u",
+      self->priv->conn->object_path, handle);
+
+  chan = g_object_new (EXAMPLE_TYPE_CSH_ROOM_CHANNEL,
+      "connection", self->priv->conn,
+      "object-path", object_path,
+      "handle", handle,
+      /* FIXME: initiator */
+      NULL);
+
+  g_free (object_path);
+
+  g_signal_connect (chan, "closed", (GCallback) channel_closed_cb, self);
+
+  g_hash_table_insert (self->priv->channels, GUINT_TO_POINTER (handle), chan);
+
+  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);
+
+  return chan;
+}
+
+static const gchar * const fixed_properties[] = {
+    TP_IFACE_CHANNEL ".ChannelType",
+    TP_IFACE_CHANNEL ".TargetHandleType",
+    NULL
+};
+
+static const gchar * const allowed_properties[] = {
+    TP_IFACE_CHANNEL ".TargetHandle",
+    TP_IFACE_CHANNEL ".TargetID",
+    NULL
+};
+
+static void
+example_csh_room_manager_foreach_channel_class (TpChannelManager *manager,
+    TpChannelManagerChannelClassFunc func,
+    gpointer user_data)
+{
+    GHashTable *table = g_hash_table_new_full (g_str_hash, g_str_equal,
+        NULL, (GDestroyNotify) tp_g_value_slice_free);
+    GValue *value;
+
+    value = tp_g_value_slice_new (G_TYPE_STRING);
+    g_value_set_static_string (value, TP_IFACE_CHANNEL_TYPE_TEXT);
+    g_hash_table_insert (table, TP_IFACE_CHANNEL ".ChannelType", value);
+
+    value = tp_g_value_slice_new (G_TYPE_UINT);
+    g_value_set_uint (value, TP_HANDLE_TYPE_ROOM);
+    g_hash_table_insert (table, TP_IFACE_CHANNEL ".TargetHandleType", value);
+
+    func (manager, table, allowed_properties, user_data);
+
+    g_hash_table_destroy (table);
+}
+
+static gboolean
+example_csh_room_manager_request (ExampleCSHRoomManager *self,
+                                  gpointer request_token,
+                                  GHashTable *request_properties,
+                                  gboolean require_new)
+{
+  TpHandle handle;
+  ExampleCSHRoomChannel *chan;
+  GError *error = NULL;
+
+  if (tp_strdiff (tp_asv_get_string (request_properties,
+          TP_IFACE_CHANNEL ".ChannelType"),
+      TP_IFACE_CHANNEL_TYPE_TEXT))
+    {
+      return FALSE;
+    }
+
+  if (tp_asv_get_uint32 (request_properties,
+      TP_IFACE_CHANNEL ".TargetHandleType", NULL) != TP_HANDLE_TYPE_ROOM)
+    {
+      return FALSE;
+    }
+
+  handle = tp_asv_get_uint32 (request_properties,
+      TP_IFACE_CHANNEL ".TargetHandle", NULL);
+  g_assert (handle != 0);
+
+  if (tp_channel_manager_asv_has_unknown_properties (request_properties,
+        fixed_properties, allowed_properties, &error))
+    {
+      goto error;
+    }
+
+  chan = g_hash_table_lookup (self->priv->channels, GUINT_TO_POINTER (handle));
+
+  if (chan == NULL)
+    {
+      chan = new_channel (self, handle, self->priv->conn->self_handle,
+          request_token);
+    }
+  else if (require_new)
+    {
+      g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+          "A Text channel for room #%u already exists", 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
+example_csh_room_manager_create_channel (TpChannelManager *manager,
+                                         gpointer request_token,
+                                         GHashTable *request_properties)
+{
+    return example_csh_room_manager_request (
+        EXAMPLE_CSH_ROOM_MANAGER (manager), request_token,
+        request_properties, TRUE);
+}
+
+static gboolean
+example_csh_room_manager_ensure_channel (TpChannelManager *manager,
+                                         gpointer request_token,
+                                         GHashTable *request_properties)
+{
+    return example_csh_room_manager_request (
+        EXAMPLE_CSH_ROOM_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 = example_csh_room_manager_foreach_channel;
+  iface->foreach_channel_class =
+      example_csh_room_manager_foreach_channel_class;
+  iface->create_channel = example_csh_room_manager_create_channel;
+  iface->ensure_channel = example_csh_room_manager_ensure_channel;
+  /* In this channel manager, Request has the same semantics as Ensure */
+  iface->request_channel = example_csh_room_manager_ensure_channel;
+}
diff --git a/tests/lib/csh/room-manager.h b/tests/lib/csh/room-manager.h
new file mode 100644
index 0000000..6e9eb27
--- /dev/null
+++ b/tests/lib/csh/room-manager.h
@@ -0,0 +1,55 @@
+/*
+ * manager.h - header for an example channel manager
+ *
+ * Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2007 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 __EXAMPLE_CSH_ROOM_MANAGER_H__
+#define __EXAMPLE_CSH_ROOM_MANAGER_H__
+
+#include <glib-object.h>
+#include <telepathy-glib/channel-manager.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ExampleCSHRoomManager ExampleCSHRoomManager;
+typedef struct _ExampleCSHRoomManagerClass ExampleCSHRoomManagerClass;
+typedef struct _ExampleCSHRoomManagerPrivate ExampleCSHRoomManagerPrivate;
+
+struct _ExampleCSHRoomManagerClass {
+    GObjectClass parent_class;
+};
+
+struct _ExampleCSHRoomManager {
+    GObject parent;
+
+    ExampleCSHRoomManagerPrivate *priv;
+};
+
+GType example_csh_room_manager_get_type (void);
+
+/* TYPE MACROS */
+#define EXAMPLE_TYPE_CSH_ROOM_MANAGER \
+  (example_csh_room_manager_get_type ())
+#define EXAMPLE_CSH_ROOM_MANAGER(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj), EXAMPLE_TYPE_CSH_ROOM_MANAGER, \
+                              ExampleCSHRoomManager))
+#define EXAMPLE_CSH_ROOM_MANAGER_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass), EXAMPLE_TYPE_CSH_ROOM_MANAGER, \
+                           ExampleCSHRoomManagerClass))
+#define EXAMPLE_IS_CSH_ROOM_MANAGER(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj), EXAMPLE_TYPE_CSH_ROOM_MANAGER))
+#define EXAMPLE_IS_CSH_ROOM_MANAGER_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass), EXAMPLE_TYPE_CSH_ROOM_MANAGER))
+#define EXAMPLE_CSH_ROOM_MANAGER_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_CSH_ROOM_MANAGER, \
+                              ExampleCSHRoomManagerClass))
+
+G_END_DECLS
+
+#endif
diff --git a/tests/lib/csh/room.c b/tests/lib/csh/room.c
new file mode 100644
index 0000000..2eaa846
--- /dev/null
+++ b/tests/lib/csh/room.c
@@ -0,0 +1,651 @@
+/*
+ * room.c - a chatroom channel
+ *
+ * Copyright (C) 2007-2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * 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.
+ */
+
+#include "room.h"
+
+#include <telepathy-glib/base-connection.h>
+#include <telepathy-glib/channel-iface.h>
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/svc-channel.h>
+#include <telepathy-glib/svc-generic.h>
+
+static void text_iface_init (gpointer iface, gpointer data);
+static void channel_iface_init (gpointer iface, gpointer data);
+
+G_DEFINE_TYPE_WITH_CODE (ExampleCSHRoomChannel,
+    example_csh_room_channel,
+    G_TYPE_OBJECT,
+    G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL, channel_iface_init);
+    G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_TYPE_TEXT, text_iface_init);
+    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))
+
+/* type definition stuff */
+
+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_INTERFACES,
+  PROP_CHANNEL_DESTROYED,
+  PROP_CHANNEL_PROPERTIES,
+  N_PROPS
+};
+
+struct _ExampleCSHRoomChannelPrivate
+{
+  TpBaseConnection *conn;
+  gchar *object_path;
+  TpHandle handle;
+  TpHandle initiator;
+
+  /* These are really booleans, but gboolean is signed. Thanks, GLib */
+  unsigned closed:1;
+  unsigned disposed:1;
+};
+
+
+static const char * example_csh_room_channel_interfaces[] = {
+    TP_IFACE_CHANNEL_INTERFACE_GROUP,
+    NULL
+};
+
+
+static void
+example_csh_room_channel_init (ExampleCSHRoomChannel *self)
+{
+  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, EXAMPLE_TYPE_CSH_ROOM_CHANNEL,
+      ExampleCSHRoomChannelPrivate);
+}
+
+static TpHandle
+suggest_room_identity (ExampleCSHRoomChannel *self)
+{
+  TpHandleRepoIface *contact_repo = tp_base_connection_get_handles
+      (self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+  TpHandleRepoIface *room_repo = tp_base_connection_get_handles
+      (self->priv->conn, TP_HANDLE_TYPE_ROOM);
+  gchar *nick, *id;
+  TpHandle ret;
+
+  nick = g_strdup (tp_handle_inspect (contact_repo,
+        self->priv->conn->self_handle));
+  g_strdelimit (nick, "@", '\0');
+  id = g_strdup_printf ("%s@%s", nick, tp_handle_inspect (room_repo,
+        self->priv->handle));
+  g_free (nick);
+
+  ret = tp_handle_ensure (contact_repo, id, NULL, NULL);
+  g_free (id);
+
+  g_assert (ret != 0);
+  return ret;
+}
+
+
+/* This timeout callback represents a successful join. In a real CM it'd
+ * happen in response to network events, rather than just a timer */
+static void
+complete_join (ExampleCSHRoomChannel *self)
+{
+  TpHandleRepoIface *contact_repo = tp_base_connection_get_handles
+      (self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+  TpHandleRepoIface *room_repo = tp_base_connection_get_handles
+      (self->priv->conn, TP_HANDLE_TYPE_ROOM);
+  const gchar *room_name = tp_handle_inspect (room_repo, self->priv->handle);
+  gchar *str;
+  TpHandle alice_local, bob_local, chris_local, anon_local;
+  TpHandle alice_global, bob_global, chris_global;
+  TpGroupMixin *mixin = TP_GROUP_MIXIN (self);
+  TpIntSet *added;
+
+  /* For this example, we assume that all chatrooms initially contain
+   * Alice, Bob and Chris (and that their global IDs are also known),
+   * and they also contain one anonymous user. */
+
+  str = g_strdup_printf ("alice@%s", room_name);
+  alice_local = tp_handle_ensure (contact_repo, str, NULL, NULL);
+  g_free (str);
+  alice_global = tp_handle_ensure (contact_repo, "alice at alpha", NULL, NULL);
+
+  str = g_strdup_printf ("bob@%s", room_name);
+  bob_local = tp_handle_ensure (contact_repo, str, NULL, NULL);
+  g_free (str);
+  bob_global = tp_handle_ensure (contact_repo, "bob at beta", NULL, NULL);
+
+  str = g_strdup_printf ("chris@%s", room_name);
+  chris_local = tp_handle_ensure (contact_repo, str, NULL, NULL);
+  g_free (str);
+  chris_global = tp_handle_ensure (contact_repo, "chris at chi", NULL, NULL);
+
+  str = g_strdup_printf ("anonymous coward@%s", room_name);
+  anon_local = tp_handle_ensure (contact_repo, str, NULL, NULL);
+  g_free (str);
+
+  /* If our chosen nick is not available, pretend the server would
+   * automatically rename us on entry. */
+  if (mixin->self_handle == alice_local ||
+      mixin->self_handle == bob_local ||
+      mixin->self_handle == chris_local ||
+      mixin->self_handle == anon_local)
+    {
+      TpHandle new_self;
+      TpIntSet *rp = tp_intset_new ();
+      TpIntSet *removed = tp_intset_new ();
+
+      str = g_strdup_printf ("renamed by server@%s", room_name);
+      new_self = tp_handle_ensure (contact_repo, str, NULL, NULL);
+      g_free (str);
+
+      tp_intset_add (rp, new_self);
+      tp_intset_add (removed, mixin->self_handle);
+
+      tp_group_mixin_add_handle_owner ((GObject *) self, new_self,
+          self->priv->conn->self_handle);
+      tp_group_mixin_change_self_handle ((GObject *) self, new_self);
+
+      tp_group_mixin_change_members ((GObject *) self, "", NULL, removed, NULL,
+          rp, 0, TP_CHANNEL_GROUP_CHANGE_REASON_RENAMED);
+
+      tp_handle_unref (contact_repo, new_self);
+      tp_intset_destroy (removed);
+      tp_intset_destroy (rp);
+    }
+
+  tp_group_mixin_add_handle_owner ((GObject *) self, alice_local,
+      alice_global);
+  tp_group_mixin_add_handle_owner ((GObject *) self, bob_local,
+      bob_global);
+  tp_group_mixin_add_handle_owner ((GObject *) self, chris_local,
+      chris_global);
+  /* we know that anon_local is channel-specific, but not whose it is,
+   * hence 0 */
+  tp_group_mixin_add_handle_owner ((GObject *) self, anon_local, 0);
+
+  /* everyone in! */
+  added = tp_intset_new();
+  tp_intset_add (added, alice_local);
+  tp_intset_add (added, bob_local);
+  tp_intset_add (added, chris_local);
+  tp_intset_add (added, anon_local);
+  tp_intset_add (added, mixin->self_handle);
+
+  tp_group_mixin_change_members ((GObject *) self, "", added, NULL, NULL,
+      NULL, 0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+
+  tp_handle_unref (contact_repo, alice_local);
+  tp_handle_unref (contact_repo, bob_local);
+  tp_handle_unref (contact_repo, chris_local);
+  tp_handle_unref (contact_repo, anon_local);
+
+  tp_handle_unref (contact_repo, alice_global);
+  tp_handle_unref (contact_repo, bob_global);
+  tp_handle_unref (contact_repo, chris_global);
+
+  /* now that the dust has settled, we can also invite people */
+  tp_group_mixin_change_flags ((GObject *) self,
+      TP_CHANNEL_GROUP_FLAG_CAN_ADD | TP_CHANNEL_GROUP_FLAG_MESSAGE_ADD,
+      0);
+}
+
+
+static void
+join_room (ExampleCSHRoomChannel *self)
+{
+  TpGroupMixin *mixin = TP_GROUP_MIXIN (self);
+  GObject *object = (GObject *) self;
+  TpIntSet *add_remote_pending;
+
+  g_assert (!tp_handle_set_is_member (mixin->members, mixin->self_handle));
+  g_assert (!tp_handle_set_is_member (mixin->remote_pending,
+        mixin->self_handle));
+
+  /* Indicate in the Group interface that a join is in progress */
+
+  add_remote_pending = tp_intset_new ();
+  tp_intset_add (add_remote_pending, mixin->self_handle);
+
+  tp_group_mixin_add_handle_owner (object, mixin->self_handle,
+      self->priv->conn->self_handle);
+  tp_group_mixin_change_members (object, "", NULL, NULL, NULL,
+      add_remote_pending, self->priv->conn->self_handle,
+      TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+
+  tp_intset_destroy (add_remote_pending);
+
+  /* Actually join the room. In a real implementation this would be a network
+   * round-trip - we don't have a network, so pretend that joining takes
+   * 500ms */
+  g_timeout_add (500, (GSourceFunc) complete_join, self);
+}
+
+
+static GObject *
+constructor (GType type,
+             guint n_props,
+             GObjectConstructParam *props)
+{
+  GObject *object =
+      G_OBJECT_CLASS (example_csh_room_channel_parent_class)->constructor (type,
+          n_props, props);
+  ExampleCSHRoomChannel *self = EXAMPLE_CSH_ROOM_CHANNEL (object);
+  TpHandleRepoIface *contact_repo = tp_base_connection_get_handles
+      (self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+  TpHandleRepoIface *room_repo = tp_base_connection_get_handles
+      (self->priv->conn, TP_HANDLE_TYPE_ROOM);
+  DBusGConnection *bus;
+  TpHandle self_handle;
+
+  tp_handle_ref (room_repo, self->priv->handle);
+
+  if (self->priv->initiator != 0)
+    tp_handle_ref (contact_repo, self->priv->initiator);
+
+  bus = tp_get_bus ();
+  dbus_g_connection_register_g_object (bus, self->priv->object_path, object);
+
+  tp_text_mixin_init (object, G_STRUCT_OFFSET (ExampleCSHRoomChannel, text),
+      contact_repo);
+
+  tp_text_mixin_set_message_types (object,
+      TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL,
+      TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION,
+      G_MAXUINT);
+
+  /* We start off remote-pending (if this CM supported other people inviting
+   * us, we'd start off local-pending in that case instead - but it doesn't),
+   * with this self-handle. */
+  self_handle = suggest_room_identity (self);
+
+  tp_group_mixin_init (object,
+      G_STRUCT_OFFSET (ExampleCSHRoomChannel, group),
+      contact_repo, self_handle);
+
+  /* Initially, we can't do anything. */
+  tp_group_mixin_change_flags (object,
+      TP_CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES |
+      TP_CHANNEL_GROUP_FLAG_PROPERTIES,
+      0);
+
+  /* Immediately attempt to join the group */
+  join_room (self);
+
+  return object;
+}
+
+
+static void
+get_property (GObject *object,
+              guint property_id,
+              GValue *value,
+              GParamSpec *pspec)
+{
+  ExampleCSHRoomChannel *self = EXAMPLE_CSH_ROOM_CHANNEL (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_TEXT);
+      break;
+    case PROP_HANDLE_TYPE:
+      g_value_set_uint (value, TP_HANDLE_TYPE_ROOM);
+      break;
+    case PROP_HANDLE:
+      g_value_set_uint (value, self->priv->handle);
+      break;
+    case PROP_TARGET_ID:
+        {
+          TpHandleRepoIface *room_repo = tp_base_connection_get_handles (
+              self->priv->conn, TP_HANDLE_TYPE_ROOM);
+
+          g_value_set_string (value,
+              tp_handle_inspect (room_repo, self->priv->handle));
+        }
+      break;
+    case PROP_REQUESTED:
+      /* this example CM doesn't yet support being invited into a chatroom,
+       * so the only way a channel can exist is if the user asked for it */
+      g_value_set_boolean (value, TRUE);
+      break;
+    case PROP_INITIATOR_HANDLE:
+      g_value_set_uint (value, self->priv->initiator);
+      break;
+    case PROP_INITIATOR_ID:
+        {
+          TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
+              self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+          g_value_set_string (value,
+              self->priv->initiator == 0
+                  ? ""
+                  : tp_handle_inspect (contact_repo, self->priv->initiator));
+        }
+      break;
+    case PROP_CONNECTION:
+      g_value_set_object (value, self->priv->conn);
+      break;
+    case PROP_INTERFACES:
+      g_value_set_boxed (value, example_csh_room_channel_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)
+{
+  ExampleCSHRoomChannel *self = EXAMPLE_CSH_ROOM_CHANNEL (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
+       * room repo yet - instead we ref it in the constructor.
+       */
+      self->priv->handle = g_value_get_uint (value);
+      break;
+    case PROP_INITIATOR_HANDLE:
+      /* similarly, we don't yet have the contact repo */
+      self->priv->initiator = g_value_get_uint (value);
+      break;
+    case PROP_HANDLE_TYPE:
+    case PROP_CHANNEL_TYPE:
+      /* these properties are 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;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+    }
+}
+
+static void
+dispose (GObject *object)
+{
+  ExampleCSHRoomChannel *self = EXAMPLE_CSH_ROOM_CHANNEL (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 *) example_csh_room_channel_parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+  ExampleCSHRoomChannel *self = EXAMPLE_CSH_ROOM_CHANNEL (object);
+  TpHandleRepoIface *contact_handles = tp_base_connection_get_handles
+      (self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+  TpHandleRepoIface *room_handles = tp_base_connection_get_handles
+      (self->priv->conn, TP_HANDLE_TYPE_ROOM);
+
+  if (self->priv->initiator != 0)
+    tp_handle_unref (contact_handles, self->priv->initiator);
+
+  tp_handle_unref (room_handles, self->priv->handle);
+  g_free (self->priv->object_path);
+
+  tp_text_mixin_finalize (object);
+
+  ((GObjectClass *) example_csh_room_channel_parent_class)->finalize (object);
+}
+
+
+static gboolean
+add_member (GObject *object,
+            TpHandle handle,
+            const gchar *message,
+            GError **error)
+{
+  /* In a real implementation, if handle was mixin->self_handle we'd accept
+   * an invitation here; otherwise we'd invite the given contact.
+   * Here, we do nothing for now. */
+  return TRUE;
+}
+
+
+static void
+example_csh_room_channel_class_init (ExampleCSHRoomChannelClass *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 (ExampleCSHRoomChannelPrivate));
+
+  object_class->constructor = constructor;
+  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_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_CONSTRUCT_ONLY | G_PARAM_READWRITE | 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);
+
+  tp_text_mixin_class_init (object_class,
+      G_STRUCT_OFFSET (ExampleCSHRoomChannelClass, text_class));
+
+  klass->dbus_properties_class.interfaces = prop_interfaces;
+  tp_dbus_properties_mixin_class_init (object_class,
+      G_STRUCT_OFFSET (ExampleCSHRoomChannelClass, dbus_properties_class));
+
+  tp_group_mixin_class_init (object_class,
+      G_STRUCT_OFFSET (ExampleCSHRoomChannelClass, group_class),
+      add_member,
+      NULL);
+  tp_group_mixin_init_dbus_properties (object_class);
+}
+
+
+static void
+channel_close (TpSvcChannel *iface,
+               DBusGMethodInvocation *context)
+{
+  ExampleCSHRoomChannel *self = EXAMPLE_CSH_ROOM_CHANNEL (iface);
+
+  if (!self->priv->closed)
+    {
+      self->priv->closed = TRUE;
+      tp_svc_channel_emit_closed (self);
+    }
+
+  tp_svc_channel_return_from_close (context);
+}
+
+
+static void
+channel_get_channel_type (TpSvcChannel *iface,
+                          DBusGMethodInvocation *context)
+{
+  tp_svc_channel_return_from_get_channel_type (context,
+      TP_IFACE_CHANNEL_TYPE_TEXT);
+}
+
+
+static void
+channel_get_handle (TpSvcChannel *iface,
+                    DBusGMethodInvocation *context)
+{
+  ExampleCSHRoomChannel *self = EXAMPLE_CSH_ROOM_CHANNEL (iface);
+
+  tp_svc_channel_return_from_get_handle (context, TP_HANDLE_TYPE_ROOM,
+      self->priv->handle);
+}
+
+
+static void
+channel_get_interfaces (TpSvcChannel *iface,
+                        DBusGMethodInvocation *context)
+{
+  tp_svc_channel_return_from_get_interfaces (context,
+      example_csh_room_channel_interfaces);
+}
+
+
+static void
+channel_iface_init (gpointer iface,
+                    gpointer data)
+{
+  TpSvcChannelClass *klass = iface;
+
+#define IMPLEMENT(x) tp_svc_channel_implement_##x (klass, channel_##x)
+  IMPLEMENT (close);
+  IMPLEMENT (get_channel_type);
+  IMPLEMENT (get_handle);
+  IMPLEMENT (get_interfaces);
+#undef IMPLEMENT
+}
+
+
+static void
+text_send (TpSvcChannelTypeText *iface,
+           guint type,
+           const gchar *text,
+           DBusGMethodInvocation *context)
+{
+  ExampleCSHRoomChannel *self = EXAMPLE_CSH_ROOM_CHANNEL (iface);
+  time_t timestamp = time (NULL);
+
+  /* The /dev/null of text channels - we claim to have sent the message,
+   * but nothing more happens */
+  tp_svc_channel_type_text_emit_sent ((GObject *) self, timestamp, type, text);
+  tp_svc_channel_type_text_return_from_send (context);
+}
+
+
+static void
+text_iface_init (gpointer iface,
+                 gpointer data)
+{
+  TpSvcChannelTypeTextClass *klass = iface;
+
+  tp_text_mixin_iface_init (iface, data);
+#define IMPLEMENT(x) tp_svc_channel_type_text_implement_##x (klass, text_##x)
+  IMPLEMENT (send);
+#undef IMPLEMENT
+}
diff --git a/tests/lib/csh/room.h b/tests/lib/csh/room.h
new file mode 100644
index 0000000..1945543
--- /dev/null
+++ b/tests/lib/csh/room.h
@@ -0,0 +1,64 @@
+/*
+ * room.h - header for an example chatroom channel
+ *
+ * Copyright (C) 2007-2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * 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.
+ */
+
+#ifndef EXAMPLE_CSH_ROOM_H
+#define EXAMPLE_CSH_ROOM_H
+
+#include <glib-object.h>
+
+#include <telepathy-glib/base-connection.h>
+#include <telepathy-glib/group-mixin.h>
+#include <telepathy-glib/text-mixin.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ExampleCSHRoomChannel ExampleCSHRoomChannel;
+typedef struct _ExampleCSHRoomChannelClass ExampleCSHRoomChannelClass;
+typedef struct _ExampleCSHRoomChannelPrivate ExampleCSHRoomChannelPrivate;
+
+GType example_csh_room_channel_get_type (void);
+
+#define EXAMPLE_TYPE_CSH_ROOM_CHANNEL \
+  (example_csh_room_channel_get_type ())
+#define EXAMPLE_CSH_ROOM_CHANNEL(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj), EXAMPLE_TYPE_CSH_ROOM_CHANNEL, \
+                               ExampleCSHRoomChannel))
+#define EXAMPLE_CSH_ROOM_CHANNEL_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST ((klass), EXAMPLE_TYPE_CSH_ROOM_CHANNEL, \
+                            ExampleCSHRoomChannelClass))
+#define EXAMPLE_IS_CSH_ROOM_CHANNEL(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EXAMPLE_TYPE_CSH_ROOM_CHANNEL))
+#define EXAMPLE_IS_CSH_ROOM_CHANNEL_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE ((klass), EXAMPLE_TYPE_CSH_ROOM_CHANNEL))
+#define EXAMPLE_CSH_ROOM_CHANNEL_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_CSH_ROOM_CHANNEL, \
+                              ExampleCSHRoomChannelClass))
+
+struct _ExampleCSHRoomChannelClass {
+    GObjectClass parent_class;
+
+    TpTextMixinClass text_class;
+    TpGroupMixinClass group_class;
+    TpDBusPropertiesMixinClass dbus_properties_class;
+};
+
+struct _ExampleCSHRoomChannel {
+    GObject parent;
+
+    TpTextMixin text;
+    TpGroupMixin group;
+
+    ExampleCSHRoomChannelPrivate *priv;
+};
+
+G_END_DECLS
+
+#endif
-- 
1.5.6.5




More information about the telepathy-commits mailing list