/* * channel.c - wraps TpChannel to do preparation and unread message counting * for a single channel we're observing * * Copyright (C) 2010 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 * * Authors: * Danielle Madeley */ #include #include "channel.h" #define GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), TYPE_CHANNEL, ChannelPrivate)) G_DEFINE_TYPE (Channel, channel, TP_TYPE_CHANNEL); enum /* properties */ { PROP_0, PROP_UNREAD }; typedef struct { TpIntSet *pending; } ChannelPrivate; typedef struct { TpChannelWhenReadyCb callback; gpointer user_data; } ReadyCallbackData; static void _channel_update_pending_msgs (Channel *self) { ChannelPrivate *priv = GET_PRIVATE (self); g_debug ("%s: pending messages %u", tp_proxy_get_object_path (self), tp_intset_size (priv->pending)); g_object_notify (G_OBJECT (self), "unread"); } static void _channel_msg_received (TpChannel *self, guint id, guint timestamp, guint sender, guint type, guint flags, const char *text, gpointer user_data, GObject *weak_obj) { ChannelPrivate *priv = GET_PRIVATE (self); /* The id is a number assigned to each message in the channel that is used * to acknowledge the message. Messages are acknowledged by the Handler when * it is sure that the user has seen them. When the client acknowledges a * message, we will receive the signal PendingMessageRemoved, and we can * remove this id from our pending list. * * For this to work it requires a well-behaved client that doesn't just * acknowledge messages immediately. For instance, recent Empathy */ if (type == TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL) { tp_intset_add (priv->pending, id); _channel_update_pending_msgs (CHANNEL (self)); } } static void _channel_pending_msg_removed (TpChannel *self, const GArray *ids, gpointer user_data, GObject *weak_obj) { ChannelPrivate *priv = GET_PRIVATE (self); guint i; /* The client has acknowledged @ids, and we can remove them from the list * of messages we know the user hasn't seen */ for (i = 0; i < ids->len; i++) { guint id = g_array_index (ids, guint, i); tp_intset_remove (priv->pending, id); } _channel_update_pending_msgs (CHANNEL (self)); } static void _channel_list_pending_messages (TpChannel *self, const GPtrArray *messages, const GError *error, gpointer user_data, GObject *weak_obj) { ReadyCallbackData *data = user_data; if (error == NULL) { guint i; /* Iterate the pending messages */ for (i = 0; i < messages->len; i++) { guint id, timestamp, sender, type, flags; const char *text; tp_value_array_unpack (g_ptr_array_index (messages, i), 6, &id, ×tamp, &sender, &type, &flags, &text); _channel_msg_received (self, id, timestamp, sender, type, flags, text, NULL, NULL); } } else { g_warning ("Failed to get pending messages: %s", error->message); } /* call the readiness callback */ data->callback (self, error, data->user_data); g_slice_free (ReadyCallbackData, data); } static void _channel_ready (TpChannel *self, const GError *error, gpointer user_data) { ReadyCallbackData *data = user_data; GError *lerror = NULL; if (error != NULL) { data->callback (self, error, data->user_data); g_slice_free (ReadyCallbackData, data); return; } /* get the pending messages on the channel */ tp_cli_channel_type_text_call_list_pending_messages (self, -1, FALSE, _channel_list_pending_messages, data, NULL, NULL); /* connect to the signals we need to listen to */ tp_cli_channel_type_text_connect_to_received (self, _channel_msg_received, NULL, NULL, NULL, &lerror); g_assert_no_error (lerror); tp_cli_channel_interface_messages_connect_to_pending_messages_removed (self, _channel_pending_msg_removed, NULL, NULL, NULL, &lerror); g_assert_no_error (lerror); } void channel_call_when_ready (Channel *self, TpChannelWhenReadyCb callback, gpointer user_data) { ReadyCallbackData *data = g_slice_new0 (ReadyCallbackData); data->callback = callback; data->user_data = user_data; /* prepare the TpChannel */ tp_channel_call_when_ready (TP_CHANNEL (self), _channel_ready, data); } static void channel_get_property (GObject *self, guint property_id, GValue *value, GParamSpec *pspec) { ChannelPrivate *priv = GET_PRIVATE (self); switch (property_id) { case PROP_UNREAD: g_value_set_uint (value, tp_intset_size (priv->pending)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (self, property_id, pspec); break; } } static void channel_dispose (GObject *self) { ChannelPrivate *priv = GET_PRIVATE (self); if (priv->pending != NULL) { tp_intset_destroy (priv->pending); priv->pending = NULL; } G_OBJECT_CLASS (channel_parent_class)->dispose (self); } static void channel_class_init (ChannelClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->get_property = channel_get_property; object_class->dispose = channel_dispose; g_object_class_install_property (object_class, PROP_UNREAD, g_param_spec_uint ("unread", "Unread", "Number of unread messages on this channel", 0, G_MAXUINT, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); g_type_class_add_private (object_class, sizeof (ChannelPrivate)); } static void channel_init (Channel *self) { ChannelPrivate *priv = GET_PRIVATE (self); priv->pending = tp_intset_new (); } Channel * channel_new (TpConnection *conn, const char *path, const GHashTable *properties, GError **error) { /* this function is copied from * telepathy-glib/channel.c:tp_channel_new_from_properties */ Channel *self = NULL; g_return_val_if_fail (TP_IS_CONNECTION (conn), NULL); g_return_val_if_fail (path != NULL, NULL); g_return_val_if_fail (properties != NULL, NULL); if (!tp_dbus_check_valid_object_path (path, error)) goto finally; self = g_object_new (TYPE_CHANNEL, "connection", conn, "dbus-daemon", TP_PROXY (conn)->dbus_daemon, "bus-name", TP_PROXY (conn)->bus_name, "object-path", path, "handle-type", (guint) TP_UNKNOWN_HANDLE_TYPE, "channel-properties", properties, NULL); finally: return self; }