aboutsummaryrefslogtreecommitdiffstats
path: root/libempathy
diff options
context:
space:
mode:
Diffstat (limited to 'libempathy')
-rw-r--r--libempathy/Makefile.am54
-rw-r--r--libempathy/empathy-chandler.c150
-rw-r--r--libempathy/empathy-chandler.h53
-rw-r--r--libempathy/empathy-chandler.xml13
-rw-r--r--libempathy/empathy-contact-list.c1793
-rw-r--r--libempathy/empathy-contact-list.h76
-rw-r--r--libempathy/empathy-contact-manager.c550
-rw-r--r--libempathy/empathy-contact-manager.h77
-rw-r--r--libempathy/empathy-marshal-main.c2
-rw-r--r--libempathy/empathy-marshal.list3
-rw-r--r--libempathy/empathy-session.c161
-rw-r--r--libempathy/empathy-session.h38
-rw-r--r--libempathy/empathy-tp-chat.c474
-rw-r--r--libempathy/empathy-tp-chat.h75
-rw-r--r--libempathy/gossip-avatar.c86
-rw-r--r--libempathy/gossip-avatar.h48
-rw-r--r--libempathy/gossip-conf.c382
-rw-r--r--libempathy/gossip-conf.h87
-rw-r--r--libempathy/gossip-contact.c815
-rw-r--r--libempathy/gossip-contact.h102
-rw-r--r--libempathy/gossip-debug.c92
-rw-r--r--libempathy/gossip-debug.h53
-rw-r--r--libempathy/gossip-message.c365
-rw-r--r--libempathy/gossip-message.h75
-rw-r--r--libempathy/gossip-paths.c51
-rw-r--r--libempathy/gossip-paths.h36
-rw-r--r--libempathy/gossip-presence.c441
-rw-r--r--libempathy/gossip-presence.h84
-rw-r--r--libempathy/gossip-telepathy-group.c496
-rw-r--r--libempathy/gossip-telepathy-group.h79
-rw-r--r--libempathy/gossip-time.c124
-rw-r--r--libempathy/gossip-time.h50
-rw-r--r--libempathy/gossip-utils.c447
-rw-r--r--libempathy/gossip-utils.h87
34 files changed, 7519 insertions, 0 deletions
diff --git a/libempathy/Makefile.am b/libempathy/Makefile.am
new file mode 100644
index 000000000..30d40c595
--- /dev/null
+++ b/libempathy/Makefile.am
@@ -0,0 +1,54 @@
+AM_CPPFLAGS = \
+ -I. \
+ -I$(top_srcdir) \
+ -DDATADIR=\""$(datadir)"\" \
+ -DLOCALEDIR=\""$(datadir)/locale"\" \
+ $(EMPATHY_CFLAGS) \
+ $(WARN_CFLAGS)
+
+BUILT_SOURCES = \
+ empathy-marshal.h \
+ empathy-marshal.c \
+ empathy-chandler-glue.h
+
+noinst_LTLIBRARIES = libempathy.la
+
+libempathy_la_SOURCES = \
+ gossip-conf.c gossip-conf.h \
+ gossip-contact.c gossip-contact.h \
+ gossip-avatar.c gossip-avatar.h \
+ gossip-time.c gossip-time.h \
+ gossip-presence.c gossip-presence.h \
+ gossip-telepathy-group.c gossip-telepathy-group.h \
+ gossip-paths.c gossip-paths.h \
+ gossip-debug.c gossip-debug.h \
+ gossip-utils.c gossip-utils.h \
+ gossip-message.c gossip-message.h \
+ empathy-session.c empathy-session.h \
+ empathy-contact-list.c empathy-contact-list.h \
+ empathy-contact-manager.c empathy-contact-manager.h \
+ empathy-tp-chat.c empathy-tp-chat.h \
+ empathy-chandler.c empathy-chandler.h \
+ empathy-marshal-main.c
+
+libempathy_la_LIBADD = \
+ $(EMPATHY_LIBS)
+
+libempathy_includedir = $(includedir)/empathy/
+
+%-marshal.h: %-marshal.list Makefile.am
+ $(GLIB_GENMARSHAL) --header --prefix=$(subst -,_,$*)_marshal $< > $*-marshal.h
+
+%-marshal.c: %-marshal.list Makefile.am
+ $(GLIB_GENMARSHAL) --body --prefix=$(subst -,_,$*)_marshal $< > $*-marshal.c
+
+%-marshal-main.c: %-marshal.c %-marshal.h
+
+empathy-chandler-glue.h: empathy-chandler.xml
+ $(LIBTOOL) --mode=execute $(DBUS_BINDING_TOOL) --prefix=empathy_chandler --mode=glib-server --output=$@ $<
+
+EXTRA_DIST = \
+ empathy-marshal.list \
+ empathy-chandler.xml
+
+CLEANFILES = $(BUILT_SOURCES)
diff --git a/libempathy/empathy-chandler.c b/libempathy/empathy-chandler.c
new file mode 100644
index 000000000..adc415a93
--- /dev/null
+++ b/libempathy/empathy-chandler.c
@@ -0,0 +1,150 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2007 Xavier Claessens <xclaesse@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include <config.h>
+
+#include <dbus/dbus-glib.h>
+
+#include <libtelepathy/tp-helpers.h>
+#include <libtelepathy/tp-conn.h>
+#include <libtelepathy/tp-chan.h>
+
+#include "empathy-chandler.h"
+#include "gossip-debug.h"
+#include "empathy-marshal.h"
+
+#define DEBUG_DOMAIN "EmpathyChandler"
+
+static gboolean empathy_chandler_handle_channel (EmpathyChandler *chandler,
+ const gchar *bus_name,
+ const gchar *connection,
+ const gchar *channel_type,
+ const gchar *channel,
+ guint handle_type,
+ guint handle,
+ GError **error);
+
+#include "empathy-chandler-glue.h"
+
+enum {
+ NEW_CHANNEL,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE (EmpathyChandler, empathy_chandler, G_TYPE_OBJECT)
+
+static void
+empathy_chandler_class_init (EmpathyChandlerClass *klass)
+{
+ signals[NEW_CHANNEL] =
+ g_signal_new ("new-channel",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ empathy_marshal_VOID__OBJECT_OBJECT,
+ G_TYPE_NONE,
+ 2, TELEPATHY_CONN_TYPE, TELEPATHY_CHAN_TYPE);
+}
+
+static void
+empathy_chandler_init (EmpathyChandler *chandler)
+{
+}
+
+EmpathyChandler *
+empathy_chandler_new (const gchar *bus_name,
+ const gchar *object_path)
+{
+ static gboolean initialized = FALSE;
+ EmpathyChandler *chandler;
+ DBusGProxy *proxy;
+ guint result;
+ GError *error = NULL;
+
+ if (!initialized) {
+ dbus_g_object_type_install_info (EMPATHY_TYPE_CHANDLER,
+ &dbus_glib_empathy_chandler_object_info);
+ initialized = TRUE;
+ }
+
+ proxy = dbus_g_proxy_new_for_name (tp_get_bus (),
+ DBUS_SERVICE_DBUS,
+ DBUS_PATH_DBUS,
+ DBUS_INTERFACE_DBUS);
+
+ if (!dbus_g_proxy_call (proxy, "RequestName", &error,
+ G_TYPE_STRING, bus_name,
+ G_TYPE_UINT, DBUS_NAME_FLAG_DO_NOT_QUEUE,
+ G_TYPE_INVALID,
+ G_TYPE_UINT, &result,
+ G_TYPE_INVALID)) {
+ gossip_debug (DEBUG_DOMAIN,
+ "Failed to request name: %s",
+ error ? error->message : "No error given");
+ g_clear_error (&error);
+
+ return NULL;
+ }
+ g_object_unref (proxy);
+
+ chandler = g_object_new (EMPATHY_TYPE_CHANDLER, NULL);
+ dbus_g_connection_register_g_object (tp_get_bus (),
+ object_path,
+ G_OBJECT (chandler));
+
+ return chandler;
+}
+
+static gboolean
+empathy_chandler_handle_channel (EmpathyChandler *chandler,
+ const gchar *bus_name,
+ const gchar *connection,
+ const gchar *channel_type,
+ const gchar *channel,
+ guint handle_type,
+ guint handle,
+ GError **error)
+{
+ TpChan *tp_chan;
+ TpConn *tp_conn;
+
+ tp_conn = tp_conn_new (tp_get_bus (),
+ bus_name,
+ connection);
+
+ tp_chan = tp_chan_new (tp_get_bus(),
+ bus_name,
+ channel,
+ channel_type,
+ handle_type,
+ handle);
+
+ g_signal_emit (chandler, signals[NEW_CHANNEL], 0, tp_conn, tp_chan);
+
+ g_object_unref (tp_chan);
+ g_object_unref (tp_conn);
+
+ return TRUE;
+}
+
diff --git a/libempathy/empathy-chandler.h b/libempathy/empathy-chandler.h
new file mode 100644
index 000000000..ca271613c
--- /dev/null
+++ b/libempathy/empathy-chandler.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2007 Xavier Claessens <xclaesse@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifndef __EMPATHY_CHANDLER_H__
+#define __EMPATHY_CHANDLER_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+#define EMPATHY_TYPE_CHANDLER (empathy_chandler_get_type ())
+#define EMPATHY_CHANDLER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), EMPATHY_TYPE_CHANDLER, EmpathyChandler))
+#define EMPATHY_CHANDLER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), EMPATHY_TYPE_CHANDLER, EmpathyChandlerClass))
+#define EMPATHY_IS_CHANDLER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), EMPATHY_TYPE_CHANDLER))
+#define EMPATHY_IS_CHANDLER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), EMPATHY_TYPE_CHANDLER))
+#define EMPATHY_CHANDLER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), EMPATHY_TYPE_CHANDLER, EmpathyChandlerClass))
+
+typedef struct _EmpathyChandler EmpathyChandler;
+typedef struct _EmpathyChandlerClass EmpathyChandlerClass;
+
+struct _EmpathyChandler {
+ GObject parent;
+};
+
+struct _EmpathyChandlerClass {
+ GObjectClass parent_class;
+};
+
+GType empathy_chandler_get_type (void) G_GNUC_CONST;
+EmpathyChandler *empathy_chandler_new (const gchar *bus_name,
+ const gchar *object_path);
+
+G_END_DECLS
+
+#endif /* __EMPATHY_CHANDLER_H__ */
diff --git a/libempathy/empathy-chandler.xml b/libempathy/empathy-chandler.xml
new file mode 100644
index 000000000..0fb264e5f
--- /dev/null
+++ b/libempathy/empathy-chandler.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<node name="/">
+ <interface name="org.freedesktop.Telepathy.ChannelHandler">
+ <method name="HandleChannel">
+ <arg direction="in" type="s" name="bus_name" />
+ <arg direction="in" type="o" name="connection" />
+ <arg direction="in" type="s" name="channel_type" />
+ <arg direction="in" type="o" name="channel" />
+ <arg direction="in" type="u" name="handle_type" />
+ <arg direction="in" type="u" name="handle" />
+ </method>
+ </interface>
+</node>
diff --git a/libempathy/empathy-contact-list.c b/libempathy/empathy-contact-list.c
new file mode 100644
index 000000000..2cc486de9
--- /dev/null
+++ b/libempathy/empathy-contact-list.c
@@ -0,0 +1,1793 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2007 Xavier Claessens <xclaesse@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+
+#include <string.h>
+
+#include <libtelepathy/tp-conn.h>
+#include <libtelepathy/tp-chan.h>
+#include <libtelepathy/tp-helpers.h>
+#include <libtelepathy/tp-chan-type-contact-list-gen.h>
+#include <libtelepathy/tp-conn-iface-aliasing-gen.h>
+#include <libtelepathy/tp-conn-iface-presence-gen.h>
+#include <libtelepathy/tp-conn-iface-avatars-gen.h>
+
+#include "empathy-contact-list.h"
+#include "empathy-session.h"
+#include "gossip-debug.h"
+#include "gossip-telepathy-group.h"
+
+#define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \
+ EMPATHY_TYPE_CONTACT_LIST, EmpathyContactListPriv))
+
+#define DEBUG_DOMAIN "ContactList"
+#define MAX_AVATAR_REQUESTS 10
+
+struct _EmpathyContactListPriv {
+ TpConn *tp_conn;
+ McAccount *account;
+ GossipContact *own_contact;
+
+ GossipTelepathyGroup *known;
+ GossipTelepathyGroup *publish;
+ GossipTelepathyGroup *subscribe;
+
+ GHashTable *groups;
+ GHashTable *contacts;
+
+ DBusGProxy *aliasing_iface;
+ DBusGProxy *avatars_iface;
+ DBusGProxy *presence_iface;
+
+ GList *avatar_requests_queue;
+};
+
+typedef enum {
+ CONTACT_LIST_TYPE_KNOWN,
+ CONTACT_LIST_TYPE_PUBLISH,
+ CONTACT_LIST_TYPE_SUBSCRIBE,
+ CONTACT_LIST_TYPE_UNKNOWN,
+ CONTACT_LIST_TYPE_COUNT
+} ContactListType;
+
+typedef struct {
+ guint handle;
+ GList *new_groups;
+} ContactListData;
+
+typedef struct {
+ EmpathyContactList *list;
+ guint handle;
+} ContactListAvatarRequestData;
+
+typedef struct {
+ EmpathyContactList *list;
+ guint *handles;
+} ContactListAliasesRequestData;
+
+static void empathy_contact_list_class_init (EmpathyContactListClass *klass);
+static void empathy_contact_list_init (EmpathyContactList *list);
+static void contact_list_finalize (GObject *object);
+static void contact_list_finalize_proxies (EmpathyContactList *list);
+static void contact_list_contact_removed_foreach (guint handle,
+ GossipContact *contact,
+ EmpathyContactList *list);
+static void contact_list_destroy_cb (DBusGProxy *proxy,
+ EmpathyContactList *list);
+static gboolean contact_list_find_foreach (guint handle,
+ GossipContact *contact,
+ gchar *id);
+static void contact_list_newchannel_cb (DBusGProxy *proxy,
+ const gchar *object_path,
+ const gchar *channel_type,
+ TelepathyHandleType handle_type,
+ guint channel_handle,
+ gboolean suppress_handle,
+ EmpathyContactList *list);
+static ContactListType contact_list_get_type (EmpathyContactList *list,
+ TpChan *list_chan);
+static void contact_list_contact_added_cb (GossipTelepathyGroup *group,
+ GArray *handles,
+ guint actor_handle,
+ guint reason,
+ const gchar *message,
+ EmpathyContactList *list);
+static void contact_list_contact_removed_cb (GossipTelepathyGroup *group,
+ GArray *handles,
+ guint actor_handle,
+ guint reason,
+ const gchar *message,
+ EmpathyContactList *list);
+static void contact_list_local_pending_cb (GossipTelepathyGroup *group,
+ GArray *handles,
+ guint actor_handle,
+ guint reason,
+ const gchar *message,
+ EmpathyContactList *list);
+static void contact_list_groups_updated_cb (GossipContact *contact,
+ GParamSpec *param,
+ EmpathyContactList *list);
+static void contact_list_subscription_updated_cb (GossipContact *contact,
+ GParamSpec *param,
+ EmpathyContactList *list);
+static void contact_list_name_updated_cb (GossipContact *contact,
+ GParamSpec *param,
+ EmpathyContactList *list);
+static void contact_list_update_groups_foreach (gchar *object_path,
+ GossipTelepathyGroup *group,
+ ContactListData *data);
+static GossipTelepathyGroup * contact_list_get_group (EmpathyContactList *list,
+ const gchar *name);
+static gboolean contact_list_find_group (gchar *key,
+ GossipTelepathyGroup *group,
+ gchar *group_name);
+static void contact_list_get_groups_foreach (gchar *key,
+ GossipTelepathyGroup *group,
+ GList **groups);
+static void contact_list_group_channel_closed_cb (TpChan *channel,
+ EmpathyContactList *list);
+static void contact_list_group_members_added_cb (GossipTelepathyGroup *group,
+ GArray *members,
+ guint actor_handle,
+ guint reason,
+ const gchar *message,
+ EmpathyContactList *list);
+static void contact_list_group_members_removed_cb (GossipTelepathyGroup *group,
+ GArray *members,
+ guint actor_handle,
+ guint reason,
+ const gchar *message,
+ EmpathyContactList *list);
+static void contact_list_get_contacts_foreach (guint handle,
+ GossipContact *contact,
+ GList **contacts);
+static void contact_list_get_info (EmpathyContactList *list,
+ GArray *handles);
+static void contact_list_request_avatar (EmpathyContactList *list,
+ guint handle);
+static void contact_list_start_avatar_requests (EmpathyContactList *list);
+static void contact_list_avatar_update_cb (DBusGProxy *proxy,
+ guint handle,
+ gchar *new_token,
+ EmpathyContactList *list);
+static void contact_list_request_avatar_cb (DBusGProxy *proxy,
+ GArray *avatar_data,
+ gchar *mime_type,
+ GError *error,
+ ContactListAvatarRequestData *data);
+static void contact_list_aliases_update_cb (DBusGProxy *proxy,
+ GPtrArray *handlers,
+ EmpathyContactList *list);
+static void contact_list_request_aliases_cb (DBusGProxy *proxy,
+ gchar **contact_names,
+ GError *error,
+ ContactListAliasesRequestData *data);
+static void contact_list_presence_update_cb (DBusGProxy *proxy,
+ GHashTable *handle_table,
+ EmpathyContactList *list);
+static void contact_list_parse_presence_foreach (guint handle,
+ GValueArray *presence_struct,
+ EmpathyContactList *list);
+static void contact_list_presences_table_foreach (const gchar *state_str,
+ GHashTable *presences_table,
+ GossipPresence **presence);
+static GossipPresenceState contact_list_presence_state_from_str (const gchar *str);
+
+enum {
+ CONTACT_ADDED,
+ CONTACT_REMOVED,
+ DESTROY,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+static guint n_avatar_requests = 0;
+
+G_DEFINE_TYPE (EmpathyContactList, empathy_contact_list, G_TYPE_OBJECT);
+
+static void
+empathy_contact_list_class_init (EmpathyContactListClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = contact_list_finalize;
+
+ signals[CONTACT_ADDED] =
+ g_signal_new ("contact-added",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1, GOSSIP_TYPE_CONTACT);
+
+ signals[CONTACT_REMOVED] =
+ g_signal_new ("contact-removed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1, GOSSIP_TYPE_CONTACT);
+
+ signals[DESTROY] =
+ g_signal_new ("destroy",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ g_type_class_add_private (object_class, sizeof (EmpathyContactListPriv));
+}
+
+static void
+empathy_contact_list_init (EmpathyContactList *list)
+{
+ EmpathyContactListPriv *priv;
+
+ priv = GET_PRIV (list);
+
+ priv->groups = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) g_object_unref);
+ priv->contacts = g_hash_table_new_full (g_direct_hash,
+ g_direct_equal,
+ NULL,
+ (GDestroyNotify) g_object_unref);
+}
+
+
+static void
+contact_list_finalize (GObject *object)
+{
+ EmpathyContactListPriv *priv;
+ EmpathyContactList *list;
+
+ list = EMPATHY_CONTACT_LIST (object);
+ priv = GET_PRIV (list);
+
+ gossip_debug (DEBUG_DOMAIN, "finalize: %p", object);
+
+ if (priv->tp_conn) {
+ contact_list_finalize_proxies (list);
+ g_object_unref (priv->tp_conn);
+ }
+
+ if (priv->known) {
+ g_object_unref (priv->known);
+ }
+
+ if (priv->subscribe) {
+ g_object_unref (priv->subscribe);
+ }
+
+ if (priv->publish) {
+ g_object_unref (priv->publish);
+ }
+
+ g_object_unref (priv->account);
+ g_object_unref (priv->own_contact);
+ g_hash_table_destroy (priv->groups);
+ g_hash_table_destroy (priv->contacts);
+
+ G_OBJECT_CLASS (empathy_contact_list_parent_class)->finalize (object);
+}
+
+EmpathyContactList *
+empathy_contact_list_new (McAccount *account)
+{
+ EmpathyContactListPriv *priv;
+ EmpathyContactList *list;
+ MissionControl *mc;
+ TpConn *tp_conn;
+ guint handle;
+ GError *error = NULL;
+
+ g_return_val_if_fail (MC_IS_ACCOUNT (account), NULL);
+
+ mc = empathy_session_get_mission_control ();
+
+ if (mission_control_get_connection_status (mc, account, NULL) != 0) {
+ /* The account is not connected, nothing to do. */
+ return NULL;
+ }
+
+ tp_conn = mission_control_get_connection (mc, account, NULL);
+ g_return_val_if_fail (tp_conn != NULL, NULL);
+
+ list = g_object_new (EMPATHY_TYPE_CONTACT_LIST, NULL);
+ priv = GET_PRIV (list);
+
+ priv->tp_conn = tp_conn;
+ priv->account = g_object_ref (account);
+
+ g_signal_connect (priv->tp_conn, "destroy",
+ G_CALLBACK (contact_list_destroy_cb),
+ list);
+
+ priv->aliasing_iface = tp_conn_get_interface (priv->tp_conn,
+ TELEPATHY_CONN_IFACE_ALIASING_QUARK);
+ priv->avatars_iface = tp_conn_get_interface (priv->tp_conn,
+ TELEPATHY_CONN_IFACE_AVATARS_QUARK);
+ priv->presence_iface = tp_conn_get_interface (priv->tp_conn,
+ TELEPATHY_CONN_IFACE_PRESENCE_QUARK);
+
+ if (priv->aliasing_iface) {
+ dbus_g_proxy_connect_signal (priv->aliasing_iface,
+ "AliasesChanged",
+ G_CALLBACK (contact_list_aliases_update_cb),
+ list, NULL);
+ }
+
+ if (priv->avatars_iface) {
+ dbus_g_proxy_connect_signal (priv->avatars_iface,
+ "AvatarUpdated",
+ G_CALLBACK (contact_list_avatar_update_cb),
+ list, NULL);
+ }
+
+ if (priv->presence_iface) {
+ dbus_g_proxy_connect_signal (priv->presence_iface,
+ "PresenceUpdate",
+ G_CALLBACK (contact_list_presence_update_cb),
+ list, NULL);
+ }
+
+ /* Get our own handle and contact */
+ if (!tp_conn_get_self_handle (DBUS_G_PROXY (priv->tp_conn),
+ &handle, &error)) {
+ gossip_debug (DEBUG_DOMAIN, "GetSelfHandle Error: %s",
+ error ? error->message : "No error given");
+ g_clear_error (&error);
+ } else {
+ priv->own_contact = empathy_contact_list_get_from_handle (list, handle);
+ }
+
+ return list;
+}
+
+void
+empathy_contact_list_setup (EmpathyContactList *list)
+{
+ EmpathyContactListPriv *priv;
+ GPtrArray *channels;
+ GError *error = NULL;
+ guint i;
+
+ g_return_if_fail (EMPATHY_IS_CONTACT_LIST (list));
+
+ priv = GET_PRIV (list);
+
+ dbus_g_proxy_connect_signal (DBUS_G_PROXY (priv->tp_conn), "NewChannel",
+ G_CALLBACK (contact_list_newchannel_cb),
+ list, NULL);
+
+ /* Get existing channels */
+ if (!tp_conn_list_channels (DBUS_G_PROXY (priv->tp_conn),
+ &channels,
+ &error)) {
+ gossip_debug (DEBUG_DOMAIN,
+ "Failed to get list of open channels: %s",
+ error ? error->message : "No error given");
+ g_clear_error (&error);
+ return;
+ }
+
+ for (i = 0; channels->len > i; i++) {
+ GValueArray *chan_struct;
+ const gchar *object_path;
+ const gchar *chan_iface;
+ TelepathyHandleType handle_type;
+ guint handle;
+
+ chan_struct = g_ptr_array_index (channels, i);
+ object_path = g_value_get_boxed (g_value_array_get_nth (chan_struct, 0));
+ chan_iface = g_value_get_string (g_value_array_get_nth (chan_struct, 1));
+ handle_type = g_value_get_uint (g_value_array_get_nth (chan_struct, 2));
+ handle = g_value_get_uint (g_value_array_get_nth (chan_struct, 3));
+
+ contact_list_newchannel_cb (DBUS_G_PROXY (priv->tp_conn),
+ object_path, chan_iface,
+ handle_type, handle,
+ FALSE, list);
+
+ g_value_array_free (chan_struct);
+ }
+
+ g_ptr_array_free (channels, TRUE);
+}
+
+McAccount *
+empathy_contact_list_get_account (EmpathyContactList *list)
+{
+ EmpathyContactListPriv *priv;
+
+ g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST (list), NULL);
+
+ priv = GET_PRIV (list);
+
+ return priv->account;
+}
+
+GossipContact *
+empathy_contact_list_get_own (EmpathyContactList *list)
+{
+ EmpathyContactListPriv *priv;
+
+ g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST (list), NULL);
+
+ priv = GET_PRIV (list);
+
+ return priv->own_contact;
+}
+
+GossipContact *
+empathy_contact_list_find (EmpathyContactList *list,
+ const gchar *id)
+{
+ EmpathyContactListPriv *priv;
+ GossipContact *contact;
+
+ g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST (list), NULL);
+
+ priv = GET_PRIV (list);
+
+ contact = g_hash_table_find (priv->contacts,
+ (GHRFunc) contact_list_find_foreach,
+ (gchar*) id);
+
+ return NULL;
+}
+
+void
+empathy_contact_list_add (EmpathyContactList *list,
+ guint handle,
+ const gchar *message)
+{
+ EmpathyContactListPriv *priv;
+
+ g_return_if_fail (EMPATHY_IS_CONTACT_LIST (list));
+
+ priv = GET_PRIV (list);
+
+ gossip_telepathy_group_add_member (priv->subscribe, handle, message);
+}
+
+void
+empathy_contact_list_remove (EmpathyContactList *list,
+ guint handle)
+{
+ EmpathyContactListPriv *priv;
+
+ g_return_if_fail (EMPATHY_IS_CONTACT_LIST (list));
+
+ priv = GET_PRIV (list);
+
+ gossip_telepathy_group_remove_member (priv->subscribe, handle, "");
+ gossip_telepathy_group_remove_member (priv->publish, handle, "");
+ gossip_telepathy_group_remove_member (priv->known, handle, "");
+}
+
+GossipContact *
+empathy_contact_list_get_from_id (EmpathyContactList *list,
+ const gchar *id)
+{
+ EmpathyContactListPriv *priv;
+ GossipContact *contact;
+ const gchar *contact_ids[] = {id, NULL};
+ GArray *handles;
+ guint handle;
+ GError *error = NULL;
+
+ g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST (list), NULL);
+ g_return_val_if_fail (id != NULL, NULL);
+
+ priv = GET_PRIV (list);
+
+ contact = empathy_contact_list_find (list, id);
+ if (contact) {
+ return contact;
+ }
+
+ /* The id is unknown, requests a new handle */
+ if (!tp_conn_request_handles (DBUS_G_PROXY (priv->tp_conn),
+ TP_HANDLE_TYPE_CONTACT,
+ contact_ids,
+ &handles, &error)) {
+ gossip_debug (DEBUG_DOMAIN,
+ "RequestHandle for %s failed: %s", id,
+ error ? error->message : "No error given");
+ g_clear_error (&error);
+ return 0;
+ }
+
+ handle = g_array_index(handles, guint, 0);
+ g_array_free (handles, TRUE);
+
+ return empathy_contact_list_get_from_handle (list, handle);
+}
+
+GossipContact *
+empathy_contact_list_get_from_handle (EmpathyContactList *list,
+ guint handle)
+{
+ GossipContact *contact;
+ GArray *handles;
+ GList *contacts;
+
+ g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST (list), NULL);
+
+ handles = g_array_new (FALSE, FALSE, sizeof (guint));
+ g_array_append_val (handles, handle);
+
+ contacts = empathy_contact_list_get_from_handles (list, handles);
+ g_array_free (handles, TRUE);
+
+ if (!contacts) {
+ return NULL;
+ }
+
+ contact = contacts->data;
+ g_list_free (contacts);
+
+ return contact;
+}
+
+GList *
+empathy_contact_list_get_from_handles (EmpathyContactList *list,
+ GArray *handles)
+{
+ EmpathyContactListPriv *priv;
+ gchar **handles_names;
+ gchar **id;
+ GArray *new_handles;
+ GList *contacts = NULL;
+ guint i;
+ GError *error = NULL;
+
+ g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST (list), NULL);
+ g_return_val_if_fail (handles != NULL, NULL);
+
+ priv = GET_PRIV (list);
+
+ /* Search all handles we already have */
+ new_handles = g_array_new (FALSE, FALSE, sizeof (guint));
+ for (i = 0; i < handles->len; i++) {
+ GossipContact *contact;
+ guint handle;
+
+ handle = g_array_index (handles, guint, i);
+ contact = g_hash_table_lookup (priv->contacts,
+ GUINT_TO_POINTER (handle));
+
+ if (contact) {
+ contacts = g_list_prepend (contacts,
+ g_object_ref (contact));
+ } else {
+ g_array_append_val (new_handles, handle);
+ }
+ }
+
+ if (new_handles->len == 0) {
+ return contacts;
+ }
+
+ /* Holds all handles we don't have yet.
+ * FIXME: We should release them at some point. */
+ if (!tp_conn_hold_handles (DBUS_G_PROXY (priv->tp_conn),
+ TP_HANDLE_TYPE_CONTACT,
+ new_handles, &error)) {
+ gossip_debug (DEBUG_DOMAIN,
+ "HoldHandles Error: %s",
+ error ? error->message : "No error given");
+ g_clear_error (&error);
+ g_array_free (new_handles, TRUE);
+ return contacts;
+ }
+
+ /* Get the IDs of all new handles */
+ if (!tp_conn_inspect_handles (DBUS_G_PROXY (priv->tp_conn),
+ TP_HANDLE_TYPE_CONTACT,
+ new_handles,
+ &handles_names,
+ &error)) {
+ gossip_debug (DEBUG_DOMAIN,
+ "InspectHandle Error: %s",
+ error ? error->message : "No error given");
+ g_clear_error (&error);
+ g_array_free (new_handles, TRUE);
+ return contacts;
+ }
+
+ /* Create contact objects */
+ for (i = 0, id = handles_names; *id && i < new_handles->len; id++, i++) {
+ GossipContact *contact;
+ guint handle;
+
+ handle = g_array_index (new_handles, guint, i);
+ contact = g_object_new (GOSSIP_TYPE_CONTACT,
+ "account", priv->account,
+ "id", *id,
+ "handle", handle,
+ NULL);
+
+ g_signal_connect (contact, "notify::groups",
+ G_CALLBACK (contact_list_groups_updated_cb),
+ list);
+ g_signal_connect (contact, "notify::subscription",
+ G_CALLBACK (contact_list_subscription_updated_cb),
+ list);
+ g_signal_connect (contact, "notify::name",
+ G_CALLBACK (contact_list_name_updated_cb),
+ list);
+
+ gossip_debug (DEBUG_DOMAIN, "new contact created: %s (%d)",
+ *id, handle);
+
+ g_hash_table_insert (priv->contacts,
+ GUINT_TO_POINTER (handle),
+ contact);
+
+ contacts = g_list_prepend (contacts, g_object_ref (contact));
+ }
+
+ contact_list_get_info (list, new_handles);
+
+ g_array_free (new_handles, TRUE);
+ g_strfreev (handles_names);
+
+ return contacts;
+}
+
+void
+empathy_contact_list_rename_group (EmpathyContactList *list,
+ const gchar *old_group,
+ const gchar *new_group)
+{
+ EmpathyContactListPriv *priv;
+ GossipTelepathyGroup *group;
+ GArray *members;
+
+ g_return_if_fail (EMPATHY_IS_CONTACT_LIST (list));
+ g_return_if_fail (old_group != NULL);
+ g_return_if_fail (new_group != NULL);
+
+ priv = GET_PRIV (list);
+
+ group = g_hash_table_find (priv->groups,
+ (GHRFunc) contact_list_find_group,
+ (gchar*) old_group);
+ if (!group) {
+ /* The group doesn't exists on this account */
+ return;
+ }
+
+ gossip_debug (DEBUG_DOMAIN, "rename group %s to %s", group, new_group);
+
+ /* Remove all members from the old group */
+ members = gossip_telepathy_group_get_members (group);
+ gossip_telepathy_group_remove_members (group, members, "");
+ contact_list_group_members_removed_cb (group, members,
+ 0,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE,
+ NULL, list);
+ g_hash_table_remove (priv->groups,
+ gossip_telepathy_group_get_object_path (group));
+
+ /* Add all members to the new group */
+ group = contact_list_get_group (list, new_group);
+ if (group) {
+ gossip_telepathy_group_add_members (group, members, "");
+ }
+}
+
+GList *
+empathy_contact_list_get_groups (EmpathyContactList *list)
+{
+ EmpathyContactListPriv *priv;
+ GList *groups = NULL;
+
+ g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST (list), NULL);
+
+ priv = GET_PRIV (list);
+
+ g_hash_table_foreach (priv->groups,
+ (GHFunc) contact_list_get_groups_foreach,
+ &groups);
+
+ groups = g_list_sort (groups, (GCompareFunc) strcmp);
+
+ return groups;
+}
+
+GList *
+empathy_contact_list_get_contacts (EmpathyContactList *list)
+{
+ EmpathyContactListPriv *priv;
+ GList *contacts = NULL;
+
+ g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST (list), NULL);
+
+ priv = GET_PRIV (list);
+
+ /* FIXME: we should only return contacts that are in the contact list */
+ g_hash_table_foreach (priv->contacts,
+ (GHFunc) contact_list_get_contacts_foreach,
+ &contacts);
+
+ return contacts;
+}
+
+static void
+contact_list_finalize_proxies (EmpathyContactList *list)
+{
+ EmpathyContactListPriv *priv;
+
+ priv = GET_PRIV (list);
+
+ g_signal_handlers_disconnect_by_func (priv->tp_conn,
+ contact_list_destroy_cb,
+ list);
+ dbus_g_proxy_disconnect_signal (DBUS_G_PROXY (priv->tp_conn), "NewChannel",
+ G_CALLBACK (contact_list_newchannel_cb),
+ list);
+
+ if (priv->aliasing_iface) {
+ dbus_g_proxy_disconnect_signal (priv->aliasing_iface,
+ "AliasesChanged",
+ G_CALLBACK (contact_list_aliases_update_cb),
+ list);
+ }
+
+ if (priv->avatars_iface) {
+ dbus_g_proxy_disconnect_signal (priv->avatars_iface,
+ "AvatarUpdated",
+ G_CALLBACK (contact_list_avatar_update_cb),
+ list);
+ }
+
+ if (priv->presence_iface) {
+ dbus_g_proxy_disconnect_signal (priv->presence_iface,
+ "PresenceUpdate",
+ G_CALLBACK (contact_list_presence_update_cb),
+ list);
+ }
+}
+
+static void
+contact_list_destroy_cb (DBusGProxy *proxy,
+ EmpathyContactList *list)
+{
+ EmpathyContactListPriv *priv;
+
+ priv = GET_PRIV (list);
+
+ gossip_debug (DEBUG_DOMAIN, "Connection destroyed. "
+ "Account disconnected or CM crashed.");
+
+ /* DBus proxies should NOT be used anymore */
+ g_object_unref (priv->tp_conn);
+ priv->tp_conn = NULL;
+ priv->aliasing_iface = NULL;
+ priv->avatars_iface = NULL;
+ priv->presence_iface = NULL;
+
+ /* Remove all contacts */
+ g_hash_table_foreach (priv->contacts,
+ (GHFunc) contact_list_contact_removed_foreach,
+ list);
+ g_hash_table_remove_all (priv->contacts);
+
+ /* Tell the world to not use us anymore */
+ g_signal_emit (list, signals[DESTROY], 0);
+}
+
+static void
+contact_list_contact_removed_foreach (guint handle,
+ GossipContact *contact,
+ EmpathyContactList *list)
+{
+ g_signal_handlers_disconnect_by_func (contact,
+ contact_list_groups_updated_cb,
+ list);
+ g_signal_handlers_disconnect_by_func (contact,
+ contact_list_subscription_updated_cb,
+ list);
+ g_signal_handlers_disconnect_by_func (contact,
+ contact_list_name_updated_cb,
+ list);
+
+ g_signal_emit (list, signals[CONTACT_REMOVED], 0, contact);
+}
+
+static void
+contact_list_block_contact (EmpathyContactList *list,
+ GossipContact *contact)
+{
+ g_signal_handlers_block_by_func (contact,
+ contact_list_groups_updated_cb,
+ list);
+ g_signal_handlers_block_by_func (contact,
+ contact_list_subscription_updated_cb,
+ list);
+ g_signal_handlers_block_by_func (contact,
+ contact_list_name_updated_cb,
+ list);
+}
+
+static void
+contact_list_unblock_contact (EmpathyContactList *list,
+ GossipContact *contact)
+{
+ g_signal_handlers_unblock_by_func (contact,
+ contact_list_groups_updated_cb,
+ list);
+ g_signal_handlers_unblock_by_func (contact,
+ contact_list_subscription_updated_cb,
+ list);
+ g_signal_handlers_unblock_by_func (contact,
+ contact_list_name_updated_cb,
+ list);
+}
+
+static gboolean
+contact_list_find_foreach (guint handle,
+ GossipContact *contact,
+ gchar *id)
+{
+ if (strcmp (gossip_contact_get_id (contact), id) == 0) {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+contact_list_newchannel_cb (DBusGProxy *proxy,
+ const gchar *object_path,
+ const gchar *channel_type,
+ TelepathyHandleType handle_type,
+ guint channel_handle,
+ gboolean suppress_handle,
+ EmpathyContactList *list)
+{
+ EmpathyContactListPriv *priv;
+ GossipTelepathyGroup *group;
+ TpChan *new_chan;
+ const gchar *bus_name;
+ GArray *members;
+
+ priv = GET_PRIV (list);
+
+ if (strcmp (channel_type, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST) != 0 ||
+ suppress_handle) {
+ return;
+ }
+
+ bus_name = dbus_g_proxy_get_bus_name (DBUS_G_PROXY (priv->tp_conn));
+ new_chan = tp_chan_new (tp_get_bus (),
+ bus_name,
+ object_path,
+ channel_type, handle_type, channel_handle);
+
+ if (handle_type == TP_HANDLE_TYPE_LIST) {
+ ContactListType list_type;
+
+ list_type = contact_list_get_type (list, new_chan);
+ if (list_type == CONTACT_LIST_TYPE_UNKNOWN) {
+ gossip_debug (DEBUG_DOMAIN, "Unknown contact list channel");
+ g_object_unref (new_chan);
+ return;
+ }
+
+ gossip_debug (DEBUG_DOMAIN, "New contact list channel of type: %d",
+ list_type);
+
+ group = gossip_telepathy_group_new (new_chan, priv->tp_conn);
+
+ switch (list_type) {
+ case CONTACT_LIST_TYPE_KNOWN:
+ if (priv->known) {
+ g_object_unref (priv->known);
+ }
+ priv->known = group;
+ break;
+ case CONTACT_LIST_TYPE_PUBLISH:
+ if (priv->publish) {
+ g_object_unref (priv->publish);
+ }
+ priv->publish = group;
+ break;
+ case CONTACT_LIST_TYPE_SUBSCRIBE:
+ if (priv->subscribe) {
+ g_object_unref (priv->subscribe);
+ }
+ priv->subscribe = group;
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ /* Connect and setup the new contact-list group */
+ if (list_type == CONTACT_LIST_TYPE_KNOWN ||
+ list_type == CONTACT_LIST_TYPE_SUBSCRIBE) {
+ g_signal_connect (group, "members-added",
+ G_CALLBACK (contact_list_contact_added_cb),
+ list);
+ g_signal_connect (group, "members-removed",
+ G_CALLBACK (contact_list_contact_removed_cb),
+ list);
+
+ members = gossip_telepathy_group_get_members (group);
+ contact_list_contact_added_cb (group, members, 0,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE,
+ NULL, list);
+ g_array_free (members, TRUE);
+ }
+ if (list_type == CONTACT_LIST_TYPE_PUBLISH) {
+ GPtrArray *info;
+ GArray *pending;
+ guint i;
+
+ g_signal_connect (group, "local-pending",
+ G_CALLBACK (contact_list_local_pending_cb),
+ list);
+
+ info = gossip_telepathy_group_get_local_pending_members_with_info (group);
+
+ if (!info) {
+ /* This happens with butterfly because
+ * GetLocalPendingMembersWithInfo is not
+ * implemented */
+ g_object_unref (new_chan);
+ return;
+ }
+
+ pending = g_array_sized_new (FALSE, FALSE, sizeof (guint), 1);
+ for (i = 0; info->len > i; i++) {
+ GValueArray *pending_struct;
+ guint member;
+ guint invitor;
+ guint reason;
+ const gchar *message;
+
+ pending_struct = g_ptr_array_index (info, i);
+ member = g_value_get_uint (g_value_array_get_nth (pending_struct, 0));
+ invitor = g_value_get_uint (g_value_array_get_nth (pending_struct, 1));
+ reason = g_value_get_uint (g_value_array_get_nth (pending_struct, 2));
+ message = g_value_get_string (g_value_array_get_nth (pending_struct, 3));
+
+ g_array_insert_val (pending, 0, member);
+
+ contact_list_local_pending_cb (group, pending,
+ invitor,
+ reason,
+ message, list);
+
+ g_value_array_free (pending_struct);
+ }
+
+ g_ptr_array_free (info, TRUE);
+ g_array_free (pending, TRUE);
+ }
+ }
+ else if (handle_type == TP_HANDLE_TYPE_GROUP) {
+ const gchar *object_path;
+
+ object_path = dbus_g_proxy_get_path (DBUS_G_PROXY (new_chan));
+ if (g_hash_table_lookup (priv->groups, object_path)) {
+ g_object_unref (new_chan);
+ return;
+ }
+
+ group = gossip_telepathy_group_new (new_chan, priv->tp_conn);
+
+ gossip_debug (DEBUG_DOMAIN, "New server-side group channel: %s",
+ gossip_telepathy_group_get_name (group));
+
+ dbus_g_proxy_connect_signal (DBUS_G_PROXY (new_chan), "Closed",
+ G_CALLBACK
+ (contact_list_group_channel_closed_cb),
+ list, NULL);
+
+ g_hash_table_insert (priv->groups, g_strdup (object_path), group);
+ g_signal_connect (group, "members-added",
+ G_CALLBACK (contact_list_group_members_added_cb),
+ list);
+ g_signal_connect (group, "members-removed",
+ G_CALLBACK (contact_list_group_members_removed_cb),
+ list);
+
+ members = gossip_telepathy_group_get_members (group);
+ contact_list_group_members_added_cb (group, members, 0,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE,
+ NULL, list);
+ g_array_free (members, TRUE);
+ }
+
+ g_object_unref (new_chan);
+}
+
+static ContactListType
+contact_list_get_type (EmpathyContactList *list,
+ TpChan *list_chan)
+{
+ EmpathyContactListPriv *priv;
+ GArray *handles;
+ gchar **handle_name;
+ ContactListType list_type;
+ GError *error = NULL;
+
+ priv = GET_PRIV (list);
+
+ handles = g_array_new (FALSE, FALSE, sizeof (guint));
+ g_array_append_val (handles, list_chan->handle);
+
+ if (!tp_conn_inspect_handles (DBUS_G_PROXY (priv->tp_conn),
+ TP_HANDLE_TYPE_LIST,
+ handles,
+ &handle_name,
+ &error)) {
+ gossip_debug (DEBUG_DOMAIN,
+ "InspectHandle Error: %s",
+ error ? error->message : "No error given");
+ g_clear_error (&error);
+ g_array_free (handles, TRUE);
+ return CONTACT_LIST_TYPE_UNKNOWN;
+ }
+
+ if (strcmp (*handle_name, "subscribe") == 0) {
+ list_type = CONTACT_LIST_TYPE_SUBSCRIBE;
+ } else if (strcmp (*handle_name, "publish") == 0) {
+ list_type = CONTACT_LIST_TYPE_PUBLISH;
+ } else if (strcmp (*handle_name, "known") == 0) {
+ list_type = CONTACT_LIST_TYPE_KNOWN;
+ } else {
+ list_type = CONTACT_LIST_TYPE_UNKNOWN;
+ }
+
+ g_strfreev (handle_name);
+ g_array_free (handles, TRUE);
+
+ return list_type;
+}
+
+static void
+contact_list_contact_added_cb (GossipTelepathyGroup *group,
+ GArray *handles,
+ guint actor_handle,
+ guint reason,
+ const gchar *message,
+ EmpathyContactList *list)
+{
+ EmpathyContactListPriv *priv;
+ GList *added_list, *l;
+
+ priv = GET_PRIV (list);
+
+ added_list = empathy_contact_list_get_from_handles (list, handles);
+
+ for (l = added_list; l; l = l->next) {
+ GossipContact *contact;
+
+ contact = GOSSIP_CONTACT (l->data);
+ contact_list_block_contact (list, contact);
+ gossip_contact_set_subscription (contact, GOSSIP_SUBSCRIPTION_BOTH);
+ contact_list_unblock_contact (list, contact);
+
+ g_signal_emit (list, signals[CONTACT_ADDED], 0, contact);
+
+ g_object_unref (contact);
+ }
+
+ g_list_free (added_list);
+}
+
+static void
+contact_list_contact_removed_cb (GossipTelepathyGroup *group,
+ GArray *handles,
+ guint actor_handle,
+ guint reason,
+ const gchar *message,
+ EmpathyContactList *list)
+{
+ EmpathyContactListPriv *priv;
+ GList *removed_list, *l;
+
+ priv = GET_PRIV (list);
+
+ removed_list = empathy_contact_list_get_from_handles (list, handles);
+
+ for (l = removed_list; l; l = l->next) {
+ GossipContact *contact;
+ guint handle;
+
+ contact = GOSSIP_CONTACT (l->data);
+
+ handle = gossip_contact_get_handle (contact);
+ g_hash_table_remove (priv->contacts, GUINT_TO_POINTER (handle));
+
+ g_signal_emit (list, signals[CONTACT_REMOVED], 0, contact);
+
+ g_object_unref (contact);
+ }
+
+ g_list_free (removed_list);
+}
+
+static void
+contact_list_local_pending_cb (GossipTelepathyGroup *group,
+ GArray *handles,
+ guint actor_handle,
+ guint reason,
+ const gchar *message,
+ EmpathyContactList *list)
+{
+ EmpathyContactListPriv *priv;
+ GList *pending_list, *l;
+
+ priv = GET_PRIV (list);
+
+ pending_list = empathy_contact_list_get_from_handles (list, handles);
+
+ for (l = pending_list; l; l = l->next) {
+ GossipContact *contact;
+
+ contact = GOSSIP_CONTACT (l->data);
+
+ /* FIXME: Is that the correct way ? */
+ contact_list_block_contact (list, contact);
+ gossip_contact_set_subscription (contact, GOSSIP_SUBSCRIPTION_FROM);
+ contact_list_unblock_contact (list, contact);
+ g_signal_emit (list, signals[CONTACT_ADDED], 0, contact);
+
+ g_object_unref (contact);
+ }
+
+ g_list_free (pending_list);
+}
+
+static void
+contact_list_groups_updated_cb (GossipContact *contact,
+ GParamSpec *param,
+ EmpathyContactList *list)
+{
+ EmpathyContactListPriv *priv;
+ ContactListData data;
+ GList *groups, *l;
+
+ priv = GET_PRIV (list);
+
+ /* Make sure all groups are created */
+ groups = gossip_contact_get_groups (contact);
+ for (l = groups; l; l = l->next) {
+ contact_list_get_group (list, l->data);
+ }
+
+ data.handle = gossip_contact_get_handle (contact);
+ data.new_groups = groups;
+
+ g_hash_table_foreach (priv->groups,
+ (GHFunc) contact_list_update_groups_foreach,
+ &data);
+}
+
+static void
+contact_list_subscription_updated_cb (GossipContact *contact,
+ GParamSpec *param,
+ EmpathyContactList *list)
+{
+ EmpathyContactListPriv *priv;
+ GossipSubscription subscription;
+ guint handle;
+
+ priv = GET_PRIV (list);
+
+ subscription = gossip_contact_get_subscription (contact);
+ handle = gossip_contact_get_handle (contact);
+
+ /* FIXME: what to do here, I'm a bit lost... */
+ if (subscription) {
+ gossip_telepathy_group_add_member (priv->publish, handle, "");
+ } else {
+ gossip_telepathy_group_remove_member (priv->publish, handle, "");
+ }
+}
+
+static void
+contact_list_name_updated_cb (GossipContact *contact,
+ GParamSpec *param,
+ EmpathyContactList *list)
+{
+ EmpathyContactListPriv *priv;
+ GHashTable *new_alias;
+ const gchar *new_name;
+ guint handle;
+ GError *error = NULL;
+
+ priv = GET_PRIV (list);
+
+ handle = gossip_contact_get_handle (contact);
+ new_name = gossip_contact_get_name (contact);
+
+ gossip_debug (DEBUG_DOMAIN, "renaming handle %d to %s",
+ handle, new_name);
+
+ new_alias = g_hash_table_new_full (g_direct_hash,
+ g_direct_equal,
+ NULL,
+ g_free);
+
+ g_hash_table_insert (new_alias,
+ GUINT_TO_POINTER (handle),
+ g_strdup (new_name));
+
+ if (!tp_conn_iface_aliasing_set_aliases (priv->aliasing_iface,
+ new_alias,
+ &error)) {
+ gossip_debug (DEBUG_DOMAIN,
+ "Couldn't rename contact: %s",
+ error ? error->message : "No error given");
+ g_clear_error (&error);
+ }
+
+ g_hash_table_destroy (new_alias);
+}
+
+static void
+contact_list_update_groups_foreach (gchar *object_path,
+ GossipTelepathyGroup *group,
+ ContactListData *data)
+{
+ gboolean is_member;
+ gboolean found = FALSE;
+ const gchar *group_name;
+ GList *l;
+
+ is_member = gossip_telepathy_group_is_member (group, data->handle);
+ group_name = gossip_telepathy_group_get_name (group);
+
+ for (l = data->new_groups; l; l = l->next) {
+ if (strcmp (group_name, l->data) == 0) {
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (is_member && !found) {
+ /* We are no longer member of this group */
+ gossip_debug (DEBUG_DOMAIN, "Contact %d removed from group '%s'",
+ data->handle, group_name);
+ gossip_telepathy_group_remove_member (group, data->handle, "");
+ }
+
+ if (!is_member && found) {
+ /* We are now member of this group */
+ gossip_debug (DEBUG_DOMAIN, "Contact %d added to group '%s'",
+ data->handle, group_name);
+ gossip_telepathy_group_add_member (group, data->handle, "");
+ }
+}
+
+static GossipTelepathyGroup *
+contact_list_get_group (EmpathyContactList *list,
+ const gchar *name)
+{
+ EmpathyContactListPriv *priv;
+ GossipTelepathyGroup *group;
+ TpChan *group_channel;
+ GArray *handles;
+ guint group_handle;
+ char *group_object_path;
+ const char *names[2] = {name, NULL};
+ GError *error = NULL;
+
+ priv = GET_PRIV (list);
+
+ group = g_hash_table_find (priv->groups,
+ (GHRFunc) contact_list_find_group,
+ (gchar*) name);
+ if (group) {
+ return group;
+ }
+
+ gossip_debug (DEBUG_DOMAIN, "creating new group: %s", name);
+
+ if (!tp_conn_request_handles (DBUS_G_PROXY (priv->tp_conn),
+ TP_HANDLE_TYPE_GROUP,
+ names,
+ &handles,
+ &error)) {
+ gossip_debug (DEBUG_DOMAIN,
+ "Couldn't request the creation of a new handle for group: %s",
+ error ? error->message : "No error given");
+ g_clear_error (&error);
+ return NULL;
+ }
+ group_handle = g_array_index (handles, guint, 0);
+ g_array_free (handles, TRUE);
+
+ if (!tp_conn_request_channel (DBUS_G_PROXY (priv->tp_conn),
+ TP_IFACE_CHANNEL_TYPE_CONTACT_LIST,
+ TP_HANDLE_TYPE_GROUP,
+ group_handle,
+ FALSE,
+ &group_object_path,
+ &error)) {
+ gossip_debug (DEBUG_DOMAIN,
+ "Couldn't request the creation of a new group channel: %s",
+ error ? error->message : "No error given");
+ g_clear_error (&error);
+ return NULL;
+ }
+
+ group_channel = tp_chan_new (tp_get_bus (),
+ dbus_g_proxy_get_bus_name (DBUS_G_PROXY (priv->tp_conn)),
+ group_object_path,
+ TP_IFACE_CHANNEL_TYPE_CONTACT_LIST,
+ TP_HANDLE_TYPE_GROUP,
+ group_handle);
+
+ dbus_g_proxy_connect_signal (DBUS_G_PROXY (group_channel),
+ "Closed",
+ G_CALLBACK
+ (contact_list_group_channel_closed_cb),
+ list,
+ NULL);
+
+ group = gossip_telepathy_group_new (group_channel, priv->tp_conn);
+ g_hash_table_insert (priv->groups, group_object_path, group);
+ g_signal_connect (group, "members-added",
+ G_CALLBACK (contact_list_group_members_added_cb),
+ list);
+ g_signal_connect (group, "members-removed",
+ G_CALLBACK (contact_list_group_members_removed_cb),
+ list);
+
+ return group;
+}
+
+static gboolean
+contact_list_find_group (gchar *key,
+ GossipTelepathyGroup *group,
+ gchar *group_name)
+{
+ if (strcmp (group_name, gossip_telepathy_group_get_name (group)) == 0) {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+contact_list_get_groups_foreach (gchar *key,
+ GossipTelepathyGroup *group,
+ GList **groups)
+{
+ const gchar *name;
+
+ name = gossip_telepathy_group_get_name (group);
+ *groups = g_list_append (*groups, g_strdup (name));
+}
+
+static void
+contact_list_group_channel_closed_cb (TpChan *channel,
+ EmpathyContactList *list)
+{
+ EmpathyContactListPriv *priv;
+
+ priv = GET_PRIV (list);
+
+ g_hash_table_remove (priv->groups,
+ dbus_g_proxy_get_path (DBUS_G_PROXY (channel)));
+}
+
+static void
+contact_list_group_members_added_cb (GossipTelepathyGroup *group,
+ GArray *members,
+ guint actor_handle,
+ guint reason,
+ const gchar *message,
+ EmpathyContactList *list)
+{
+ EmpathyContactListPriv *priv;
+ GList *added_list, *l;
+ const gchar *group_name;
+
+ priv = GET_PRIV (list);
+
+ group_name = gossip_telepathy_group_get_name (group);
+ added_list = empathy_contact_list_get_from_handles (list, members);
+
+ for (l = added_list; l; l = l->next) {
+ GossipContact *contact;
+ GList *contact_groups;
+
+ contact = GOSSIP_CONTACT (l->data);
+ contact_groups = gossip_contact_get_groups (contact);
+
+ if (!g_list_find_custom (contact_groups,
+ group_name,
+ (GCompareFunc) strcmp)) {
+ gossip_debug (DEBUG_DOMAIN, "Contact %s added to group '%s'",
+ gossip_contact_get_name (contact),
+ group_name);
+ contact_groups = g_list_append (contact_groups,
+ g_strdup (group_name));
+ contact_list_block_contact (list, contact);
+ gossip_contact_set_groups (contact, contact_groups);
+ contact_list_unblock_contact (list, contact);
+ }
+
+ g_object_unref (contact);
+ }
+
+ g_list_free (added_list);
+}
+
+static void
+contact_list_group_members_removed_cb (GossipTelepathyGroup *group,
+ GArray *members,
+ guint actor_handle,
+ guint reason,
+ const gchar *message,
+ EmpathyContactList *list)
+{
+ EmpathyContactListPriv *priv;
+ GList *removed_list, *l;
+ const gchar *group_name;
+
+ priv = GET_PRIV (list);
+
+ group_name = gossip_telepathy_group_get_name (group);
+ removed_list = empathy_contact_list_get_from_handles (list, members);
+
+ for (l = removed_list; l; l = l->next) {
+ GossipContact *contact;
+ GList *contact_groups;
+ GList *to_remove;
+
+ /* FIXME: Does it leak ? */
+ contact = GOSSIP_CONTACT (l->data);
+ contact_groups = gossip_contact_get_groups (contact);
+ contact_groups = g_list_copy (contact_groups);
+
+ to_remove = g_list_find_custom (contact_groups,
+ group_name,
+ (GCompareFunc) strcmp);
+ if (to_remove) {
+ gossip_debug (DEBUG_DOMAIN, "Contact %d removed from group '%s'",
+ gossip_contact_get_handle (contact),
+ group_name);
+ contact_groups = g_list_remove_link (contact_groups,
+ to_remove);
+ contact_list_block_contact (list, contact);
+ gossip_contact_set_groups (contact, contact_groups);
+ contact_list_unblock_contact (list, contact);
+ }
+
+ g_list_free (contact_groups);
+
+ g_object_unref (contact);
+ }
+
+ g_list_free (removed_list);
+}
+
+static void
+contact_list_get_contacts_foreach (guint handle,
+ GossipContact *contact,
+ GList **contacts)
+{
+ *contacts = g_list_append (*contacts, g_object_ref (contact));
+}
+
+static void
+contact_list_get_info (EmpathyContactList *list,
+ GArray *handles)
+{
+ EmpathyContactListPriv *priv;
+ GError *error = NULL;
+
+ priv = GET_PRIV (list);
+
+ if (priv->presence_iface) {
+ /* FIXME: We should use GetPresence instead */
+ if (!tp_conn_iface_presence_request_presence (priv->presence_iface,
+ handles, &error)) {
+ gossip_debug (DEBUG_DOMAIN,
+ "Could not request presences: %s",
+ error ? error->message : "No error given");
+ g_clear_error (&error);
+ }
+ }
+
+ if (priv->aliasing_iface) {
+ ContactListAliasesRequestData *data;
+
+ data = g_slice_new (ContactListAliasesRequestData);
+ data->list = list;
+ data->handles = g_memdup (handles->data, handles->len * sizeof (guint));
+
+ tp_conn_iface_aliasing_request_aliases_async (priv->aliasing_iface,
+ handles,
+ (tp_conn_iface_aliasing_request_aliases_reply)
+ contact_list_request_aliases_cb,
+ data);
+ }
+
+ if (priv->avatars_iface) {
+ guint i;
+
+ for (i = 0; i < handles->len; i++) {
+ guint handle;
+
+ handle = g_array_index (handles, gint, i);
+ contact_list_request_avatar (list, handle);
+ }
+ }
+}
+
+static void
+contact_list_request_avatar (EmpathyContactList *list,
+ guint handle)
+{
+ EmpathyContactListPriv *priv;
+
+ priv = GET_PRIV (list);
+
+ /* We queue avatar requests to not send too many dbus async
+ * calls at once. If we don't we reach the dbus's limit of
+ * pending calls */
+ priv->avatar_requests_queue = g_list_append (priv->avatar_requests_queue,
+ GUINT_TO_POINTER (handle));
+ contact_list_start_avatar_requests (list);
+}
+
+static void
+contact_list_start_avatar_requests (EmpathyContactList *list)
+{
+ EmpathyContactListPriv *priv;
+ ContactListAvatarRequestData *data;
+
+ priv = GET_PRIV (list);
+
+ while (n_avatar_requests < MAX_AVATAR_REQUESTS &&
+ priv->avatar_requests_queue) {
+ data = g_slice_new (ContactListAvatarRequestData);
+ data->list = list;
+ data->handle = GPOINTER_TO_UINT (priv->avatar_requests_queue->data);
+
+ n_avatar_requests++;
+ priv->avatar_requests_queue = g_list_remove (priv->avatar_requests_queue,
+ priv->avatar_requests_queue->data);
+
+ tp_conn_iface_avatars_request_avatar_async (priv->avatars_iface,
+ data->handle,
+ (tp_conn_iface_avatars_request_avatar_reply)
+ contact_list_request_avatar_cb,
+ data);
+ }
+}
+
+static void
+contact_list_avatar_update_cb (DBusGProxy *proxy,
+ guint handle,
+ gchar *new_token,
+ EmpathyContactList *list)
+{
+ gossip_debug (DEBUG_DOMAIN, "Changing avatar for %d to %s",
+ handle, new_token);
+
+ contact_list_request_avatar (list, handle);
+}
+
+static void
+contact_list_request_avatar_cb (DBusGProxy *proxy,
+ GArray *avatar_data,
+ gchar *mime_type,
+ GError *error,
+ ContactListAvatarRequestData *data)
+{
+ GossipContact *contact;
+
+ contact = empathy_contact_list_get_from_handle (data->list, data->handle);
+
+ if (error) {
+ gossip_debug (DEBUG_DOMAIN, "Error requesting avatar for %s: %s",
+ gossip_contact_get_name (contact),
+ error ? error->message : "No error given");
+ } else {
+ GossipAvatar *avatar;
+
+ avatar = gossip_avatar_new (avatar_data->data,
+ avatar_data->len,
+ mime_type);
+ contact_list_block_contact (data->list, contact);
+ gossip_contact_set_avatar (contact, avatar);
+ contact_list_unblock_contact (data->list, contact);
+ gossip_avatar_unref (avatar);
+ }
+
+ n_avatar_requests--;
+ contact_list_start_avatar_requests (data->list);
+
+ g_slice_free (ContactListAvatarRequestData, data);
+}
+
+static void
+contact_list_aliases_update_cb (DBusGProxy *proxy,
+ GPtrArray *renamed_handlers,
+ EmpathyContactList *list)
+{
+ gint i;
+
+ for (i = 0; renamed_handlers->len > i; i++) {
+ guint handle;
+ const gchar *alias;
+ GValueArray *renamed_struct;
+ GossipContact *contact;
+
+ renamed_struct = g_ptr_array_index (renamed_handlers, i);
+ handle = g_value_get_uint(g_value_array_get_nth (renamed_struct, 0));
+ alias = g_value_get_string(g_value_array_get_nth (renamed_struct, 1));
+
+ if (alias && *alias == '\0') {
+ alias = NULL;
+ }
+
+ contact = empathy_contact_list_get_from_handle (list, handle);
+ contact_list_block_contact (list, contact);
+ gossip_contact_set_name (contact, alias);
+ contact_list_unblock_contact (list, contact);
+
+ gossip_debug (DEBUG_DOMAIN, "contact %d renamed to %s (update cb)",
+ handle, alias);
+ }
+}
+
+static void
+contact_list_request_aliases_cb (DBusGProxy *proxy,
+ gchar **contact_names,
+ GError *error,
+ ContactListAliasesRequestData *data)
+{
+ guint i = 0;
+ gchar **name;
+
+ for (name = contact_names; *name && !error; name++) {
+ GossipContact *contact;
+
+ contact = empathy_contact_list_get_from_handle (data->list,
+ data->handles[i]);
+ contact_list_block_contact (data->list, contact);
+ gossip_contact_set_name (contact, *name);
+ contact_list_unblock_contact (data->list, contact);
+
+ gossip_debug (DEBUG_DOMAIN, "contact %d renamed to %s (request cb)",
+ data->handles[i], *name);
+
+ i++;
+ }
+
+ g_free (data->handles);
+ g_slice_free (ContactListAliasesRequestData, data);
+}
+
+static void
+contact_list_presence_update_cb (DBusGProxy *proxy,
+ GHashTable *handle_table,
+ EmpathyContactList *list)
+{
+ g_hash_table_foreach (handle_table,
+ (GHFunc) contact_list_parse_presence_foreach,
+ list);
+}
+
+static void
+contact_list_parse_presence_foreach (guint handle,
+ GValueArray *presence_struct,
+ EmpathyContactList *list)
+{
+ GHashTable *presences_table;
+ GossipContact *contact;
+ GossipPresence *presence = NULL;
+
+ contact = empathy_contact_list_get_from_handle (list, handle);
+ presences_table = g_value_get_boxed (g_value_array_get_nth (presence_struct, 1));
+
+ g_hash_table_foreach (presences_table,
+ (GHFunc) contact_list_presences_table_foreach,
+ &presence);
+
+ gossip_debug (DEBUG_DOMAIN, "Presence changed for %s (%d)",
+ gossip_contact_get_name (contact),
+ handle);
+
+ contact_list_block_contact (list, contact);
+ if (presence) {
+ gossip_contact_add_presence (contact, presence);
+ g_object_unref (presence);
+ } else {
+ g_object_set (contact, "presences", NULL, NULL);
+ }
+ contact_list_unblock_contact (list, contact);
+}
+
+static void
+contact_list_presences_table_foreach (const gchar *state_str,
+ GHashTable *presences_table,
+ GossipPresence **presence)
+{
+ GossipPresenceState state;
+ const GValue *message;
+
+ if (*presence) {
+ g_object_unref (*presence);
+ *presence = NULL;
+ }
+
+ state = contact_list_presence_state_from_str (state_str);
+ if (state == GOSSIP_PRESENCE_STATE_UNAVAILABLE) {
+ return;
+ }
+
+ *presence = gossip_presence_new ();
+ gossip_presence_set_state (*presence, state);
+
+ message = g_hash_table_lookup (presences_table, "message");
+ if (message != NULL) {
+ gossip_presence_set_status (*presence,
+ g_value_get_string (message));
+ }
+
+ gossip_presence_set_resource (*presence, "");
+}
+
+static GossipPresenceState
+contact_list_presence_state_from_str (const gchar *str)
+{
+ if (strcmp (str, "available") == 0) {
+ return GOSSIP_PRESENCE_STATE_AVAILABLE;
+ } else if ((strcmp (str, "dnd") == 0) || (strcmp (str, "busy") == 0)) {
+ return GOSSIP_PRESENCE_STATE_BUSY;
+ } else if ((strcmp (str, "away") == 0) || (strcmp (str, "brb") == 0)) {
+ return GOSSIP_PRESENCE_STATE_AWAY;
+ } else if (strcmp (str, "xa") == 0) {
+ return GOSSIP_PRESENCE_STATE_EXT_AWAY;
+ } else if (strcmp (str, "hidden") == 0) {
+ return GOSSIP_PRESENCE_STATE_HIDDEN;
+ } else if (strcmp (str, "offline") == 0) {
+ return GOSSIP_PRESENCE_STATE_UNAVAILABLE;
+ } else if (strcmp (str, "chat") == 0) {
+ /* We don't support chat, so treat it like available. */
+ return GOSSIP_PRESENCE_STATE_AVAILABLE;
+ }
+
+ return GOSSIP_PRESENCE_STATE_AVAILABLE;
+}
+
diff --git a/libempathy/empathy-contact-list.h b/libempathy/empathy-contact-list.h
new file mode 100644
index 000000000..db8b36e39
--- /dev/null
+++ b/libempathy/empathy-contact-list.h
@@ -0,0 +1,76 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2007 Xavier Claessens <xclaesse@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __EMPATHY_CONTACT_LIST_H__
+#define __EMPATHY_CONTACT_LIST_H__
+
+#include <glib.h>
+#include <libmissioncontrol/mc-account.h>
+
+#include "gossip-contact.h"
+
+G_BEGIN_DECLS
+
+#define EMPATHY_TYPE_CONTACT_LIST (empathy_contact_list_get_type ())
+#define EMPATHY_CONTACT_LIST(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), EMPATHY_TYPE_CONTACT_LIST, EmpathyContactList))
+#define EMPATHY_CONTACT_LIST_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), EMPATHY_TYPE_CONTACT_LIST, EmpathyContactListClass))
+#define EMPATHY_IS_CONTACT_LIST(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), EMPATHY_TYPE_CONTACT_LIST))
+#define EMPATHY_IS_CONTACT_LIST_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), EMPATHY_TYPE_CONTACT_LIST))
+#define EMPATHY_CONTACT_LIST_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), EMPATHY_TYPE_CONTACT_LIST, EmpathyContactListClass))
+
+typedef struct _EmpathyContactList EmpathyContactList;
+typedef struct _EmpathyContactListClass EmpathyContactListClass;
+typedef struct _EmpathyContactListPriv EmpathyContactListPriv;
+
+struct _EmpathyContactList {
+ GObject parent;
+};
+
+struct _EmpathyContactListClass {
+ GObjectClass parent_class;
+};
+
+GType empathy_contact_list_get_type (void) G_GNUC_CONST;
+EmpathyContactList * empathy_contact_list_new (McAccount *account);
+void empathy_contact_list_setup (EmpathyContactList *list);
+McAccount * empathy_contact_list_get_account (EmpathyContactList *list);
+GossipContact * empathy_contact_list_get_own (EmpathyContactList *list);
+GossipContact * empathy_contact_list_find (EmpathyContactList *list,
+ const gchar *id);
+void empathy_contact_list_add (EmpathyContactList *list,
+ guint handle,
+ const gchar *message);
+void empathy_contact_list_remove (EmpathyContactList *list,
+ guint handle);
+GossipContact * empathy_contact_list_get_from_id (EmpathyContactList *list,
+ const gchar *id);
+GossipContact * empathy_contact_list_get_from_handle (EmpathyContactList *list,
+ guint handle);
+GList * empathy_contact_list_get_from_handles (EmpathyContactList *list,
+ GArray *handles);
+void empathy_contact_list_rename_group (EmpathyContactList *list,
+ const gchar *old_group,
+ const gchar *new_group);
+GList * empathy_contact_list_get_groups (EmpathyContactList *list);
+GList * empathy_contact_list_get_contacts (EmpathyContactList *list);
+
+G_END_DECLS
+
+#endif /* __EMPATHY_CONTACT_LIST_H__ */
diff --git a/libempathy/empathy-contact-manager.c b/libempathy/empathy-contact-manager.c
new file mode 100644
index 000000000..15fb82eed
--- /dev/null
+++ b/libempathy/empathy-contact-manager.c
@@ -0,0 +1,550 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2007 Xavier Claessens <xclaesse@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+
+#include <string.h>
+
+#include <libtelepathy/tp-constants.h>
+
+#include "empathy-contact-manager.h"
+#include "empathy-session.h"
+#include "gossip-utils.h"
+#include "gossip-debug.h"
+
+#define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \
+ EMPATHY_TYPE_CONTACT_MANAGER, EmpathyContactManagerPriv))
+
+#define DEBUG_DOMAIN "ContactManager"
+
+struct _EmpathyContactManagerPriv {
+ GHashTable *lists;
+ gboolean setup;
+};
+
+typedef struct {
+ const gchar *old_group;
+ const gchar *new_group;
+} ContactManagerRenameGroupData;
+
+typedef struct {
+ GossipContact *contact;
+ const gchar *id;
+} ContactManagerFindData;
+
+static void empathy_contact_manager_class_init (EmpathyContactManagerClass *klass);
+static void empathy_contact_manager_init (EmpathyContactManager *manager);
+static void contact_manager_finalize (GObject *object);
+static void contact_manager_setup_foreach (McAccount *account,
+ EmpathyContactList *list,
+ EmpathyContactManager *manager);
+static gboolean contact_manager_find_foreach (McAccount *account,
+ EmpathyContactList *list,
+ ContactManagerFindData *data);
+static void contact_manager_add_account (EmpathyContactManager *manager,
+ McAccount *account);
+static void contact_manager_added_cb (EmpathyContactList *list,
+ GossipContact *contact,
+ EmpathyContactManager *manager);
+static void contact_manager_removed_cb (EmpathyContactList *list,
+ GossipContact *contact,
+ EmpathyContactManager *manager);
+static void contact_manager_destroy_cb (EmpathyContactList *list,
+ EmpathyContactManager *manager);
+static void contact_manager_rename_group_foreach (McAccount *account,
+ EmpathyContactList *list,
+ ContactManagerRenameGroupData *data);
+static void contact_manager_get_groups_foreach (McAccount *account,
+ EmpathyContactList *list,
+ GList **all_groups);
+static void contact_manager_get_contacts_foreach (McAccount *account,
+ EmpathyContactList *list,
+ GList **contacts);
+static void contact_manager_status_changed_cb (MissionControl *mc,
+ TelepathyConnectionStatus status,
+ McPresence presence,
+ TelepathyConnectionStatusReason reason,
+ const gchar *unique_name,
+ EmpathyContactManager *manager);
+
+enum {
+ CONTACT_ADDED,
+ CONTACT_REMOVED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE (EmpathyContactManager, empathy_contact_manager, G_TYPE_OBJECT);
+
+static void
+empathy_contact_manager_class_init (EmpathyContactManagerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = contact_manager_finalize;
+
+ signals[CONTACT_ADDED] =
+ g_signal_new ("contact-added",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1, GOSSIP_TYPE_CONTACT);
+
+ signals[CONTACT_REMOVED] =
+ g_signal_new ("contact-removed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1, GOSSIP_TYPE_CONTACT);
+
+ g_type_class_add_private (object_class, sizeof (EmpathyContactManagerPriv));
+}
+
+static void
+empathy_contact_manager_init (EmpathyContactManager *manager)
+{
+ EmpathyContactManagerPriv *priv;
+ MissionControl *mc;
+ GSList *accounts, *l;
+
+ priv = GET_PRIV (manager);
+
+ priv->lists = g_hash_table_new_full (gossip_account_hash,
+ gossip_account_equal,
+ (GDestroyNotify) g_object_unref,
+ (GDestroyNotify) g_object_unref);
+
+ mc = empathy_session_get_mission_control ();
+
+ dbus_g_proxy_connect_signal (DBUS_G_PROXY (mc), "AccountStatusChanged",
+ G_CALLBACK (contact_manager_status_changed_cb),
+ manager, NULL);
+
+ /* Get ContactList for existing connections */
+ accounts = mission_control_get_online_connections (mc, NULL);
+ for (l = accounts; l; l = l->next) {
+ McAccount *account;
+
+ account = l->data;
+ contact_manager_add_account (manager, account);
+
+ g_object_unref (account);
+ }
+ g_slist_free (accounts);
+}
+
+static void
+contact_manager_finalize (GObject *object)
+{
+ EmpathyContactManagerPriv *priv;
+
+ priv = GET_PRIV (object);
+
+ g_hash_table_destroy (priv->lists);
+}
+
+EmpathyContactManager *
+empathy_contact_manager_new (void)
+{
+ return g_object_new (EMPATHY_TYPE_CONTACT_MANAGER, NULL);
+}
+
+void
+empathy_contact_manager_setup (EmpathyContactManager *manager)
+{
+ EmpathyContactManagerPriv *priv;
+
+ g_return_if_fail (EMPATHY_IS_CONTACT_MANAGER (manager));
+
+ priv = GET_PRIV (manager);
+
+ if (priv->setup) {
+ /* Already done */
+ return;
+ }
+
+ g_hash_table_foreach (priv->lists,
+ (GHFunc) contact_manager_setup_foreach,
+ manager);
+
+ priv->setup = TRUE;
+}
+
+EmpathyContactList *
+empathy_contact_manager_get_list (EmpathyContactManager *manager,
+ McAccount *account)
+{
+ EmpathyContactManagerPriv *priv;
+
+ g_return_val_if_fail (EMPATHY_IS_CONTACT_MANAGER (manager), NULL);
+ g_return_val_if_fail (MC_IS_ACCOUNT (account), NULL);
+
+ priv = GET_PRIV (manager);
+
+ return g_hash_table_lookup (priv->lists, account);
+}
+
+GossipContact *
+empathy_contact_manager_get_own (EmpathyContactManager *manager,
+ McAccount *account)
+{
+ EmpathyContactManagerPriv *priv;
+ EmpathyContactList *list;
+
+ g_return_val_if_fail (EMPATHY_IS_CONTACT_MANAGER (manager), NULL);
+ g_return_val_if_fail (MC_IS_ACCOUNT (account), NULL);
+
+ priv = GET_PRIV (manager);
+
+ list = g_hash_table_lookup (priv->lists, account);
+
+ if (!list) {
+ return NULL;
+ }
+
+ return empathy_contact_list_get_own (list);
+}
+
+GossipContact *
+empathy_contact_manager_create (EmpathyContactManager *manager,
+ McAccount *account,
+ const gchar *id)
+{
+ EmpathyContactManagerPriv *priv;
+ EmpathyContactList *list;
+
+ g_return_val_if_fail (EMPATHY_IS_CONTACT_MANAGER (manager), NULL);
+ g_return_val_if_fail (MC_IS_ACCOUNT (account), NULL);
+ g_return_val_if_fail (id != NULL, NULL);
+
+ priv = GET_PRIV (manager);
+
+ list = g_hash_table_lookup (priv->lists, account);
+
+ if (!list) {
+ return NULL;
+ }
+
+ return empathy_contact_list_get_from_id (list, id);
+}
+
+GossipContact *
+empathy_contact_manager_find (EmpathyContactManager *manager,
+ const gchar *id)
+{
+ EmpathyContactManagerPriv *priv;
+ ContactManagerFindData data;
+
+ g_return_val_if_fail (EMPATHY_IS_CONTACT_MANAGER (manager), NULL);
+ g_return_val_if_fail (id != NULL, NULL);
+
+ priv = GET_PRIV (manager);
+
+ data.contact = NULL;
+ data.id = id;
+
+ g_hash_table_find (priv->lists,
+ (GHRFunc) contact_manager_find_foreach,
+ &data);
+
+ return data.contact;
+}
+
+void
+empathy_contact_manager_add (EmpathyContactManager *manager,
+ GossipContact *contact,
+ const gchar *message)
+{
+ EmpathyContactManagerPriv *priv;
+ EmpathyContactList *list;
+ McAccount *account;
+ guint handle;
+
+ g_return_if_fail (EMPATHY_IS_CONTACT_MANAGER (manager));
+ g_return_if_fail (GOSSIP_IS_CONTACT (contact));
+
+ priv = GET_PRIV (manager);
+
+ account = gossip_contact_get_account (contact);
+ handle = gossip_contact_get_handle (contact);
+ list = g_hash_table_lookup (priv->lists, account);
+
+ if (list) {
+ empathy_contact_list_add (list, handle, message);
+ }
+}
+
+void
+empathy_contact_manager_remove (EmpathyContactManager *manager,
+ GossipContact *contact)
+{
+ EmpathyContactManagerPriv *priv;
+ EmpathyContactList *list;
+ McAccount *account;
+ guint handle;
+
+ g_return_if_fail (EMPATHY_IS_CONTACT_MANAGER (manager));
+ g_return_if_fail (GOSSIP_IS_CONTACT (contact));
+
+ priv = GET_PRIV (manager);
+
+ account = gossip_contact_get_account (contact);
+ handle = gossip_contact_get_handle (contact);
+ list = g_hash_table_lookup (priv->lists, account);
+
+ if (list) {
+ empathy_contact_list_remove (list, handle);
+ }
+}
+
+void
+empathy_contact_manager_rename_group (EmpathyContactManager *manager,
+ const gchar *old_group,
+ const gchar *new_group)
+{
+ EmpathyContactManagerPriv *priv;
+ ContactManagerRenameGroupData data;
+
+ g_return_if_fail (EMPATHY_IS_CONTACT_MANAGER (manager));
+ g_return_if_fail (old_group != NULL);
+ g_return_if_fail (new_group != NULL);
+
+ priv = GET_PRIV (manager);
+
+ data.old_group = old_group;
+ data.new_group = new_group;
+
+ g_hash_table_foreach (priv->lists,
+ (GHFunc) contact_manager_rename_group_foreach,
+ &data);
+}
+
+GList *
+empathy_contact_manager_get_groups (EmpathyContactManager *manager)
+{
+ EmpathyContactManagerPriv *priv;
+ GList *groups = NULL;
+
+ g_return_val_if_fail (EMPATHY_IS_CONTACT_MANAGER (manager), NULL);
+
+ priv = GET_PRIV (manager);
+
+ g_hash_table_foreach (priv->lists,
+ (GHFunc) contact_manager_get_groups_foreach,
+ &groups);
+
+ return groups;
+}
+
+GList *
+empathy_contact_manager_get_contacts (EmpathyContactManager *manager)
+{
+ EmpathyContactManagerPriv *priv;
+ GList *contacts = NULL;
+
+ g_return_val_if_fail (EMPATHY_IS_CONTACT_MANAGER (manager), NULL);
+
+ priv = GET_PRIV (manager);
+
+ g_hash_table_foreach (priv->lists,
+ (GHFunc) contact_manager_get_contacts_foreach,
+ &contacts);
+
+ return contacts;
+}
+
+static void
+contact_manager_setup_foreach (McAccount *account,
+ EmpathyContactList *list,
+ EmpathyContactManager *manager)
+{
+ empathy_contact_list_setup (list);
+}
+
+static gboolean
+contact_manager_find_foreach (McAccount *account,
+ EmpathyContactList *list,
+ ContactManagerFindData *data)
+{
+ data->contact = empathy_contact_list_find (list, data->id);
+
+ if (data->contact) {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+contact_manager_add_account (EmpathyContactManager *manager,
+ McAccount *account)
+{
+ EmpathyContactManagerPriv *priv;
+ EmpathyContactList *list;
+
+ priv = GET_PRIV (manager);
+
+ if (g_hash_table_lookup (priv->lists, account)) {
+ return;
+ }
+
+ gossip_debug (DEBUG_DOMAIN, "Adding new account: %s",
+ mc_account_get_display_name (account));
+
+ list = empathy_contact_list_new (account);
+ if (!list) {
+ return;
+ }
+
+ g_hash_table_insert (priv->lists, g_object_ref (account), list);
+
+ /* Connect signals */
+ g_signal_connect (list, "contact-added",
+ G_CALLBACK (contact_manager_added_cb),
+ manager);
+ g_signal_connect (list, "contact-removed",
+ G_CALLBACK (contact_manager_removed_cb),
+ manager);
+ g_signal_connect (list, "destroy",
+ G_CALLBACK (contact_manager_destroy_cb),
+ manager);
+
+ if (priv->setup) {
+ empathy_contact_list_setup (list);
+ }
+}
+
+static void
+contact_manager_added_cb (EmpathyContactList *list,
+ GossipContact *contact,
+ EmpathyContactManager *manager)
+{
+ g_signal_emit (manager, signals[CONTACT_ADDED], 0, contact);
+}
+
+static void
+contact_manager_removed_cb (EmpathyContactList *list,
+ GossipContact *contact,
+ EmpathyContactManager *manager)
+{
+ g_signal_emit (manager, signals[CONTACT_REMOVED], 0, contact);
+}
+
+static void
+contact_manager_destroy_cb (EmpathyContactList *list,
+ EmpathyContactManager *manager)
+{
+ EmpathyContactManagerPriv *priv;
+ McAccount *account;
+
+ priv = GET_PRIV (manager);
+
+ account = empathy_contact_list_get_account (list);
+
+ gossip_debug (DEBUG_DOMAIN, "Removing account: %s",
+ mc_account_get_display_name (account));
+
+ /* Disconnect signals from the list */
+ g_signal_handlers_disconnect_by_func (list,
+ contact_manager_added_cb,
+ manager);
+ g_signal_handlers_disconnect_by_func (list,
+ contact_manager_removed_cb,
+ manager);
+ g_signal_handlers_disconnect_by_func (list,
+ contact_manager_destroy_cb,
+ manager);
+
+ g_hash_table_remove (priv->lists, account);
+}
+
+static void
+contact_manager_rename_group_foreach (McAccount *account,
+ EmpathyContactList *list,
+ ContactManagerRenameGroupData *data)
+{
+ empathy_contact_list_rename_group (list,
+ data->old_group,
+ data->new_group);
+}
+
+static void
+contact_manager_get_groups_foreach (McAccount *account,
+ EmpathyContactList *list,
+ GList **all_groups)
+{
+ GList *groups, *l;
+
+ groups = empathy_contact_list_get_groups (list);
+ for (l = groups; l; l = l->next) {
+ if (!g_list_find_custom (*all_groups,
+ l->data,
+ (GCompareFunc) strcmp)) {
+ *all_groups = g_list_append (*all_groups,
+ g_strdup (l->data));
+ }
+ g_free (l->data);
+ }
+
+ g_list_free (groups);
+}
+
+static void
+contact_manager_get_contacts_foreach (McAccount *account,
+ EmpathyContactList *list,
+ GList **contacts)
+{
+ GList *l;
+
+ l = empathy_contact_list_get_contacts (list);
+ *contacts = g_list_concat (*contacts, l);
+}
+
+static void
+contact_manager_status_changed_cb (MissionControl *mc,
+ TelepathyConnectionStatus status,
+ McPresence presence,
+ TelepathyConnectionStatusReason reason,
+ const gchar *unique_name,
+ EmpathyContactManager *manager)
+{
+ EmpathyContactManagerPriv *priv;
+ McAccount *account;
+
+ priv = GET_PRIV (manager);
+
+ if (status != TP_CONN_STATUS_CONNECTED) {
+ /* We only care about newly connected accounts */
+ return;
+ }
+
+ account = mc_account_lookup (unique_name);
+ contact_manager_add_account (manager, account);
+
+ g_object_unref (account);
+}
+
diff --git a/libempathy/empathy-contact-manager.h b/libempathy/empathy-contact-manager.h
new file mode 100644
index 000000000..ca6cb3fe5
--- /dev/null
+++ b/libempathy/empathy-contact-manager.h
@@ -0,0 +1,77 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2007 Xavier Claessens <xclaesse@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __EMPATHY_CONTACT_MANAGER_H__
+#define __EMPATHY_CONTACT_MANAGER_H__
+
+#include <glib.h>
+
+#include <libmissioncontrol/mc-account.h>
+
+#include "gossip-contact.h"
+#include "empathy-contact-list.h"
+
+G_BEGIN_DECLS
+
+#define EMPATHY_TYPE_CONTACT_MANAGER (empathy_contact_manager_get_type ())
+#define EMPATHY_CONTACT_MANAGER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), EMPATHY_TYPE_CONTACT_MANAGER, EmpathyContactManager))
+#define EMPATHY_CONTACT_MANAGER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), EMPATHY_TYPE_CONTACT_MANAGER, EmpathyContactManagerClass))
+#define EMPATHY_IS_CONTACT_MANAGER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), EMPATHY_TYPE_CONTACT_MANAGER))
+#define EMPATHY_IS_CONTACT_MANAGER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), EMPATHY_TYPE_CONTACT_MANAGER))
+#define EMPATHY_CONTACT_MANAGER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), EMPATHY_TYPE_CONTACT_MANAGER, EmpathyContactManagerClass))
+
+typedef struct _EmpathyContactManager EmpathyContactManager;
+typedef struct _EmpathyContactManagerClass EmpathyContactManagerClass;
+typedef struct _EmpathyContactManagerPriv EmpathyContactManagerPriv;
+
+struct _EmpathyContactManager {
+ GObject parent;
+};
+
+struct _EmpathyContactManagerClass {
+ GObjectClass parent_class;
+};
+
+GType empathy_contact_manager_get_type (void) G_GNUC_CONST;
+EmpathyContactManager *empathy_contact_manager_new (void);
+void empathy_contact_manager_setup (EmpathyContactManager *manager);
+EmpathyContactList * empathy_contact_manager_get_list (EmpathyContactManager *manager,
+ McAccount *account);
+GossipContact * empathy_contact_manager_get_own (EmpathyContactManager *manager,
+ McAccount *account);
+GossipContact * empathy_contact_manager_find (EmpathyContactManager *manager,
+ const gchar *id);
+GossipContact * empathy_contact_manager_create (EmpathyContactManager *manager,
+ McAccount *account,
+ const gchar *id);
+void empathy_contact_manager_add (EmpathyContactManager *manager,
+ GossipContact *contact,
+ const gchar *message);
+void empathy_contact_manager_remove (EmpathyContactManager *manager,
+ GossipContact *contact);
+void empathy_contact_manager_rename_group (EmpathyContactManager *manager,
+ const gchar *old_group,
+ const gchar *new_group);
+GList * empathy_contact_manager_get_groups (EmpathyContactManager *manager);
+GList * empathy_contact_manager_get_contacts (EmpathyContactManager *manager);
+
+G_END_DECLS
+
+#endif /* __EMPATHY_CONTACT_MANAGER_H__ */
diff --git a/libempathy/empathy-marshal-main.c b/libempathy/empathy-marshal-main.c
new file mode 100644
index 000000000..f3ab95f54
--- /dev/null
+++ b/libempathy/empathy-marshal-main.c
@@ -0,0 +1,2 @@
+#include "empathy-marshal.h"
+#include "empathy-marshal.c"
diff --git a/libempathy/empathy-marshal.list b/libempathy/empathy-marshal.list
new file mode 100644
index 000000000..6035b9451
--- /dev/null
+++ b/libempathy/empathy-marshal.list
@@ -0,0 +1,3 @@
+VOID:POINTER,UINT,UINT,STRING
+VOID:OBJECT,UINT
+VOID:OBJECT,OBJECT
diff --git a/libempathy/empathy-session.c b/libempathy/empathy-session.c
new file mode 100644
index 000000000..fefda7694
--- /dev/null
+++ b/libempathy/empathy-session.c
@@ -0,0 +1,161 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2007 Xavier Claessens <xclaesse@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+
+#include <glib.h>
+
+#include <libtelepathy/tp-helpers.h>
+
+#include <libmissioncontrol/mc-account-monitor.h>
+
+#include "empathy-session.h"
+#include "gossip-debug.h"
+
+#define DEBUG_DOMAIN "Session"
+
+static void session_start_mission_control (void);
+static void session_error_cb (MissionControl *mc,
+ GError *error,
+ gpointer data);
+static void session_account_enabled_cb (McAccountMonitor *monitor,
+ gchar *unique_name,
+ gpointer user_data);
+static void session_service_ended_cb (MissionControl *mc,
+ gpointer user_data);
+
+static MissionControl *mission_control = NULL;
+static EmpathyContactManager *contact_manager = NULL;
+
+void
+empathy_session_connect (void)
+{
+ MissionControl *mc;
+ McAccountMonitor *monitor;
+ static gboolean started = FALSE;
+
+ if (started) {
+ return;
+ }
+
+ mc = empathy_session_get_mission_control ();
+ monitor = mc_account_monitor_new ();
+
+ g_signal_connect (monitor, "account-enabled",
+ G_CALLBACK (session_account_enabled_cb),
+ NULL);
+ g_signal_connect (mc, "ServiceEnded",
+ G_CALLBACK (session_service_ended_cb),
+ NULL);
+
+ g_object_unref (monitor);
+ session_start_mission_control ();
+
+ started = TRUE;
+}
+
+void
+empathy_session_finalize (void)
+{
+ if (mission_control) {
+ g_object_unref (mission_control);
+ mission_control = NULL;
+ }
+
+ if (contact_manager) {
+ g_object_unref (contact_manager);
+ contact_manager = NULL;
+ }
+}
+
+MissionControl *
+empathy_session_get_mission_control (void)
+{
+ if (!mission_control) {
+ mission_control = mission_control_new (tp_get_bus ());
+ }
+
+ return mission_control;
+}
+
+EmpathyContactManager *
+empathy_session_get_contact_manager (void)
+{
+ if (!contact_manager) {
+ contact_manager = empathy_contact_manager_new ();
+ }
+
+ return contact_manager;
+}
+
+static void
+session_start_mission_control (void)
+{
+ MissionControl *mc;
+ McPresence presence;
+
+ mc = empathy_session_get_mission_control ();
+ presence = mission_control_get_presence_actual (mc, NULL);
+
+ if (presence != MC_PRESENCE_UNSET &&
+ presence != MC_PRESENCE_OFFLINE) {
+ /* MC is already running and online, nothing to do */
+ return;
+ }
+
+ gossip_debug (DEBUG_DOMAIN, "Starting Mission Control...");
+
+ /* FIXME: Save/Restore status message */
+ mission_control_set_presence (mc, MC_PRESENCE_AVAILABLE,
+ NULL,
+ (McCallback) session_error_cb,
+ NULL);
+
+ mission_control_connect_all_with_default_presence (mc,
+ (McCallback) session_error_cb,
+ NULL);
+}
+
+static void
+session_error_cb (MissionControl *mc,
+ GError *error,
+ gpointer data)
+{
+ if (error) {
+ gossip_debug (DEBUG_DOMAIN, "Error: %s", error->message);
+ }
+}
+
+static void
+session_account_enabled_cb (McAccountMonitor *monitor,
+ gchar *unique_name,
+ gpointer user_data)
+{
+ gossip_debug (DEBUG_DOMAIN, "Account enabled: %s", unique_name);
+ session_start_mission_control ();
+}
+
+static void
+session_service_ended_cb (MissionControl *mc,
+ gpointer user_data)
+{
+ gossip_debug (DEBUG_DOMAIN, "Mission Control stopped");
+}
+
diff --git a/libempathy/empathy-session.h b/libempathy/empathy-session.h
new file mode 100644
index 000000000..0d3a2dce1
--- /dev/null
+++ b/libempathy/empathy-session.h
@@ -0,0 +1,38 @@
+#include <libtelepathy/tp-helpers.h>/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2007 Xavier Claessens <xclaesse@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __EMPATHY_SESSION_H__
+#define __EMPATHY_SESSION_H__
+
+#include <glib.h>
+
+#include <libmissioncontrol/mission-control.h>
+#include "empathy-contact-manager.h"
+
+G_BEGIN_DECLS
+
+void empathy_session_connect (void);
+void empathy_session_finalize (void);
+MissionControl * empathy_session_get_mission_control (void);
+EmpathyContactManager *empathy_session_get_contact_manager (void);
+
+G_END_DECLS
+
+#endif /* __EMPATHY_MISSION_CONTROL_H__ */
diff --git a/libempathy/empathy-tp-chat.c b/libempathy/empathy-tp-chat.c
new file mode 100644
index 000000000..ad00711d9
--- /dev/null
+++ b/libempathy/empathy-tp-chat.c
@@ -0,0 +1,474 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2007 Xavier Claessens <xclaesse@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+
+#include <libtelepathy/tp-helpers.h>
+#include <libtelepathy/tp-chan-type-text-gen.h>
+#include <libtelepathy/tp-chan-iface-chat-state-gen.h>
+
+#include "empathy-tp-chat.h"
+#include "empathy-contact-manager.h"
+#include "empathy-contact-list.h"
+#include "empathy-session.h"
+#include "empathy-marshal.h"
+#include "gossip-debug.h"
+#include "gossip-time.h"
+
+#define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \
+ EMPATHY_TYPE_TP_CHAT, EmpathyTpChatPriv))
+
+#define DEBUG_DOMAIN "TpChat"
+
+struct _EmpathyTpChatPriv {
+ EmpathyContactList *list;
+ TpChan *tp_chan;
+ DBusGProxy *text_iface;
+ DBusGProxy *chat_state_iface;
+};
+
+static void empathy_tp_chat_class_init (EmpathyTpChatClass *klass);
+static void empathy_tp_chat_init (EmpathyTpChat *chat);
+static void tp_chat_finalize (GObject *object);
+static void tp_chat_destroy_cb (TpChan *text_chan,
+ EmpathyTpChat *chat);
+static void tp_chat_received_cb (DBusGProxy *text_iface,
+ guint message_id,
+ guint timestamp,
+ guint from_handle,
+ guint message_type,
+ guint message_flags,
+ gchar *message_body,
+ EmpathyTpChat *chat);
+static void tp_chat_sent_cb (DBusGProxy *text_iface,
+ guint timestamp,
+ guint message_type,
+ gchar *message_body,
+ EmpathyTpChat *chat);
+static void tp_chat_state_changed_cb (DBusGProxy *chat_state_iface,
+ guint handle,
+ EmpathyTpChatState state,
+ EmpathyTpChat *chat);
+static void tp_chat_emit_message (EmpathyTpChat *chat,
+ guint type,
+ guint timestamp,
+ guint from_handle,
+ const gchar *message_body);
+
+enum {
+ MESSAGE_RECEIVED,
+ CHAT_STATE_CHANGED,
+ DESTROY,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE (EmpathyTpChat, empathy_tp_chat, G_TYPE_OBJECT);
+
+static void
+empathy_tp_chat_class_init (EmpathyTpChatClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = tp_chat_finalize;
+
+ signals[MESSAGE_RECEIVED] =
+ g_signal_new ("message-received",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1, GOSSIP_TYPE_MESSAGE);
+
+ signals[CHAT_STATE_CHANGED] =
+ g_signal_new ("chat-state-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ empathy_marshal_VOID__OBJECT_UINT,
+ G_TYPE_NONE,
+ 2, GOSSIP_TYPE_CONTACT, G_TYPE_UINT);
+
+ signals[DESTROY] =
+ g_signal_new ("destroy",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ g_type_class_add_private (object_class, sizeof (EmpathyTpChatPriv));
+}
+
+static void
+empathy_tp_chat_init (EmpathyTpChat *chat)
+{
+}
+
+
+static void
+tp_chat_finalize (GObject *object)
+{
+ EmpathyTpChatPriv *priv;
+ EmpathyTpChat *chat;
+ GError *error = NULL;
+
+ chat = EMPATHY_TP_CHAT (object);
+ priv = GET_PRIV (chat);
+
+ if (priv->tp_chan) {
+ gossip_debug (DEBUG_DOMAIN, "Closing channel...");
+
+ g_signal_handlers_disconnect_by_func (priv->tp_chan,
+ tp_chat_destroy_cb,
+ object);
+
+ if (!tp_chan_close (DBUS_G_PROXY (priv->tp_chan), &error)) {
+ gossip_debug (DEBUG_DOMAIN,
+ "Error closing text channel: %s",
+ error ? error->message : "No error given");
+ g_clear_error (&error);
+ }
+ g_object_unref (priv->tp_chan);
+ }
+
+ if (priv->list) {
+ g_object_unref (priv->list);
+ }
+
+ G_OBJECT_CLASS (empathy_tp_chat_parent_class)->finalize (object);
+}
+
+EmpathyTpChat *
+empathy_tp_chat_new (McAccount *account,
+ TpChan *tp_chan)
+{
+ EmpathyTpChatPriv *priv;
+ EmpathyTpChat *chat;
+ EmpathyContactManager *manager;
+
+ g_return_val_if_fail (MC_IS_ACCOUNT (account), NULL);
+ g_return_val_if_fail (TELEPATHY_IS_CHAN (tp_chan), NULL);
+
+ chat = g_object_new (EMPATHY_TYPE_TP_CHAT, NULL);
+ priv = GET_PRIV (chat);
+
+ manager = empathy_session_get_contact_manager ();
+ priv->list = empathy_contact_manager_get_list (manager, account);
+ priv->tp_chan = g_object_ref (tp_chan);
+ g_object_ref (priv->list);
+
+ priv->text_iface = tp_chan_get_interface (tp_chan,
+ TELEPATHY_CHAN_IFACE_TEXT_QUARK);
+ priv->chat_state_iface = tp_chan_get_interface (tp_chan,
+ TELEPATHY_CHAN_IFACE_CHAT_STATE_QUARK);
+
+ g_signal_connect (priv->tp_chan, "destroy",
+ G_CALLBACK (tp_chat_destroy_cb),
+ chat);
+ dbus_g_proxy_connect_signal (priv->text_iface, "Received",
+ G_CALLBACK (tp_chat_received_cb),
+ chat, NULL);
+ dbus_g_proxy_connect_signal (priv->text_iface, "Sent",
+ G_CALLBACK (tp_chat_sent_cb),
+ chat, NULL);
+
+ if (priv->chat_state_iface != NULL) {
+ dbus_g_proxy_connect_signal (priv->chat_state_iface,
+ "ChatStateChanged",
+ G_CALLBACK (tp_chat_state_changed_cb),
+ chat, NULL);
+ }
+
+ return chat;
+}
+
+EmpathyTpChat *
+empathy_tp_chat_new_with_contact (GossipContact *contact)
+{
+ EmpathyTpChat *chat;
+ MissionControl *mc;
+ McAccount *account;
+ TpConn *tp_conn;
+ TpChan *text_chan;
+ const gchar *bus_name;
+ guint handle;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), NULL);
+
+ mc = empathy_session_get_mission_control ();
+ account = gossip_contact_get_account (contact);
+
+ if (mission_control_get_connection_status (mc, account, NULL) != 0) {
+ /* The account is not connected, nothing to do. */
+ return NULL;
+ }
+
+ tp_conn = mission_control_get_connection (mc, account, NULL);
+ g_return_val_if_fail (tp_conn != NULL, NULL);
+ bus_name = dbus_g_proxy_get_bus_name (DBUS_G_PROXY (tp_conn));
+ handle = gossip_contact_get_handle (contact);
+
+ text_chan = tp_conn_new_channel (tp_get_bus (),
+ tp_conn,
+ bus_name,
+ TP_IFACE_CHANNEL_TYPE_TEXT,
+ TP_HANDLE_TYPE_CONTACT,
+ handle,
+ TRUE);
+
+ chat = empathy_tp_chat_new (account, text_chan);
+
+ g_object_unref (tp_conn);
+ g_object_unref (text_chan);
+
+ return chat;
+}
+
+void
+empathy_tp_chat_request_pending (EmpathyTpChat *chat)
+{
+ EmpathyTpChatPriv *priv;
+ GPtrArray *messages_list;
+ guint i;
+ GError *error = NULL;
+
+ g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
+
+ priv = GET_PRIV (chat);
+
+ /* If we do this call async, don't forget to ignore Received signal
+ * until we get the answer */
+ if (!tp_chan_type_text_list_pending_messages (priv->text_iface,
+ TRUE,
+ &messages_list,
+ &error)) {
+ gossip_debug (DEBUG_DOMAIN,
+ "Error retrieving pending messages: %s",
+ error ? error->message : "No error given");
+ g_clear_error (&error);
+ return;
+ }
+
+ for (i = 0; i < messages_list->len; i++) {
+ GValueArray *message_struct;
+ const gchar *message_body;
+ guint message_id;
+ guint timestamp;
+ guint from_handle;
+ guint message_type;
+ guint message_flags;
+
+ message_struct = g_ptr_array_index (messages_list, i);
+
+ message_id = g_value_get_uint (g_value_array_get_nth (message_struct, 0));
+ timestamp = g_value_get_uint (g_value_array_get_nth (message_struct, 1));
+ from_handle = g_value_get_uint (g_value_array_get_nth (message_struct, 2));
+ message_type = g_value_get_uint (g_value_array_get_nth (message_struct, 3));
+ message_flags = g_value_get_uint (g_value_array_get_nth (message_struct, 4));
+ message_body = g_value_get_string (g_value_array_get_nth (message_struct, 5));
+
+ gossip_debug (DEBUG_DOMAIN, "Message pending: %s", message_body);
+
+ tp_chat_emit_message (chat,
+ message_type,
+ timestamp,
+ from_handle,
+ message_body);
+
+ g_value_array_free (message_struct);
+ }
+
+ g_ptr_array_free (messages_list, TRUE);
+}
+
+void
+empathy_tp_chat_send (EmpathyTpChat *chat,
+ GossipMessage *message)
+{
+ EmpathyTpChatPriv *priv;
+ const gchar *message_body;
+ GossipMessageType message_type;
+ GError *error = NULL;
+
+ g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
+ g_return_if_fail (GOSSIP_IS_MESSAGE (message));
+
+ priv = GET_PRIV (chat);
+
+ message_body = gossip_message_get_body (message);
+ message_type = gossip_message_get_type (message);
+
+ gossip_debug (DEBUG_DOMAIN, "Sending message: %s", message_body);
+ if (!tp_chan_type_text_send (priv->text_iface,
+ message_type,
+ message_body,
+ &error)) {
+ gossip_debug (DEBUG_DOMAIN,
+ "Send Error: %s",
+ error ? error->message : "No error given");
+ g_clear_error (&error);
+ }
+}
+
+void
+empathy_tp_chat_send_state (EmpathyTpChat *chat,
+ EmpathyTpChatState state)
+{
+ EmpathyTpChatPriv *priv;
+ GError *error = NULL;
+
+ g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
+
+ priv = GET_PRIV (chat);
+
+ if (priv->chat_state_iface) {
+ gossip_debug (DEBUG_DOMAIN, "Set state: %d", state);
+ if (!tp_chan_iface_chat_state_set_chat_state (priv->chat_state_iface,
+ state,
+ &error)) {
+ gossip_debug (DEBUG_DOMAIN,
+ "Set Chat State Error: %s",
+ error ? error->message : "No error given");
+ g_clear_error (&error);
+ }
+ }
+}
+
+static void
+tp_chat_destroy_cb (TpChan *text_chan,
+ EmpathyTpChat *chat)
+{
+ EmpathyTpChatPriv *priv;
+
+ priv = GET_PRIV (chat);
+
+ gossip_debug (DEBUG_DOMAIN, "Channel destroyed");
+
+ g_object_unref (priv->tp_chan);
+ priv->tp_chan = NULL;
+ priv->text_iface = NULL;
+ priv->chat_state_iface = NULL;
+
+ g_signal_emit (chat, signals[DESTROY], 0);
+}
+
+static void
+tp_chat_received_cb (DBusGProxy *text_iface,
+ guint message_id,
+ guint timestamp,
+ guint from_handle,
+ guint message_type,
+ guint message_flags,
+ gchar *message_body,
+ EmpathyTpChat *chat)
+{
+ EmpathyTpChatPriv *priv;
+ GArray *message_ids;
+
+ priv = GET_PRIV (chat);
+
+ gossip_debug (DEBUG_DOMAIN, "Message received: %s", message_body);
+
+ tp_chat_emit_message (chat,
+ message_type,
+ timestamp,
+ from_handle,
+ message_body);
+
+ message_ids = g_array_new (FALSE, FALSE, sizeof (guint));
+ g_array_append_val (message_ids, message_id);
+ tp_chan_type_text_acknowledge_pending_messages (priv->text_iface,
+ message_ids, NULL);
+ g_array_free (message_ids, TRUE);
+}
+
+static void
+tp_chat_sent_cb (DBusGProxy *text_iface,
+ guint timestamp,
+ guint message_type,
+ gchar *message_body,
+ EmpathyTpChat *chat)
+{
+ gossip_debug (DEBUG_DOMAIN, "Message sent: %s", message_body);
+
+ tp_chat_emit_message (chat,
+ message_type,
+ timestamp,
+ 0,
+ message_body);
+}
+
+static void
+tp_chat_state_changed_cb (DBusGProxy *chat_state_iface,
+ guint handle,
+ EmpathyTpChatState state,
+ EmpathyTpChat *chat)
+{
+ EmpathyTpChatPriv *priv;
+ GossipContact *contact;
+
+ priv = GET_PRIV (chat);
+
+ contact = empathy_contact_list_get_from_handle (priv->list, handle);
+
+ g_signal_emit (chat, signals[CHAT_STATE_CHANGED], 0, contact, state);
+
+ g_object_unref (contact);
+}
+
+static void
+tp_chat_emit_message (EmpathyTpChat *chat,
+ guint type,
+ guint timestamp,
+ guint from_handle,
+ const gchar *message_body)
+{
+ EmpathyTpChatPriv *priv;
+ GossipMessage *message;
+ GossipContact *sender;
+
+ priv = GET_PRIV (chat);
+
+ if (from_handle == 0) {
+ sender = empathy_contact_list_get_own (priv->list);
+ g_object_ref (sender);
+ } else {
+ sender = empathy_contact_list_get_from_handle (priv->list,
+ from_handle);
+ }
+
+ message = gossip_message_new (message_body);
+ gossip_message_set_type (message, type);
+ gossip_message_set_sender (message, sender);
+ gossip_message_set_timestamp (message, (GossipTime) timestamp);
+
+ g_signal_emit (chat, signals[MESSAGE_RECEIVED], 0, message);
+
+ g_object_unref (message);
+ g_object_unref (sender);
+}
+
diff --git a/libempathy/empathy-tp-chat.h b/libempathy/empathy-tp-chat.h
new file mode 100644
index 000000000..c64bbd738
--- /dev/null
+++ b/libempathy/empathy-tp-chat.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2007 Xavier Claessens <xclaesse@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __EMPATHY_TP_CHAT_H__
+#define __EMPATHY_TP_CHAT_H__
+
+#include <glib.h>
+
+#include <libtelepathy/tp-chan.h>
+
+#include <libmissioncontrol/mc-account.h>
+
+#include "gossip-message.h"
+#include "gossip-contact.h"
+
+
+G_BEGIN_DECLS
+
+#define EMPATHY_TYPE_TP_CHAT (empathy_tp_chat_get_type ())
+#define EMPATHY_TP_CHAT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), EMPATHY_TYPE_TP_CHAT, EmpathyTpChat))
+#define EMPATHY_TP_CHAT_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), EMPATHY_TYPE_TP_CHAT, EmpathyTpChatClass))
+#define EMPATHY_IS_TP_CHAT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), EMPATHY_TYPE_TP_CHAT))
+#define EMPATHY_IS_TP_CHAT_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), EMPATHY_TYPE_TP_CHAT))
+#define EMPATHY_TP_CHAT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), EMPATHY_TYPE_TP_CHAT, EmpathyTpChatClass))
+
+typedef struct _EmpathyTpChat EmpathyTpChat;
+typedef struct _EmpathyTpChatClass EmpathyTpChatClass;
+typedef struct _EmpathyTpChatPriv EmpathyTpChatPriv;
+
+struct _EmpathyTpChat {
+ GObject parent;
+};
+
+struct _EmpathyTpChatClass {
+ GObjectClass parent_class;
+};
+
+typedef enum {
+ EMPATHY_TP_CHAT_STATE_GONE,
+ EMPATHY_TP_CHAT_STATE_INACTIVE,
+ EMPATHY_TP_CHAT_STATE_ACTIVE,
+ EMPATHY_TP_CHAT_STATE_PAUSED,
+ EMPATHY_TP_CHAT_STATE_COMPOSING
+} EmpathyTpChatState;
+
+GType empathy_tp_chat_get_type (void) G_GNUC_CONST;
+EmpathyTpChat *empathy_tp_chat_new (McAccount *account,
+ TpChan *tp_chan);
+EmpathyTpChat *empathy_tp_chat_new_with_contact (GossipContact *contact);
+void empathy_tp_chat_request_pending (EmpathyTpChat *chat);
+void empathy_tp_chat_send (EmpathyTpChat *chat,
+ GossipMessage *message);
+void empathy_tp_chat_send_state (EmpathyTpChat *chat,
+ EmpathyTpChatState state);
+
+G_END_DECLS
+
+#endif /* __EMPATHY_TP_CHAT_H__ */
diff --git a/libempathy/gossip-avatar.c b/libempathy/gossip-avatar.c
new file mode 100644
index 000000000..5c17a5176
--- /dev/null
+++ b/libempathy/gossip-avatar.c
@@ -0,0 +1,86 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2002-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Martyn Russell <martyn@imendio.com>
+ * Xavier Claessens <xclaesse@gmail.com>
+ */
+
+#include "config.h"
+
+#include "gossip-avatar.h"
+
+#define DEBUG_DOMAIN "Avatar"
+
+GType
+gossip_avatar_get_gtype (void)
+{
+ static GType type_id = 0;
+
+ if (!type_id) {
+ type_id = g_boxed_type_register_static ("GossipAvatar",
+ (GBoxedCopyFunc) gossip_avatar_ref,
+ (GBoxedFreeFunc) gossip_avatar_unref);
+ }
+
+ return type_id;
+}
+
+GossipAvatar *
+gossip_avatar_new (guchar *data,
+ gsize len,
+ gchar *format)
+{
+ GossipAvatar *avatar;
+
+ g_return_val_if_fail (data != NULL, NULL);
+ g_return_val_if_fail (len > 0, NULL);
+ g_return_val_if_fail (format != NULL, NULL);
+
+ avatar = g_slice_new0 (GossipAvatar);
+ avatar->data = g_memdup (data, len);
+ avatar->len = len;
+ avatar->format = g_strdup (format);
+ avatar->refcount = 1;
+
+ return avatar;
+}
+
+void
+gossip_avatar_unref (GossipAvatar *avatar)
+{
+ g_return_if_fail (avatar != NULL);
+
+ avatar->refcount--;
+ if (avatar->refcount == 0) {
+ g_free (avatar->data);
+ g_free (avatar->format);
+ g_slice_free (GossipAvatar, avatar);
+ }
+}
+
+GossipAvatar *
+gossip_avatar_ref (GossipAvatar *avatar)
+{
+ g_return_val_if_fail (avatar != NULL, NULL);
+
+ avatar->refcount++;
+
+ return avatar;
+}
+
diff --git a/libempathy/gossip-avatar.h b/libempathy/gossip-avatar.h
new file mode 100644
index 000000000..44fa9aba3
--- /dev/null
+++ b/libempathy/gossip-avatar.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2006 Xavier Claessens <xclaesse@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GOSSIP_AVATAR_H__
+#define __GOSSIP_AVATAR_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GOSSIP_TYPE_AVATAR (gossip_avatar_get_gtype ())
+
+typedef struct _GossipAvatar GossipAvatar;
+
+struct _GossipAvatar {
+ guchar *data;
+ gsize len;
+ gchar *format;
+ guint refcount;
+};
+
+GType gossip_avatar_get_gtype (void) G_GNUC_CONST;
+GossipAvatar * gossip_avatar_new (guchar *avatar,
+ gsize len,
+ gchar *format);
+GossipAvatar * gossip_avatar_ref (GossipAvatar *avatar);
+void gossip_avatar_unref (GossipAvatar *avatar);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_AVATAR_H__ */
diff --git a/libempathy/gossip-conf.c b/libempathy/gossip-conf.c
new file mode 100644
index 000000000..9625a700d
--- /dev/null
+++ b/libempathy/gossip-conf.c
@@ -0,0 +1,382 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2006 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Richard Hult <richard@imendio.com>
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gconf/gconf-client.h>
+
+#include "gossip-conf.h"
+#include "gossip-debug.h"
+
+#define DEBUG_DOMAIN "Config"
+
+#define GOSSIP_CONF_ROOT "/apps/empathy"
+#define DESKTOP_INTERFACE_ROOT "/desktop/gnome/interface"
+
+#define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_CONF, GossipConfPriv))
+
+typedef struct {
+ GConfClient *gconf_client;
+} GossipConfPriv;
+
+typedef struct {
+ GossipConf *conf;
+ GossipConfNotifyFunc func;
+ gpointer user_data;
+} GossipConfNotifyData;
+
+static void conf_finalize (GObject *object);
+
+G_DEFINE_TYPE (GossipConf, gossip_conf, G_TYPE_OBJECT);
+
+static GossipConf *global_conf = NULL;
+
+static void
+gossip_conf_class_init (GossipConfClass *class)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (class);
+
+ object_class->finalize = conf_finalize;
+
+ g_type_class_add_private (object_class, sizeof (GossipConfPriv));
+}
+
+static void
+gossip_conf_init (GossipConf *conf)
+{
+ GossipConfPriv *priv;
+
+ priv = GET_PRIV (conf);
+
+ priv->gconf_client = gconf_client_get_default ();
+
+ gconf_client_add_dir (priv->gconf_client,
+ GOSSIP_CONF_ROOT,
+ GCONF_CLIENT_PRELOAD_ONELEVEL,
+ NULL);
+ gconf_client_add_dir (priv->gconf_client,
+ DESKTOP_INTERFACE_ROOT,
+ GCONF_CLIENT_PRELOAD_NONE,
+ NULL);
+}
+
+static void
+conf_finalize (GObject *object)
+{
+ GossipConfPriv *priv;
+
+ priv = GET_PRIV (object);
+
+ gconf_client_remove_dir (priv->gconf_client,
+ GOSSIP_CONF_ROOT,
+ NULL);
+ gconf_client_remove_dir (priv->gconf_client,
+ DESKTOP_INTERFACE_ROOT,
+ NULL);
+
+ g_object_unref (priv->gconf_client);
+
+ G_OBJECT_CLASS (gossip_conf_parent_class)->finalize (object);
+}
+
+GossipConf *
+gossip_conf_get (void)
+{
+ if (!global_conf) {
+ global_conf = g_object_new (GOSSIP_TYPE_CONF, NULL);
+ }
+
+ return global_conf;
+}
+
+void
+gossip_conf_shutdown (void)
+{
+ if (global_conf) {
+ g_object_unref (global_conf);
+ global_conf = NULL;
+ }
+}
+
+gboolean
+gossip_conf_set_int (GossipConf *conf,
+ const gchar *key,
+ gint value)
+{
+ GossipConfPriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_CONF (conf), FALSE);
+
+ gossip_debug (DEBUG_DOMAIN, "Setting int:'%s' to %d", key, value);
+
+ priv = GET_PRIV (conf);
+
+ return gconf_client_set_int (priv->gconf_client,
+ key,
+ value,
+ NULL);
+}
+
+gboolean
+gossip_conf_get_int (GossipConf *conf,
+ const gchar *key,
+ gint *value)
+{
+ GossipConfPriv *priv;
+ GError *error = NULL;
+
+ *value = 0;
+
+ g_return_val_if_fail (GOSSIP_IS_CONF (conf), FALSE);
+ g_return_val_if_fail (value != NULL, FALSE);
+
+ priv = GET_PRIV (conf);
+
+ *value = gconf_client_get_int (priv->gconf_client,
+ key,
+ &error);
+
+ gossip_debug (DEBUG_DOMAIN, "Getting int:'%s' (=%d), error:'%s'",
+ key, *value, error ? error->message : "None");
+
+ if (error) {
+ g_error_free (error);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+gossip_conf_set_bool (GossipConf *conf,
+ const gchar *key,
+ gboolean value)
+{
+ GossipConfPriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_CONF (conf), FALSE);
+
+ gossip_debug (DEBUG_DOMAIN, "Setting bool:'%s' to %d ---> %s",
+ key, value, value ? "true" : "false");
+
+ priv = GET_PRIV (conf);
+
+ return gconf_client_set_bool (priv->gconf_client,
+ key,
+ value,
+ NULL);
+}
+
+gboolean
+gossip_conf_get_bool (GossipConf *conf,
+ const gchar *key,
+ gboolean *value)
+{
+ GossipConfPriv *priv;
+ GError *error = NULL;
+
+ *value = FALSE;
+
+ g_return_val_if_fail (GOSSIP_IS_CONF (conf), FALSE);
+ g_return_val_if_fail (value != NULL, FALSE);
+
+ priv = GET_PRIV (conf);
+
+ *value = gconf_client_get_bool (priv->gconf_client,
+ key,
+ &error);
+
+ gossip_debug (DEBUG_DOMAIN, "Getting bool:'%s' (=%d ---> %s), error:'%s'",
+ key, *value, *value ? "true" : "false",
+ error ? error->message : "None");
+
+ if (error) {
+ g_error_free (error);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+gossip_conf_set_string (GossipConf *conf,
+ const gchar *key,
+ const gchar *value)
+{
+ GossipConfPriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_CONF (conf), FALSE);
+
+ gossip_debug (DEBUG_DOMAIN, "Setting string:'%s' to '%s'",
+ key, value);
+
+ priv = GET_PRIV (conf);
+
+ return gconf_client_set_string (priv->gconf_client,
+ key,
+ value,
+ NULL);
+}
+
+gboolean
+gossip_conf_get_string (GossipConf *conf,
+ const gchar *key,
+ gchar **value)
+{
+ GossipConfPriv *priv;
+ GError *error = NULL;
+
+ *value = NULL;
+
+ g_return_val_if_fail (GOSSIP_IS_CONF (conf), FALSE);
+
+ priv = GET_PRIV (conf);
+
+ *value = gconf_client_get_string (priv->gconf_client,
+ key,
+ &error);
+
+ gossip_debug (DEBUG_DOMAIN, "Getting string:'%s' (='%s'), error:'%s'",
+ key, *value, error ? error->message : "None");
+
+ if (error) {
+ g_error_free (error);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+gossip_conf_set_string_list (GossipConf *conf,
+ const gchar *key,
+ GSList *value)
+{
+ GossipConfPriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_CONF (conf), FALSE);
+
+ priv = GET_PRIV (conf);
+
+ return gconf_client_set_list (priv->gconf_client,
+ key,
+ GCONF_VALUE_STRING,
+ value,
+ NULL);
+}
+
+gboolean
+gossip_conf_get_string_list (GossipConf *conf,
+ const gchar *key,
+ GSList **value)
+{
+ GossipConfPriv *priv;
+ GError *error = NULL;
+
+ *value = NULL;
+
+ g_return_val_if_fail (GOSSIP_IS_CONF (conf), FALSE);
+
+ priv = GET_PRIV (conf);
+
+ *value = gconf_client_get_list (priv->gconf_client,
+ key,
+ GCONF_VALUE_STRING,
+ &error);
+ if (error) {
+ g_error_free (error);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+conf_notify_data_free (GossipConfNotifyData *data)
+{
+ g_object_unref (data->conf);
+ g_slice_free (GossipConfNotifyData, data);
+}
+
+static void
+conf_notify_func (GConfClient *client,
+ guint id,
+ GConfEntry *entry,
+ gpointer user_data)
+{
+ GossipConfNotifyData *data;
+
+ data = user_data;
+
+ data->func (data->conf,
+ gconf_entry_get_key (entry),
+ data->user_data);
+}
+
+guint
+gossip_conf_notify_add (GossipConf *conf,
+ const gchar *key,
+ GossipConfNotifyFunc func,
+ gpointer user_data)
+{
+ GossipConfPriv *priv;
+ guint id;
+ GossipConfNotifyData *data;
+
+ g_return_val_if_fail (GOSSIP_IS_CONF (conf), 0);
+
+ priv = GET_PRIV (conf);
+
+ data = g_slice_new (GossipConfNotifyData);
+ data->func = func;
+ data->user_data = user_data;
+ data->conf = g_object_ref (conf);
+
+ id = gconf_client_notify_add (priv->gconf_client,
+ key,
+ conf_notify_func,
+ data,
+ (GFreeFunc) conf_notify_data_free,
+ NULL);
+
+ return id;
+}
+
+gboolean
+gossip_conf_notify_remove (GossipConf *conf,
+ guint id)
+{
+ GossipConfPriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_CONF (conf), FALSE);
+
+ priv = GET_PRIV (conf);
+
+ gconf_client_notify_remove (priv->gconf_client, id);
+
+ return TRUE;
+}
+
diff --git a/libempathy/gossip-conf.h b/libempathy/gossip-conf.h
new file mode 100644
index 000000000..35fdfb902
--- /dev/null
+++ b/libempathy/gossip-conf.h
@@ -0,0 +1,87 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2006 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GOSSIP_CONF_H__
+#define __GOSSIP_CONF_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GOSSIP_TYPE_CONF (gossip_conf_get_type ())
+#define GOSSIP_CONF(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOSSIP_TYPE_CONF, GossipConf))
+#define GOSSIP_CONF_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GOSSIP_TYPE_CONF, GossipConfClass))
+#define GOSSIP_IS_CONF(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOSSIP_TYPE_CONF))
+#define GOSSIP_IS_CONF_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GOSSIP_TYPE_CONF))
+#define GOSSIP_CONF_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GOSSIP_TYPE_CONF, GossipConfClass))
+
+typedef struct _GossipConf GossipConf;
+typedef struct _GossipConfClass GossipConfClass;
+
+struct _GossipConf {
+ GObject parent;
+};
+
+struct _GossipConfClass {
+ GObjectClass parent_class;
+};
+
+typedef void (*GossipConfNotifyFunc) (GossipConf *conf,
+ const gchar *key,
+ gpointer user_data);
+
+GType gossip_conf_get_type (void) G_GNUC_CONST;
+GossipConf *gossip_conf_get (void);
+void gossip_conf_shutdown (void);
+guint gossip_conf_notify_add (GossipConf *conf,
+ const gchar *key,
+ GossipConfNotifyFunc func,
+ gpointer data);
+gboolean gossip_conf_notify_remove (GossipConf *conf,
+ guint id);
+gboolean gossip_conf_set_int (GossipConf *conf,
+ const gchar *key,
+ gint value);
+gboolean gossip_conf_get_int (GossipConf *conf,
+ const gchar *key,
+ gint *value);
+gboolean gossip_conf_set_bool (GossipConf *conf,
+ const gchar *key,
+ gboolean value);
+gboolean gossip_conf_get_bool (GossipConf *conf,
+ const gchar *key,
+ gboolean *value);
+gboolean gossip_conf_set_string (GossipConf *conf,
+ const gchar *key,
+ const gchar *value);
+gboolean gossip_conf_get_string (GossipConf *conf,
+ const gchar *key,
+ gchar **value);
+gboolean gossip_conf_set_string_list (GossipConf *conf,
+ const gchar *key,
+ GSList *value);
+gboolean gossip_conf_get_string_list (GossipConf *conf,
+ const gchar *key,
+ GSList **value);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_CONF_H__ */
+
diff --git a/libempathy/gossip-contact.c b/libempathy/gossip-contact.c
new file mode 100644
index 000000000..6950c89b7
--- /dev/null
+++ b/libempathy/gossip-contact.c
@@ -0,0 +1,815 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2004-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Mikael Hallendal <micke@imendio.com>
+ * Martyn Russell <martyn@imendio.com>
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <glib/gi18n.h>
+
+#include "gossip-contact.h"
+#include "gossip-utils.h"
+#include "gossip-debug.h"
+
+#define DEBUG_DOMAIN "Contact"
+
+#define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_CONTACT, GossipContactPriv))
+
+typedef struct _GossipContactPriv GossipContactPriv;
+
+struct _GossipContactPriv {
+ gchar *id;
+ gchar *name;
+ guint handle;
+ GList *presences;
+ GList *groups;
+ GossipSubscription subscription;
+ GossipAvatar *avatar;
+ McAccount *account;
+};
+
+static void contact_class_init (GossipContactClass *class);
+static void contact_init (GossipContact *contact);
+static void contact_finalize (GObject *object);
+static void contact_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void contact_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void contact_set_presences (GossipContact *contact,
+ GList *presences);
+
+enum {
+ PROP_0,
+ PROP_NAME,
+ PROP_ID,
+ PROP_PRESENCES,
+ PROP_GROUPS,
+ PROP_SUBSCRIPTION,
+ PROP_AVATAR,
+ PROP_HANDLE,
+ PROP_ACCOUNT
+};
+
+static gpointer parent_class = NULL;
+
+GType
+gossip_contact_get_gtype (void)
+{
+ static GType type = 0;
+
+ if (!type) {
+ static const GTypeInfo info = {
+ sizeof (GossipContactClass),
+ NULL, /* base_init */
+ NULL, /* base_finalize */
+ (GClassInitFunc) contact_class_init,
+ NULL, /* class_finalize */
+ NULL, /* class_data */
+ sizeof (GossipContact),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) contact_init
+ };
+
+ type = g_type_register_static (G_TYPE_OBJECT,
+ "GossipContact",
+ &info, 0);
+ }
+
+ return type;
+}
+
+static void
+contact_class_init (GossipContactClass *class)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (class);
+ parent_class = g_type_class_peek_parent (class);
+
+ object_class->finalize = contact_finalize;
+ object_class->get_property = contact_get_property;
+ object_class->set_property = contact_set_property;
+
+ g_object_class_install_property (object_class,
+ PROP_NAME,
+ g_param_spec_string ("name",
+ "Contact Name",
+ "The name of the contact",
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_ID,
+ g_param_spec_string ("id",
+ "Contact id",
+ "String identifying contact",
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_PRESENCES,
+ g_param_spec_pointer ("presences",
+ "Contact presences",
+ "Presences of contact",
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_GROUPS,
+ g_param_spec_pointer ("groups",
+ "Contact groups",
+ "Groups of contact",
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_SUBSCRIPTION,
+ g_param_spec_int ("subscription",
+ "Contact Subscription",
+ "The subscription status of the contact",
+ GOSSIP_SUBSCRIPTION_NONE,
+ GOSSIP_SUBSCRIPTION_BOTH,
+ GOSSIP_SUBSCRIPTION_NONE,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_AVATAR,
+ g_param_spec_boxed ("avatar",
+ "Avatar image",
+ "The avatar image",
+ GOSSIP_TYPE_AVATAR,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_ACCOUNT,
+ g_param_spec_object ("account",
+ "Contact Account",
+ "The account associated with the contact",
+ MC_TYPE_ACCOUNT,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_HANDLE,
+ g_param_spec_uint ("handle",
+ "Contact Handle",
+ "The handle of the contact",
+ 0,
+ G_MAXUINT,
+ 0,
+ G_PARAM_READWRITE));
+
+ g_type_class_add_private (object_class, sizeof (GossipContactPriv));
+}
+
+static void
+contact_init (GossipContact *contact)
+{
+ GossipContactPriv *priv;
+
+ priv = GET_PRIV (contact);
+
+ priv->name = NULL;
+ priv->id = NULL;
+ priv->presences = NULL;
+ priv->account = NULL;
+ priv->groups = NULL;
+ priv->avatar = NULL;
+ priv->handle = 0;
+}
+
+static void
+contact_finalize (GObject *object)
+{
+ GossipContactPriv *priv;
+
+ priv = GET_PRIV (object);
+
+ gossip_debug (DEBUG_DOMAIN, "finalize: %p", object);
+
+ g_free (priv->name);
+ g_free (priv->id);
+
+ if (priv->avatar) {
+ gossip_avatar_unref (priv->avatar);
+ }
+
+ if (priv->presences) {
+ g_list_foreach (priv->presences, (GFunc) g_object_unref, NULL);
+ g_list_free (priv->presences);
+ }
+
+ if (priv->groups) {
+ g_list_foreach (priv->groups, (GFunc) g_free, NULL);
+ g_list_free (priv->groups);
+ }
+
+ if (priv->account) {
+ g_object_unref (priv->account);
+ }
+
+ (G_OBJECT_CLASS (parent_class)->finalize) (object);
+}
+
+static void
+contact_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GossipContactPriv *priv;
+
+ priv = GET_PRIV (object);
+
+ switch (param_id) {
+ case PROP_NAME:
+ g_value_set_string (value,
+ gossip_contact_get_name (GOSSIP_CONTACT (object)));
+ break;
+ case PROP_ID:
+ g_value_set_string (value,
+ gossip_contact_get_id (GOSSIP_CONTACT (object)));
+ break;
+ case PROP_PRESENCES:
+ g_value_set_pointer (value, priv->presences);
+ break;
+ case PROP_GROUPS:
+ g_value_set_pointer (value, priv->groups);
+ break;
+ case PROP_SUBSCRIPTION:
+ g_value_set_int (value, priv->subscription);
+ break;
+ case PROP_AVATAR:
+ g_value_set_boxed (value, priv->avatar);
+ break;
+ case PROP_ACCOUNT:
+ g_value_set_object (value, priv->account);
+ break;
+ case PROP_HANDLE:
+ g_value_set_uint (value, priv->handle);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ };
+}
+
+static void
+contact_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GossipContactPriv *priv;
+
+ priv = GET_PRIV (object);
+
+ switch (param_id) {
+ case PROP_NAME:
+ gossip_contact_set_name (GOSSIP_CONTACT (object),
+ g_value_get_string (value));
+ break;
+ case PROP_ID:
+ gossip_contact_set_id (GOSSIP_CONTACT (object),
+ g_value_get_string (value));
+ break;
+ case PROP_PRESENCES:
+ contact_set_presences (GOSSIP_CONTACT (object),
+ g_value_get_pointer (value));
+ break;
+ case PROP_GROUPS:
+ gossip_contact_set_groups (GOSSIP_CONTACT (object),
+ g_value_get_pointer (value));
+ break;
+ case PROP_SUBSCRIPTION:
+ gossip_contact_set_subscription (GOSSIP_CONTACT (object),
+ g_value_get_int (value));
+ break;
+ case PROP_AVATAR:
+ gossip_contact_set_avatar (GOSSIP_CONTACT (object),
+ g_value_get_boxed (value));
+ break;
+ case PROP_ACCOUNT:
+ gossip_contact_set_account (GOSSIP_CONTACT (object),
+ MC_ACCOUNT (g_value_get_object (value)));
+ break;
+ case PROP_HANDLE:
+ gossip_contact_set_handle (GOSSIP_CONTACT (object),
+ g_value_get_uint (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ };
+}
+
+GossipContact *
+gossip_contact_new (McAccount *account)
+{
+ return g_object_new (GOSSIP_TYPE_CONTACT,
+ "account", account,
+ NULL);
+}
+
+GossipContact *
+gossip_contact_new_full (McAccount *account,
+ const gchar *id,
+ const gchar *name)
+{
+ return g_object_new (GOSSIP_TYPE_CONTACT,
+ "account", account,
+ "name", name,
+ "id", id,
+ NULL);
+}
+
+const gchar *
+gossip_contact_get_id (GossipContact *contact)
+{
+ GossipContactPriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), "");
+
+ priv = GET_PRIV (contact);
+
+ if (priv->id) {
+ return priv->id;
+ }
+
+ return "";
+}
+
+const gchar *
+gossip_contact_get_name (GossipContact *contact)
+{
+ GossipContactPriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), "");
+
+ priv = GET_PRIV (contact);
+
+ if (priv->name == NULL) {
+ return gossip_contact_get_id (contact);
+ }
+
+ return priv->name;
+}
+
+GossipAvatar *
+gossip_contact_get_avatar (GossipContact *contact)
+{
+ GossipContactPriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), NULL);
+
+ priv = GET_PRIV (contact);
+
+ return priv->avatar;
+}
+
+McAccount *
+gossip_contact_get_account (GossipContact *contact)
+{
+ GossipContactPriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), NULL);
+
+ priv = GET_PRIV (contact);
+
+ return priv->account;
+}
+
+GossipPresence *
+gossip_contact_get_active_presence (GossipContact *contact)
+{
+ GossipContactPriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), NULL);
+
+ priv = GET_PRIV (contact);
+
+ if (priv->presences) {
+ /* Highest priority of the presences is first */
+ return GOSSIP_PRESENCE (priv->presences->data);
+ }
+
+ return NULL;
+}
+
+GossipPresence *
+gossip_contact_get_presence_for_resource (GossipContact *contact,
+ const gchar *resource)
+{
+ GossipContactPriv *priv;
+ GList *l;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), NULL);
+ g_return_val_if_fail (resource != NULL, NULL);
+
+ priv = GET_PRIV (contact);
+
+ for (l = priv->presences; l; l = l->next) {
+ const gchar *p_res;
+
+ p_res = gossip_presence_get_resource (GOSSIP_PRESENCE (l->data));
+ if (p_res && strcmp (resource, p_res) == 0) {
+ return GOSSIP_PRESENCE (l->data);
+ }
+ }
+
+ return NULL;
+}
+
+GList *
+gossip_contact_get_presence_list (GossipContact *contact)
+{
+ GossipContactPriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), NULL);
+
+ priv = GET_PRIV (contact);
+
+ return priv->presences;
+}
+
+GList *
+gossip_contact_get_groups (GossipContact *contact)
+{
+ GossipContactPriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), NULL);
+
+ priv = GET_PRIV (contact);
+
+ return priv->groups;
+}
+
+GossipSubscription
+gossip_contact_get_subscription (GossipContact *contact)
+{
+ GossipContactPriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT (contact),
+ GOSSIP_SUBSCRIPTION_NONE);
+
+ priv = GET_PRIV (contact);
+
+ return priv->subscription;
+}
+
+guint
+gossip_contact_get_handle (GossipContact *contact)
+{
+ GossipContactPriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), 0);
+
+ priv = GET_PRIV (contact);
+
+ return priv->handle;
+}
+
+void
+gossip_contact_set_id (GossipContact *contact,
+ const gchar *id)
+{
+ GossipContactPriv *priv;
+
+ g_return_if_fail (GOSSIP_IS_CONTACT (contact));
+ g_return_if_fail (id != NULL);
+
+ priv = GET_PRIV (contact);
+
+ g_free (priv->id);
+ priv->id = g_strdup (id);
+
+ g_object_notify (G_OBJECT (contact), "id");
+}
+
+void
+gossip_contact_set_name (GossipContact *contact,
+ const gchar *name)
+{
+ GossipContactPriv *priv;
+
+ g_return_if_fail (GOSSIP_IS_CONTACT (contact));
+ g_return_if_fail (name != NULL);
+
+ priv = GET_PRIV (contact);
+
+ g_free (priv->name);
+ priv->name = g_strdup (name);
+
+ g_object_notify (G_OBJECT (contact), "name");
+}
+
+static void
+contact_set_presences (GossipContact *contact,
+ GList *presences)
+{
+ GossipContactPriv *priv;
+
+ priv = GET_PRIV (contact);
+
+ if (priv->presences) {
+ g_list_foreach (priv->presences, (GFunc) g_object_unref, NULL);
+ g_list_free (priv->presences);
+ }
+
+ priv->presences = g_list_copy (presences);
+ g_list_foreach (priv->presences, (GFunc) g_object_ref, NULL);
+
+ g_object_notify (G_OBJECT (contact), "presences");
+}
+
+void
+gossip_contact_set_avatar (GossipContact *contact,
+ GossipAvatar *avatar)
+{
+ GossipContactPriv *priv;
+
+ g_return_if_fail (GOSSIP_IS_CONTACT (contact));
+
+ priv = GET_PRIV (contact);
+
+ if (priv->avatar) {
+ gossip_avatar_unref (priv->avatar);
+ priv->avatar = NULL;
+ }
+
+ if (avatar) {
+ priv->avatar = gossip_avatar_ref (avatar);
+ }
+
+ g_object_notify (G_OBJECT (contact), "avatar");
+}
+
+void
+gossip_contact_set_account (GossipContact *contact,
+ McAccount *account)
+{
+ GossipContactPriv *priv;
+
+ g_return_if_fail (GOSSIP_IS_CONTACT (contact));
+ g_return_if_fail (MC_IS_ACCOUNT (account));
+
+ priv = GET_PRIV (contact);
+
+ if (priv->account) {
+ g_object_unref (priv->account);
+ }
+
+ if (account) {
+ priv->account = g_object_ref (account);
+ } else {
+ priv->account = NULL;
+ }
+
+ g_object_notify (G_OBJECT (contact), "account");
+}
+
+void
+gossip_contact_add_presence (GossipContact *contact,
+ GossipPresence *presence)
+{
+ GossipContactPriv *priv;
+ GossipPresence *this_presence;
+ GList *l;
+
+ g_return_if_fail (GOSSIP_IS_CONTACT (contact));
+ g_return_if_fail (GOSSIP_IS_PRESENCE (presence));
+
+ priv = GET_PRIV (contact);
+
+ for (l = priv->presences; l && presence; l = l->next) {
+ this_presence = l->data;
+
+ if (gossip_presence_resource_equal (this_presence, presence)) {
+ gint ref_count;
+
+ ref_count = G_OBJECT (presence)->ref_count;
+
+ /* Remove old presence for this resource, we
+ * would use g_list_remove_all() here but we
+ * want to make sure we unref for each
+ * instance we find it in the list.
+ */
+ priv->presences = g_list_remove (priv->presences, this_presence);
+ g_object_unref (this_presence);
+
+ if (!priv->presences || ref_count <= 1) {
+ break;
+ }
+
+ /* Reset list to beginnging to make sure we
+ * didn't miss any duplicates.
+ */
+ l = priv->presences;
+ }
+ }
+
+ /* Add new presence */
+ priv->presences = g_list_insert_sorted (priv->presences,
+ g_object_ref (presence),
+ gossip_presence_sort_func);
+
+ g_object_notify (G_OBJECT (contact), "presences");
+}
+
+void
+gossip_contact_remove_presence (GossipContact *contact,
+ GossipPresence *presence)
+{
+ GossipContactPriv *priv;
+ GossipPresence *this_presence;
+ GList *l;
+
+ g_return_if_fail (GOSSIP_IS_CONTACT (contact));
+ g_return_if_fail (GOSSIP_IS_PRESENCE (presence));
+
+ priv = GET_PRIV (contact);
+
+ for (l = priv->presences; l; l = l->next) {
+ this_presence = l->data;
+
+ if (gossip_presence_resource_equal (this_presence, presence)) {
+ gint ref_count;
+
+ ref_count = G_OBJECT (presence)->ref_count;
+
+ /* Remove old presence for this resource, we
+ * would use g_list_remove_all() here but we
+ * want to make sure we unref for each
+ * instance we find it in the list.
+ */
+ priv->presences = g_list_remove (priv->presences, this_presence);
+ g_object_unref (this_presence);
+
+ if (!priv->presences || ref_count <= 1) {
+ break;
+ }
+
+ /* Reset list to beginnging to make sure we
+ * didn't miss any duplicates.
+ */
+ l = priv->presences;
+ }
+ }
+
+ priv->presences = g_list_sort (priv->presences,
+ gossip_presence_sort_func);
+
+ g_object_notify (G_OBJECT (contact), "presences");
+}
+
+void
+gossip_contact_set_groups (GossipContact *contact,
+ GList *groups)
+{
+ GossipContactPriv *priv;
+ GList *old_groups, *l;
+
+ g_return_if_fail (GOSSIP_IS_CONTACT (contact));
+
+ priv = GET_PRIV (contact);
+
+ old_groups = priv->groups;
+ priv->groups = NULL;
+
+ for (l = groups; l; l = l->next) {
+ priv->groups = g_list_append (priv->groups,
+ g_strdup (l->data));
+ }
+
+ g_list_foreach (old_groups, (GFunc) g_free, NULL);
+ g_list_free (old_groups);
+
+ g_object_notify (G_OBJECT (contact), "groups");
+}
+
+void
+gossip_contact_set_subscription (GossipContact *contact,
+ GossipSubscription subscription)
+{
+ GossipContactPriv *priv;
+
+ g_return_if_fail (GOSSIP_IS_CONTACT (contact));
+
+ priv = GET_PRIV (contact);
+
+ priv->subscription = subscription;
+
+ g_object_notify (G_OBJECT (contact), "subscription");
+}
+
+void
+gossip_contact_set_handle (GossipContact *contact,
+ guint handle)
+{
+ GossipContactPriv *priv;
+
+ priv = GET_PRIV (contact);
+
+ priv->handle = handle;
+
+ g_object_notify (G_OBJECT (contact), "handle");
+}
+
+gboolean
+gossip_contact_is_online (GossipContact *contact)
+{
+ GossipContactPriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), FALSE);
+
+ priv = GET_PRIV (contact);
+
+ if (priv->presences == NULL) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+const gchar *
+gossip_contact_get_status (GossipContact *contact)
+{
+ GossipContactPriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), "");
+
+ priv = GET_PRIV (contact);
+
+ if (priv->presences) {
+ GossipPresence *p;
+ const gchar *status;
+
+ p = GOSSIP_PRESENCE (priv->presences->data);
+ status = gossip_presence_get_status (p);
+ if (!status) {
+ status = gossip_presence_state_get_default_status (gossip_presence_get_state (p));
+ }
+ return status;
+ } else {
+ return _("Offline");
+ }
+}
+
+gboolean
+gossip_contact_equal (gconstpointer v1,
+ gconstpointer v2)
+{
+ McAccount *account_a;
+ McAccount *account_b;
+ const gchar *id_a;
+ const gchar *id_b;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT (v1), FALSE);
+ g_return_val_if_fail (GOSSIP_IS_CONTACT (v2), FALSE);
+
+ account_a = gossip_contact_get_account (GOSSIP_CONTACT (v1));
+ account_b = gossip_contact_get_account (GOSSIP_CONTACT (v2));
+
+ id_a = gossip_contact_get_id (GOSSIP_CONTACT (v1));
+ id_b = gossip_contact_get_id (GOSSIP_CONTACT (v2));
+
+ return gossip_account_equal (account_a, account_b) && g_str_equal (id_a, id_b);
+}
+
+guint
+gossip_contact_hash (gconstpointer key)
+{
+ GossipContactPriv *priv;
+ guint hash;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT (key), +1);
+
+ priv = GET_PRIV (GOSSIP_CONTACT (key));
+
+ hash = gossip_account_hash (gossip_contact_get_account (GOSSIP_CONTACT (key)));
+ hash += g_str_hash (gossip_contact_get_id (GOSSIP_CONTACT (key)));
+
+ return hash;
+}
+
diff --git a/libempathy/gossip-contact.h b/libempathy/gossip-contact.h
new file mode 100644
index 000000000..92caee9ea
--- /dev/null
+++ b/libempathy/gossip-contact.h
@@ -0,0 +1,102 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2004 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GOSSIP_CONTACT_H__
+#define __GOSSIP_CONTACT_H__
+
+#include <glib-object.h>
+
+#include <libmissioncontrol/mc-account.h>
+
+#include "gossip-avatar.h"
+#include "gossip-presence.h"
+
+G_BEGIN_DECLS
+
+#define GOSSIP_TYPE_CONTACT (gossip_contact_get_gtype ())
+#define GOSSIP_CONTACT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOSSIP_TYPE_CONTACT, GossipContact))
+#define GOSSIP_CONTACT_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GOSSIP_TYPE_CONTACT, GossipContactClass))
+#define GOSSIP_IS_CONTACT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOSSIP_TYPE_CONTACT))
+#define GOSSIP_IS_CONTACT_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GOSSIP_TYPE_CONTACT))
+#define GOSSIP_CONTACT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GOSSIP_TYPE_CONTACT, GossipContactClass))
+
+typedef struct _GossipContact GossipContact;
+typedef struct _GossipContactClass GossipContactClass;
+
+struct _GossipContact {
+ GObject parent;
+};
+
+struct _GossipContactClass {
+ GObjectClass parent_class;
+};
+
+typedef enum {
+ GOSSIP_SUBSCRIPTION_NONE = 0,
+ GOSSIP_SUBSCRIPTION_TO = 1 << 0, /* We send our presence to that contact */
+ GOSSIP_SUBSCRIPTION_FROM = 1 << 1, /* That contact sends his presence to us */
+ GOSSIP_SUBSCRIPTION_BOTH = GOSSIP_SUBSCRIPTION_TO | GOSSIP_SUBSCRIPTION_FROM
+} GossipSubscription;
+
+GType gossip_contact_get_gtype (void) G_GNUC_CONST;
+
+GossipContact * gossip_contact_new (McAccount *account);
+GossipContact * gossip_contact_new_full (McAccount *account,
+ const gchar *id,
+ const gchar *name);
+const gchar * gossip_contact_get_id (GossipContact *contact);
+const gchar * gossip_contact_get_name (GossipContact *contact);
+GossipAvatar * gossip_contact_get_avatar (GossipContact *contact);
+McAccount * gossip_contact_get_account (GossipContact *contact);
+void gossip_contact_add_presence (GossipContact *contact,
+ GossipPresence *presence);
+void gossip_contact_remove_presence (GossipContact *contact,
+ GossipPresence *presence);
+GossipPresence * gossip_contact_get_presence_for_resource (GossipContact *contact,
+ const gchar *resource);
+GossipPresence * gossip_contact_get_active_presence (GossipContact *contact);
+GList * gossip_contact_get_presence_list (GossipContact *contact);
+GList * gossip_contact_get_groups (GossipContact *contact);
+GossipSubscription gossip_contact_get_subscription (GossipContact *contact);
+guint gossip_contact_get_handle (GossipContact *contact);
+void gossip_contact_set_id (GossipContact *contact,
+ const gchar *id);
+void gossip_contact_set_name (GossipContact *contact,
+ const gchar *name);
+void gossip_contact_set_avatar (GossipContact *contact,
+ GossipAvatar *avatar);
+void gossip_contact_set_account (GossipContact *contact,
+ McAccount *account);
+void gossip_contact_set_groups (GossipContact *contact,
+ GList *categories);
+void gossip_contact_set_subscription (GossipContact *contact,
+ GossipSubscription subscription);
+void gossip_contact_set_handle (GossipContact *contact,
+ guint handle);
+gboolean gossip_contact_is_online (GossipContact *contact);
+const gchar * gossip_contact_get_status (GossipContact *contact);
+gboolean gossip_contact_equal (gconstpointer v1,
+ gconstpointer v2);
+guint gossip_contact_hash (gconstpointer key);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_CONTACT_H__ */
+
diff --git a/libempathy/gossip-debug.c b/libempathy/gossip-debug.c
new file mode 100644
index 000000000..a6bbeea2a
--- /dev/null
+++ b/libempathy/gossip-debug.c
@@ -0,0 +1,92 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2006-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Richard Hult <richard@imendio.com>
+ */
+
+#include "config.h"
+
+#include <stdarg.h>
+#include <string.h>
+
+#include <glib.h>
+#include <glib/gprintf.h>
+
+/* Set EMPATHY_DEBUG to a colon/comma/space separated list of domains, or "all"
+ * to get all debug output.
+ */
+
+#include "gossip-debug.h"
+
+static gchar **debug_strv;
+static gboolean all_domains = FALSE;
+
+static void
+debug_init (void)
+{
+ static gboolean inited = FALSE;
+
+ if (!inited) {
+ const gchar *env;
+ gint i;
+
+ env = g_getenv ("EMPATHY_DEBUG");
+
+ if (env) {
+ debug_strv = g_strsplit_set (env, ":, ", 0);
+ } else {
+ debug_strv = NULL;
+ }
+
+ for (i = 0; debug_strv && debug_strv[i]; i++) {
+ if (strcmp ("all", debug_strv[i]) == 0) {
+ all_domains = TRUE;
+ }
+ }
+
+ inited = TRUE;
+ }
+}
+
+void
+gossip_debug_impl (const gchar *domain, const gchar *msg, ...)
+{
+ gint i;
+
+ g_return_if_fail (domain != NULL);
+ g_return_if_fail (msg != NULL);
+
+ debug_init ();
+
+ for (i = 0; debug_strv && debug_strv[i]; i++) {
+ if (all_domains || strcmp (domain, debug_strv[i]) == 0) {
+ va_list args;
+
+ g_print ("%s: ", domain);
+
+ va_start (args, msg);
+ g_vprintf (msg, args);
+ va_end (args);
+
+ g_print ("\n");
+ break;
+ }
+ }
+}
+
diff --git a/libempathy/gossip-debug.h b/libempathy/gossip-debug.h
new file mode 100644
index 000000000..39dae0f1c
--- /dev/null
+++ b/libempathy/gossip-debug.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2006 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GOSSIP_DEBUG_H__
+#define __GOSSIP_DEBUG_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+#ifdef G_HAVE_ISO_VARARGS
+# ifdef GOSSIP_DISABLE_DEBUG
+# define gossip_debug(...)
+# else
+# define gossip_debug(...) gossip_debug_impl (__VA_ARGS__)
+# endif
+#elif defined(G_HAVE_GNUC_VARARGS)
+# if GOSSIP_DISABLE_DEBUG
+# define gossip_debug(fmt...)
+# else
+# define gossip_debug(fmt...) gossip_debug_impl(fmt)
+# endif
+#else
+# if GOSSIP_DISABLE_DEBUG
+# define gossip_debug(x)
+# else
+# define gossip_debug gossip_debug_impl
+# endif
+#endif
+
+void gossip_debug_impl (const gchar *domain, const gchar *msg, ...);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_DEBUG_H__ */
+
diff --git a/libempathy/gossip-message.c b/libempathy/gossip-message.c
new file mode 100644
index 000000000..85889e7da
--- /dev/null
+++ b/libempathy/gossip-message.c
@@ -0,0 +1,365 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2004-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Mikael Hallendal <micke@imendio.com>
+ */
+
+#include "config.h"
+
+#include "gossip-message.h"
+
+#define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_MESSAGE, GossipMessagePriv))
+
+typedef struct _GossipMessagePriv GossipMessagePriv;
+
+struct _GossipMessagePriv {
+ GossipMessageType type;
+ GossipContact *sender;
+ gchar *body;
+ GossipTime timestamp;
+
+};
+
+static void gossip_message_class_init (GossipMessageClass *class);
+static void gossip_message_init (GossipMessage *message);
+static void gossip_message_finalize (GObject *object);
+static void message_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void message_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec);
+
+enum {
+ PROP_0,
+ PROP_TYPE,
+ PROP_SENDER,
+ PROP_BODY,
+ PROP_TIMESTAMP,
+};
+
+static gpointer parent_class = NULL;
+
+GType
+gossip_message_get_gtype (void)
+{
+ static GType type = 0;
+
+ if (!type) {
+ static const GTypeInfo info = {
+ sizeof (GossipMessageClass),
+ NULL, /* base_init */
+ NULL, /* base_finalize */
+ (GClassInitFunc) gossip_message_class_init,
+ NULL, /* class_finalize */
+ NULL, /* class_data */
+ sizeof (GossipMessage),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) gossip_message_init
+ };
+
+ type = g_type_register_static (G_TYPE_OBJECT,
+ "GossipMessage",
+ &info, 0);
+ }
+
+ return type;
+}
+
+static void
+gossip_message_class_init (GossipMessageClass *class)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (class);
+ parent_class = g_type_class_peek_parent (class);
+
+ object_class->finalize = gossip_message_finalize;
+ object_class->get_property = message_get_property;
+ object_class->set_property = message_set_property;
+
+ g_object_class_install_property (object_class,
+ PROP_TYPE,
+ g_param_spec_int ("type",
+ "Message Type",
+ "The type of message",
+ GOSSIP_MESSAGE_TYPE_NORMAL,
+ GOSSIP_MESSAGE_TYPE_LAST,
+ GOSSIP_MESSAGE_TYPE_NORMAL,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_SENDER,
+ g_param_spec_object ("sender",
+ "Message Sender",
+ "The sender of the message",
+ GOSSIP_TYPE_CONTACT,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_BODY,
+ g_param_spec_string ("body",
+ "Message Body",
+ "The content of the message",
+ NULL,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_TIMESTAMP,
+ g_param_spec_long ("timestamp",
+ "timestamp",
+ "timestamp",
+ -1,
+ G_MAXLONG,
+ -1,
+ G_PARAM_READWRITE));
+
+
+ g_type_class_add_private (object_class, sizeof (GossipMessagePriv));
+
+}
+
+static void
+gossip_message_init (GossipMessage *message)
+{
+ GossipMessagePriv *priv;
+
+ priv = GET_PRIV (message);
+
+ priv->type = GOSSIP_MESSAGE_TYPE_NORMAL;
+ priv->sender = NULL;
+ priv->body = NULL;
+ priv->timestamp = gossip_time_get_current ();
+}
+
+static void
+gossip_message_finalize (GObject *object)
+{
+ GossipMessagePriv *priv;
+
+ priv = GET_PRIV (object);
+
+ if (priv->sender) {
+ g_object_unref (priv->sender);
+ }
+
+ g_free (priv->body);
+
+ (G_OBJECT_CLASS (parent_class)->finalize) (object);
+}
+
+static void
+message_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GossipMessagePriv *priv;
+
+ priv = GET_PRIV (object);
+
+ switch (param_id) {
+ case PROP_TYPE:
+ g_value_set_int (value, priv->type);
+ break;
+ case PROP_SENDER:
+ g_value_set_object (value, priv->sender);
+ break;
+ case PROP_BODY:
+ g_value_set_string (value, priv->body);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ };
+}
+
+static void
+message_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GossipMessagePriv *priv;
+
+ priv = GET_PRIV (object);
+
+ switch (param_id) {
+ case PROP_TYPE:
+ gossip_message_set_type (GOSSIP_MESSAGE (object),
+ g_value_get_int (value));
+ break;
+ case PROP_SENDER:
+ gossip_message_set_sender (GOSSIP_MESSAGE (object),
+ GOSSIP_CONTACT (g_value_get_object (value)));
+ break;
+ case PROP_BODY:
+ gossip_message_set_body (GOSSIP_MESSAGE (object),
+ g_value_get_string (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ };
+}
+
+GossipMessage *
+gossip_message_new (const gchar *body)
+{
+ return g_object_new (GOSSIP_TYPE_MESSAGE,
+ "body", body,
+ NULL);
+}
+
+GossipMessageType
+gossip_message_get_type (GossipMessage *message)
+{
+ GossipMessagePriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_MESSAGE (message),
+ GOSSIP_MESSAGE_TYPE_NORMAL);
+
+ priv = GET_PRIV (message);
+
+ return priv->type;
+}
+
+void
+gossip_message_set_type (GossipMessage *message,
+ GossipMessageType type)
+{
+ GossipMessagePriv *priv;
+
+ g_return_if_fail (GOSSIP_IS_MESSAGE (message));
+
+ priv = GET_PRIV (message);
+
+ priv->type = type;
+
+ g_object_notify (G_OBJECT (message), "type");
+}
+
+GossipContact *
+gossip_message_get_sender (GossipMessage *message)
+{
+ GossipMessagePriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_MESSAGE (message), NULL);
+
+ priv = GET_PRIV (message);
+
+ return priv->sender;
+}
+
+void
+gossip_message_set_sender (GossipMessage *message, GossipContact *contact)
+{
+ GossipMessagePriv *priv;
+ GossipContact *old_sender;
+
+ g_return_if_fail (GOSSIP_IS_MESSAGE (message));
+ g_return_if_fail (GOSSIP_IS_CONTACT (contact));
+
+ priv = GET_PRIV (message);
+
+ old_sender = priv->sender;
+ priv->sender = g_object_ref (contact);
+
+ if (old_sender) {
+ g_object_unref (old_sender);
+ }
+
+ g_object_notify (G_OBJECT (message), "sender");
+}
+
+const gchar *
+gossip_message_get_body (GossipMessage *message)
+{
+ GossipMessagePriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_MESSAGE (message), NULL);
+
+ priv = GET_PRIV (message);
+
+ return priv->body;
+}
+
+void
+gossip_message_set_body (GossipMessage *message,
+ const gchar *body)
+{
+ GossipMessagePriv *priv;
+ GossipMessageType type;
+
+ g_return_if_fail (GOSSIP_IS_MESSAGE (message));
+
+ priv = GET_PRIV (message);
+
+ g_free (priv->body);
+ priv->body = NULL;
+
+ type = GOSSIP_MESSAGE_TYPE_NORMAL;
+ if (g_str_has_prefix (body, "/me")) {
+ type = GOSSIP_MESSAGE_TYPE_ACTION;
+ body += 4;
+ }
+
+ if (body) {
+ priv->body = g_strdup (body);
+ }
+
+ if (type != priv->type) {
+ gossip_message_set_type (message, type);
+ }
+
+ g_object_notify (G_OBJECT (message), "body");
+}
+
+GossipTime
+gossip_message_get_timestamp (GossipMessage *message)
+{
+ GossipMessagePriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_MESSAGE (message), -1);
+
+ priv = GET_PRIV (message);
+
+ return priv->timestamp;
+}
+
+void
+gossip_message_set_timestamp (GossipMessage *message,
+ GossipTime timestamp)
+{
+ GossipMessagePriv *priv;
+
+ g_return_if_fail (GOSSIP_IS_MESSAGE (message));
+ g_return_if_fail (timestamp >= -1);
+
+ priv = GET_PRIV (message);
+
+ if (timestamp <= 0) {
+ priv->timestamp = gossip_time_get_current ();
+ } else {
+ priv->timestamp = timestamp;
+ }
+
+ g_object_notify (G_OBJECT (message), "timestamp");
+}
+
diff --git a/libempathy/gossip-message.h b/libempathy/gossip-message.h
new file mode 100644
index 000000000..8defba7e7
--- /dev/null
+++ b/libempathy/gossip-message.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2004 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GOSSIP_MESSAGE_H__
+#define __GOSSIP_MESSAGE_H__
+
+#include <glib-object.h>
+
+#include "gossip-contact.h"
+#include "gossip-time.h"
+
+G_BEGIN_DECLS
+
+#define GOSSIP_TYPE_MESSAGE (gossip_message_get_gtype ())
+#define GOSSIP_MESSAGE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOSSIP_TYPE_MESSAGE, GossipMessage))
+#define GOSSIP_MESSAGE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GOSSIP_TYPE_MESSAGE, GossipMessageClass))
+#define GOSSIP_IS_MESSAGE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOSSIP_TYPE_MESSAGE))
+#define GOSSIP_IS_MESSAGE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GOSSIP_TYPE_MESSAGE))
+#define GOSSIP_MESSAGE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GOSSIP_TYPE_MESSAGE, GossipMessageClass))
+
+typedef struct _GossipMessage GossipMessage;
+typedef struct _GossipMessageClass GossipMessageClass;
+
+struct _GossipMessage {
+ GObject parent;
+};
+
+struct _GossipMessageClass {
+ GObjectClass parent_class;
+};
+
+typedef enum {
+ GOSSIP_MESSAGE_TYPE_NORMAL,
+ GOSSIP_MESSAGE_TYPE_ACTION,
+ GOSSIP_MESSAGE_TYPE_NOTICE,
+ GOSSIP_MESSAGE_TYPE_AUTO_REPLY,
+ GOSSIP_MESSAGE_TYPE_LAST
+} GossipMessageType;
+
+GType gossip_message_get_gtype (void) G_GNUC_CONST;
+GossipMessage * gossip_message_new (const gchar *body);
+GossipMessageType gossip_message_get_type (GossipMessage *message);
+void gossip_message_set_type (GossipMessage *message,
+ GossipMessageType type);
+GossipContact * gossip_message_get_sender (GossipMessage *message);
+void gossip_message_set_sender (GossipMessage *message,
+ GossipContact *contact);
+const gchar * gossip_message_get_body (GossipMessage *message);
+void gossip_message_set_body (GossipMessage *message,
+ const gchar *body);
+/* What return value should we have here? */
+GossipTime gossip_message_get_timestamp (GossipMessage *message);
+void gossip_message_set_timestamp (GossipMessage *message,
+ GossipTime timestamp);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_MESSAGE_H__ */
diff --git a/libempathy/gossip-paths.c b/libempathy/gossip-paths.c
new file mode 100644
index 000000000..aa97bdcc7
--- /dev/null
+++ b/libempathy/gossip-paths.c
@@ -0,0 +1,51 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2006-2007 Imendio AB
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Richard Hult <richard@imendio.com>
+ */
+
+#include "config.h"
+
+#include "gossip-paths.h"
+
+#define EMPATHY "empathy"
+
+gchar *
+gossip_paths_get_glade_path (const gchar *filename)
+{
+ return g_build_filename (DATADIR, EMPATHY, filename, NULL);
+}
+
+gchar *
+gossip_paths_get_image_path (const gchar *filename)
+{
+ return g_build_filename (DATADIR, EMPATHY, filename, NULL);
+}
+
+gchar *
+gossip_paths_get_dtd_path (const gchar *filename)
+{
+ return g_build_filename (DATADIR, EMPATHY, filename, NULL);
+}
+
+gchar *
+gossip_paths_get_locale_path ()
+{
+ return g_strdup (LOCALEDIR);
+}
diff --git a/libempathy/gossip-paths.h b/libempathy/gossip-paths.h
new file mode 100644
index 000000000..e00a33e28
--- /dev/null
+++ b/libempathy/gossip-paths.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2006 Imendio AB
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GOSSIP_PATHS_H__
+#define __GOSSIP_PATHS_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+gchar *gossip_paths_get_glade_path (const gchar *filename);
+gchar *gossip_paths_get_image_path (const gchar *filename);
+gchar *gossip_paths_get_dtd_path (const gchar *filename);
+gchar *gossip_paths_get_sound_path (const gchar *filename);
+gchar *gossip_paths_get_locale_path (void);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_PATHS_H__ */
diff --git a/libempathy/gossip-presence.c b/libempathy/gossip-presence.c
new file mode 100644
index 000000000..e41ae5548
--- /dev/null
+++ b/libempathy/gossip-presence.c
@@ -0,0 +1,441 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2004-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Mikael Hallendal <micke@imendio.com>
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <glib/gi18n.h>
+
+#include "gossip-presence.h"
+#include "gossip-time.h"
+
+#define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_PRESENCE, GossipPresencePriv))
+
+typedef struct _GossipPresencePriv GossipPresencePriv;
+
+struct _GossipPresencePriv {
+ GossipPresenceState state;
+
+ gchar *status;
+ gchar *resource;
+
+ gint priority;
+ GossipTime timestamp;
+};
+
+static void presence_finalize (GObject *object);
+static void presence_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void presence_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec);
+
+enum {
+ PROP_0,
+ PROP_STATE,
+ PROP_STATUS,
+ PROP_RESOURCE,
+ PROP_PRIORITY
+};
+
+G_DEFINE_TYPE (GossipPresence, gossip_presence, G_TYPE_OBJECT);
+
+static void
+gossip_presence_class_init (GossipPresenceClass *class)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (class);
+
+ object_class->finalize = presence_finalize;
+ object_class->get_property = presence_get_property;
+ object_class->set_property = presence_set_property;
+
+ g_object_class_install_property (object_class,
+ PROP_STATE,
+ g_param_spec_int ("state",
+ "Presence State",
+ "The current state of the presence",
+ GOSSIP_PRESENCE_STATE_AVAILABLE,
+ GOSSIP_PRESENCE_STATE_EXT_AWAY,
+ GOSSIP_PRESENCE_STATE_AVAILABLE,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_STATUS,
+ g_param_spec_string ("status",
+ "Presence Status",
+ "Status string set on presence",
+ NULL,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_RESOURCE,
+ g_param_spec_string ("resource",
+ "Presence Resource",
+ "Resource that this presence is for",
+ NULL,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_PRIORITY,
+ g_param_spec_int ("priority",
+ "Presence Priority",
+ "Priority value of presence",
+ G_MININT,
+ G_MAXINT,
+ 0,
+ G_PARAM_READWRITE));
+
+ g_type_class_add_private (object_class, sizeof (GossipPresencePriv));
+}
+
+static void
+gossip_presence_init (GossipPresence *presence)
+{
+ GossipPresencePriv *priv;
+
+ priv = GET_PRIV (presence);
+
+ priv->state = GOSSIP_PRESENCE_STATE_AVAILABLE;
+
+ priv->status = NULL;
+ priv->resource = NULL;
+
+ priv->priority = 0;
+
+ priv->timestamp = gossip_time_get_current ();
+}
+
+static void
+presence_finalize (GObject *object)
+{
+ GossipPresencePriv *priv;
+
+ priv = GET_PRIV (object);
+
+ g_free (priv->status);
+ g_free (priv->resource);
+
+ (G_OBJECT_CLASS (gossip_presence_parent_class)->finalize) (object);
+}
+
+static void
+presence_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GossipPresencePriv *priv;
+
+ priv = GET_PRIV (object);
+
+ switch (param_id) {
+ case PROP_STATE:
+ g_value_set_int (value, priv->state);
+ break;
+ case PROP_STATUS:
+ g_value_set_string (value,
+ gossip_presence_get_status (GOSSIP_PRESENCE (object)));
+ break;
+ case PROP_RESOURCE:
+ g_value_set_string (value,
+ gossip_presence_get_resource (GOSSIP_PRESENCE (object)));
+ break;
+ case PROP_PRIORITY:
+ g_value_set_int (value, priv->priority);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ }
+}
+static void
+presence_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GossipPresencePriv *priv;
+
+ priv = GET_PRIV (object);
+
+ switch (param_id) {
+ case PROP_STATE:
+ priv->state = g_value_get_int (value);
+ break;
+ case PROP_STATUS:
+ gossip_presence_set_status (GOSSIP_PRESENCE (object),
+ g_value_get_string (value));
+ break;
+ case PROP_RESOURCE:
+ gossip_presence_set_resource (GOSSIP_PRESENCE (object),
+ g_value_get_string (value));
+ break;
+ case PROP_PRIORITY:
+ priv->priority = g_value_get_int (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ }
+}
+
+GossipPresence *
+gossip_presence_new (void)
+{
+ return g_object_new (GOSSIP_TYPE_PRESENCE, NULL);
+}
+
+GossipPresence *
+gossip_presence_new_full (GossipPresenceState state,
+ const gchar *status)
+{
+ return g_object_new (GOSSIP_TYPE_PRESENCE,
+ "state", state,
+ "status", status,
+ NULL);
+}
+
+const gchar *
+gossip_presence_get_resource (GossipPresence *presence)
+{
+ GossipPresencePriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_PRESENCE (presence), NULL);
+
+ priv = GET_PRIV (presence);
+
+ if (priv->resource) {
+ return priv->resource;
+ }
+
+ return NULL;
+}
+
+const gchar *
+gossip_presence_get_status (GossipPresence *presence)
+{
+ GossipPresencePriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_PRESENCE (presence),
+ _("Offline"));
+
+ priv = GET_PRIV (presence);
+
+ return priv->status;
+}
+
+gint
+gossip_presence_get_priority (GossipPresence *presence)
+{
+ GossipPresencePriv *priv;
+
+ priv = GET_PRIV (presence);
+ g_return_val_if_fail (GOSSIP_IS_PRESENCE (presence), 0);
+
+ return priv->priority;
+}
+
+void
+gossip_presence_set_resource (GossipPresence *presence,
+ const gchar *resource)
+{
+ GossipPresencePriv *priv;
+
+ g_return_if_fail (GOSSIP_IS_PRESENCE (presence));
+ g_return_if_fail (resource != NULL);
+
+ priv = GET_PRIV (presence);
+
+ g_free (priv->resource);
+ priv->resource = g_strdup (resource);
+
+ g_object_notify (G_OBJECT (presence), "resource");
+}
+
+GossipPresenceState
+gossip_presence_get_state (GossipPresence *presence)
+{
+ GossipPresencePriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_PRESENCE (presence),
+ GOSSIP_PRESENCE_STATE_AVAILABLE);
+
+ priv = GET_PRIV (presence);
+
+ return priv->state;
+}
+
+void
+gossip_presence_set_state (GossipPresence *presence,
+ GossipPresenceState state)
+{
+ GossipPresencePriv *priv;
+
+ g_return_if_fail (GOSSIP_IS_PRESENCE (presence));
+
+ priv = GET_PRIV (presence);
+
+ priv->state = state;
+
+ g_object_notify (G_OBJECT (presence), "state");
+}
+
+void
+gossip_presence_set_status (GossipPresence *presence,
+ const gchar *status)
+{
+ GossipPresencePriv *priv;
+
+ priv = GET_PRIV (presence);
+ g_return_if_fail (GOSSIP_IS_PRESENCE (presence));
+
+ g_free (priv->status);
+
+ if (status) {
+ priv->status = g_strdup (status);
+ } else {
+ priv->status = NULL;
+ }
+
+ g_object_notify (G_OBJECT (presence), "status");
+}
+
+void
+gossip_presence_set_priority (GossipPresence *presence,
+ gint priority)
+{
+ GossipPresencePriv *priv;
+
+ g_return_if_fail (GOSSIP_IS_PRESENCE (presence));
+
+ priv = GET_PRIV (presence);
+
+ priv->priority = priority;
+
+ g_object_notify (G_OBJECT (presence), "priority");
+}
+
+gboolean
+gossip_presence_resource_equal (gconstpointer a,
+ gconstpointer b)
+{
+ GossipPresencePriv *priv1;
+ GossipPresencePriv *priv2;
+
+ g_return_val_if_fail (GOSSIP_IS_PRESENCE (a), FALSE);
+ g_return_val_if_fail (GOSSIP_IS_PRESENCE (b), FALSE);
+
+ priv1 = GET_PRIV (a);
+ priv2 = GET_PRIV (b);
+
+ if (!priv1->resource) {
+ if (!priv2->resource) {
+ return TRUE;
+ }
+
+ return FALSE;
+ }
+
+ if (!priv2->resource) {
+ return FALSE;
+ }
+
+ if (strcmp (priv1->resource, priv2->resource) == 0) {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+gint
+gossip_presence_sort_func (gconstpointer a,
+ gconstpointer b)
+{
+ GossipPresencePriv *priv_a;
+ GossipPresencePriv *priv_b;
+ gint diff;
+
+ g_return_val_if_fail (GOSSIP_IS_PRESENCE (a), 0);
+ g_return_val_if_fail (GOSSIP_IS_PRESENCE (b), 0);
+
+ /* We sort here by priority AND status, in theory, the
+ * priority would be enough for JUST Jabber contacts which
+ * actually abide to the protocol, but for other protocols and
+ * dodgy clients, we will sort by:
+ *
+ * 1. State
+ * 2. Priority
+ * 3. Time it was set (most recent first).
+ */
+
+ priv_a = GET_PRIV (a);
+ priv_b = GET_PRIV (b);
+
+ /* 1. State */
+ diff = priv_a->state - priv_b->state;
+ if (diff != 0) {
+ return diff < 1 ? -1 : +1;
+ }
+
+ /* 2. Priority */
+ diff = priv_a->priority - priv_b->priority;
+ if (diff != 0) {
+ return diff < 1 ? -1 : +1;
+ }
+
+ /* 3. Time (newest first) */
+ diff = priv_b->timestamp - priv_a->timestamp;
+ if (diff != 0) {
+ return diff < 1 ? -1 : +1;
+ }
+
+ /* No real difference, except maybe resource */
+ return 0;
+}
+
+const gchar *
+gossip_presence_state_get_default_status (GossipPresenceState state)
+{
+ switch (state) {
+ case GOSSIP_PRESENCE_STATE_AVAILABLE:
+ return _("Available");
+ break;
+
+ case GOSSIP_PRESENCE_STATE_BUSY:
+ return _("Busy");
+ break;
+
+ case GOSSIP_PRESENCE_STATE_AWAY:
+ case GOSSIP_PRESENCE_STATE_EXT_AWAY:
+ return _("Away");
+ break;
+
+ case GOSSIP_PRESENCE_STATE_HIDDEN:
+ case GOSSIP_PRESENCE_STATE_UNAVAILABLE:
+ return _("Unavailable");
+ }
+
+ return _("Available");
+}
diff --git a/libempathy/gossip-presence.h b/libempathy/gossip-presence.h
new file mode 100644
index 000000000..64a0b8ec3
--- /dev/null
+++ b/libempathy/gossip-presence.h
@@ -0,0 +1,84 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2004 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GOSSIP_PRESENCE_H__
+#define __GOSSIP_PRESENCE_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GOSSIP_TYPE_PRESENCE (gossip_presence_get_type ())
+#define GOSSIP_PRESENCE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOSSIP_TYPE_PRESENCE, GossipPresence))
+#define GOSSIP_PRESENCE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GOSSIP_TYPE_PRESENCE, GossipPresenceClass))
+#define GOSSIP_IS_PRESENCE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOSSIP_TYPE_PRESENCE))
+#define GOSSIP_IS_PRESENCE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GOSSIP_TYPE_PRESENCE))
+#define GOSSIP_PRESENCE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GOSSIP_TYPE_PRESENCE, GossipPresenceClass))
+
+typedef struct _GossipPresence GossipPresence;
+typedef struct _GossipPresenceClass GossipPresenceClass;
+
+struct _GossipPresence {
+ GObject parent;
+};
+
+struct _GossipPresenceClass {
+ GObjectClass parent_class;
+};
+
+typedef enum {
+ GOSSIP_PRESENCE_STATE_AVAILABLE,
+ GOSSIP_PRESENCE_STATE_BUSY,
+ GOSSIP_PRESENCE_STATE_AWAY,
+ GOSSIP_PRESENCE_STATE_EXT_AWAY,
+ GOSSIP_PRESENCE_STATE_HIDDEN, /* When you appear offline to others */
+ GOSSIP_PRESENCE_STATE_UNAVAILABLE,
+} GossipPresenceState;
+
+GType gossip_presence_get_type (void) G_GNUC_CONST;
+
+GossipPresence * gossip_presence_new (void);
+GossipPresence * gossip_presence_new_full (GossipPresenceState state,
+ const gchar *status);
+
+const gchar * gossip_presence_get_resource (GossipPresence *presence);
+GossipPresenceState gossip_presence_get_state (GossipPresence *presence);
+const gchar * gossip_presence_get_status (GossipPresence *presence);
+gint gossip_presence_get_priority (GossipPresence *presence);
+
+void gossip_presence_set_resource (GossipPresence *presence,
+ const gchar *resource);
+void gossip_presence_set_state (GossipPresence *presence,
+ GossipPresenceState state);
+void gossip_presence_set_status (GossipPresence *presence,
+ const gchar *status);
+void gossip_presence_set_priority (GossipPresence *presence,
+ gint priority);
+gboolean gossip_presence_resource_equal (gconstpointer a,
+ gconstpointer b);
+gint gossip_presence_sort_func (gconstpointer a,
+ gconstpointer b);
+
+const gchar * gossip_presence_state_get_default_status (GossipPresenceState state);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_PRESENCE_H__ */
+
diff --git a/libempathy/gossip-telepathy-group.c b/libempathy/gossip-telepathy-group.c
new file mode 100644
index 000000000..4b04ac42f
--- /dev/null
+++ b/libempathy/gossip-telepathy-group.c
@@ -0,0 +1,496 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2006 Xavier Claessens <xclaesse@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+
+#include <dbus/dbus-glib.h>
+#include <libtelepathy/tp-chan.h>
+#include <libtelepathy/tp-chan-iface-group-gen.h>
+#include <libtelepathy/tp-constants.h>
+#include <libtelepathy/tp-conn.h>
+
+#include "gossip-debug.h"
+#include "gossip-telepathy-group.h"
+#include "empathy-marshal.h"
+
+#define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \
+ GOSSIP_TYPE_TELEPATHY_GROUP, GossipTelepathyGroupPriv))
+
+#define DEBUG_DOMAIN "TelepathyGroup"
+
+struct _GossipTelepathyGroupPriv {
+ DBusGProxy *group_iface;
+ TpConn *tp_conn;
+ TpChan *tp_chan;
+ gchar *group_name;
+};
+
+static void gossip_telepathy_group_class_init (GossipTelepathyGroupClass *klass);
+static void gossip_telepathy_group_init (GossipTelepathyGroup *group);
+static void telepathy_group_finalize (GObject *object);
+static void telepathy_group_destroy_cb (DBusGProxy *proxy,
+ GossipTelepathyGroup *group);
+static void telepathy_group_members_changed_cb (DBusGProxy *group_iface,
+ gchar *message,
+ GArray *added,
+ GArray *removed,
+ GArray *local_pending,
+ GArray *remote_pending,
+ guint actor,
+ guint reason,
+ GossipTelepathyGroup *group);
+
+enum {
+ MEMBERS_ADDED,
+ MEMBERS_REMOVED,
+ LOCAL_PENDING,
+ REMOTE_PENDING,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE (GossipTelepathyGroup, gossip_telepathy_group, G_TYPE_OBJECT);
+
+static void
+gossip_telepathy_group_class_init (GossipTelepathyGroupClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = telepathy_group_finalize;
+
+ signals[MEMBERS_ADDED] =
+ g_signal_new ("members-added",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ empathy_marshal_VOID__POINTER_UINT_UINT_STRING,
+ G_TYPE_NONE,
+ 4, G_TYPE_POINTER, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_STRING);
+
+ signals[MEMBERS_REMOVED] =
+ g_signal_new ("members-removed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ empathy_marshal_VOID__POINTER_UINT_UINT_STRING,
+ G_TYPE_NONE,
+ 4, G_TYPE_POINTER, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_STRING);
+
+ signals[LOCAL_PENDING] =
+ g_signal_new ("local-pending",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ empathy_marshal_VOID__POINTER_UINT_UINT_STRING,
+ G_TYPE_NONE,
+ 4, G_TYPE_POINTER, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_STRING);
+
+ signals[REMOTE_PENDING] =
+ g_signal_new ("remote-pending",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ empathy_marshal_VOID__POINTER_UINT_UINT_STRING,
+ G_TYPE_NONE,
+ 4, G_TYPE_POINTER, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_STRING);
+
+ g_type_class_add_private (object_class, sizeof (GossipTelepathyGroupPriv));
+}
+
+static void
+gossip_telepathy_group_init (GossipTelepathyGroup *group)
+{
+}
+
+static void
+telepathy_group_finalize (GObject *object)
+{
+ GossipTelepathyGroupPriv *priv;
+
+ priv = GET_PRIV (object);
+
+ if (priv->group_iface) {
+ g_signal_handlers_disconnect_by_func (priv->group_iface,
+ telepathy_group_destroy_cb,
+ object);
+ dbus_g_proxy_disconnect_signal (priv->group_iface, "MembersChanged",
+ G_CALLBACK (telepathy_group_members_changed_cb),
+ object);
+ g_object_unref (priv->group_iface);
+ }
+
+ if (priv->tp_conn) {
+ g_object_unref (priv->tp_conn);
+ }
+
+ if (priv->tp_chan) {
+ g_object_unref (priv->tp_chan);
+ }
+
+ g_free (priv->group_name);
+
+ G_OBJECT_CLASS (gossip_telepathy_group_parent_class)->finalize (object);
+}
+
+GossipTelepathyGroup *
+gossip_telepathy_group_new (TpChan *tp_chan,
+ TpConn *tp_conn)
+{
+ GossipTelepathyGroup *group;
+ GossipTelepathyGroupPriv *priv;
+ DBusGProxy *group_iface;
+
+ g_return_val_if_fail (TELEPATHY_IS_CHAN (tp_chan), NULL);
+
+ group_iface = tp_chan_get_interface (tp_chan,
+ TELEPATHY_CHAN_IFACE_GROUP_QUARK);
+ g_return_val_if_fail (group_iface != NULL, NULL);
+
+ group = g_object_new (GOSSIP_TYPE_TELEPATHY_GROUP, NULL);
+ priv = GET_PRIV (group);
+
+ priv->tp_conn = g_object_ref (tp_conn);
+ priv->tp_chan = g_object_ref (tp_chan);
+ priv->group_iface = g_object_ref (group_iface);
+
+ dbus_g_proxy_connect_signal (priv->group_iface, "MembersChanged",
+ G_CALLBACK (telepathy_group_members_changed_cb),
+ group, NULL);
+ g_signal_connect (group_iface, "destroy",
+ G_CALLBACK (telepathy_group_destroy_cb),
+ group);
+
+
+ return group;
+}
+
+void
+gossip_telepathy_group_add_members (GossipTelepathyGroup *group,
+ GArray *handles,
+ const gchar *message)
+{
+ GossipTelepathyGroupPriv *priv;
+ GError *error = NULL;
+
+ g_return_if_fail (GOSSIP_IS_TELEPATHY_GROUP (group));
+ g_return_if_fail (handles != NULL);
+
+ priv = GET_PRIV (group);
+
+ if (!tp_chan_iface_group_add_members (priv->group_iface,
+ handles,
+ message,
+ &error)) {
+ gossip_debug (DEBUG_DOMAIN,
+ "Failed to add members: %s",
+ error ? error->message : "No error given");
+ g_clear_error (&error);
+ }
+}
+
+void
+gossip_telepathy_group_add_member (GossipTelepathyGroup *group,
+ guint handle,
+ const gchar *message)
+{
+ GArray *handles;
+
+ handles = g_array_new (FALSE, FALSE, sizeof (guint));
+ g_array_append_val (handles, handle);
+
+ gossip_telepathy_group_add_members (group, handles, message);
+
+ g_array_free (handles, TRUE);
+}
+
+void
+gossip_telepathy_group_remove_members (GossipTelepathyGroup *group,
+ GArray *handles,
+ const gchar *message)
+{
+ GossipTelepathyGroupPriv *priv;
+ GError *error = NULL;
+
+ g_return_if_fail (GOSSIP_IS_TELEPATHY_GROUP (group));
+
+ priv = GET_PRIV (group);
+
+ if (!tp_chan_iface_group_remove_members (priv->group_iface,
+ handles,
+ message,
+ &error)) {
+ gossip_debug (DEBUG_DOMAIN,
+ "Failed to remove members: %s",
+ error ? error->message : "No error given");
+ g_clear_error (&error);
+ }
+}
+
+void
+gossip_telepathy_group_remove_member (GossipTelepathyGroup *group,
+ guint handle,
+ const gchar *message)
+{
+ GArray *handles;
+
+ g_return_if_fail (GOSSIP_IS_TELEPATHY_GROUP (group));
+
+ handles = g_array_new (FALSE, FALSE, sizeof (guint));
+ g_array_append_val (handles, handle);
+
+ gossip_telepathy_group_remove_members (group, handles, message);
+
+ g_array_free (handles, TRUE);
+}
+
+GArray *
+gossip_telepathy_group_get_members (GossipTelepathyGroup *group)
+{
+ GossipTelepathyGroupPriv *priv;
+ GArray *members;
+ GError *error = NULL;
+
+ g_return_val_if_fail (GOSSIP_IS_TELEPATHY_GROUP (group), NULL);
+
+ priv = GET_PRIV (group);
+
+ if (!tp_chan_iface_group_get_members (priv->group_iface,
+ &members,
+ &error)) {
+ gossip_debug (DEBUG_DOMAIN,
+ "Couldn't get members: %s",
+ error ? error->message : "No error given");
+ g_clear_error (&error);
+ return NULL;
+ }
+
+ return members;
+}
+
+void
+gossip_telepathy_group_get_all_members (GossipTelepathyGroup *group,
+ GArray **members,
+ GArray **local_pending,
+ GArray **remote_pending)
+{
+ GossipTelepathyGroupPriv *priv;
+ GError *error = NULL;
+
+ g_return_if_fail (GOSSIP_IS_TELEPATHY_GROUP (group));
+
+ priv = GET_PRIV (group);
+
+ if (!tp_chan_iface_group_get_all_members (priv->group_iface,
+ members,
+ local_pending,
+ remote_pending,
+ &error)) {
+ gossip_debug (DEBUG_DOMAIN,
+ "Couldn't get all members: %s",
+ error ? error->message : "No error given");
+ g_clear_error (&error);
+ }
+}
+
+GPtrArray *
+gossip_telepathy_group_get_local_pending_members_with_info (GossipTelepathyGroup *group)
+{
+ GossipTelepathyGroupPriv *priv;
+ GPtrArray *info = NULL;
+ GError *error = NULL;
+
+ g_return_val_if_fail (GOSSIP_IS_TELEPATHY_GROUP (group), NULL);
+
+ priv = GET_PRIV (group);
+
+ if (!tp_chan_iface_group_get_local_pending_members_with_info (priv->group_iface,
+ &info,
+ &error)) {
+ gossip_debug (DEBUG_DOMAIN,
+ "GetLocalPendingMembersWithInfo failed: %s",
+ error ? error->message : "No error given");
+ g_clear_error (&error);
+ }
+
+ return info;
+}
+
+static void
+telepathy_group_destroy_cb (DBusGProxy *proxy,
+ GossipTelepathyGroup *group)
+{
+ GossipTelepathyGroupPriv *priv;
+
+ priv = GET_PRIV (group);
+
+ g_object_unref (priv->group_iface);
+ g_object_unref (priv->tp_conn);
+ g_object_unref (priv->tp_chan);
+ priv->group_iface = NULL;
+ priv->tp_chan = NULL;
+ priv->tp_conn = NULL;
+}
+
+static void
+telepathy_group_members_changed_cb (DBusGProxy *group_iface,
+ gchar *message,
+ GArray *added,
+ GArray *removed,
+ GArray *local_pending,
+ GArray *remote_pending,
+ guint actor,
+ guint reason,
+ GossipTelepathyGroup *group)
+{
+ GossipTelepathyGroupPriv *priv;
+
+ priv = GET_PRIV (group);
+
+ /* emit signals */
+ if (added->len > 0) {
+ g_signal_emit (group, signals[MEMBERS_ADDED], 0,
+ added, actor, reason, message);
+ }
+ if (removed->len > 0) {
+ g_signal_emit (group, signals[MEMBERS_REMOVED], 0,
+ removed, actor, reason, message);
+ }
+ if (local_pending->len > 0) {
+ g_signal_emit (group, signals[LOCAL_PENDING], 0,
+ local_pending, actor, reason, message);
+ }
+ if (remote_pending->len > 0) {
+ g_signal_emit (group, signals[REMOTE_PENDING], 0,
+ remote_pending, actor, reason, message);
+ }
+}
+
+const gchar *
+gossip_telepathy_group_get_name (GossipTelepathyGroup *group)
+{
+ TelepathyHandleType handle_type;
+ guint channel_handle;
+ GArray *group_handles;
+ gchar **group_names;
+ GError *error = NULL;
+
+ GossipTelepathyGroupPriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_TELEPATHY_GROUP (group), NULL);
+
+ priv = GET_PRIV (group);
+
+ /* Lazy initialisation */
+ if (priv->group_name) {
+ return priv->group_name;
+ }
+
+ if (!tp_chan_get_handle (DBUS_G_PROXY (priv->tp_chan),
+ &handle_type,
+ &channel_handle,
+ &error)) {
+ gossip_debug (DEBUG_DOMAIN,
+ "Couldn't retreive channel handle for group: %s",
+ error ? error->message : "No error given");
+ g_clear_error (&error);
+ return NULL;
+ }
+
+ group_handles = g_array_new (FALSE, FALSE, sizeof (gint));
+ g_array_append_val (group_handles, channel_handle);
+ if (!tp_conn_inspect_handles (DBUS_G_PROXY (priv->tp_conn),
+ handle_type,
+ group_handles,
+ &group_names,
+ &error)) {
+ gossip_debug (DEBUG_DOMAIN,
+ "Couldn't get group name: %s",
+ error ? error->message : "No error given");
+ g_clear_error (&error);
+ g_array_free (group_handles, TRUE);
+ return NULL;
+ }
+
+ priv->group_name = *group_names;
+ g_array_free (group_handles, TRUE);
+ g_free (group_names);
+
+ return priv->group_name;
+}
+
+guint
+gossip_telepathy_group_get_self_handle (GossipTelepathyGroup *group)
+{
+ GossipTelepathyGroupPriv *priv;
+ guint handle;
+ GError *error = NULL;
+
+ g_return_val_if_fail (GOSSIP_IS_TELEPATHY_GROUP (group), 0 );
+
+ priv = GET_PRIV (group);
+
+ if (!tp_chan_iface_group_get_self_handle (priv->group_iface, &handle, &error)) {
+ gossip_debug (DEBUG_DOMAIN,
+ "Failed to get self handle: %s",
+ error ? error->message : "No error given");
+ g_clear_error (&error);
+ return 0;
+ }
+
+ return handle;
+}
+
+const gchar *
+gossip_telepathy_group_get_object_path (GossipTelepathyGroup *group)
+{
+ GossipTelepathyGroupPriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_TELEPATHY_GROUP (group), NULL);
+
+ priv = GET_PRIV (group);
+
+ return dbus_g_proxy_get_path (DBUS_G_PROXY (priv->tp_chan));
+}
+
+gboolean
+gossip_telepathy_group_is_member (GossipTelepathyGroup *group,
+ guint handle)
+{
+ GArray *members;
+ guint i;
+ gboolean found = FALSE;
+
+ members = gossip_telepathy_group_get_members (group);
+ for (i = 0; i < members->len; i++) {
+ if (g_array_index (members, guint, i) == handle) {
+ found = TRUE;
+ break;
+ }
+ }
+ g_array_free (members, TRUE);
+
+ return found;
+}
+
diff --git a/libempathy/gossip-telepathy-group.h b/libempathy/gossip-telepathy-group.h
new file mode 100644
index 000000000..9c61bdbc4
--- /dev/null
+++ b/libempathy/gossip-telepathy-group.h
@@ -0,0 +1,79 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2006 Xavier Claessens <xclaesse@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GOSSIP_TELEPATHY_GROUP_H__
+#define __GOSSIP_TELEPATHY_GROUP_H__
+
+#include <glib.h>
+
+#include <libtelepathy/tp-chan.h>
+
+G_BEGIN_DECLS
+
+#define GOSSIP_TYPE_TELEPATHY_GROUP (gossip_telepathy_group_get_type ())
+#define GOSSIP_TELEPATHY_GROUP(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOSSIP_TYPE_TELEPATHY_GROUP, GossipTelepathyGroup))
+#define GOSSIP_TELEPATHY_GROUP_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GOSSIP_TYPE_TELEPATHY_GROUP, GossipTelepathyGroupClass))
+#define GOSSIP_IS_TELEPATHY_GROUP(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOSSIP_TYPE_TELEPATHY_GROUP))
+#define GOSSIP_IS_TELEPATHY_GROUP_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GOSSIP_TYPE_TELEPATHY_GROUP))
+#define GOSSIP_TELEPATHY_GROUP_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GOSSIP_TYPE_TELEPATHY_GROUP, GossipTelepathyGroupClass))
+
+typedef struct _GossipTelepathyGroup GossipTelepathyGroup;
+typedef struct _GossipTelepathyGroupClass GossipTelepathyGroupClass;
+typedef struct _GossipTelepathyGroupPriv GossipTelepathyGroupPriv;
+
+struct _GossipTelepathyGroup {
+ GObject parent;
+};
+
+struct _GossipTelepathyGroupClass {
+ GObjectClass parent_class;
+};
+
+GType gossip_telepathy_group_get_type (void) G_GNUC_CONST;
+GossipTelepathyGroup *gossip_telepathy_group_new (TpChan *tp_chan,
+ TpConn *tp_conn);
+void gossip_telepathy_group_add_members (GossipTelepathyGroup *group,
+ GArray *handles,
+ const gchar *message);
+void gossip_telepathy_group_add_member (GossipTelepathyGroup *group,
+ guint handle,
+ const gchar *message);
+void gossip_telepathy_group_remove_members (GossipTelepathyGroup *group,
+ GArray *handle,
+ const gchar *message);
+void gossip_telepathy_group_remove_member (GossipTelepathyGroup *group,
+ guint handle,
+ const gchar *message);
+GArray * gossip_telepathy_group_get_members (GossipTelepathyGroup *group);
+void gossip_telepathy_group_get_all_members (GossipTelepathyGroup *group,
+ GArray **members,
+ GArray **local_pending,
+ GArray **remote_pending);
+GPtrArray * gossip_telepathy_group_get_local_pending_members_with_info
+ (GossipTelepathyGroup *group);
+const gchar * gossip_telepathy_group_get_name (GossipTelepathyGroup *group);
+guint gossip_telepathy_group_get_self_handle (GossipTelepathyGroup *group);
+const gchar * gossip_telepathy_group_get_object_path (GossipTelepathyGroup *group);
+gboolean gossip_telepathy_group_is_member (GossipTelepathyGroup *group,
+ guint handle);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_TELEPATHY_GROUP_H__ */
diff --git a/libempathy/gossip-time.c b/libempathy/gossip-time.c
new file mode 100644
index 000000000..a1956354e
--- /dev/null
+++ b/libempathy/gossip-time.c
@@ -0,0 +1,124 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2003-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Richard Hult <richard@imendio.com>
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "gossip-time.h"
+
+/* Note: GossipTime is always in UTC. */
+
+GossipTime
+gossip_time_get_current (void)
+{
+ return time (NULL);
+}
+
+time_t
+gossip_time_get_local_time (struct tm *tm)
+{
+ const gchar *timezone;
+ time_t t;
+
+ timezone = g_getenv ("TZ");
+ g_setenv ("TZ", "", TRUE);
+
+ tzset ();
+
+ t = mktime (tm);
+
+ if (timezone) {
+ g_setenv ("TZ", timezone, TRUE);
+ } else {
+ g_unsetenv ("TZ");
+ }
+
+ tzset ();
+
+ return t;
+}
+
+/* The format is: "20021209T23:51:30" and is in UTC. 0 is returned on
+ * failure. The alternative format "20021209" is also accepted.
+ */
+GossipTime
+gossip_time_parse (const gchar *str)
+{
+ struct tm tm;
+ gint year, month;
+ gint n_parsed;
+
+ memset (&tm, 0, sizeof (struct tm));
+
+ n_parsed = sscanf (str, "%4d%2d%2dT%2d:%2d:%2d",
+ &year, &month, &tm.tm_mday, &tm.tm_hour,
+ &tm.tm_min, &tm.tm_sec);
+ if (n_parsed != 3 && n_parsed != 6) {
+ return 0;
+ }
+
+ tm.tm_year = year - 1900;
+ tm.tm_mon = month - 1;
+ tm.tm_isdst = -1;
+
+ return gossip_time_get_local_time (&tm);
+}
+
+/* Converts the UTC timestamp to a string, also in UTC. Returns NULL on failure. */
+gchar *
+gossip_time_to_string_utc (GossipTime t,
+ const gchar *format)
+{
+ gchar stamp[128];
+ struct tm *tm;
+
+ g_return_val_if_fail (format != NULL, NULL);
+
+ tm = gmtime (&t);
+ if (strftime (stamp, sizeof (stamp), format, tm) == 0) {
+ return NULL;
+ }
+
+ return g_strdup (stamp);
+}
+
+/* Converts the UTC timestamp to a string, in local time. Returns NULL on failure. */
+gchar *
+gossip_time_to_string_local (GossipTime t,
+ const gchar *format)
+{
+ gchar stamp[128];
+ struct tm *tm;
+
+ g_return_val_if_fail (format != NULL, NULL);
+
+ tm = localtime (&t);
+ if (strftime (stamp, sizeof (stamp), format, tm) == 0) {
+ return NULL;
+ }
+
+ return g_strdup (stamp);
+}
+
diff --git a/libempathy/gossip-time.h b/libempathy/gossip-time.h
new file mode 100644
index 000000000..06057aa52
--- /dev/null
+++ b/libempathy/gossip-time.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2004 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GOSSIP_TIME_H__
+#define __GOSSIP_TIME_H__
+
+#define __USE_XOPEN
+#include <time.h>
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+#define GOSSIP_TIME_FORMAT_DISPLAY_SHORT "%H:%M"
+#define GOSSIP_TIME_FORMAT_DISPLAY_LONG "%a %d %b %Y"
+
+/* Note: Always in UTC. */
+typedef long GossipTime;
+
+GossipTime gossip_time_get_current (void);
+time_t gossip_time_get_local_time (struct tm *tm);
+GossipTime gossip_time_parse (const gchar *str);
+GossipTime gossip_time_parse_format (const gchar *str,
+ const gchar *format);
+gchar *gossip_time_to_string_utc (GossipTime t,
+ const gchar *format);
+gchar *gossip_time_to_string_local (GossipTime t,
+ const gchar *format);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_TIME_H__ */
+
diff --git a/libempathy/gossip-utils.c b/libempathy/gossip-utils.c
new file mode 100644
index 000000000..24f5cd432
--- /dev/null
+++ b/libempathy/gossip-utils.c
@@ -0,0 +1,447 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2003-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Richard Hult <richard@imendio.com>
+ * Martyn Russell <martyn@imendio.com>
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <time.h>
+#include <sys/types.h>
+#include <regex.h>
+
+#include <glib/gi18n.h>
+
+#include <libxml/uri.h>
+#include <libmissioncontrol/mc-account.h>
+
+#include "gossip-debug.h"
+#include "gossip-utils.h"
+#include "gossip-paths.h"
+#include "empathy-session.h"
+#include "empathy-contact-manager.h"
+
+#define DEBUG_DOMAIN "Utils"
+
+static void regex_init (void);
+
+gchar *
+gossip_substring (const gchar *str,
+ gint start,
+ gint end)
+{
+ return g_strndup (str + start, end - start);
+}
+
+/*
+ * Regular Expression code to match urls.
+ */
+#define USERCHARS "-A-Za-z0-9"
+#define PASSCHARS "-A-Za-z0-9,?;.:/!%$^*&~\"#'"
+#define HOSTCHARS "-A-Za-z0-9"
+#define PATHCHARS "-A-Za-z0-9_$.+!*(),;:@&=?/~#%"
+#define SCHEME "(news:|telnet:|nntp:|file:/|https?:|ftps?:|webcal:)"
+#define USER "[" USERCHARS "]+(:["PASSCHARS "]+)?"
+#define URLPATH "/[" PATHCHARS "]*[^]'.}>) \t\r\n,\\\"]"
+
+static regex_t dingus[GOSSIP_REGEX_ALL];
+
+static void
+regex_init (void)
+{
+ static gboolean inited = FALSE;
+ const gchar *expression;
+ gint i;
+
+ if (inited) {
+ return;
+ }
+
+ for (i = 0; i < GOSSIP_REGEX_ALL; i++) {
+ switch (i) {
+ case GOSSIP_REGEX_AS_IS:
+ expression =
+ SCHEME "//(" USER "@)?[" HOSTCHARS ".]+"
+ "(:[0-9]+)?(" URLPATH ")?";
+ break;
+ case GOSSIP_REGEX_BROWSER:
+ expression =
+ "(www|ftp)[" HOSTCHARS "]*\\.[" HOSTCHARS ".]+"
+ "(:[0-9]+)?(" URLPATH ")?";
+ break;
+ case GOSSIP_REGEX_EMAIL:
+ expression =
+ "(mailto:)?[a-z0-9][a-z0-9.-]*@[a-z0-9]"
+ "[a-z0-9-]*(\\.[a-z0-9][a-z0-9-]*)+";
+ break;
+ case GOSSIP_REGEX_OTHER:
+ expression =
+ "news:[-A-Z\\^_a-z{|}~!\"#$%&'()*+,./0-9;:=?`]+"
+ "@[" HOSTCHARS ".]+(:[0-9]+)?";
+ break;
+ default:
+ /* Silence the compiler. */
+ expression = NULL;
+ continue;
+ }
+
+ memset (&dingus[i], 0, sizeof (regex_t));
+ regcomp (&dingus[i], expression, REG_EXTENDED | REG_ICASE);
+ }
+
+ inited = TRUE;
+}
+
+gint
+gossip_regex_match (GossipRegExType type,
+ const gchar *msg,
+ GArray *start,
+ GArray *end)
+{
+ regmatch_t matches[1];
+ gint ret = 0;
+ gint num_matches = 0;
+ gint offset = 0;
+ gint i;
+
+ g_return_val_if_fail (type >= 0 || type <= GOSSIP_REGEX_ALL, 0);
+
+ regex_init ();
+
+ while (!ret && type != GOSSIP_REGEX_ALL) {
+ ret = regexec (&dingus[type], msg + offset, 1, matches, 0);
+ if (ret == 0) {
+ gint s;
+
+ num_matches++;
+
+ s = matches[0].rm_so + offset;
+ offset = matches[0].rm_eo + offset;
+
+ g_array_append_val (start, s);
+ g_array_append_val (end, offset);
+ }
+ }
+
+ if (type != GOSSIP_REGEX_ALL) {
+ gossip_debug (DEBUG_DOMAIN,
+ "Found %d matches for regex type:%d",
+ num_matches, type);
+ return num_matches;
+ }
+
+ /* If GOSSIP_REGEX_ALL then we run ALL regex's on the string. */
+ for (i = 0; i < GOSSIP_REGEX_ALL; i++, ret = 0) {
+ while (!ret) {
+ ret = regexec (&dingus[i], msg + offset, 1, matches, 0);
+ if (ret == 0) {
+ gint s;
+
+ num_matches++;
+
+ s = matches[0].rm_so + offset;
+ offset = matches[0].rm_eo + offset;
+
+ g_array_append_val (start, s);
+ g_array_append_val (end, offset);
+ }
+ }
+ }
+
+ gossip_debug (DEBUG_DOMAIN,
+ "Found %d matches for ALL regex types",
+ num_matches);
+
+ return num_matches;
+}
+
+gint
+gossip_strcasecmp (const gchar *s1,
+ const gchar *s2)
+{
+ return gossip_strncasecmp (s1, s2, -1);
+}
+
+gint
+gossip_strncasecmp (const gchar *s1,
+ const gchar *s2,
+ gsize n)
+{
+ gchar *u1, *u2;
+ gint ret_val;
+
+ u1 = g_utf8_casefold (s1, n);
+ u2 = g_utf8_casefold (s2, n);
+
+ ret_val = g_utf8_collate (u1, u2);
+ g_free (u1);
+ g_free (u2);
+
+ return ret_val;
+}
+
+gboolean
+gossip_xml_validate (xmlDoc *doc,
+ const gchar *dtd_filename)
+{
+ gchar *path, *escaped;
+ xmlValidCtxt cvp;
+ xmlDtd *dtd;
+ gboolean ret;
+
+ path = gossip_paths_get_dtd_path (dtd_filename);
+
+ /* The list of valid chars is taken from libxml. */
+ escaped = xmlURIEscapeStr (path, ":@&=+$,/?;");
+
+ g_free (path);
+
+ memset (&cvp, 0, sizeof (cvp));
+ dtd = xmlParseDTD (NULL, escaped);
+ ret = xmlValidateDtd (&cvp, doc, dtd);
+
+ xmlFree (escaped);
+ xmlFreeDtd (dtd);
+
+ return ret;
+}
+
+xmlNodePtr
+gossip_xml_node_get_child (xmlNodePtr node,
+ const gchar *child_name)
+{
+ xmlNodePtr l;
+
+ g_return_val_if_fail (node != NULL, NULL);
+ g_return_val_if_fail (child_name != NULL, NULL);
+
+ for (l = node->children; l; l = l->next) {
+ if (l->name && strcmp (l->name, child_name) == 0) {
+ return l;
+ }
+ }
+
+ return NULL;
+}
+
+xmlChar *
+gossip_xml_node_get_child_content (xmlNodePtr node,
+ const gchar *child_name)
+{
+ xmlNodePtr l;
+
+ g_return_val_if_fail (node != NULL, NULL);
+ g_return_val_if_fail (child_name != NULL, NULL);
+
+ l = gossip_xml_node_get_child (node, child_name);
+ if (l) {
+ return xmlNodeGetContent (l);
+ }
+
+ return NULL;
+}
+
+xmlNodePtr
+gossip_xml_node_find_child_prop_value (xmlNodePtr node,
+ const gchar *prop_name,
+ const gchar *prop_value)
+{
+ xmlNodePtr l;
+ xmlNodePtr found = NULL;
+
+ g_return_val_if_fail (node != NULL, NULL);
+ g_return_val_if_fail (prop_name != NULL, NULL);
+ g_return_val_if_fail (prop_value != NULL, NULL);
+
+ for (l = node->children; l && !found; l = l->next) {
+ xmlChar *prop;
+
+ if (!xmlHasProp (l, prop_name)) {
+ continue;
+ }
+
+ prop = xmlGetProp (l, prop_name);
+ if (prop && strcmp (prop, prop_value) == 0) {
+ found = l;
+ }
+
+ xmlFree (prop);
+ }
+
+ return found;
+}
+
+GType
+gossip_dbus_type_to_g_type (const gchar *dbus_type_string)
+{
+ if (dbus_type_string == NULL)
+ return G_TYPE_NONE;
+
+ if (dbus_type_string[0] == 's') {
+ return G_TYPE_STRING;
+ }
+ else if (dbus_type_string[0] == 'b') {
+ return G_TYPE_BOOLEAN;
+ }
+ else if (dbus_type_string[0] == 'q') {
+ return G_TYPE_UINT;
+ }
+ else if (dbus_type_string[0] == 'n') {
+ return G_TYPE_INT;
+ }
+
+ g_assert_not_reached ();
+ return G_TYPE_NONE;
+}
+
+const gchar *
+gossip_g_type_to_dbus_type (GType g_type)
+{
+ switch (g_type)
+ {
+ case G_TYPE_STRING:
+ return "s";
+ case G_TYPE_BOOLEAN:
+ return "b";
+ case G_TYPE_UINT:
+ return "q";
+ case G_TYPE_INT:
+ return "n";
+ default:
+ g_assert_not_reached ();
+ }
+
+ return NULL;
+}
+
+gchar *
+gossip_g_value_to_string (const GValue *value)
+{
+ gchar *return_string = NULL;
+ GValue string_g_value = {0, };
+
+ g_value_init (&string_g_value, G_TYPE_STRING);
+ g_value_transform (value, &string_g_value);
+ return_string = g_value_dup_string (&string_g_value);
+ g_value_unset (&string_g_value);
+
+ return return_string;
+}
+
+GValue *
+gossip_string_to_g_value (const gchar *str, GType type)
+{
+ GValue *g_value;
+
+ g_value = g_new0 (GValue, 1);
+ g_value_init (g_value, type);
+
+ switch (type) {
+ case G_TYPE_STRING:
+ g_value_set_string (g_value, str);
+ break;
+ case G_TYPE_BOOLEAN:
+ g_value_set_boolean (g_value, (str[0] == 'y' || str[0] == 'T'));
+ break;
+ case G_TYPE_UINT:
+ g_value_set_uint (g_value, atoi (str));
+ break;
+ case G_TYPE_INT:
+ g_value_set_int (g_value, atoi (str));
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ return g_value;
+}
+
+gboolean
+gossip_g_value_equal (const GValue *value1,
+ const GValue *value2)
+{
+ GType type;
+
+ g_return_val_if_fail (value1 != NULL, FALSE);
+ g_return_val_if_fail (value2 != NULL, FALSE);
+
+ type = G_VALUE_TYPE (value1);
+ if (type != G_VALUE_TYPE (value2)) {
+ return FALSE;
+ }
+
+ switch (type)
+ {
+ case G_TYPE_STRING: {
+ const gchar *str1;
+ const gchar *str2;
+
+ str1 = g_value_get_string (value1);
+ str2 = g_value_get_string (value2);
+ return (str1 && str2 && strcmp (str1, str2) == 0) ||
+ (G_STR_EMPTY (str1) && G_STR_EMPTY (str2));
+ }
+ case G_TYPE_BOOLEAN:
+ return g_value_get_boolean (value1) == g_value_get_boolean (value2);
+ case G_TYPE_UINT:
+ return g_value_get_uint (value1) == g_value_get_uint (value2);
+ case G_TYPE_INT:
+ return g_value_get_int (value1) == g_value_get_int (value2);
+ default:
+ g_warning ("Unsupported GType in value comparaison");
+ }
+
+ return FALSE;
+}
+
+guint
+gossip_account_hash (gconstpointer key)
+{
+ return g_str_hash (mc_account_get_unique_name (MC_ACCOUNT (key)));
+}
+
+gboolean
+gossip_account_equal (gconstpointer a,
+ gconstpointer b)
+{
+ const gchar *name_a;
+ const gchar *name_b;
+
+ name_a = mc_account_get_unique_name (MC_ACCOUNT (a));
+ name_b = mc_account_get_unique_name (MC_ACCOUNT (b));
+
+ return g_str_equal (name_a, name_b);
+}
+
+GossipContact *
+gossip_get_own_contact_from_contact (GossipContact *contact)
+{
+ EmpathyContactManager *manager;
+ McAccount *account;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), NULL);
+
+ manager = empathy_session_get_contact_manager ();
+ account = gossip_contact_get_account (contact);
+
+ return empathy_contact_manager_get_own (manager, account);
+}
+
diff --git a/libempathy/gossip-utils.h b/libempathy/gossip-utils.h
new file mode 100644
index 000000000..7f0ab90ef
--- /dev/null
+++ b/libempathy/gossip-utils.h
@@ -0,0 +1,87 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2002-2005 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GOSSIP_UTILS_H__
+#define __GOSSIP_UTILS_H__
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+
+#include "gossip-contact.h"
+
+G_BEGIN_DECLS
+
+#define G_STR_EMPTY(x) ((x) == NULL || (x)[0] == '\0')
+
+typedef enum {
+ GOSSIP_REGEX_AS_IS,
+ GOSSIP_REGEX_BROWSER,
+ GOSSIP_REGEX_EMAIL,
+ GOSSIP_REGEX_OTHER,
+ GOSSIP_REGEX_ALL,
+} GossipRegExType;
+
+/* Regular expressions */
+gchar * gossip_substring (const gchar *str,
+ gint start,
+ gint end);
+gint gossip_regex_match (GossipRegExType type,
+ const gchar *msg,
+ GArray *start,
+ GArray *end);
+
+/* Strings */
+gint gossip_strcasecmp (const gchar *s1,
+ const gchar *s2);
+gint gossip_strncasecmp (const gchar *s1,
+ const gchar *s2,
+ gsize n);
+
+/* XML */
+gboolean gossip_xml_validate (xmlDoc *doc,
+ const gchar *dtd_filename);
+xmlNodePtr gossip_xml_node_get_child (xmlNodePtr node,
+ const gchar *child_name);
+xmlChar * gossip_xml_node_get_child_content (xmlNodePtr node,
+ const gchar *child_name);
+xmlNodePtr gossip_xml_node_find_child_prop_value (xmlNodePtr node,
+ const gchar *prop_name,
+ const gchar *prop_value);
+
+
+/* GValue/GType */
+GType gossip_dbus_type_to_g_type (const gchar *dbus_type_string);
+const gchar *gossip_g_type_to_dbus_type (GType g_type);
+gchar * gossip_g_value_to_string (const GValue *value);
+GValue * gossip_string_to_g_value (const gchar *str,
+ GType type);
+gboolean gossip_g_value_equal (const GValue *value1,
+ const GValue *value2);
+
+guint gossip_account_hash (gconstpointer key);
+gboolean gossip_account_equal (gconstpointer a,
+ gconstpointer b);
+GossipContact * gossip_get_own_contact_from_contact (GossipContact *contact);
+G_END_DECLS
+
+#endif /* __GOSSIP_UTILS_H__ */