[Telepathy-commits] [telepathy-glib/master] Copy echo connection manager as echo-message-parts (doesn't actually implement that interface yet though) and libraryize it for easier testing

Simon McVittie simon.mcvittie at collabora.co.uk
Thu Dec 18 10:41:24 PST 2008


[Resynched with echo CM during rebase.]
---
 configure.ac                               |    1 +
 examples/cm/Makefile.am                    |    1 +
 examples/cm/echo-message-parts/Makefile.am |   25 ++
 examples/cm/echo-message-parts/chan.c      |  531 ++++++++++++++++++++++++++++
 examples/cm/echo-message-parts/chan.h      |   58 +++
 examples/cm/echo-message-parts/conn.c      |  188 ++++++++++
 examples/cm/echo-message-parts/conn.h      |   54 +++
 examples/cm/echo-message-parts/factory.c   |  274 ++++++++++++++
 examples/cm/echo-message-parts/factory.h   |   55 +++
 examples/cm/echo-message-parts/main.c      |   43 +++
 examples/cm/echo-message-parts/manager.c   |   96 +++++
 examples/cm/echo-message-parts/manager.h   |   60 ++++
 12 files changed, 1386 insertions(+), 0 deletions(-)
 create mode 100644 examples/cm/echo-message-parts/Makefile.am
 create mode 100644 examples/cm/echo-message-parts/chan.c
 create mode 100644 examples/cm/echo-message-parts/chan.h
 create mode 100644 examples/cm/echo-message-parts/conn.c
 create mode 100644 examples/cm/echo-message-parts/conn.h
 create mode 100644 examples/cm/echo-message-parts/factory.c
 create mode 100644 examples/cm/echo-message-parts/factory.h
 create mode 100644 examples/cm/echo-message-parts/main.c
 create mode 100644 examples/cm/echo-message-parts/manager.c
 create mode 100644 examples/cm/echo-message-parts/manager.h

diff --git a/configure.ac b/configure.ac
index 69ea074..7f44468 100644
--- a/configure.ac
+++ b/configure.ac
@@ -217,6 +217,7 @@ AC_OUTPUT( Makefile \
 	   examples/cm/Makefile \
 	   examples/cm/channelspecific/Makefile \
 	   examples/cm/echo/Makefile \
+	   examples/cm/echo-message-parts/Makefile \
 	   examples/cm/extended/Makefile \
 	   examples/cm/no-protocols/Makefile \
 	   examples/extensions/Makefile \
diff --git a/examples/cm/Makefile.am b/examples/cm/Makefile.am
index d9fb11e..0d84b06 100644
--- a/examples/cm/Makefile.am
+++ b/examples/cm/Makefile.am
@@ -1,5 +1,6 @@
 SUBDIRS = \
     channelspecific \
     echo \
+    echo-message-parts \
     extended \
     no-protocols
diff --git a/examples/cm/echo-message-parts/Makefile.am b/examples/cm/echo-message-parts/Makefile.am
new file mode 100644
index 0000000..ded3b6a
--- /dev/null
+++ b/examples/cm/echo-message-parts/Makefile.am
@@ -0,0 +1,25 @@
+noinst_PROGRAMS = telepathy-example-cm-echo-2
+noinst_LTLIBRARIES = libexample-cm-echo-2.la
+
+libexample_cm_echo_2_la_SOURCES = \
+    chan.c \
+    chan.h \
+    conn.c \
+    conn.h \
+    factory.c \
+    factory.h \
+    manager.c \
+    manager.h
+
+telepathy_example_cm_echo_2_SOURCES = \
+    main.c
+
+telepathy_example_cm_echo_2_LDADD = \
+    $(TP_GLIB_LIBS) \
+    libexample-cm-echo-2.la
+
+AM_CFLAGS = \
+    $(ERROR_CFLAGS) \
+    $(DBUS_CFLAGS) \
+    $(GLIB_CFLAGS) \
+    $(TP_GLIB_CFLAGS)
diff --git a/examples/cm/echo-message-parts/chan.c b/examples/cm/echo-message-parts/chan.c
new file mode 100644
index 0000000..90e7fbd
--- /dev/null
+++ b/examples/cm/echo-message-parts/chan.c
@@ -0,0 +1,531 @@
+/*
+ * chan.c - an example text channel talking to a particular
+ * contact. Similar code is used for 1-1 IM channels in many protocols
+ * (IRC private messages ("/query"), XMPP IM etc.)
+ *
+ * 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.
+ */
+
+#include "chan.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 (ExampleEcho2Channel,
+    example_echo_2_channel,
+    G_TYPE_OBJECT,
+    G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
+      tp_dbus_properties_mixin_iface_init);
+    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_CHANNEL_IFACE, NULL);
+    G_IMPLEMENT_INTERFACE (TP_TYPE_EXPORTABLE_CHANNEL, 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 _ExampleEcho2ChannelPrivate
+{
+  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_echo_2_channel_interfaces[] = { NULL };
+/* FIXME: when supported, add TP_IFACE_CHANNEL_INTERFACE_DESTROYABLE */
+
+static void
+example_echo_2_channel_init (ExampleEcho2Channel *self)
+{
+  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, EXAMPLE_TYPE_ECHO_2_CHANNEL,
+      ExampleEcho2ChannelPrivate);
+}
+
+static GObject *
+constructor (GType type,
+             guint n_props,
+             GObjectConstructParam *props)
+{
+  GObject *object =
+      G_OBJECT_CLASS (example_echo_2_channel_parent_class)->constructor (type,
+          n_props, props);
+  ExampleEcho2Channel *self = EXAMPLE_ECHO_2_CHANNEL (object);
+  TpHandleRepoIface *contact_repo = tp_base_connection_get_handles
+      (self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+  DBusGConnection *bus;
+
+  tp_handle_ref (contact_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 (ExampleEcho2Channel, text),
+      contact_repo);
+
+  tp_text_mixin_set_message_types (object,
+      TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL,
+      TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION,
+      TP_CHANNEL_TEXT_MESSAGE_TYPE_NOTICE,
+      G_MAXUINT);
+
+  return object;
+}
+
+static void
+get_property (GObject *object,
+              guint property_id,
+              GValue *value,
+              GParamSpec *pspec)
+{
+  ExampleEcho2Channel *self = EXAMPLE_ECHO_2_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_CONTACT);
+      break;
+    case PROP_HANDLE:
+      g_value_set_uint (value, self->priv->handle);
+      break;
+    case PROP_TARGET_ID:
+        {
+          TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
+              self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+          g_value_set_string (value,
+              tp_handle_inspect (contact_repo, self->priv->handle));
+        }
+      break;
+    case PROP_REQUESTED:
+      g_value_set_boolean (value,
+          (self->priv->initiator == self->priv->conn->self_handle));
+      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_echo_2_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)
+{
+  ExampleEcho2Channel *self = EXAMPLE_ECHO_2_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
+       * contact repo yet - instead we ref it in the constructor.
+       */
+      self->priv->handle = g_value_get_uint (value);
+      break;
+    case PROP_INITIATOR_HANDLE:
+      /* likewise */
+      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)
+{
+  ExampleEcho2Channel *self = EXAMPLE_ECHO_2_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_echo_2_channel_parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+  ExampleEcho2Channel *self = EXAMPLE_ECHO_2_CHANNEL (object);
+  TpHandleRepoIface *contact_handles = tp_base_connection_get_handles
+      (self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+  tp_handle_unref (contact_handles, self->priv->handle);
+
+  if (self->priv->initiator != 0)
+    tp_handle_unref (contact_handles, self->priv->initiator);
+
+  g_free (self->priv->object_path);
+
+  tp_text_mixin_finalize (object);
+
+  ((GObjectClass *) example_echo_2_channel_parent_class)->finalize (object);
+}
+
+static void
+example_echo_2_channel_class_init (ExampleEcho2ChannelClass *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 (ExampleEcho2ChannelPrivate));
+
+  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", "Peer's ID",
+      "The string obtained by inspecting the target 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 (ExampleEcho2ChannelClass, text_class));
+
+  klass->dbus_properties_class.interfaces = prop_interfaces;
+  tp_dbus_properties_mixin_class_init (object_class,
+      G_STRUCT_OFFSET (ExampleEcho2ChannelClass, dbus_properties_class));
+}
+
+static void
+example_echo_2_channel_close (ExampleEcho2Channel *self)
+{
+  GObject *object = (GObject *) self;
+
+  if (!self->priv->closed)
+    {
+      TpHandle first_sender;
+
+      /* The factory wants to be able to respawn the channel if it has pending
+       * messages. When respawned, the channel must have the initiator set
+       * to the contact who sent us those messages (if it isn't already),
+       * and the messages must be marked as having been rescued so they
+       * don't get logged twice. */
+      if (tp_text_mixin_has_pending_messages (object, &first_sender))
+        {
+          if (self->priv->initiator != first_sender)
+            {
+              TpHandleRepoIface *contact_repo = tp_base_connection_get_handles
+                (self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+              TpHandle old_initiator = self->priv->initiator;
+
+              if (first_sender != 0)
+                tp_handle_ref (contact_repo, first_sender);
+
+              self->priv->initiator = first_sender;
+
+              if (old_initiator != 0)
+                tp_handle_unref (contact_repo, old_initiator);
+            }
+
+          tp_text_mixin_set_rescued (object);
+        }
+      else
+        {
+          /* No pending messages, so it's OK to really close */
+          self->priv->closed = TRUE;
+        }
+
+      tp_svc_channel_emit_closed (self);
+    }
+}
+
+static void
+channel_close (TpSvcChannel *iface,
+               DBusGMethodInvocation *context)
+{
+  ExampleEcho2Channel *self = EXAMPLE_ECHO_2_CHANNEL (iface);
+
+  example_echo_2_channel_close (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)
+{
+  ExampleEcho2Channel *self = EXAMPLE_ECHO_2_CHANNEL (iface);
+
+  tp_svc_channel_return_from_get_handle (context, TP_HANDLE_TYPE_CONTACT,
+      self->priv->handle);
+}
+
+static void
+channel_get_interfaces (TpSvcChannel *iface,
+                        DBusGMethodInvocation *context)
+{
+  tp_svc_channel_return_from_get_interfaces (context,
+      example_echo_2_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)
+{
+  ExampleEcho2Channel *self = EXAMPLE_ECHO_2_CHANNEL (iface);
+  time_t timestamp = time (NULL);
+  gchar *echo;
+  guint echo_type = type;
+
+  /* Tell the client that the message was sent successfully. If it's possible
+   * to tell whether a message has been delivered, you should delay emitting
+   * this signal until it's actually been successful, and emit SendError
+   * instead if there was an error; if you can't tell, emit Sent immediately,
+   * like this */
+  tp_svc_channel_type_text_emit_sent ((GObject *) self, timestamp, type, text);
+
+  /* Pretend that the remote contact has replied. Normally,
+   * you'd call tp_text_mixin_receive in response to network events */
+
+  switch (type)
+    {
+    case TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL:
+      echo = g_strdup_printf ("You said: %s", text);
+      break;
+    case TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION:
+      echo = g_strdup_printf ("notices that the user %s", text);
+      break;
+    case TP_CHANNEL_TEXT_MESSAGE_TYPE_NOTICE:
+      echo = g_strdup_printf ("You sent a notice: %s", text);
+      break;
+    default:
+      echo = g_strdup_printf ("You sent some weird message type, %u: \"%s\"",
+          type, text);
+      echo_type = TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL;
+    }
+
+  tp_text_mixin_receive ((GObject *) self, echo_type, self->priv->handle,
+      timestamp, echo);
+
+  g_free (echo);
+
+  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
+}
+
+/* FIXME: enable this when Destroyable is supported */
+#if 0
+static void
+destroyable_destroy (TpSvcChannelInterfaceDestroyable *iface,
+                     DBusGMethodInvocation *context)
+{
+  ExampleEcho2Channel *self = EXAMPLE_ECHO_2_CHANNEL (iface);
+
+  tp_text_mixin_clear ((GObject *) self);
+  example_echo_2_channel_close (self);
+  g_assert (self->priv->closed);
+  tp_svc_channel_return_from_close (context);
+}
+
+static void
+destroyable_iface_init (gpointer iface,
+                        gpointer data)
+{
+  TpSvcChannelInterfaceDestroyableClass *klass = iface;
+
+#define IMPLEMENT(x) \
+  tp_svc_channel_interface_destroyable_implement_##x (klass, destroyable_##x)
+  IMPLEMENT (destroy);
+#undef IMPLEMENT
+}
+#endif
diff --git a/examples/cm/echo-message-parts/chan.h b/examples/cm/echo-message-parts/chan.h
new file mode 100644
index 0000000..1117d58
--- /dev/null
+++ b/examples/cm/echo-message-parts/chan.h
@@ -0,0 +1,58 @@
+/*
+ * chan.h - header for an example channel
+ *
+ * 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_ECHO_MESSAGE_PARTS_CHAN_H
+#define EXAMPLE_ECHO_MESSAGE_PARTS_CHAN_H
+
+#include <glib-object.h>
+#include <telepathy-glib/base-connection.h>
+#include <telepathy-glib/text-mixin.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ExampleEcho2Channel ExampleEcho2Channel;
+typedef struct _ExampleEcho2ChannelClass ExampleEcho2ChannelClass;
+typedef struct _ExampleEcho2ChannelPrivate ExampleEcho2ChannelPrivate;
+
+GType example_echo_2_channel_get_type (void);
+
+#define EXAMPLE_TYPE_ECHO_2_CHANNEL \
+  (example_echo_2_channel_get_type ())
+#define EXAMPLE_ECHO_2_CHANNEL(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj), EXAMPLE_TYPE_ECHO_2_CHANNEL, \
+                               ExampleEcho2Channel))
+#define EXAMPLE_ECHO_2_CHANNEL_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST ((klass), EXAMPLE_TYPE_ECHO_2_CHANNEL, \
+                            ExampleEcho2ChannelClass))
+#define EXAMPLE_IS_ECHO_2_CHANNEL(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EXAMPLE_TYPE_ECHO_2_CHANNEL))
+#define EXAMPLE_IS_ECHO_2_CHANNEL_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE ((klass), EXAMPLE_TYPE_ECHO_2_CHANNEL))
+#define EXAMPLE_ECHO_2_CHANNEL_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_ECHO_2_CHANNEL, \
+                              ExampleEcho2ChannelClass))
+
+struct _ExampleEcho2ChannelClass {
+    GObjectClass parent_class;
+    TpTextMixinClass text_class;
+    TpDBusPropertiesMixinClass dbus_properties_class;
+};
+
+struct _ExampleEcho2Channel {
+    GObject parent;
+    TpTextMixin text;
+
+    ExampleEcho2ChannelPrivate *priv;
+};
+
+G_END_DECLS
+
+#endif
diff --git a/examples/cm/echo-message-parts/conn.c b/examples/cm/echo-message-parts/conn.c
new file mode 100644
index 0000000..56aab0c
--- /dev/null
+++ b/examples/cm/echo-message-parts/conn.c
@@ -0,0 +1,188 @@
+/*
+ * conn.c - an example connection
+ *
+ * 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.
+ */
+
+#include "conn.h"
+
+#include <dbus/dbus-glib.h>
+
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/errors.h>
+#include <telepathy-glib/handle-repo-dynamic.h>
+
+#include "factory.h"
+
+G_DEFINE_TYPE (ExampleEcho2Connection,
+    example_echo_2_connection,
+    TP_TYPE_BASE_CONNECTION)
+
+enum
+{
+  PROP_ACCOUNT = 1,
+  N_PROPS
+};
+
+struct _ExampleEcho2ConnectionPrivate
+{
+  gchar *account;
+};
+
+static void
+example_echo_2_connection_init (ExampleEcho2Connection *self)
+{
+  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+      EXAMPLE_TYPE_ECHO_2_CONNECTION,
+      ExampleEcho2ConnectionPrivate);
+}
+
+static void
+get_property (GObject *object,
+              guint property_id,
+              GValue *value,
+              GParamSpec *spec)
+{
+  ExampleEcho2Connection *self = EXAMPLE_ECHO_2_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)
+{
+  ExampleEcho2Connection *self = EXAMPLE_ECHO_2_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)
+{
+  ExampleEcho2Connection *self = EXAMPLE_ECHO_2_CONNECTION (object);
+
+  g_free (self->priv->account);
+
+  G_OBJECT_CLASS (example_echo_2_connection_parent_class)->finalize (object);
+}
+
+static gchar *
+get_unique_connection_name (TpBaseConnection *conn)
+{
+  ExampleEcho2Connection *self = EXAMPLE_ECHO_2_CONNECTION (conn);
+
+  return g_strdup (self->priv->account);
+}
+
+static gchar *
+example_normalize_contact (TpHandleRepoIface *repo,
+                           const gchar *id,
+                           gpointer context,
+                           GError **error)
+{
+  if (id[0] == '\0')
+    {
+      g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+          "ID must not be empty");
+      return NULL;
+    }
+
+  return g_utf8_strdown (id, -1);
+}
+
+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_normalize_contact, NULL);
+}
+
+static GPtrArray *
+create_channel_factories (TpBaseConnection *conn)
+{
+  GPtrArray *ret = g_ptr_array_sized_new (1);
+
+  g_ptr_array_add (ret, g_object_new (EXAMPLE_TYPE_ECHO_2_FACTORY,
+        "connection", conn,
+        NULL));
+
+  return ret;
+}
+
+static gboolean
+start_connecting (TpBaseConnection *conn,
+                  GError **error)
+{
+  ExampleEcho2Connection *self = EXAMPLE_ECHO_2_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, NULL);
+
+  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_echo_2_connection_class_init (ExampleEcho2ConnectionClass *klass)
+{
+  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 (ExampleEcho2ConnectionPrivate));
+
+  base_class->create_handle_repos = create_handle_repos;
+  base_class->get_unique_connection_name = get_unique_connection_name;
+  base_class->create_channel_factories = create_channel_factories;
+  base_class->start_connecting = start_connecting;
+  base_class->shut_down = shut_down;
+
+  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_BLURB);
+  g_object_class_install_property (object_class, PROP_ACCOUNT, param_spec);
+}
diff --git a/examples/cm/echo-message-parts/conn.h b/examples/cm/echo-message-parts/conn.h
new file mode 100644
index 0000000..4813eae
--- /dev/null
+++ b/examples/cm/echo-message-parts/conn.h
@@ -0,0 +1,54 @@
+/*
+ * conn.h - header for an example connection
+ *
+ * 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_ECHO_MESSAGE_PARTS_CONN_H
+#define EXAMPLE_ECHO_MESSAGE_PARTS_CONN_H
+
+#include <glib-object.h>
+#include <telepathy-glib/base-connection.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ExampleEcho2Connection ExampleEcho2Connection;
+typedef struct _ExampleEcho2ConnectionClass ExampleEcho2ConnectionClass;
+typedef struct _ExampleEcho2ConnectionPrivate ExampleEcho2ConnectionPrivate;
+
+struct _ExampleEcho2ConnectionClass {
+    TpBaseConnectionClass parent_class;
+};
+
+struct _ExampleEcho2Connection {
+    TpBaseConnection parent;
+
+    ExampleEcho2ConnectionPrivate *priv;
+};
+
+GType example_echo_2_connection_get_type (void);
+
+#define EXAMPLE_TYPE_ECHO_2_CONNECTION \
+  (example_echo_2_connection_get_type ())
+#define EXAMPLE_ECHO_2_CONNECTION(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj), EXAMPLE_TYPE_ECHO_2_CONNECTION, \
+                              ExampleEcho2Connection))
+#define EXAMPLE_ECHO_2_CONNECTION_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass), EXAMPLE_TYPE_ECHO_2_CONNECTION, \
+                           ExampleEcho2ConnectionClass))
+#define EXAMPLE_IS_ECHO_2_CONNECTION(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj), EXAMPLE_TYPE_ECHO_2_CONNECTION))
+#define EXAMPLE_IS_ECHO_2_CONNECTION_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass), EXAMPLE_TYPE_ECHO_2_CONNECTION))
+#define EXAMPLE_ECHO_2_CONNECTION_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_ECHO_2_CONNECTION, \
+                              ExampleEcho2ConnectionClass))
+
+G_END_DECLS
+
+#endif
diff --git a/examples/cm/echo-message-parts/factory.c b/examples/cm/echo-message-parts/factory.c
new file mode 100644
index 0000000..2c4918b
--- /dev/null
+++ b/examples/cm/echo-message-parts/factory.c
@@ -0,0 +1,274 @@
+/*
+ * factory.c - an example channel factory for channels talking to a particular
+ * contact. Similar code is used for 1-1 IM channels in many protocols
+ * (IRC private messages ("/query"), XMPP IM etc.)
+ *
+ * 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.
+ */
+
+#include "factory.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 "chan.h"
+
+/* FIXME: we really ought to have a base class in the library for this,
+ * it's such a common pattern... */
+
+static void iface_init (gpointer iface, gpointer data);
+
+G_DEFINE_TYPE_WITH_CODE (ExampleEcho2Factory,
+    example_echo_2_factory,
+    G_TYPE_OBJECT,
+    G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_FACTORY_IFACE, iface_init))
+
+/* type definition stuff */
+
+enum
+{
+  PROP_CONNECTION = 1,
+  N_PROPS
+};
+
+struct _ExampleEcho2FactoryPrivate
+{
+  TpBaseConnection *conn;
+
+  /* GUINT_TO_POINTER (handle) => ExampleEcho2Channel */
+  GHashTable *channels;
+};
+
+static void
+example_echo_2_factory_init (ExampleEcho2Factory *self)
+{
+  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, EXAMPLE_TYPE_ECHO_2_FACTORY,
+      ExampleEcho2FactoryPrivate);
+
+  self->priv->channels = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+      NULL, g_object_unref);
+}
+
+static void
+dispose (GObject *object)
+{
+  ExampleEcho2Factory *self = EXAMPLE_ECHO_2_FACTORY (object);
+
+  tp_channel_factory_iface_close_all ((TpChannelFactoryIface *) object);
+  g_assert (self->priv->channels == NULL);
+
+  ((GObjectClass *) example_echo_2_factory_parent_class)->dispose (object);
+}
+
+static void
+get_property (GObject *object,
+              guint property_id,
+              GValue *value,
+              GParamSpec *pspec)
+{
+  ExampleEcho2Factory *self = EXAMPLE_ECHO_2_FACTORY (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)
+{
+  ExampleEcho2Factory *self = EXAMPLE_ECHO_2_FACTORY (object);
+
+  switch (property_id)
+    {
+    case PROP_CONNECTION:
+      /* We don't ref the connection, because it owns a reference to the
+       * factory, and it guarantees that the factory'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
+example_echo_2_factory_class_init (ExampleEcho2FactoryClass *klass)
+{
+  GParamSpec *param_spec;
+  GObjectClass *object_class = (GObjectClass *) klass;
+
+  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 factory",
+      TP_TYPE_BASE_CONNECTION,
+      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+      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 (ExampleEcho2FactoryPrivate));
+}
+
+static void
+close_all (TpChannelFactoryIface *iface)
+{
+  ExampleEcho2Factory *self = EXAMPLE_ECHO_2_FACTORY (iface);
+
+  if (self->priv->channels != NULL)
+    {
+      GHashTable *tmp = self->priv->channels;
+
+      self->priv->channels = NULL;
+      g_hash_table_destroy (tmp);
+    }
+}
+
+struct _ForeachData
+{
+  gpointer user_data;
+  TpChannelFunc callback;
+};
+
+static void
+_foreach (gpointer key,
+          gpointer value,
+          gpointer user_data)
+{
+  struct _ForeachData *data = user_data;
+  TpChannelIface *chan = TP_CHANNEL_IFACE (value);
+
+  data->callback (chan, data->user_data);
+}
+
+static void
+foreach (TpChannelFactoryIface *iface,
+         TpChannelFunc callback,
+         gpointer user_data)
+{
+  ExampleEcho2Factory *self = EXAMPLE_ECHO_2_FACTORY (iface);
+  struct _ForeachData data = { user_data, callback };
+
+  g_hash_table_foreach (self->priv->channels, _foreach, &data);
+}
+
+static void
+channel_closed_cb (ExampleEcho2Channel *chan,
+                   ExampleEcho2Factory *self)
+{
+  if (self->priv->channels != NULL)
+    {
+      TpHandle handle;
+      gboolean really_destroyed;
+
+      g_object_get (chan,
+          "handle", &handle,
+          "channel-destroyed", &really_destroyed,
+          NULL);
+
+      /* Re-announce the channel if it's not yet ready to go away (pending
+       * messages) */
+      if (really_destroyed)
+        g_hash_table_remove (self->priv->channels, GUINT_TO_POINTER (handle));
+      else
+        tp_channel_manager_emit_new_channel (self,
+            TP_EXPORTABLE_CHANNEL (chan), NULL);
+    }
+}
+
+static ExampleEcho2Channel *
+new_channel (ExampleEcho2Factory *self,
+             TpHandle handle,
+             TpHandle initiator)
+{
+  ExampleEcho2Channel *chan;
+  gchar *object_path;
+
+  object_path = g_strdup_printf ("%s/Echo2Channel%u",
+      self->priv->conn->object_path, handle);
+
+  chan = g_object_new (EXAMPLE_TYPE_ECHO_2_CHANNEL,
+      "connection", self->priv->conn,
+      "object-path", object_path,
+      "handle", handle,
+      "initiator-handle", 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);
+
+  tp_channel_factory_iface_emit_new_channel (self, (TpChannelIface *) chan,
+      NULL);
+
+  return chan;
+}
+
+static TpChannelFactoryRequestStatus
+request (TpChannelFactoryIface *iface,
+         const gchar *chan_type,
+         TpHandleType handle_type,
+         guint handle,
+         gpointer request_id,
+         TpChannelIface **ret,
+         GError **error)
+{
+  ExampleEcho2Factory *self = EXAMPLE_ECHO_2_FACTORY (iface);
+  ExampleEcho2Channel *chan;
+  TpChannelFactoryRequestStatus status;
+  TpHandleRepoIface *contact_repo = tp_base_connection_get_handles
+      (self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+  if (tp_strdiff (chan_type, TP_IFACE_CHANNEL_TYPE_TEXT))
+    return TP_CHANNEL_FACTORY_REQUEST_STATUS_NOT_IMPLEMENTED;
+
+  if (handle_type != TP_HANDLE_TYPE_CONTACT)
+    return TP_CHANNEL_FACTORY_REQUEST_STATUS_NOT_IMPLEMENTED;
+
+  if (!tp_handle_is_valid (contact_repo, handle, error))
+    return TP_CHANNEL_FACTORY_REQUEST_STATUS_ERROR;
+
+  chan = g_hash_table_lookup (self->priv->channels, GUINT_TO_POINTER (handle));
+
+  status = TP_CHANNEL_FACTORY_REQUEST_STATUS_EXISTING;
+  if (chan == NULL)
+    {
+      status = TP_CHANNEL_FACTORY_REQUEST_STATUS_CREATED;
+      chan = new_channel (self, handle, self->priv->conn->self_handle);
+    }
+
+  g_assert (chan != NULL);
+  *ret = TP_CHANNEL_IFACE (chan);
+  return status;
+}
+
+static void
+iface_init (gpointer iface,
+            gpointer data)
+{
+  TpChannelFactoryIfaceClass *klass = iface;
+
+  klass->close_all = close_all;
+  klass->foreach = foreach;
+  klass->request = request;
+}
diff --git a/examples/cm/echo-message-parts/factory.h b/examples/cm/echo-message-parts/factory.h
new file mode 100644
index 0000000..6c09cb4
--- /dev/null
+++ b/examples/cm/echo-message-parts/factory.h
@@ -0,0 +1,55 @@
+/*
+ * factory.h - header for an example channel factory
+ *
+ * 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_ECHO_MESSAGE_PARTS_FACTORY_H
+#define EXAMPLE_ECHO_MESSAGE_PARTS_FACTORY_H
+
+#include <glib-object.h>
+#include <telepathy-glib/channel-factory-iface.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ExampleEcho2Factory ExampleEcho2Factory;
+typedef struct _ExampleEcho2FactoryClass ExampleEcho2FactoryClass;
+typedef struct _ExampleEcho2FactoryPrivate ExampleEcho2FactoryPrivate;
+
+struct _ExampleEcho2FactoryClass {
+    GObjectClass parent_class;
+};
+
+struct _ExampleEcho2Factory {
+    GObject parent;
+
+    ExampleEcho2FactoryPrivate *priv;
+};
+
+GType example_echo_2_factory_get_type (void);
+
+/* TYPE MACROS */
+#define EXAMPLE_TYPE_ECHO_2_FACTORY \
+  (example_echo_2_factory_get_type ())
+#define EXAMPLE_ECHO_2_FACTORY(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj), EXAMPLE_TYPE_ECHO_2_FACTORY, \
+                              ExampleEcho2Factory))
+#define EXAMPLE_ECHO_2_FACTORY_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass), EXAMPLE_TYPE_ECHO_2_FACTORY, \
+                           ExampleEcho2FactoryClass))
+#define EXAMPLE_IS_ECHO_2_FACTORY(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj), EXAMPLE_TYPE_ECHO_2_FACTORY))
+#define EXAMPLE_IS_ECHO_2_FACTORY_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass), EXAMPLE_TYPE_ECHO_2_FACTORY))
+#define EXAMPLE_ECHO_2_FACTORY_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_ECHO_2_FACTORY, \
+                              ExampleEcho2FactoryClass))
+
+G_END_DECLS
+
+#endif
diff --git a/examples/cm/echo-message-parts/main.c b/examples/cm/echo-message-parts/main.c
new file mode 100644
index 0000000..1c9f88e
--- /dev/null
+++ b/examples/cm/echo-message-parts/main.c
@@ -0,0 +1,43 @@
+/*
+ * main.c - entry point for an example Telepathy connection 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.
+ */
+
+#include "config.h"
+
+#include <telepathy-glib/debug.h>
+#include <telepathy-glib/run.h>
+#include "manager.h"
+
+static TpBaseConnectionManager *
+construct_cm (void)
+{
+  return (TpBaseConnectionManager *) g_object_new (
+      EXAMPLE_TYPE_ECHO_2_CONNECTION_MANAGER,
+      NULL);
+}
+
+int
+main (int argc,
+      char **argv)
+{
+#ifdef ENABLE_DEBUG
+  tp_debug_divert_messages (g_getenv ("EXAMPLE_CM_LOGFILE"));
+  tp_debug_set_flags (g_getenv ("EXAMPLE_DEBUG"));
+
+  if (g_getenv ("EXAMPLE_TIMING") != NULL)
+    g_log_set_default_handler (tp_debug_timestamped_log_handler, NULL);
+
+  if (g_getenv ("EXAMPLE_PERSIST") != NULL)
+    tp_debug_set_persistent (TRUE);
+#endif
+
+  return tp_run_connection_manager ("telepathy-example-cm-echo-2",
+      VERSION, construct_cm, argc, argv);
+}
diff --git a/examples/cm/echo-message-parts/manager.c b/examples/cm/echo-message-parts/manager.c
new file mode 100644
index 0000000..5c467d4
--- /dev/null
+++ b/examples/cm/echo-message-parts/manager.c
@@ -0,0 +1,96 @@
+/*
+ * manager.c - an example connection manager
+ *
+ * Copyright (C) 2007 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.
+ */
+
+#include "manager.h"
+
+#include <dbus/dbus-protocol.h>
+#include <dbus/dbus-glib.h>
+
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/errors.h>
+
+#include "conn.h"
+
+G_DEFINE_TYPE (ExampleEcho2ConnectionManager,
+    example_echo_2_connection_manager,
+    TP_TYPE_BASE_CONNECTION_MANAGER)
+
+/* type definition stuff */
+
+static void
+example_echo_2_connection_manager_init (
+    ExampleEcho2ConnectionManager *self)
+{
+}
+
+/* private data */
+
+typedef struct {
+    gchar *account;
+} ExampleParams;
+
+static const TpCMParamSpec example_params[] = {
+  { "account", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING,
+    TP_CONN_MGR_PARAM_FLAG_REQUIRED | TP_CONN_MGR_PARAM_FLAG_REGISTER, NULL,
+    G_STRUCT_OFFSET (ExampleParams, account),
+    tp_cm_param_filter_string_nonempty, NULL },
+
+  { NULL }
+};
+
+static gpointer
+alloc_params (void)
+{
+  return g_slice_new0 (ExampleParams);
+}
+
+static void
+free_params (gpointer p)
+{
+  ExampleParams *params = p;
+
+  g_free (params->account);
+
+  g_slice_free (ExampleParams, params);
+}
+
+static const TpCMProtocolSpec example_protocols[] = {
+  { "example", example_params, alloc_params, free_params },
+  { NULL, NULL }
+};
+
+static TpBaseConnection *
+new_connection (TpBaseConnectionManager *self,
+                const gchar *proto,
+                TpIntSet *params_present,
+                gpointer parsed_params,
+                GError **error)
+{
+  ExampleParams *params = parsed_params;
+  ExampleEcho2Connection *conn =
+      EXAMPLE_ECHO_2_CONNECTION (g_object_new (EXAMPLE_TYPE_ECHO_2_CONNECTION,
+            "account", params->account,
+            "protocol", proto,
+            NULL));
+
+  return (TpBaseConnection *) conn;
+}
+
+static void
+example_echo_2_connection_manager_class_init (
+    ExampleEcho2ConnectionManagerClass *klass)
+{
+  TpBaseConnectionManagerClass *base_class =
+      (TpBaseConnectionManagerClass *) klass;
+
+  base_class->new_connection = new_connection;
+  base_class->cm_dbus_name = "example_echo_2";
+  base_class->protocol_params = example_protocols;
+}
diff --git a/examples/cm/echo-message-parts/manager.h b/examples/cm/echo-message-parts/manager.h
new file mode 100644
index 0000000..8fcd360
--- /dev/null
+++ b/examples/cm/echo-message-parts/manager.h
@@ -0,0 +1,60 @@
+/*
+ * manager.h - header for an example connection manager
+ * Copyright (C) 2007 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.
+ */
+
+#ifndef EXAMPLE_ECHO_MESSAGE_PARTS_MANAGER_H
+#define EXAMPLE_ECHO_MESSAGE_PARTS_MANAGER_H
+
+#include <glib-object.h>
+#include <telepathy-glib/base-connection-manager.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ExampleEcho2ConnectionManager
+    ExampleEcho2ConnectionManager;
+typedef struct _ExampleEcho2ConnectionManagerClass
+    ExampleEcho2ConnectionManagerClass;
+
+struct _ExampleEcho2ConnectionManagerClass {
+    TpBaseConnectionManagerClass parent_class;
+
+    gpointer priv;
+};
+
+struct _ExampleEcho2ConnectionManager {
+    TpBaseConnectionManager parent;
+
+    gpointer priv;
+};
+
+GType example_echo_2_connection_manager_get_type (void);
+
+#define EXAMPLE_TYPE_ECHO_2_CONNECTION_MANAGER \
+    (example_echo_2_connection_manager_get_type ())
+#define EXAMPLE_ECHO_2_CONNECTION_MANAGER(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+        EXAMPLE_TYPE_ECHO_2_CONNECTION_MANAGER, \
+        ExampleEcho2ConnectionManager))
+#define EXAMPLE_ECHO_2_CONNECTION_MANAGER_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST ((klass), \
+        EXAMPLE_TYPE_ECHO_2_CONNECTION_MANAGER, \
+        ExampleEcho2ConnectionManagerClass))
+#define EXAMPLE_IS_ECHO_2_CONNECTION_MANAGER(obj) \
+    (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+        EXAMPLE_TYPE_ECHO_2_CONNECTION_MANAGER))
+#define EXAMPLE_IS_ECHO_2_CONNECTION_MANAGER_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+        EXAMPLE_TYPE_ECHO_2_CONNECTION_MANAGER))
+#define EXAMPLE_ECHO_2_CONNECTION_MANAGER_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+        EXAMPLE_TYPE_ECHO_2_CONNECTION_MANAGER, \
+        ExampleEcho2ConnectionManagerClass))
+
+G_END_DECLS
+
+#endif
-- 
1.5.6.5




More information about the Telepathy-commits mailing list