/*
* message-mixin.c - Source for TpMessageMixin
* Copyright (C) 2006-2008 Collabora Ltd.
* Copyright (C) 2006-2008 Nokia Corporation
*
* 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
*/
/**
* SECTION:message-mixin
* @title: TpMessageMixin
* @short_description: a mixin implementation of the text channel type and the
* Messages interface
* @see_also: #TpSvcChannelTypeText, #TpSvcChannelInterfaceMessages,
* TpDBusPropertiesMixin
*
* This mixin can be added to a channel GObject class to implement the
* text channel type (with the Messages interface) in a general way.
* The channel class should also have a #TpDBusPropertiesMixinClass.
*
* To use the messages mixin, include a #TpMessageMixin somewhere in your
* instance structure, and call tp_message_mixin_init() from your
* constructor function, and tp_message_mixin_finalize() from your dispose
* or finalize function. In the class_init function, call
* tp_message_mixin_init_dbus_properties() to hook this mixin into the D-Bus
* properties mixin class. Finally, include the following in the fourth
* argument of G_DEFINE_TYPE_WITH_CODE():
*
*
* G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_TYPE_TEXT,
* tp_message_mixin_text_iface_init);
* G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_MESSAGES,
* tp_message_mixin_messages_iface_init);
*
*
* To support sending messages, you must call
* tp_message_mixin_implement_sending() in the constructor function. If you do
* not, any attempt to send a message will fail with NotImplemented.
*
* To support chat state, you must call
* tp_message_mixin_implement_send_chat_state() in the constructor function, and
* include the following in the fourth argument of G_DEFINE_TYPE_WITH_CODE():
*
*
* G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_CHAT_STATE,
* tp_message_mixin_chat_state_iface_init);
*
*
* Since: 0.7.21
*/
#include "config.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define DEBUG_FLAG TP_DEBUG_IM
#include "debug-internal.h"
/**
* TpMessageMixin:
*
* Structure to be included in the instance structure of objects that
* use this mixin. Initialize it with tp_message_mixin_init().
*
* There are no public fields.
*
* Since: 0.7.21
*/
struct _TpMessageMixinPrivate
{
TpBaseConnection *connection;
/* Sending */
TpMessageMixinSendImpl send_message;
GArray *msg_types;
TpMessagePartSupportFlags message_part_support_flags;
TpDeliveryReportingSupportFlags delivery_reporting_support_flags;
gchar **supported_content_types;
/* Receiving */
guint recv_id;
GQueue *pending;
/* ChatState */
/* TpHandle -> TpChannelChatState */
GHashTable *chat_states;
TpMessageMixinSendChatStateImpl send_chat_state;
/* FALSE unless at least one chat state notification has been sent;
* will only be sent when the channel closes if this is TRUE. This prevents
* opening a channel and closing it immediately sending a spurious to
* the peer.
*/
gboolean send_gone;
};
static const char * const forbidden_keys[] = {
"pending-message-id",
NULL
};
static const char * const body_only[] = {
"alternative",
"content-type",
"type", /* deprecated in 0.17.14 */
"content",
"identifier",
"needs-retrieval",
"truncated",
"size",
NULL
};
static const char * const body_only_incoming[] = {
"needs-retrieval",
"truncated",
"size",
NULL
};
static const char * const headers_only[] = {
"message-type",
"message-sender",
"message-sender-id",
"message-sent",
"message-received",
"message-token",
NULL
};
static const char * const headers_only_incoming[] = {
"message-sender",
"message-sender-id",
"message-sent",
"message-received",
NULL
};
#define TP_MESSAGE_MIXIN_OFFSET_QUARK (tp_message_mixin_get_offset_quark ())
#define TP_MESSAGE_MIXIN_OFFSET(o) \
(GPOINTER_TO_UINT (g_type_get_qdata (G_OBJECT_TYPE (o), \
TP_MESSAGE_MIXIN_OFFSET_QUARK)))
#define TP_MESSAGE_MIXIN(o) \
((TpMessageMixin *) tp_mixin_offset_cast (o, TP_MESSAGE_MIXIN_OFFSET (o)))
/**
* tp_message_mixin_get_offset_quark:
*
*
*
* Returns: the quark used for storing mixin offset on a GObject
*
* Since: 0.7.21
*/
static GQuark
tp_message_mixin_get_offset_quark (void)
{
static GQuark offset_quark = 0;
if (G_UNLIKELY (offset_quark == 0))
offset_quark = g_quark_from_static_string (
"tp_message_mixin_get_offset_quark@0.7.7");
return offset_quark;
}
static gint
pending_item_id_equals_data (gconstpointer item,
gconstpointer data)
{
const TpCMMessage *self = item;
guint id = GPOINTER_TO_UINT (data);
/* The sense of this comparison is correct: the callback passed to
* g_queue_find_custom() should return 0 when the desired item is found.
*/
return (self->incoming_id != id);
}
static gchar *
parts_to_text (TpMessage *msg,
TpChannelTextMessageFlags *out_flags,
TpChannelTextMessageType *out_type,
TpHandle *out_sender,
guint *out_timestamp)
{
GHashTable *header = g_ptr_array_index (msg->parts, 0);
if (out_type != NULL)
{
/* The fallback behaviour of tp_asv_get_uint32 is OK here, because
* message type NORMAL is zero */
*out_type = tp_asv_get_uint32 (header, "message-type", NULL);
}
if (out_sender != NULL)
{
/* The fallback behaviour of tp_asv_get_uint32 is OK here - if there's
* no good sender, then 0 is the least bad */
*out_sender = tp_asv_get_uint32 (header, "message-sender", NULL);
}
if (out_timestamp != NULL)
{
/* The fallback behaviour of tp_asv_get_uint32 is OK here - we assume
* that we won't legitimately receive messages from 1970-01-01 :-) */
*out_timestamp = tp_asv_get_uint32 (header, "message-sent", NULL);
if (*out_timestamp == 0)
*out_timestamp = tp_asv_get_uint32 (header, "message-received", NULL);
if (*out_timestamp == 0)
*out_timestamp = time (NULL);
}
return tp_message_to_text (msg, out_flags);
}
/**
* TpMessageMixinSendImpl:
* @object: An instance of the implementation that uses this mixin
* @message: An outgoing message
* @flags: flags with which to send the message
*
* Signature of a virtual method which may be implemented to allow messages
* to be sent. It must arrange for tp_message_mixin_sent() to be called when
* the message has submitted or when message submission has failed.
*/
/**
* tp_message_mixin_implement_sending:
* @object: An instance of the implementation that uses this mixin
* @send: An implementation of SendMessage()
* @n_types: Number of supported message types
* @types: @n_types supported message types
* @message_part_support_flags: Flags indicating what message part structures
* are supported
* @delivery_reporting_support_flags: Flags indicating what kind of delivery
* reports are supported
* @supported_content_types: The supported content types
*
* Set the callback used to implement SendMessage, and the types of message
* that can be sent. This must be called from the init, constructor or
* constructed callback, after tp_message_mixin_init(), and may only be called
* once per object.
*
* Since: 0.7.21
*/
void
tp_message_mixin_implement_sending (GObject *object,
TpMessageMixinSendImpl send,
guint n_types,
const TpChannelTextMessageType *types,
TpMessagePartSupportFlags
message_part_support_flags,
TpDeliveryReportingSupportFlags
delivery_reporting_support_flags,
const gchar * const *
supported_content_types)
{
TpMessageMixin *mixin = TP_MESSAGE_MIXIN (object);
g_return_if_fail (mixin->priv->send_message == NULL);
mixin->priv->send_message = send;
if (mixin->priv->msg_types->len > 0)
g_array_remove_range (mixin->priv->msg_types, 0,
mixin->priv->msg_types->len);
g_assert (mixin->priv->msg_types->len == 0);
g_array_append_vals (mixin->priv->msg_types, types, n_types);
mixin->priv->message_part_support_flags = message_part_support_flags;
mixin->priv->delivery_reporting_support_flags = delivery_reporting_support_flags;
g_strfreev (mixin->priv->supported_content_types);
mixin->priv->supported_content_types = g_strdupv (
(gchar **) supported_content_types);
}
static TpChannelChatState
lookup_current_chat_state (TpMessageMixin *mixin,
TpHandle member)
{
gpointer tmp;
if (g_hash_table_lookup_extended (mixin->priv->chat_states,
GUINT_TO_POINTER (member), NULL, &tmp))
{
return GPOINTER_TO_UINT (tmp);
}
return TP_CHANNEL_CHAT_STATE_INACTIVE;
}
/**
* tp_message_mixin_change_chat_state:
* @object: an instance of the implementation that uses this mixin
* @member: a member of this chat
* @state: the new state to set
*
* Change the current chat state of @member to be @state. This emits
* ChatStateChanged signal and update ChatStates property.
*
* Since: 0.19.0
*/
void
tp_message_mixin_change_chat_state (GObject *object,
TpHandle member,
TpChannelChatState state)
{
TpMessageMixin *mixin = TP_MESSAGE_MIXIN (object);
g_return_if_fail (state < TP_NUM_CHANNEL_CHAT_STATES);
if (state == lookup_current_chat_state (mixin, member))
return;
if (state == TP_CHANNEL_CHAT_STATE_INACTIVE ||
state == TP_CHANNEL_CHAT_STATE_GONE)
{
g_hash_table_remove (mixin->priv->chat_states,
GUINT_TO_POINTER (member));
}
else
{
g_hash_table_insert (mixin->priv->chat_states,
GUINT_TO_POINTER (member),
GUINT_TO_POINTER (state));
}
tp_svc_channel_interface_chat_state_emit_chat_state_changed (object,
member, state);
}
/**
* TpMessageMixinSendChatStateImpl:
* @object: an instance of the implementation that uses this mixin
* @state: a #TpChannelChatState to be send
* @error: a #GError to fill
*
* Signature of a virtual method which may be implemented to allow sending chat
* state.
*
* Returns: %TRUE on success, %FALSE otherwise.
* Since: 0.19.0
*/
/**
* tp_message_mixin_implement_send_chat_state:
* @object: an instance of the implementation that uses this mixin
* @send_chat_state: send our chat state
*
* Set the callback used to implement SetChatState. This must be called from the
* init, constructor or constructed callback, after tp_message_mixin_init(),
* and may only be called once per object.
*
* Since: 0.19.0
*/
void
tp_message_mixin_implement_send_chat_state (GObject *object,
TpMessageMixinSendChatStateImpl send_chat_state)
{
TpMessageMixin *mixin = TP_MESSAGE_MIXIN (object);
g_return_if_fail (mixin->priv->send_chat_state == NULL);
mixin->priv->send_chat_state = send_chat_state;
}
/**
* tp_message_mixin_maybe_send_gone:
* @object: An instance of the implementation that uses this mixin
*
* Send #TP_CHANNEL_CHAT_STATE_GONE if needed. This should be called on private
* chats when channel is closed.
*
* Since: 0.19.0
*/
void
tp_message_mixin_maybe_send_gone (GObject *object)
{
TpMessageMixin *mixin = TP_MESSAGE_MIXIN (object);
if (mixin->priv->send_gone && !TP_HAS_GROUP_MIXIN (object) &&
mixin->priv->send_chat_state != NULL)
{
mixin->priv->send_chat_state (object, TP_CHANNEL_CHAT_STATE_GONE, NULL);
}
mixin->priv->send_gone = FALSE;
}
/* FIXME: Use tp_base_channel_get_self_handle() when TpMessageMixin requires
* TpBaseChannel. See bug #49366 */
static TpHandle
get_self_handle (GObject *object)
{
TpMessageMixin *mixin = TP_MESSAGE_MIXIN (object);
if (TP_HAS_GROUP_MIXIN (object))
{
guint ret = 0;
tp_group_mixin_get_self_handle (object, &ret, NULL);
if (ret != 0)
return ret;
}
return tp_base_connection_get_self_handle (mixin->priv->connection);
}
static void
tp_message_mixin_set_chat_state_async (TpSvcChannelInterfaceChatState *iface,
guint state,
DBusGMethodInvocation *context)
{
GObject *object = (GObject *) iface;
TpMessageMixin *mixin = TP_MESSAGE_MIXIN (object);
GError *error = NULL;
if (mixin->priv->send_chat_state == NULL)
{
tp_dbus_g_method_return_not_implemented (context);
return;
}
if (state >= TP_NUM_CHANNEL_CHAT_STATES)
{
DEBUG ("invalid chat state %u", state);
g_set_error (&error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
"invalid state: %u", state);
goto error;
}
if (state == TP_CHANNEL_CHAT_STATE_GONE)
{
/* We cannot explicitly set the Gone state */
DEBUG ("you may not explicitly set the Gone state");
g_set_error (&error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
"you may not explicitly set the Gone state");
goto error;
}
if (!mixin->priv->send_chat_state (object, state, &error))
goto error;
mixin->priv->send_gone = TRUE;
tp_message_mixin_change_chat_state (object, get_self_handle (object), state);
tp_svc_channel_interface_chat_state_return_from_set_chat_state (context);
return;
error:
dbus_g_method_return_error (context, error);
g_clear_error (&error);
}
/**
* tp_message_mixin_init:
* @obj: An instance of the implementation that uses this mixin
* @offset: The byte offset of the TpMessageMixin within the object structure
* @connection: A #TpBaseConnection
*
* Initialize the mixin. Should be called from the implementation's
* instance init function or constructor like so:
*
*
* tp_message_mixin_init ((GObject *) self,
* G_STRUCT_OFFSET (SomeObject, message_mixin),
* self->connection);
*
*
* Since: 0.7.21
*/
void
tp_message_mixin_init (GObject *obj,
gsize offset,
TpBaseConnection *connection)
{
TpMessageMixin *mixin;
g_assert (G_IS_OBJECT (obj));
g_type_set_qdata (G_OBJECT_TYPE (obj),
TP_MESSAGE_MIXIN_OFFSET_QUARK,
GINT_TO_POINTER (offset));
mixin = TP_MESSAGE_MIXIN (obj);
mixin->priv = g_slice_new0 (TpMessageMixinPrivate);
mixin->priv->pending = g_queue_new ();
mixin->priv->recv_id = 0;
mixin->priv->msg_types = g_array_sized_new (FALSE, FALSE, sizeof (guint),
TP_NUM_CHANNEL_TEXT_MESSAGE_TYPES);
mixin->priv->connection = g_object_ref (connection);
mixin->priv->supported_content_types = g_new0 (gchar *, 1);
mixin->priv->chat_states = g_hash_table_new (NULL, NULL);
}
/**
* tp_message_mixin_clear:
* @obj: An object with this mixin
*
* Clear the pending message queue, deleting all messages without emitting
* PendingMessagesRemoved.
*/
void
tp_message_mixin_clear (GObject *obj)
{
TpMessageMixin *mixin = TP_MESSAGE_MIXIN (obj);
TpMessage *item;
while ((item = g_queue_pop_head (mixin->priv->pending)) != NULL)
{
tp_message_destroy (item);
}
}
/**
* tp_message_mixin_finalize:
* @obj: An object with this mixin.
*
* Free resources held by the text mixin.
*
* Since: 0.7.21
*/
void
tp_message_mixin_finalize (GObject *obj)
{
TpMessageMixin *mixin = TP_MESSAGE_MIXIN (obj);
DEBUG ("%p", obj);
tp_message_mixin_clear (obj);
g_assert (g_queue_is_empty (mixin->priv->pending));
g_queue_free (mixin->priv->pending);
g_array_unref (mixin->priv->msg_types);
g_strfreev (mixin->priv->supported_content_types);
g_object_unref (mixin->priv->connection);
g_hash_table_unref (mixin->priv->chat_states);
g_slice_free (TpMessageMixinPrivate, mixin->priv);
}
static void
tp_message_mixin_acknowledge_pending_messages_async (
TpSvcChannelTypeText *iface,
const GArray *ids,
DBusGMethodInvocation *context)
{
TpMessageMixin *mixin = TP_MESSAGE_MIXIN (iface);
GPtrArray *links = g_ptr_array_sized_new (ids->len);
TpIntset *seen = tp_intset_new ();
guint i;
for (i = 0; i < ids->len; i++)
{
guint id = g_array_index (ids, guint, i);
GList *link_;
if (tp_intset_is_member (seen, id))
{
gchar *client = dbus_g_method_get_sender (context);
DEBUG ("%s passed message id %u more than once in one call to "
"AcknowledgePendingMessages. Foolish pup.", client, id);
g_free (client);
continue;
}
tp_intset_add (seen, id);
link_ = g_queue_find_custom (mixin->priv->pending,
GUINT_TO_POINTER (id), pending_item_id_equals_data);
if (link_ == NULL)
{
GError *error = g_error_new (TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
"invalid message id %u", id);
DEBUG ("%s", error->message);
dbus_g_method_return_error (context, error);
g_error_free (error);
g_ptr_array_unref (links);
tp_intset_destroy (seen);
return;
}
g_ptr_array_add (links, link_);
}
tp_svc_channel_interface_messages_emit_pending_messages_removed (iface,
ids);
for (i = 0; i < links->len; i++)
{
GList *link_ = g_ptr_array_index (links, i);
TpMessage *item = link_->data;
TpCMMessage *cm_msg = link_->data;
DEBUG ("acknowledging message id %u", cm_msg->incoming_id);
g_queue_delete_link (mixin->priv->pending, link_);
tp_message_destroy (item);
}
g_ptr_array_unref (links);
tp_intset_destroy (seen);
tp_svc_channel_type_text_return_from_acknowledge_pending_messages (context);
}
static void
tp_message_mixin_list_pending_messages_async (TpSvcChannelTypeText *iface,
gboolean clear,
DBusGMethodInvocation *context)
{
TpMessageMixin *mixin = TP_MESSAGE_MIXIN (iface);
GType pending_type = TP_STRUCT_TYPE_PENDING_TEXT_MESSAGE;
guint count;
GPtrArray *messages;
GList *cur;
guint i;
count = g_queue_get_length (mixin->priv->pending);
messages = g_ptr_array_sized_new (count);
for (cur = g_queue_peek_head_link (mixin->priv->pending);
cur != NULL;
cur = cur->next)
{
TpMessage *msg = cur->data;
TpCMMessage *cm_msg = cur->data;
GValue val = { 0, };
gchar *text;
TpChannelTextMessageFlags flags;
TpChannelTextMessageType type;
TpHandle sender;
guint timestamp;
text = parts_to_text (msg, &flags, &type, &sender, ×tamp);
g_value_init (&val, pending_type);
g_value_take_boxed (&val,
dbus_g_type_specialized_construct (pending_type));
dbus_g_type_struct_set (&val,
0, cm_msg->incoming_id,
1, timestamp,
2, sender,
3, type,
4, flags,
5, text,
G_MAXUINT);
g_free (text);
g_ptr_array_add (messages, g_value_get_boxed (&val));
}
if (clear)
{
GArray *ids;
DEBUG ("WARNING: ListPendingMessages(clear=TRUE) is deprecated");
cur = g_queue_peek_head_link (mixin->priv->pending);
ids = g_array_sized_new (FALSE, FALSE, sizeof (guint), count);
while (cur != NULL)
{
TpMessage *msg = cur->data;
TpCMMessage *cm_msg = cur->data;
GList *next = cur->next;
i = cm_msg->incoming_id;
g_array_append_val (ids, i);
g_queue_delete_link (mixin->priv->pending, cur);
tp_message_destroy (msg);
cur = next;
}
tp_svc_channel_interface_messages_emit_pending_messages_removed (iface,
ids);
g_array_unref (ids);
}
tp_svc_channel_type_text_return_from_list_pending_messages (context,
messages);
for (i = 0; i < messages->len; i++)
tp_value_array_free (g_ptr_array_index (messages, i));
g_ptr_array_unref (messages);
}
static void
tp_message_mixin_get_pending_message_content_async (
TpSvcChannelInterfaceMessages *iface,
guint message_id,
const GArray *part_numbers,
DBusGMethodInvocation *context)
{
TpMessageMixin *mixin = TP_MESSAGE_MIXIN (iface);
GList *node;
TpMessage *item;
GHashTable *ret;
guint i;
node = g_queue_find_custom (mixin->priv->pending,
GUINT_TO_POINTER (message_id), pending_item_id_equals_data);
if (node == NULL)
{
GError *error = g_error_new (TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
"invalid message id %u", message_id);
DEBUG ("%s", error->message);
dbus_g_method_return_error (context, error);
g_error_free (error);
return;
}
item = node->data;
for (i = 0; i < part_numbers->len; i++)
{
guint part = g_array_index (part_numbers, guint, i);
if (part == 0 || part >= item->parts->len)
{
GError *error = g_error_new (TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
"part number %u out of range", part);
DEBUG ("%s", error->message);
dbus_g_method_return_error (context, error);
g_error_free (error);
return;
}
}
/* no free callbacks set - we borrow the content from the message */
ret = g_hash_table_new (g_direct_hash, g_direct_equal);
/* FIXME: later, need a way to support streaming content */
for (i = 0; i < part_numbers->len; i++)
{
guint part = g_array_index (part_numbers, guint, i);
GHashTable *part_data;
GValue *value;
g_assert (part != 0 && part < item->parts->len);
part_data = g_ptr_array_index (item->parts, part);
/* skip parts with no type (reserved) */
if (tp_asv_get_string (part_data, "content-type") == NULL &&
/* Renamed to "content-type" in spec 0.17.14 */
tp_asv_get_string (part_data, "type") == NULL)
continue;
value = g_hash_table_lookup (part_data, "content");
/* skip parts with no content */
if (value == NULL)
continue;
g_hash_table_insert (ret, GUINT_TO_POINTER (part), value);
}
tp_svc_channel_interface_messages_return_from_get_pending_message_content (
context, ret);
g_hash_table_unref (ret);
}
static void
tp_message_mixin_get_message_types_async (TpSvcChannelTypeText *iface,
DBusGMethodInvocation *context)
{
TpMessageMixin *mixin = TP_MESSAGE_MIXIN (iface);
tp_svc_channel_type_text_return_from_get_message_types (context,
mixin->priv->msg_types);
}
static void
queue_pending (GObject *object, TpMessage *pending)
{
TpMessageMixin *mixin = TP_MESSAGE_MIXIN (object);
TpChannelTextMessageFlags flags;
TpChannelTextMessageType type;
TpHandle sender;
guint timestamp;
gchar *text;
const GHashTable *header;
TpDeliveryStatus delivery_status;
TpCMMessage *cm_message = (TpCMMessage *) pending;
g_queue_push_tail (mixin->priv->pending, pending);
text = parts_to_text (pending, &flags, &type, &sender, ×tamp);
tp_svc_channel_type_text_emit_received (object, cm_message->incoming_id,
timestamp, sender, type, flags, text);
g_free (text);
tp_svc_channel_interface_messages_emit_message_received (object,
pending->parts);
/* Check if it's a failed delivery report; if so, emit SendError too. */
header = tp_message_peek (pending, 0);
delivery_status = tp_asv_get_uint32 (header, "delivery-status", NULL);
if (delivery_status == TP_DELIVERY_STATUS_TEMPORARILY_FAILED ||
delivery_status == TP_DELIVERY_STATUS_PERMANENTLY_FAILED)
{
/* Fallback behaviour here is okay: 0 is Send_Error_Unknown */
TpChannelTextSendError send_error = tp_asv_get_uint32 (header,
"delivery-error", NULL);
GPtrArray *echo = tp_asv_get_boxed (header, "delivery-echo",
TP_ARRAY_TYPE_MESSAGE_PART_LIST);
type = TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL;
text = NULL;
timestamp = 0;
if (echo != NULL && echo->len < 1)
{
WARNING ("delivery-echo should contain at least 1 part");
}
else if (echo != NULL)
{
const GHashTable *echo_header = g_ptr_array_index (echo, 0);
TpMessage *echo_msg;
echo_msg = _tp_cm_message_new_from_parts (mixin->priv->connection,
echo);
/* The specification says that the timestamp in SendError should be the
* time at which the original message was sent. parts_to_text falls
* back to setting timestamp to time (NULL) if it can't find out when
* the message was sent, but we want to use 0 in that case. Hence,
* we look up timestamp here rather than delegating to parts_to_text.
* The fallback behaviour of tp_asv_get_uint32 is correct: we want
* timestamp to be 0 if we can't determine when the original message
* was sent.
*/
text = parts_to_text (echo_msg, NULL, &type, NULL, NULL);
timestamp = tp_asv_get_uint32 (echo_header, "message-sent", NULL);
g_object_unref (echo_msg);
}
tp_svc_channel_type_text_emit_send_error (object, send_error, timestamp,
type, text != NULL ? text : "");
g_free (text);
}
}
/**
* tp_message_mixin_take_received:
* @object: a channel with this mixin
* @message: the message. Its ownership is claimed by the message
* mixin, so it must no longer be modified or freed
*
* Receive a message into the pending messages queue, where it will stay
* until acknowledged, and emit the Received and ReceivedMessage signals. Also
* emit the SendError signal if the message is a failed delivery report.
*
* Returns: the message ID
*
* Since: 0.7.21
*/
guint
tp_message_mixin_take_received (GObject *object,
TpMessage *message)
{
TpMessageMixin *mixin = TP_MESSAGE_MIXIN (object);
TpCMMessage *cm_msg = (TpCMMessage *) message;
GHashTable *header;
g_return_val_if_fail (cm_msg->incoming_id == G_MAXUINT32, 0);
g_return_val_if_fail (message->parts->len >= 1, 0);
header = g_ptr_array_index (message->parts, 0);
g_return_val_if_fail (g_hash_table_lookup (header, "pending-message-id")
== NULL, 0);
/* FIXME: we don't check for overflow, so in highly pathological cases we
* might end up with multiple messages with the same ID */
cm_msg->incoming_id = mixin->priv->recv_id++;
tp_message_set_uint32 (message, 0, "pending-message-id",
cm_msg->incoming_id);
if (tp_asv_get_uint64 (header, "message-received", NULL) == 0)
tp_message_set_uint64 (message, 0, "message-received",
time (NULL));
/* Here we add the message to the incoming queue: Although we have not
* returned the message ID to the caller directly at this point, we
* have poked it into the TpMessage, which the caller (and anyone connected
* to the relevant signals) has access to, so there isn't actually a race
* between putting the message into the queue and making its ID available.
*/
queue_pending (object, message);
return cm_msg->incoming_id;
}
/**
* tp_message_mixin_has_pending_messages:
* @object: An object with this mixin
* @first_sender: If not %NULL, used to store the sender of the oldest pending
* message
*
* Return whether the channel @obj has unacknowledged messages. If so, and
* @first_sender is not %NULL, the handle of the sender of the first message
* is placed in it, without incrementing the handle's reference count.
*
* Returns: %TRUE if there are pending messages
*/
gboolean
tp_message_mixin_has_pending_messages (GObject *object,
TpHandle *first_sender)
{
TpMessageMixin *mixin = TP_MESSAGE_MIXIN (object);
TpMessage *msg = g_queue_peek_head (mixin->priv->pending);
if (msg != NULL && first_sender != NULL)
{
const GHashTable *header = tp_message_peek (msg, 0);
gboolean valid = TRUE;
TpHandle h = tp_asv_get_uint32 (header, "message-sender", &valid);
if (valid)
*first_sender = h;
else
WARNING ("oldest message's message-sender is mistyped");
}
return (msg != NULL);
}
/**
* tp_message_mixin_set_rescued:
* @obj: An object with this mixin
*
* Mark all pending messages as having been "rescued" from a channel that
* previously closed.
*/
void
tp_message_mixin_set_rescued (GObject *obj)
{
TpMessageMixin *mixin = TP_MESSAGE_MIXIN (obj);
GList *cur;
for (cur = g_queue_peek_head_link (mixin->priv->pending);
cur != NULL;
cur = cur->next)
{
TpMessage *msg = cur->data;
tp_message_set_boolean (msg, 0, "rescued", TRUE);
}
}
/**
* TpMessageMixinOutgoingMessage:
* @flags: Flags indicating how this message should be sent
* @parts: The parts that make up the message (an array of #GHashTable,
* with the first one containing message headers)
* @priv: Pointer to opaque private data used by the messages mixin
*
* Structure representing a message which is to be sent.
*
* Connection managers may (and should) edit the @parts in-place to remove
* keys that could not be sent, using g_hash_table_remove(). Connection
* managers may also alter @parts to include newly allocated GHashTable
* structures.
*
* However, they must not add keys to an existing GHashTable (this is because
* the connection manager has no way to know how the keys and values will be
* freed).
*
* Since: 0.7.21
*/
struct _TpMessageMixinOutgoingMessagePrivate {
DBusGMethodInvocation *context;
gboolean messages:1;
};
/**
* tp_message_mixin_sent:
* @object: An object implementing the Text and Messages interfaces with this
* mixin
* @message: The outgoing message
* @flags: The flags used when sending the message, which may be a subset of
* those passed to the #TpMessageMixinSendImpl implementation if not all are
* supported, or 0 on error.
* @token: A token representing the sent message (see the Telepathy D-Bus API
* specification), or an empty string if no suitable identifier is available,
* or %NULL on error
* @error: %NULL on success, or the error with which message submission failed
*
* Indicate to the message mixin that message submission to the IM server has
* succeeded or failed. This should be called as soon as the CM determines
* it's theoretically possible to send the message (e.g. the parameters are
* supported and correct).
*
* After this function is called, @message will have been freed, and must not
* be dereferenced.
*
* Since: 0.7.21
*/
void
tp_message_mixin_sent (GObject *object,
TpMessage *message,
TpMessageSendingFlags flags,
const gchar *token,
const GError *error)
{
TpMessageMixin *mixin = TP_MESSAGE_MIXIN (object);
TpCMMessage *cm_msg = (TpCMMessage *) message;
time_t now = time (NULL);
g_return_if_fail (mixin != NULL);
g_return_if_fail (G_IS_OBJECT (object));
g_return_if_fail (TP_IS_CM_MESSAGE (message));
g_return_if_fail (message->parts != NULL);
g_return_if_fail (cm_msg->outgoing_context != NULL);
g_return_if_fail (token == NULL || error == NULL);
g_return_if_fail (token != NULL || error != NULL);
if (error != NULL)
{
GError *e = g_error_copy (error);
dbus_g_method_return_error (cm_msg->outgoing_context, e);
g_error_free (e);
}
else
{
TpChannelTextMessageType message_type;
gchar *string;
GHashTable *header = g_ptr_array_index (message->parts, 0);
mixin->priv->send_gone = TRUE;
if (tp_asv_get_uint64 (header, "message-sent", NULL) == 0)
tp_message_set_uint64 (message, 0, "message-sent", time (NULL));
tp_cm_message_set_sender (message, get_self_handle (object));
/* emit Sent and MessageSent */
tp_svc_channel_interface_messages_emit_message_sent (object,
message->parts, flags, token);
string = parts_to_text (message, NULL, &message_type, NULL, NULL);
tp_svc_channel_type_text_emit_sent (object, now, message_type,
string);
g_free (string);
/* return successfully */
if (cm_msg->outgoing_text_api)
{
tp_svc_channel_type_text_return_from_send (
cm_msg->outgoing_context);
}
else
{
tp_svc_channel_interface_messages_return_from_send_message (
cm_msg->outgoing_context, token);
}
}
cm_msg->outgoing_context = NULL;
tp_message_destroy (message);
}
static void
tp_message_mixin_send_async (TpSvcChannelTypeText *iface,
guint message_type,
const gchar *text,
DBusGMethodInvocation *context)
{
TpMessageMixin *mixin = TP_MESSAGE_MIXIN (iface);
TpMessage *message;
TpCMMessage *cm_msg;
if (mixin->priv->send_message == NULL)
{
tp_dbus_g_method_return_not_implemented (context);
return;
}
message = tp_cm_message_new (mixin->priv->connection, 2);
cm_msg = (TpCMMessage *) message;
if (message_type != 0)
tp_message_set_uint32 (message, 0, "message-type", message_type);
tp_message_set_string (message, 1, "content-type", "text/plain");
tp_message_set_string (message, 1, "type", "text/plain"); /* Removed in 0.17.14 */
tp_message_set_string (message, 1, "content", text);
cm_msg->outgoing_context = context;
cm_msg->outgoing_text_api = TRUE;
mixin->priv->send_message ((GObject *) iface, message, 0);
}
static void
tp_message_mixin_send_message_async (TpSvcChannelInterfaceMessages *iface,
const GPtrArray *parts,
guint flags,
DBusGMethodInvocation *context)
{
TpMessageMixin *mixin = TP_MESSAGE_MIXIN (iface);
TpMessage *message;
TpCMMessage *cm_msg;
GHashTable *header;
guint i;
const char * const *iter;
if (mixin->priv->send_message == NULL)
{
tp_dbus_g_method_return_not_implemented (context);
return;
}
/* it must have at least a header part */
if (parts->len < 1)
{
GError e = { TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
"Cannot send a message that does not have at least one part" };
dbus_g_method_return_error (context, &e);
return;
}
header = g_ptr_array_index (parts, 0);
for (i = 0; i < parts->len; i++)
{
for (iter = forbidden_keys; *iter != NULL; iter++)
{
if (g_hash_table_lookup (header, *iter) != NULL)
{
GError *error = g_error_new (TP_ERROR,
TP_ERROR_INVALID_ARGUMENT,
"Key '%s' not allowed in a sent message", *iter);
dbus_g_method_return_error (context, error);
return;
}
}
}
for (iter = body_only; *iter != NULL; iter++)
{
if (g_hash_table_lookup (header, *iter) != NULL)
{
GError *error = g_error_new (TP_ERROR,
TP_ERROR_INVALID_ARGUMENT,
"Key '%s' not allowed in a message header", *iter);
dbus_g_method_return_error (context, error);
return;
}
}
for (iter = headers_only_incoming; *iter != NULL; iter++)
{
if (g_hash_table_lookup (header, *iter) != NULL)
{
GError *error = g_error_new (TP_ERROR,
TP_ERROR_INVALID_ARGUMENT,
"Key '%s' not allowed in an outgoing message header", *iter);
dbus_g_method_return_error (context, error);
return;
}
}
for (i = 1; i < parts->len; i++)
{
for (iter = headers_only; *iter != NULL; iter++)
{
if (g_hash_table_lookup (g_ptr_array_index (parts, i), *iter)
!= NULL)
{
GError *error = g_error_new (TP_ERROR,
TP_ERROR_INVALID_ARGUMENT,
"Key '%s' not allowed in a message body", *iter);
dbus_g_method_return_error (context, error);
return;
}
}
}
message = tp_cm_message_new (mixin->priv->connection, parts->len);
cm_msg = (TpCMMessage *) message;
for (i = 0; i < parts->len; i++)
{
tp_g_hash_table_update (g_ptr_array_index (message->parts, i),
g_ptr_array_index (parts, i),
(GBoxedCopyFunc) g_strdup,
(GBoxedCopyFunc) tp_g_value_slice_dup);
}
cm_msg->outgoing_context = context;
cm_msg->outgoing_text_api = FALSE;
mixin->priv->send_message ((GObject *) iface, message, flags);
}
/**
* tp_message_mixin_init_dbus_properties:
* @cls: The class of an object with this mixin
*
* Set up a #TpDBusPropertiesMixinClass to use this mixin's implementation
* of the Messages interface's properties.
*
* This uses tp_message_mixin_get_dbus_property() as the property getter
* and sets a list of the supported properties for it.
*/
void
tp_message_mixin_init_dbus_properties (GObjectClass *cls)
{
static TpDBusPropertiesMixinPropImpl props[] = {
{ "PendingMessages", NULL, NULL },
{ "SupportedContentTypes", NULL, NULL },
{ "MessagePartSupportFlags", NULL, NULL },
{ "MessageTypes", NULL, NULL },
{ "DeliveryReportingSupport", NULL, NULL },
{ NULL }
};
static TpDBusPropertiesMixinPropImpl chat_state_props[] = {
{ "ChatStates", NULL, NULL },
{ NULL }
};
GType type = G_OBJECT_CLASS_TYPE (cls);
g_return_if_fail (g_type_is_a (type, TP_TYPE_SVC_CHANNEL_TYPE_TEXT));
g_return_if_fail (g_type_is_a (type,
TP_TYPE_SVC_CHANNEL_INTERFACE_MESSAGES));
tp_dbus_properties_mixin_implement_interface (cls,
TP_IFACE_QUARK_CHANNEL_INTERFACE_MESSAGES,
tp_message_mixin_get_dbus_property, NULL, props);
if (g_type_is_a (type, TP_TYPE_SVC_CHANNEL_INTERFACE_CHAT_STATE))
{
tp_dbus_properties_mixin_implement_interface (cls,
TP_IFACE_QUARK_CHANNEL_INTERFACE_CHAT_STATE,
tp_message_mixin_get_dbus_property, NULL, chat_state_props);
}
}
/**
* tp_message_mixin_get_dbus_property:
* @object: An object with this mixin
* @interface: Must be %TP_IFACE_QUARK_CHANNEL_INTERFACE_MESSAGES
* @name: A quark representing the D-Bus property name, either
* "PendingMessages", "SupportedContentTypes" or "MessagePartSupportFlags"
* @value: A GValue pre-initialized to the right type, into which to put
* the value
* @unused: Ignored
*
* An implementation of #TpDBusPropertiesMixinGetter which assumes that
* the @object has the messages mixin. It can only be used for the Messages
* interface.
*/
void
tp_message_mixin_get_dbus_property (GObject *object,
GQuark interface,
GQuark name,
GValue *value,
gpointer unused G_GNUC_UNUSED)
{
TpMessageMixin *mixin;
static GQuark q_pending_messages = 0;
static GQuark q_supported_content_types = 0;
static GQuark q_message_part_support_flags = 0;
static GQuark q_delivery_reporting_support_flags = 0;
static GQuark q_message_types = 0;
static GQuark q_chat_states = 0;
if (G_UNLIKELY (q_pending_messages == 0))
{
q_pending_messages = g_quark_from_static_string ("PendingMessages");
q_supported_content_types =
g_quark_from_static_string ("SupportedContentTypes");
q_message_part_support_flags =
g_quark_from_static_string ("MessagePartSupportFlags");
q_delivery_reporting_support_flags =
g_quark_from_static_string ("DeliveryReportingSupport");
q_message_types =
g_quark_from_static_string ("MessageTypes");
q_chat_states =
g_quark_from_static_string ("ChatStates");
}
mixin = TP_MESSAGE_MIXIN (object);
g_return_if_fail (interface == TP_IFACE_QUARK_CHANNEL_INTERFACE_MESSAGES ||
interface == TP_IFACE_QUARK_CHANNEL_INTERFACE_CHAT_STATE);
g_return_if_fail (object != NULL);
g_return_if_fail (name != 0);
g_return_if_fail (value != NULL);
g_return_if_fail (mixin != NULL);
if (name == q_pending_messages)
{
GPtrArray *arrays = g_ptr_array_sized_new (g_queue_get_length (
mixin->priv->pending));
GList *l;
GType type = dbus_g_type_get_collection ("GPtrArray",
TP_HASH_TYPE_MESSAGE_PART);
for (l = g_queue_peek_head_link (mixin->priv->pending);
l != NULL;
l = g_list_next (l))
{
TpMessage *msg = l->data;
g_ptr_array_add (arrays, g_boxed_copy (type, msg->parts));
}
g_value_take_boxed (value, arrays);
}
else if (name == q_message_part_support_flags)
{
g_value_set_uint (value, mixin->priv->message_part_support_flags);
}
else if (name == q_delivery_reporting_support_flags)
{
g_value_set_uint (value, mixin->priv->delivery_reporting_support_flags);
}
else if (name == q_supported_content_types)
{
g_value_set_boxed (value, mixin->priv->supported_content_types);
}
else if (name == q_message_types)
{
g_value_set_boxed (value, mixin->priv->msg_types);
}
else if (name == q_chat_states)
{
g_value_set_boxed (value, mixin->priv->chat_states);
}
}
/**
* tp_message_mixin_text_iface_init:
* @g_iface: A pointer to the #TpSvcChannelTypeTextClass in an object class
* @iface_data: Ignored
*
* Fill in this mixin's Text method implementations in the given interface
* vtable.
*
* Since: 0.7.21
*/
void
tp_message_mixin_text_iface_init (gpointer g_iface,
gpointer iface_data)
{
TpSvcChannelTypeTextClass *klass = g_iface;
#define IMPLEMENT(x) tp_svc_channel_type_text_implement_##x (klass,\
tp_message_mixin_##x##_async)
IMPLEMENT (acknowledge_pending_messages);
IMPLEMENT (get_message_types);
IMPLEMENT (list_pending_messages);
IMPLEMENT (send);
#undef IMPLEMENT
}
/**
* tp_message_mixin_messages_iface_init:
* @g_iface: A pointer to the #TpSvcChannelInterfaceMessagesClass in an object
* class
* @iface_data: Ignored
*
* Fill in this mixin's Messages method implementations in the given interface
* vtable.
*
* Since: 0.7.21
*/
void
tp_message_mixin_messages_iface_init (gpointer g_iface,
gpointer iface_data)
{
TpSvcChannelInterfaceMessagesClass *klass = g_iface;
#define IMPLEMENT(x) tp_svc_channel_interface_messages_implement_##x (\
klass, tp_message_mixin_##x##_async)
IMPLEMENT (send_message);
IMPLEMENT (get_pending_message_content);
#undef IMPLEMENT
}
/**
* tp_message_mixin_chat_state_iface_init:
* @g_iface: A pointer to the #TpSvcChannelInterfaceChatStateClass in an object
* class
* @iface_data: Ignored
*
* Fill in this mixin's ChatState method implementations in the given interface
* vtable.
*
* Since: 0.19.0
*/
void
tp_message_mixin_chat_state_iface_init (gpointer g_iface,
gpointer iface_data)
{
TpSvcChannelInterfaceChatStateClass *klass = g_iface;
#define IMPLEMENT(x) tp_svc_channel_interface_chat_state_implement_##x (\
klass, tp_message_mixin_##x##_async)
IMPLEMENT (set_chat_state);
#undef IMPLEMENT
}