src/protocols/sametime/meanwhile/srvc_conf.c

changeset 12956
39a4efae983c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/sametime/meanwhile/srvc_conf.c	Fri Jan 20 00:19:53 2006 +0000
@@ -0,0 +1,865 @@
+
+/*
+  Meanwhile - Unofficial Lotus Sametime Community Client Library
+  Copyright (C) 2004  Christopher (siege) O'Brien
+  
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Library General Public
+  License as published by the Free Software Foundation; either
+  version 2 of the License, or (at your option) any later version.
+  
+  This library is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  Library General Public License for more details.
+  
+  You should have received a copy of the GNU Library General Public
+  License along with this library; if not, write to the Free
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#include <glib.h>
+#include <glib/ghash.h>
+#include <glib/glist.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "mw_channel.h"
+#include "mw_debug.h"
+#include "mw_error.h"
+#include "mw_message.h"
+#include "mw_service.h"
+#include "mw_session.h"
+#include "mw_srvc_conf.h"
+#include "mw_util.h"
+
+
+/* This thing needs a re-write. More than anything else, I need to
+   re-examine the conferencing service protocol from more modern
+   clients */
+
+
+#define PROTOCOL_TYPE   0x00000010
+#define PROTOCOL_VER    0x00000002
+
+
+/** @see mwMsgChannelSend::type
+    @see recv */
+enum msg_type {
+  msg_WELCOME  = 0x0000,  /**< welcome message */
+  msg_INVITE   = 0x0001,  /**< outgoing invitation */
+  msg_JOIN     = 0x0002,  /**< someone joined */
+  msg_PART     = 0x0003,  /**< someone left */
+  msg_MESSAGE  = 0x0004,  /**< conference message */
+};
+
+
+/** the conferencing service */
+struct mwServiceConference {
+  struct mwService service;
+
+  /** call-back handler for this service */
+  struct mwConferenceHandler *handler;
+
+  /** collection of conferences in this service */
+  GList *confs;
+};
+
+
+/** a conference and its members */
+struct mwConference {
+  enum mwConferenceState state;   /**< state of the conference */
+  struct mwServiceConference *service;  /**< owning service */
+  struct mwChannel *channel;      /**< conference's channel */
+
+  char *name;   /**< server identifier for the conference */
+  char *title;  /**< topic for the conference */
+
+  struct mwLoginInfo owner;  /**< person who created this conference */
+  GHashTable *members;       /**< mapping guint16:mwLoginInfo */
+  struct mw_datum client_data;
+};
+
+
+#define MEMBER_FIND(conf, id) \
+  g_hash_table_lookup(conf->members, GUINT_TO_POINTER((guint) id))
+
+
+#define MEMBER_ADD(conf, id, member) \
+  g_hash_table_insert(conf->members, GUINT_TO_POINTER((guint) id), member)
+
+
+#define MEMBER_REM(conf, id) \
+  g_hash_table_remove(conf->members, GUINT_TO_POINTER((guint) id));
+
+
+/** clear and free a login info block */
+static void login_free(struct mwLoginInfo *li) {
+  mwLoginInfo_clear(li);
+  g_free(li);
+}
+
+
+/** generates a random conference name built around a user name */
+static char *conf_generate_name(const char *user) {
+  guint a, b;
+  char *ret;
+  
+  user = user? user: "";
+
+  srand(clock() + rand());
+  a = ((rand() & 0xff) << 8) | (rand() & 0xff);
+  b = time(NULL);
+
+  ret = g_strdup_printf("%s(%08x,%04x)", user, b, a);
+  g_debug("generated random conference name: '%s'", ret);
+  return ret;
+}
+
+
+
+
+
+static struct mwConference *conf_new(struct mwServiceConference *srvc) {
+
+  struct mwConference *conf;
+  struct mwSession *session;
+  const char *user;
+
+  conf = g_new0(struct mwConference, 1);
+  conf->state = mwConference_NEW;
+  conf->service = srvc;
+  conf->members = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL,
+					(GDestroyNotify) login_free);
+
+  session = mwService_getSession(MW_SERVICE(srvc));
+  user = mwSession_getProperty(session, mwSession_AUTH_USER_ID);
+
+  srvc->confs = g_list_prepend(srvc->confs, conf);
+
+  return conf;
+}
+
+
+/** clean and free a conference structure */
+static void conf_free(struct mwConference *conf) {
+  struct mwServiceConference *srvc;
+
+  /* this shouldn't ever happen, but just to be sure */
+  g_return_if_fail(conf != NULL);
+  
+  srvc = conf->service;
+
+  if(conf->members)
+    g_hash_table_destroy(conf->members);
+
+  g_list_remove_all(srvc->confs, conf);
+
+  mw_datum_clear(&conf->client_data);
+  
+  g_free(conf->name);
+  g_free(conf->title);
+  g_free(conf);
+}
+
+
+static struct mwConference *conf_find(struct mwServiceConference *srvc,
+				      struct mwChannel *chan) {
+  GList *l;
+
+  g_return_val_if_fail(srvc != NULL, NULL);
+  g_return_val_if_fail(chan != NULL, NULL);
+  
+  for(l = srvc->confs; l; l = l->next) {
+    struct mwConference *conf = l->data;
+    if(conf->channel == chan) return conf;
+  }
+
+  return NULL;
+}
+
+
+static const char *conf_state_str(enum mwConferenceState state) {
+  switch(state) {
+  case mwConference_NEW:      return "new";
+  case mwConference_PENDING:  return "pending";
+  case mwConference_INVITED:  return "invited";
+  case mwConference_OPEN:     return "open";
+  case mwConference_CLOSING:  return "closing";
+  case mwConference_ERROR:    return "error";
+
+  case mwConference_UNKNOWN:  /* fall through */
+  default:                    return "UNKNOWN";
+  }
+}
+
+
+static void conf_state(struct mwConference *conf,
+		       enum mwConferenceState state) {
+  g_return_if_fail(conf != NULL);
+
+  if(conf->state == state) return;
+
+  conf->state = state;
+  g_message("conference %s state: %s",
+	    NSTR(conf->name), conf_state_str(state));
+}
+
+
+static void recv_channelCreate(struct mwService *srvc,
+			       struct mwChannel *chan,
+			       struct mwMsgChannelCreate *msg) {
+
+  /* - this is how we really receive invitations
+     - create a conference and associate it with the channel
+     - obtain the invite data from the msg addtl info
+     - mark the conference as INVITED
+     - trigger the got_invite event
+  */
+
+  struct mwServiceConference *srvc_conf = (struct mwServiceConference *) srvc;
+  struct mwConference *conf;
+
+  struct mwGetBuffer *b;
+
+  char *invite = NULL;
+  guint tmp;
+
+  conf = conf_new(srvc_conf);
+  conf->channel = chan;
+
+  b = mwGetBuffer_wrap(&msg->addtl);
+
+  guint32_get(b, &tmp);
+  mwString_get(b, &conf->name);
+  mwString_get(b, &conf->title);
+  guint32_get(b, &tmp);
+  mwLoginInfo_get(b, &conf->owner);
+  guint32_get(b, &tmp);
+  mwString_get(b, &invite);
+
+  if(mwGetBuffer_error(b)) {
+    g_warning("failure parsing addtl for conference invite");
+    mwConference_destroy(conf, ERR_FAILURE, NULL);
+
+  } else {
+    struct mwConferenceHandler *h = srvc_conf->handler;
+    conf_state(conf, mwConference_INVITED);
+    if(h->on_invited)
+      h->on_invited(conf, &conf->owner, invite);
+  }
+
+  mwGetBuffer_free(b);
+  g_free(invite);
+}
+
+
+static void recv_channelAccept(struct mwService *srvc,
+			       struct mwChannel *chan,
+			       struct mwMsgChannelAccept *msg) {
+
+  ;
+}
+
+
+static void recv_channelDestroy(struct mwService *srvc,
+				struct mwChannel *chan,
+				struct mwMsgChannelDestroy *msg) {
+
+  /* - find conference from channel
+     - trigger got_closed
+     - remove conference, dealloc
+  */
+
+  struct mwServiceConference *srvc_conf = (struct mwServiceConference *) srvc;
+  struct mwConference *conf = conf_find(srvc_conf, chan);
+  struct mwConferenceHandler *h = srvc_conf->handler;
+
+  /* if there's no such conference, then I guess there's nothing to worry
+     about. Except of course for the fact that we should never receive a
+     channel destroy for a conference that doesn't exist. */
+  if(! conf) return;
+
+  conf->channel = NULL;
+
+  conf_state(conf, msg->reason? mwConference_ERROR: mwConference_CLOSING);
+
+  if(h->conf_closed)
+    h->conf_closed(conf, msg->reason);
+
+  mwConference_destroy(conf, ERR_SUCCESS, NULL);
+}
+
+
+static void WELCOME_recv(struct mwServiceConference *srvc,
+			 struct mwConference *conf,
+			 struct mwGetBuffer *b) {
+
+  struct mwConferenceHandler *h;
+  guint16 tmp16;
+  guint32 tmp32;
+  guint32 count;
+  GList *l = NULL;
+
+  /* re-read name and title */
+  g_free(conf->name);
+  g_free(conf->title);
+  conf->name = NULL;
+  conf->title = NULL;
+  mwString_get(b, &conf->name);
+  mwString_get(b, &conf->title);
+
+  /* some numbers we don't care about, then a count of members */
+  guint16_get(b, &tmp16);
+  guint32_get(b, &tmp32);
+  guint32_get(b, &count);
+
+  if(mwGetBuffer_error(b)) {
+    g_warning("error parsing welcome message for conference");
+    mwConference_destroy(conf, ERR_FAILURE, NULL);
+    return;
+  }
+  
+  while(count--) {
+    guint16 member_id;
+    struct mwLoginInfo *member = g_new0(struct mwLoginInfo, 1);
+
+    guint16_get(b, &member_id);
+    mwLoginInfo_get(b, member);
+
+    if(mwGetBuffer_error(b)) {
+      login_free(member);
+      break;
+    }
+
+    MEMBER_ADD(conf, member_id, member);
+    l = g_list_append(l, member);
+  }
+
+  conf_state(conf, mwConference_OPEN);
+
+  h = srvc->handler;
+  if(h->conf_opened)
+    h->conf_opened(conf, l);
+
+  /* get rid of the GList, but not its contents */
+  g_list_free(l);
+}
+
+
+static void JOIN_recv(struct mwServiceConference *srvc,
+		      struct mwConference *conf,
+		      struct mwGetBuffer *b) {
+
+  struct mwConferenceHandler *h;
+  guint16 m_id;
+  struct mwLoginInfo *m;
+  
+  /* for some inane reason, conferences we create will send a join
+     message for ourselves before the welcome message. Since the
+     welcome message will list our ID among those in the channel,
+     we're going to just pretend that these join messages don't
+     exist */
+  if(conf->state == mwConference_PENDING)
+    return;
+
+  m = g_new0(struct mwLoginInfo, 1);
+
+  guint16_get(b, &m_id);
+  mwLoginInfo_get(b, m);
+
+  if(mwGetBuffer_error(b)) {
+    g_warning("failed parsing JOIN message in conference");
+    login_free(m);
+    return;
+  }
+
+  MEMBER_ADD(conf, m_id, m);
+
+  h = srvc->handler;
+  if(h->on_peer_joined)
+    h->on_peer_joined(conf, m);
+}
+
+
+static void PART_recv(struct mwServiceConference *srvc,
+		      struct mwConference *conf,
+		      struct mwGetBuffer *b) {
+
+  /* - parse who left
+     - look up their membership
+     - remove them from the members list
+     - trigger the event
+  */
+
+  struct mwConferenceHandler *h;
+  guint16 id = 0;
+  struct mwLoginInfo *m;
+
+  guint16_get(b, &id);
+
+  if(mwGetBuffer_error(b)) return;
+
+  m = MEMBER_FIND(conf, id);
+  if(! m) return;
+
+  h = srvc->handler;
+  if(h->on_peer_parted)
+    h->on_peer_parted(conf, m);
+
+  MEMBER_REM(conf, id);
+}
+
+
+static void text_recv(struct mwServiceConference *srvc,
+		      struct mwConference *conf,
+		      struct mwLoginInfo *m,
+		      struct mwGetBuffer *b) {
+
+  /* this function acts a lot like receiving an IM Text message. The text
+     message contains only a string */
+
+  char *text = NULL;
+  struct mwConferenceHandler *h;
+  
+  mwString_get(b, &text);
+
+  if(mwGetBuffer_error(b)) {
+    g_warning("failed to parse text message in conference");
+    g_free(text);
+    return;
+  }
+
+  h = srvc->handler;
+  if(text && h->on_text) {
+    h->on_text(conf, m, text);
+  }
+
+  g_free(text);
+}
+
+
+static void data_recv(struct mwServiceConference *srvc,
+		      struct mwConference *conf,
+		      struct mwLoginInfo *m,
+		      struct mwGetBuffer *b) {
+
+  /* this function acts a lot like receiving an IM Data message. The
+     data message has a type, a subtype, and an opaque. We only
+     support typing notification though. */
+
+  /** @todo it's possible that some clients send text in a data
+      message, as we've seen rarely in the IM service. Have to add
+      support for that here */
+
+  guint32 type, subtype;
+  struct mwConferenceHandler *h;
+
+  guint32_get(b, &type);
+  guint32_get(b, &subtype);
+
+  if(mwGetBuffer_error(b)) return;
+
+  /* don't know how to deal with any others yet */
+  if(type != 0x01) {
+    g_message("unknown data message type (0x%08x, 0x%08x)", type, subtype);
+    return;
+  }
+
+  h = srvc->handler;
+  if(h->on_typing) {
+    h->on_typing(conf, m, !subtype);
+  }
+}
+
+
+static void MESSAGE_recv(struct mwServiceConference *srvc,
+			 struct mwConference *conf,
+			 struct mwGetBuffer *b) {
+
+  /* - look up who send the message by their id
+     - trigger the event
+  */
+
+  guint16 id;
+  guint32 type;
+  struct mwLoginInfo *m;
+
+  /* an empty buffer isn't an error, just ignored */
+  if(! mwGetBuffer_remaining(b)) return;
+
+  guint16_get(b, &id);
+  guint32_get(b, &type); /* reuse type variable */
+  guint32_get(b, &type);
+
+  if(mwGetBuffer_error(b)) return;
+
+  m = MEMBER_FIND(conf, id);
+  if(! m) {
+    g_warning("received message type 0x%04x from"
+	      " unknown conference member %u", type, id);
+    return;
+  }
+  
+  switch(type) {
+  case 0x01:  /* type is text */
+    text_recv(srvc, conf, m, b);
+    break;
+
+  case 0x02:  /* type is data */
+    data_recv(srvc, conf, m, b);
+    break;
+
+  default:
+    g_warning("unknown message type 0x%4x received in conference", type);
+  }
+}
+
+
+static void recv(struct mwService *srvc, struct mwChannel *chan,
+		 guint16 type, struct mwOpaque *data) {
+
+  struct mwServiceConference *srvc_conf = (struct mwServiceConference *) srvc;
+  struct mwConference *conf = conf_find(srvc_conf, chan);
+  struct mwGetBuffer *b;
+
+  g_return_if_fail(conf != NULL);
+
+  b = mwGetBuffer_wrap(data);
+
+  switch(type) {
+  case msg_WELCOME:
+    WELCOME_recv(srvc_conf, conf, b);
+    break;
+
+  case msg_JOIN:
+    JOIN_recv(srvc_conf, conf, b);
+    break;
+
+  case msg_PART:
+    PART_recv(srvc_conf, conf, b);
+    break;
+
+  case msg_MESSAGE:
+    MESSAGE_recv(srvc_conf, conf, b);
+    break;
+
+  default:
+    ; /* hrm. should log this. TODO */
+  }
+}
+
+
+static void clear(struct mwServiceConference *srvc) {
+  struct mwConferenceHandler *h;
+
+  while(srvc->confs)
+    conf_free(srvc->confs->data);
+
+  h = srvc->handler;
+  if(h && h->clear)
+    h->clear(srvc);
+  srvc->handler = NULL;
+}
+
+
+static const char *name(struct mwService *srvc) {
+  return "Basic Conferencing";
+}
+
+
+static const char *desc(struct mwService *srvc) {
+  return "Multi-user plain-text conferencing";
+}
+
+
+static void start(struct mwService *srvc) {
+  mwService_started(srvc);
+}
+
+
+static void stop(struct mwServiceConference *srvc) {
+  while(srvc->confs)
+    mwConference_destroy(srvc->confs->data, ERR_SUCCESS, NULL);
+
+  mwService_stopped(MW_SERVICE(srvc));
+}
+
+
+struct mwServiceConference *
+mwServiceConference_new(struct mwSession *session,
+			struct mwConferenceHandler *handler) {
+
+  struct mwServiceConference *srvc_conf;
+  struct mwService *srvc;
+
+  g_return_val_if_fail(session != NULL, NULL);
+  g_return_val_if_fail(handler != NULL, NULL);
+
+  srvc_conf = g_new0(struct mwServiceConference, 1);
+  srvc = &srvc_conf->service;
+
+  mwService_init(srvc, session, mwService_CONFERENCE);
+  srvc->start = start;
+  srvc->stop = (mwService_funcStop) stop;
+  srvc->recv_create = recv_channelCreate;
+  srvc->recv_accept = recv_channelAccept;
+  srvc->recv_destroy = recv_channelDestroy;
+  srvc->recv = recv;
+  srvc->clear = (mwService_funcClear) clear;
+  srvc->get_name = name;
+  srvc->get_desc = desc;
+
+  srvc_conf->handler = handler;
+
+  return srvc_conf;
+}
+
+
+struct mwConference *mwConference_new(struct mwServiceConference *srvc,
+				      const char *title) {
+  struct mwConference *conf;
+
+  g_return_val_if_fail(srvc != NULL, NULL);
+
+  conf = conf_new(srvc);
+  conf->title = g_strdup(title);
+
+  return conf;
+}
+
+
+struct mwServiceConference *
+mwConference_getService(struct mwConference *conf) {
+  g_return_val_if_fail(conf != NULL, NULL);
+  return conf->service;
+}
+
+
+const char *mwConference_getName(struct mwConference *conf) {
+  g_return_val_if_fail(conf != NULL, NULL);
+  return conf->name;
+}
+
+
+const char *mwConference_getTitle(struct mwConference *conf) {
+  g_return_val_if_fail(conf != NULL, NULL);
+  return conf->title;
+}
+
+
+GList *mwConference_memebers(struct mwConference *conf) {
+  g_return_val_if_fail(conf != NULL, NULL);
+  g_return_val_if_fail(conf->members != NULL, NULL);
+
+  return map_collect_values(conf->members);
+}
+
+
+int mwConference_open(struct mwConference *conf) {
+  struct mwSession *session;
+  struct mwChannel *chan;
+  struct mwPutBuffer *b;
+  int ret;
+  
+  g_return_val_if_fail(conf != NULL, -1);
+  g_return_val_if_fail(conf->service != NULL, -1);
+  g_return_val_if_fail(conf->state == mwConference_NEW, -1);
+  g_return_val_if_fail(conf->channel == NULL, -1);
+
+  session = mwService_getSession(MW_SERVICE(conf->service));
+  g_return_val_if_fail(session != NULL, -1);
+
+  if(! conf->name) {
+    char *user = mwSession_getProperty(session, mwSession_AUTH_USER_ID);
+    conf->name = conf_generate_name(user? user: "meanwhile");
+  }
+
+  chan = mwChannel_newOutgoing(mwSession_getChannels(session));
+  mwChannel_setService(chan, MW_SERVICE(conf->service));
+  mwChannel_setProtoType(chan, PROTOCOL_TYPE);
+  mwChannel_setProtoVer(chan, PROTOCOL_VER);
+  
+  /* offer all known ciphers */
+  mwChannel_populateSupportedCipherInstances(chan);
+
+  b = mwPutBuffer_new();
+  mwString_put(b, conf->name);
+  mwString_put(b, conf->title);
+  guint32_put(b, 0x00);
+  mwPutBuffer_finalize(mwChannel_getAddtlCreate(chan), b);
+
+  ret = mwChannel_create(chan);
+  if(ret) {
+    conf_state(conf, mwConference_ERROR);
+  } else {
+    conf_state(conf, mwConference_PENDING);
+    conf->channel = chan;
+  }
+
+  return ret;
+}
+
+
+int mwConference_destroy(struct mwConference *conf,
+			 guint32 reason, const char *text) {
+
+  struct mwServiceConference *srvc;
+  struct mwOpaque info = { 0, 0 };
+  int ret = 0;
+
+  g_return_val_if_fail(conf != NULL, -1);
+
+  srvc = conf->service;
+  g_return_val_if_fail(srvc != NULL, -1);
+
+  /* remove conference from the service */
+  srvc->confs = g_list_remove_all(srvc->confs, conf);
+
+  /* close the channel if applicable */
+  if(conf->channel) {
+    if(text && *text) {
+      info.len = strlen(text);
+      info.data = (guchar *) text;
+    }
+
+    ret = mwChannel_destroy(conf->channel, reason, &info);
+  }
+  
+  /* free the conference */
+  conf_free(conf);
+
+  return ret;
+}
+
+
+int mwConference_accept(struct mwConference *conf) {
+  /* - if conference is not INVITED, return -1
+     - accept the conference channel
+     - send an empty JOIN message
+  */
+
+  struct mwChannel *chan;
+  int ret;
+
+  g_return_val_if_fail(conf != NULL, -1);
+  g_return_val_if_fail(conf->state == mwConference_INVITED, -1);
+
+  chan = conf->channel;
+  ret = mwChannel_accept(chan);
+
+  if(! ret)
+    ret = mwChannel_sendEncrypted(chan, msg_JOIN, NULL, FALSE);
+
+  return ret;
+}
+
+
+int mwConference_invite(struct mwConference *conf,
+			struct mwIdBlock *who,
+			const char *text) {
+
+  struct mwPutBuffer *b;
+  struct mwOpaque o;
+  int ret;
+
+  g_return_val_if_fail(conf != NULL, -1);
+  g_return_val_if_fail(conf->channel != NULL, -1);
+  g_return_val_if_fail(who != NULL, -1);
+
+  b = mwPutBuffer_new();
+
+  mwIdBlock_put(b, who);
+  guint16_put(b, 0x00);
+  guint32_put(b, 0x00);
+  mwString_put(b, text);
+  mwString_put(b, who->user);
+
+  mwPutBuffer_finalize(&o, b);
+  ret = mwChannel_sendEncrypted(conf->channel, msg_INVITE, &o, FALSE);
+  mwOpaque_clear(&o);
+
+  return ret;
+}
+
+
+int mwConference_sendText(struct mwConference *conf, const char *text) {
+  struct mwPutBuffer *b;
+  struct mwOpaque o;
+  int ret;
+
+  g_return_val_if_fail(conf != NULL, -1);
+  g_return_val_if_fail(conf->channel != NULL, -1);
+
+  b = mwPutBuffer_new();
+
+  guint32_put(b, 0x01);
+  mwString_put(b, text);
+
+  mwPutBuffer_finalize(&o, b);
+  ret = mwChannel_sendEncrypted(conf->channel, msg_MESSAGE, &o, FALSE);
+  mwOpaque_clear(&o);
+
+  return ret;
+}
+
+
+int mwConference_sendTyping(struct mwConference *conf, gboolean typing) {
+  struct mwPutBuffer *b;
+  struct mwOpaque o;
+  int ret;
+
+  g_return_val_if_fail(conf != NULL, -1);
+  g_return_val_if_fail(conf->channel != NULL, -1);
+  g_return_val_if_fail(conf->state == mwConference_OPEN, -1);
+
+  b = mwPutBuffer_new();
+
+  guint32_put(b, 0x02);
+  guint32_put(b, 0x01);
+  guint32_put(b, !typing);
+  mwOpaque_put(b, NULL);
+
+  mwPutBuffer_finalize(&o, b);
+  ret = mwChannel_sendEncrypted(conf->channel, msg_MESSAGE, &o, FALSE);
+  mwOpaque_clear(&o);
+
+  return ret;
+}
+
+
+void mwConference_setClientData(struct mwConference *conference,
+			     gpointer data, GDestroyNotify clear) {
+
+  g_return_if_fail(conference != NULL);
+  mw_datum_set(&conference->client_data, data, clear);
+}
+
+
+gpointer mwConference_getClientData(struct mwConference *conference) {
+  g_return_val_if_fail(conference != NULL, NULL);
+  return mw_datum_get(&conference->client_data);
+}
+
+
+void mwConference_removeClientData(struct mwConference *conference) {
+  g_return_if_fail(conference != NULL);
+  mw_datum_clear(&conference->client_data);
+}
+
+
+struct mwConferenceHandler *
+mwServiceConference_getHandler(struct mwServiceConference *srvc) {
+  g_return_val_if_fail(srvc != NULL, NULL);
+  return srvc->handler;
+}
+
+
+GList *mwServiceConference_getConferences(struct mwServiceConference *srvc) {
+  g_return_val_if_fail(srvc != NULL, NULL);
+  return g_list_copy(srvc->confs);
+}
+

mercurial