summaryrefslogtreecommitdiff
path: root/src/olpc-activity.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/olpc-activity.c')
-rw-r--r--src/olpc-activity.c699
1 files changed, 699 insertions, 0 deletions
diff --git a/src/olpc-activity.c b/src/olpc-activity.c
new file mode 100644
index 00000000..3265a295
--- /dev/null
+++ b/src/olpc-activity.c
@@ -0,0 +1,699 @@
+/*
+ * olpc-activity.c - Source for SalutOlpcActivity
+ * Copyright (C) 2008 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <dbus/dbus-glib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <gibber/gibber-namespaces.h>
+#include <wocky/wocky-stanza.h>
+
+#include "contact-manager.h"
+#include "olpc-activity.h"
+#include "muc-manager.h"
+#include "util.h"
+
+#include "signals-marshal.h"
+
+#define DEBUG_FLAG DEBUG_OLPC_ACTIVITY
+#include "debug.h"
+
+G_DEFINE_TYPE (SalutOlpcActivity, salut_olpc_activity, G_TYPE_OBJECT);
+
+/* properties */
+enum {
+ PROP_CONNECTION = 1,
+ LAST_PROP
+};
+
+/* signal enum */
+enum
+{
+ MODIFIED,
+ VALID,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+/* private structure */
+typedef struct _SalutOlpcActivityPrivate SalutOlpcActivityPrivate;
+
+struct _SalutOlpcActivityPrivate
+{
+ /* Handles of contacts we invited to join this activity */
+ TpHandleSet *invited;
+ /* can be NULL if we are not in the activity */
+ SalutMucChannel *muc;
+
+ gboolean dispose_has_run;
+};
+
+#define SALUT_OLPC_ACTIVITY_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SALUT_TYPE_OLPC_ACTIVITY, SalutOlpcActivityPrivate))
+
+static void
+salut_olpc_activity_init (SalutOlpcActivity *self)
+{
+ self->connection = NULL;
+
+ self->is_private = TRUE;
+}
+
+static void
+salut_olpc_activity_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SalutOlpcActivity *self = SALUT_OLPC_ACTIVITY (object);
+
+ switch (property_id)
+ {
+ case PROP_CONNECTION:
+ g_value_set_object (value, self->connection);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+salut_olpc_activity_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SalutOlpcActivity *self = SALUT_OLPC_ACTIVITY (object);
+
+ switch (property_id)
+ {
+ case PROP_CONNECTION:
+ self->connection = g_value_get_object (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static GObject *
+salut_olpc_activity_constructor (GType type,
+ guint n_props,
+ GObjectConstructParam *props)
+{
+ GObject *obj;
+ SalutOlpcActivity *self;
+ SalutOlpcActivityPrivate *priv;
+ TpHandleRepoIface *contact_repo;
+
+ obj = G_OBJECT_CLASS (salut_olpc_activity_parent_class)->
+ constructor (type, n_props, props);
+
+ self = SALUT_OLPC_ACTIVITY (obj);
+ priv = SALUT_OLPC_ACTIVITY_GET_PRIVATE (self);
+
+ g_assert (self->connection != NULL);
+ contact_repo = tp_base_connection_get_handles (
+ (TpBaseConnection *) self->connection, TP_HANDLE_TYPE_CONTACT);
+
+ priv->invited = tp_handle_set_new (contact_repo);
+
+ return obj;
+}
+
+static void salut_olpc_activity_dispose (GObject *object);
+static void salut_olpc_activity_finalize (GObject *object);
+
+static void
+salut_olpc_activity_class_init (SalutOlpcActivityClass *salut_olpc_activity_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (salut_olpc_activity_class);
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (salut_olpc_activity_class,
+ sizeof (SalutOlpcActivityPrivate));
+
+ object_class->get_property = salut_olpc_activity_get_property;
+ object_class->set_property = salut_olpc_activity_set_property;
+
+ object_class->constructor = salut_olpc_activity_constructor;
+ object_class->dispose = salut_olpc_activity_dispose;
+ object_class->finalize = salut_olpc_activity_finalize;
+
+ param_spec = g_param_spec_object (
+ "connection",
+ "SalutConnection object",
+ "The Salut Connection associated with this muc manager",
+ SALUT_TYPE_CONNECTION,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONNECTION,
+ param_spec);
+
+ signals[MODIFIED] = g_signal_new ("modified",
+ G_OBJECT_CLASS_TYPE (salut_olpc_activity_class),
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+ salut_signals_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[VALID] = g_signal_new ("valid",
+ G_OBJECT_CLASS_TYPE (salut_olpc_activity_class),
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+ salut_signals_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+static void
+salut_olpc_activity_dispose (GObject *object)
+{
+ SalutOlpcActivity *self = SALUT_OLPC_ACTIVITY (object);
+ SalutOlpcActivityPrivate *priv = SALUT_OLPC_ACTIVITY_GET_PRIVATE (self);
+ TpHandleRepoIface *room_repo = tp_base_connection_get_handles
+ ((TpBaseConnection *) self->connection, TP_HANDLE_TYPE_ROOM);
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ if (self->room != 0)
+ {
+ tp_handle_unref (room_repo, self->room);
+ self->room = 0;
+ }
+
+ if (priv->muc != NULL)
+ {
+ g_signal_handlers_disconnect_matched (priv->muc, G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL, self);
+ g_object_unref (priv->muc);
+ priv->muc = NULL;
+ }
+
+ tp_handle_set_destroy (priv->invited);
+
+ if (G_OBJECT_CLASS (salut_olpc_activity_parent_class)->dispose)
+ G_OBJECT_CLASS (salut_olpc_activity_parent_class)->dispose (object);
+}
+
+static void
+salut_olpc_activity_finalize (GObject *object)
+{
+ SalutOlpcActivity *self = SALUT_OLPC_ACTIVITY (object);
+
+ g_free (self->id);
+ g_free (self->name);
+ g_free (self->type);
+ g_free (self->color);
+ g_free (self->tags);
+
+ G_OBJECT_CLASS (salut_olpc_activity_parent_class)->finalize (object);
+}
+
+GHashTable *
+salut_olpc_activity_create_properties_table (SalutOlpcActivity *self)
+{
+ GHashTable *properties;
+ GValue *val;
+
+ properties = g_hash_table_new_full (g_str_hash, g_str_equal,
+ NULL, (GDestroyNotify) tp_g_value_slice_free);
+
+ if (self->color != NULL)
+ {
+ val = tp_g_value_slice_new (G_TYPE_STRING);
+ g_value_set_static_string (val, self->color);
+ g_hash_table_insert (properties, "color", val);
+ }
+
+ if (self->name != NULL)
+ {
+ val = tp_g_value_slice_new (G_TYPE_STRING);
+ g_value_set_static_string (val, self->name);
+ g_hash_table_insert (properties, "name", val);
+ }
+
+ if (self->type != NULL)
+ {
+ val = tp_g_value_slice_new (G_TYPE_STRING);
+ g_value_set_static_string (val, self->type);
+ g_hash_table_insert (properties, "type", val);
+ }
+
+ if (self->tags != NULL)
+ {
+ val = tp_g_value_slice_new (G_TYPE_STRING);
+ g_value_set_static_string (val, self->tags);
+ g_hash_table_insert (properties, "tags", val);
+ }
+
+ val = tp_g_value_slice_new (G_TYPE_BOOLEAN);
+ g_value_set_boolean (val, self->is_private);
+ g_hash_table_insert (properties, "private", val);
+
+ return properties;
+}
+
+static gboolean
+send_properties_change_msg (SalutOlpcActivity *self,
+ GError **error)
+{
+ SalutOlpcActivityPrivate *priv = SALUT_OLPC_ACTIVITY_GET_PRIVATE (self);
+ GHashTable *properties;
+ GValue *activity_id_val;
+ WockyStanza *stanza;
+ WockyNode *top_node;
+ WockyNode *properties_node;
+ gchar *muc_name;
+ GibberMucConnection *muc_connection;
+ gboolean result;
+ GError *err = NULL;
+
+ if (priv->muc == NULL)
+ /* we are not in the muc */
+ return TRUE;
+
+ g_object_get (priv->muc,
+ "name", &muc_name,
+ "muc-connection", &muc_connection,
+ NULL);
+
+ if (muc_connection->state != GIBBER_MUC_CONNECTION_CONNECTED)
+ {
+ DEBUG ("Muc connection not connected yet. Drop activity change message");
+ g_object_unref (muc_connection);
+ g_free (muc_name);
+ return TRUE;
+ }
+
+ properties = salut_olpc_activity_create_properties_table (self);
+
+ /* add the activity id */
+ activity_id_val = g_slice_new0 (GValue);
+ g_value_init (activity_id_val, G_TYPE_STRING);
+ g_value_set_static_string (activity_id_val, self->id);
+ g_hash_table_insert (properties, "id", activity_id_val);
+
+ stanza = wocky_stanza_build (WOCKY_STANZA_TYPE_MESSAGE,
+ WOCKY_STANZA_SUB_TYPE_GROUPCHAT,
+ self->connection->name, muc_name,
+ WOCKY_NODE_START, "properties",
+ WOCKY_NODE_XMLNS, GIBBER_TELEPATHY_NS_OLPC_ACTIVITY_PROPS,
+ WOCKY_NODE_END, NULL);
+ top_node = wocky_stanza_get_top_node (stanza);
+
+ properties_node = wocky_node_get_child_ns (top_node, "properties",
+ GIBBER_TELEPATHY_NS_OLPC_ACTIVITY_PROPS);
+
+ salut_wocky_node_add_children_from_properties (properties_node,
+ properties, "property");
+
+ result = gibber_muc_connection_send (muc_connection, stanza, &err);
+ if (!result)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_NETWORK_ERROR, "%s",
+ err->message);
+ g_error_free (err);
+ }
+
+ g_object_unref (stanza);
+ g_object_unref (muc_connection);
+ g_free (muc_name);
+ g_hash_table_destroy (properties);
+
+ return result;
+}
+
+static void
+resend_invite_foreach (TpHandleSet *set,
+ TpHandle handle,
+ SalutOlpcActivity *self)
+{
+ SalutOlpcActivityPrivate *priv = SALUT_OLPC_ACTIVITY_GET_PRIVATE (self);
+ GError *error = NULL;
+
+ if (!salut_muc_channel_send_invitation (priv->muc, handle,
+ "OLPC activity properties update", &error))
+ {
+ DEBUG ("failed to re-invite contact %d to activity %s", handle,
+ self->id);
+ }
+}
+
+static void
+resend_invite (SalutOlpcActivity *self)
+{
+ SalutOlpcActivityPrivate *priv = SALUT_OLPC_ACTIVITY_GET_PRIVATE (self);
+
+ if (priv->muc == NULL)
+ /* we are not in the muc */
+ return;
+
+ /* Resend pending invitations so contacts will know about new properties */
+ tp_handle_set_foreach (priv->invited,
+ (TpHandleSetMemberFunc) resend_invite_foreach, self);
+}
+
+static void
+activity_changed (SalutOlpcActivity *self)
+{
+ SalutOlpcActivityPrivate *priv = SALUT_OLPC_ACTIVITY_GET_PRIVATE (self);
+ GError *error = NULL;
+
+ if (!send_properties_change_msg (self, &error))
+ {
+ DEBUG ("send properties changes msg failed: %s", error->message);
+ g_error_free (error);
+ error = NULL;
+ }
+
+ if (!self->is_private && priv->muc != NULL)
+ {
+ /* update announcement */
+ if (!SALUT_OLPC_ACTIVITY_GET_CLASS (self)->update (self, &error))
+ {
+ DEBUG ("update activity failed: %s", error->message);
+ g_error_free (error);
+ }
+ }
+ else
+ {
+ resend_invite (self);
+ }
+}
+
+static gboolean
+salut_olpc_activity_announce (SalutOlpcActivity *self,
+ GError **error)
+{
+ GError *err = NULL;
+
+ if (!SALUT_OLPC_ACTIVITY_GET_CLASS (self)->announce (self, &err))
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_NETWORK_ERROR, "%s",
+ err->message);
+ g_error_free (err);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+salut_olpc_activity_stop_announce (SalutOlpcActivity *self)
+{
+ SALUT_OLPC_ACTIVITY_GET_CLASS (self)->stop_announce (self);
+}
+
+gboolean
+salut_olpc_activity_update (SalutOlpcActivity *self,
+ TpHandle room,
+ const gchar *id,
+ const gchar *name,
+ const gchar *type,
+ const gchar *color,
+ const gchar *tags,
+ gboolean is_private)
+{
+ SalutOlpcActivityPrivate *priv = SALUT_OLPC_ACTIVITY_GET_PRIVATE (self);
+ TpBaseConnection *base_conn = (TpBaseConnection *) (self->connection);
+ TpHandleRepoIface *room_repo = tp_base_connection_get_handles (base_conn,
+ TP_HANDLE_TYPE_ROOM);
+ gboolean changed = FALSE;
+ GError *error = NULL;
+ gboolean become_valid = FALSE;
+
+ if (room != 0 && room != self->room)
+ {
+ if (self->room != 0)
+ {
+ tp_handle_unref (room_repo, self->room);
+ }
+
+ self->room = room;
+ tp_handle_ref (room_repo, room);
+ }
+
+ if (id != NULL && tp_strdiff (id, self->id))
+ {
+ if (self->id == NULL)
+ become_valid = TRUE;
+
+ g_free (self->id);
+ self->id = g_strdup (id);
+ changed = TRUE;
+ }
+
+ if (name != NULL && tp_strdiff (name, self->name))
+ {
+ g_free (self->name);
+ self->name = g_strdup (name);
+ changed = TRUE;
+ }
+
+ if (type != NULL && tp_strdiff (type, self->type))
+ {
+ g_free (self->type);
+ self->type = g_strdup (type);
+ changed = TRUE;
+ }
+
+ if (color != NULL && tp_strdiff (color, self->color))
+ {
+ g_free (self->color);
+ self->color = g_strdup (color);
+ changed = TRUE;
+ }
+
+ if (tp_strdiff (tags, self->tags))
+ {
+ g_free (self->tags);
+ self->tags = g_strdup (tags);
+ changed = TRUE;
+ }
+
+ if (is_private != self->is_private)
+ {
+ self->is_private = is_private;
+ changed = TRUE;
+
+ if (priv->muc != NULL)
+ {
+ if (is_private)
+ {
+ DEBUG ("activity is not public anymore. Stop to announce it");
+ salut_olpc_activity_stop_announce (self);
+ }
+ else
+ {
+ DEBUG ("activity becomes public. Announce it");
+ if (!salut_olpc_activity_announce (self, &error))
+ {
+ DEBUG ("activity announce failed: %s", error->message);
+ g_error_free (error);
+ error = NULL;
+ }
+ }
+ }
+ }
+
+ if (become_valid)
+ {
+ g_signal_emit (self, signals[VALID], 0);
+ }
+
+ if (changed)
+ {
+ activity_changed (self);
+
+ g_signal_emit (self, signals[MODIFIED], 0);
+ }
+
+ return changed;
+}
+
+static void
+muc_channel_closed_cb (SalutMucChannel *chan,
+ SalutOlpcActivity *self)
+{
+ SalutOlpcActivityPrivate *priv = SALUT_OLPC_ACTIVITY_GET_PRIVATE (self);
+
+ g_object_unref (priv->muc);
+ priv->muc = NULL;
+}
+
+gboolean
+salut_olpc_activity_joined (SalutOlpcActivity *self,
+ GError **error)
+{
+ SalutOlpcActivityPrivate *priv = SALUT_OLPC_ACTIVITY_GET_PRIVATE (self);
+ TpHandleRepoIface *room_repo;
+ SalutMucManager *muc_manager;
+
+ if (priv->muc != NULL)
+ return TRUE;
+
+ room_repo = tp_base_connection_get_handles (
+ (TpBaseConnection *) self->connection, TP_HANDLE_TYPE_ROOM);
+
+ g_object_get (self->connection,
+ "muc-manager", &muc_manager,
+ NULL);
+
+ priv->muc = salut_muc_manager_get_text_channel (muc_manager, self->room);
+ g_object_unref (muc_manager);
+
+ if (priv->muc == NULL)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Can't find muc channel for room %s", tp_handle_inspect (
+ room_repo, self->room));
+ return FALSE;
+ }
+
+ if (!self->is_private)
+ {
+ /* This might fail but that doesn't prevent us from joining the
+ * activity.. */
+ salut_olpc_activity_announce (self, NULL);
+ }
+
+ g_signal_connect (priv->muc, "closed", G_CALLBACK (muc_channel_closed_cb),
+ self);
+
+ return TRUE;
+}
+
+void
+salut_olpc_activity_left (SalutOlpcActivity *self)
+{
+ SalutOlpcActivityPrivate *priv = SALUT_OLPC_ACTIVITY_GET_PRIVATE (self);
+
+ if (priv->muc == NULL)
+ return;
+
+ if (!self->is_private)
+ salut_olpc_activity_stop_announce (self);
+
+ g_object_unref (priv->muc);
+ g_signal_handlers_disconnect_matched (priv->muc, G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL, self);
+ priv->muc = NULL;
+}
+
+void
+salut_olpc_activity_revoke_invitations (SalutOlpcActivity *self)
+{
+ SalutOlpcActivityPrivate *priv = SALUT_OLPC_ACTIVITY_GET_PRIVATE (self);
+ WockyStanza *msg;
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
+ (TpBaseConnection *) self->connection, TP_HANDLE_TYPE_CONTACT);
+ TpHandleRepoIface *room_repo = tp_base_connection_get_handles (
+ (TpBaseConnection *) self->connection, TP_HANDLE_TYPE_CONTACT);
+ TpIntSetIter iter = TP_INTSET_ITER_INIT (tp_handle_set_peek (
+ priv->invited));
+ SalutContactManager *contact_mgr;
+ WockyNode *top_node;
+
+ if (tp_handle_set_size (priv->invited) <= 0)
+ return;
+
+ msg = wocky_stanza_build (WOCKY_STANZA_TYPE_MESSAGE,
+ WOCKY_STANZA_SUB_TYPE_NONE,
+ self->connection->name, NULL,
+ WOCKY_NODE_START, "uninvite",
+ WOCKY_NODE_XMLNS, GIBBER_TELEPATHY_NS_OLPC_ACTIVITY_PROPS,
+ WOCKY_NODE_ATTRIBUTE, "room", tp_handle_inspect (room_repo,
+ self->room),
+ WOCKY_NODE_ATTRIBUTE, "id", self->id,
+ WOCKY_NODE_END, NULL);
+ top_node = wocky_stanza_get_top_node (msg);
+
+ g_object_get (self->connection,
+ "contact-manager", &contact_mgr,
+ NULL);
+ g_assert (contact_mgr != NULL);
+
+ DEBUG ("revoke invitations for activity %s", self->id);
+ while (tp_intset_iter_next (&iter))
+ {
+ TpHandle contact_handle;
+ SalutContact *contact;
+ const gchar *to;
+
+ contact_handle = iter.element;
+ contact = salut_contact_manager_get_contact (contact_mgr, contact_handle);
+ if (contact == NULL)
+ {
+ DEBUG ("Can't find contact %d", contact_handle);
+ continue;
+ }
+
+ to = tp_handle_inspect (contact_repo, contact_handle);
+ wocky_node_set_attribute (top_node, "to", to);
+
+ wocky_stanza_set_to_contact (msg, WOCKY_CONTACT (contact));
+ wocky_porter_send (self->connection->porter, msg);
+
+ g_object_unref (contact);
+ }
+
+ g_object_unref (msg);
+ g_object_unref (contact_mgr);
+}
+
+void
+salut_olpc_activity_augment_invitation (SalutOlpcActivity *self,
+ TpHandle contact,
+ WockyNode *invite_node)
+{
+ SalutOlpcActivityPrivate *priv = SALUT_OLPC_ACTIVITY_GET_PRIVATE (self);
+ WockyNode *properties_node;
+ GHashTable *properties;
+ GValue *activity_id_val;
+
+ properties = salut_olpc_activity_create_properties_table (self);
+
+ properties_node = wocky_node_add_child_ns (invite_node, "properties",
+ GIBBER_TELEPATHY_NS_OLPC_ACTIVITY_PROPS);
+
+ /* add the activity id */
+ activity_id_val = g_slice_new0 (GValue);
+ g_value_init (activity_id_val, G_TYPE_STRING);
+ g_value_set_static_string (activity_id_val, self->id);
+ g_hash_table_insert (properties, "id", activity_id_val);
+
+ salut_wocky_node_add_children_from_properties (properties_node,
+ properties, "property");
+
+ tp_handle_set_add (priv->invited, contact);
+
+ g_hash_table_destroy (properties);
+}
+
+gboolean
+salut_olpc_activity_remove_invited (SalutOlpcActivity *self,
+ TpHandle contact)
+{
+ SalutOlpcActivityPrivate *priv = SALUT_OLPC_ACTIVITY_GET_PRIVATE (self);
+
+ return tp_handle_set_remove (priv->invited, contact);
+}