/* * message.c - Source for TpMessage * Copyright (C) 2006-2010 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 * @title: TpMessage * @short_description: a message in the Telepathy message interface * @see_also: #TpCMMessage, #TpClientMessage, #TpSignalledMessage * * #TpMessage represent a message send or received using the Message * interface. * * Since: 0.7.21 */ #include "config.h" #include "message.h" #include "message-internal.h" #include #include #include #include #include #define DEBUG_FLAG TP_DEBUG_MISC #include "telepathy-glib/debug-internal.h" G_DEFINE_TYPE (TpMessage, tp_message, G_TYPE_OBJECT) /** * TpMessage: * * Opaque structure representing a message in the Telepathy messages interface * (an array of at least one mapping from string to variant, where the first * mapping contains message headers and subsequent mappings contain the * message body). * * This base class provides convenience API for most of the common keys that * can appear in the header. One notable exception is the sender of the * message. Inside a connection manager, messages are represented by the * #TpCMMessage subclass, and you should use tp_cm_message_get_sender(). * When composing a message in a client using #TpClientMessage, messages do * not have an explicit sender (the sender is automatically the local user). * When a client sees a sent or received message signalled by the connection * manager (represented by #TpSignalledMessage), the message's sender (if any) * can be accessed with tp_signalled_message_get_sender(). */ struct _TpMessagePrivate { gboolean mutable; }; static void tp_message_dispose (GObject *object) { TpMessage *self = TP_MESSAGE (object); void (*dispose) (GObject *) = G_OBJECT_CLASS (tp_message_parent_class)->dispose; guint i; if (self->parts != NULL) { for (i = 0; i < self->parts->len; i++) { g_hash_table_unref (g_ptr_array_index (self->parts, i)); } g_ptr_array_unref (self->parts); self->parts = NULL; } if (dispose != NULL) dispose (object); } static void tp_message_class_init (TpMessageClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->dispose = tp_message_dispose; g_type_class_add_private (gobject_class, sizeof (TpMessagePrivate)); } static void tp_message_init (TpMessage *self) { self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self), TP_TYPE_MESSAGE, TpMessagePrivate); /* Message can be modified until _tp_message_set_immutable() is called */ self->priv->mutable = TRUE; /* Create header part */ self->parts = g_ptr_array_sized_new (1); tp_message_append_part (self); } /** * tp_message_new: * @connection: a connection on which to reference handles * @initial_parts: number of parts to create (at least 1) * @size_hint: preallocate space for this many parts (at least @initial_parts) * * * * Returns: a newly allocated message suitable to be passed to * tp_message_mixin_take_received() * * Since: 0.7.21 * Deprecated: since 0.13.9. Use tp_cm_message_new() */ TpMessage * tp_message_new (TpBaseConnection *connection, guint initial_parts, guint size_hint) { g_return_val_if_fail (size_hint >= initial_parts, NULL); return tp_cm_message_new (connection, initial_parts); } /** * tp_message_destroy: * @self: a message * * Since 0.13.9 this function is a simple wrapper around * g_object_unref() * * Since: 0.7.21 */ void tp_message_destroy (TpMessage *self) { g_object_unref (self); } /** * tp_message_count_parts: * @self: a message * * * * Returns: the number of parts in the message, including the headers in * part 0 * * Since: 0.7.21 */ guint tp_message_count_parts (TpMessage *self) { return self->parts->len; } /** * tp_message_peek: * @self: a message * @part: a part number * * * * Returns: (transfer none) (element-type utf8 GObject.Value): * the #GHashTable used to implement the given part, or %NULL if the * part number is out of range. The hash table is only valid as long as the * message is valid and the part is not deleted. * * Since: 0.7.21 */ const GHashTable * tp_message_peek (TpMessage *self, guint part) { if (part >= self->parts->len) return NULL; return g_ptr_array_index (self->parts, part); } /** * tp_message_dup_part: * @self: a message * @part: a part number * * * * Returns: (transfer full): * the current contents of the given part, or %NULL if the part number is * out of range * * Since: 0.19.10 */ GVariant * tp_message_dup_part (TpMessage *self, guint part) { if (part >= self->parts->len) return NULL; return _tp_asv_to_vardict (g_ptr_array_index (self->parts, part)); } /** * tp_message_append_part: * @self: a message * * Append a body part to the message. * * Returns: the part number * * Since: 0.7.21 */ guint tp_message_append_part (TpMessage *self) { g_return_val_if_fail (self->priv->mutable, 0); g_ptr_array_add (self->parts, g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) tp_g_value_slice_free)); return self->parts->len - 1; } /** * tp_message_delete_part: * @self: a message * @part: a part number, which must be strictly greater than 0, and strictly * less than the number returned by tp_message_count_parts() * * Delete the given body part from the message. * * Since: 0.7.21 */ void tp_message_delete_part (TpMessage *self, guint part) { g_return_if_fail (part < self->parts->len); g_return_if_fail (part > 0); g_return_if_fail (self->priv->mutable); g_hash_table_unref (g_ptr_array_remove_index (self->parts, part)); } /** * tp_message_ref_handle: * @self: a message * @handle_type: a handle type, greater than %TP_HANDLE_TYPE_NONE and less than * %TP_NUM_HANDLE_TYPES * @handle: a handle of the given type * * Reference the given handle until this message is destroyed. * * Since: 0.7.21 * Deprecated: since 0.13.9. Handles are now immortal so there is * no point to ref them. Furthermore, the only handle that should be stored * in a TpMessage is message-sender which should be set using * tp_cm_message_set_sender(). */ void tp_message_ref_handle (TpMessage *self, TpHandleType handle_type, TpHandle handle) { g_return_if_fail (TP_IS_CM_MESSAGE (self)); g_return_if_fail (self->priv->mutable); /* Handles are now immortal so we don't have to anything */ } /** * tp_message_delete_key: * @self: a message * @part: a part number, which must be strictly less than the number * returned by tp_message_count_parts() * @key: a key in the mapping representing the part * * Remove the given key and its value from the given part. * * Returns: %TRUE if the key previously existed * * Since: 0.7.21 */ gboolean tp_message_delete_key (TpMessage *self, guint part, const gchar *key) { g_return_val_if_fail (part < self->parts->len, FALSE); g_return_val_if_fail (self->priv->mutable, FALSE); return g_hash_table_remove (g_ptr_array_index (self->parts, part), key); } /** * tp_message_set_handle: * @self: a #TpCMMessage * @part: a part number, which must be strictly less than the number * returned by tp_message_count_parts() * @key: a key in the mapping representing the part * @handle_type: a handle type * @handle_or_0: a handle of that type, or 0 * * If @handle_or_0 is not zero, reference it with tp_message_ref_handle(). * * Set @key in part @part of @self to have @handle_or_0 as an unsigned integer * value. * * Since 0.13.9 this function has been deprecated in favor or * tp_cm_message_set_sender() as 'message-sender' is the only handle * you can put in a #TpCMMessage. * * Since: 0.7.21 * Deprecated: since 0.13.9. Use tp_cm_message_set_sender() */ void tp_message_set_handle (TpMessage *self, guint part, const gchar *key, TpHandleType handle_type, TpHandle handle_or_0) { g_return_if_fail (TP_IS_CM_MESSAGE (self)); g_return_if_fail (self->priv->mutable); tp_message_set_uint32 (self, part, key, handle_or_0); } /** * tp_message_set_boolean: * @self: a message * @part: a part number, which must be strictly less than the number * returned by tp_message_count_parts() * @key: a key in the mapping representing the part * @b: a boolean value * * Set @key in part @part of @self to have @b as a boolean value. * * Since: 0.7.21 */ void tp_message_set_boolean (TpMessage *self, guint part, const gchar *key, gboolean b) { g_return_if_fail (part < self->parts->len); g_return_if_fail (key != NULL); g_return_if_fail (self->priv->mutable); g_hash_table_insert (g_ptr_array_index (self->parts, part), g_strdup (key), tp_g_value_slice_new_boolean (b)); } /** * tp_message_set_int16: * @s: a message * @p: a part number, which must be strictly less than the number * returned by tp_message_count_parts() * @k: a key in the mapping representing the part * @i: an integer value * * Set @key in part @part of @self to have @i as a signed integer value. * * Since: 0.7.21 */ /** * tp_message_set_int32: * @self: a message * @part: a part number, which must be strictly less than the number * returned by tp_message_count_parts() * @key: a key in the mapping representing the part * @i: an integer value * * Set @key in part @part of @self to have @i as a signed integer value. * * Since: 0.7.21 */ void tp_message_set_int32 (TpMessage *self, guint part, const gchar *key, gint32 i) { g_return_if_fail (part < self->parts->len); g_return_if_fail (key != NULL); g_return_if_fail (self->priv->mutable); g_hash_table_insert (g_ptr_array_index (self->parts, part), g_strdup (key), tp_g_value_slice_new_int (i)); } /** * tp_message_set_int64: * @self: a message * @part: a part number, which must be strictly less than the number * returned by tp_message_count_parts() * @key: a key in the mapping representing the part * @i: an integer value * * Set @key in part @part of @self to have @i as a signed integer value. * * Since: 0.7.21 */ void tp_message_set_int64 (TpMessage *self, guint part, const gchar *key, gint64 i) { g_return_if_fail (part < self->parts->len); g_return_if_fail (key != NULL); g_return_if_fail (self->priv->mutable); g_hash_table_insert (g_ptr_array_index (self->parts, part), g_strdup (key), tp_g_value_slice_new_int64 (i)); } /** * tp_message_set_uint16: * @s: a message * @p: a part number, which must be strictly less than the number * returned by tp_message_count_parts() * @k: a key in the mapping representing the part * @u: an unsigned integer value * * Set @key in part @part of @self to have @u as an unsigned integer value. * * Since: 0.7.21 */ /** * tp_message_set_uint32: * @self: a message * @part: a part number, which must be strictly less than the number * returned by tp_message_count_parts() * @key: a key in the mapping representing the part * @u: an unsigned integer value * * Set @key in part @part of @self to have @u as an unsigned integer value. * * Since: 0.7.21 */ void tp_message_set_uint32 (TpMessage *self, guint part, const gchar *key, guint32 u) { g_return_if_fail (part < self->parts->len); g_return_if_fail (key != NULL); g_return_if_fail (self->priv->mutable); g_hash_table_insert (g_ptr_array_index (self->parts, part), g_strdup (key), tp_g_value_slice_new_uint (u)); } /** * tp_message_set_uint64: * @self: a message * @part: a part number, which must be strictly less than the number * returned by tp_message_count_parts() * @key: a key in the mapping representing the part * @u: an unsigned integer value * * Set @key in part @part of @self to have @u as an unsigned integer value. * * Since: 0.7.21 */ void tp_message_set_uint64 (TpMessage *self, guint part, const gchar *key, guint64 u) { g_return_if_fail (part < self->parts->len); g_return_if_fail (key != NULL); g_return_if_fail (self->priv->mutable); g_hash_table_insert (g_ptr_array_index (self->parts, part), g_strdup (key), tp_g_value_slice_new_uint64 (u)); } /** * tp_message_set_string: * @self: a message * @part: a part number, which must be strictly less than the number * returned by tp_message_count_parts() * @key: a key in the mapping representing the part * @s: a string value * * Set @key in part @part of @self to have @s as a string value. * * Since: 0.7.21 */ void tp_message_set_string (TpMessage *self, guint part, const gchar *key, const gchar *s) { g_return_if_fail (part < self->parts->len); g_return_if_fail (key != NULL); g_return_if_fail (s != NULL); g_return_if_fail (self->priv->mutable); g_hash_table_insert (g_ptr_array_index (self->parts, part), g_strdup (key), tp_g_value_slice_new_string (s)); } /** * tp_message_set_string_printf: * @self: a message * @part: a part number, which must be strictly less than the number * returned by tp_message_count_parts() * @key: a key in the mapping representing the part * @fmt: a printf-style format string for the string value * @...: arguments for the format string * * Set @key in part @part of @self to have a string value constructed from a * printf-style format string. * * Since: 0.7.21 */ void tp_message_set_string_printf (TpMessage *self, guint part, const gchar *key, const gchar *fmt, ...) { va_list va; gchar *s; g_return_if_fail (part < self->parts->len); g_return_if_fail (key != NULL); g_return_if_fail (fmt != NULL); g_return_if_fail (self->priv->mutable); va_start (va, fmt); s = g_strdup_vprintf (fmt, va); va_end (va); g_hash_table_insert (g_ptr_array_index (self->parts, part), g_strdup (key), tp_g_value_slice_new_take_string (s)); } /** * tp_message_set_bytes: * @self: a message * @part: a part number, which must be strictly less than the number * returned by tp_message_count_parts() * @key: a key in the mapping representing the part * @len: a number of bytes * @bytes: an array of @len bytes * * Set @key in part @part of @self to have @bytes as a byte-array value. * * Since: 0.7.21 */ void tp_message_set_bytes (TpMessage *self, guint part, const gchar *key, guint len, gconstpointer bytes) { g_return_if_fail (part < self->parts->len); g_return_if_fail (key != NULL); g_return_if_fail (bytes != NULL); g_return_if_fail (self->priv->mutable); g_hash_table_insert (g_ptr_array_index (self->parts, part), g_strdup (key), tp_g_value_slice_new_bytes (len, bytes)); } /** * tp_message_set: * @self: a message * @part: a part number, which must be strictly less than the number * returned by tp_message_count_parts() * @key: a key in the mapping representing the part * @source: a value, encoded as dbus-glib would * * Set @key in part @part of @self to have a copy of @source as its value. * * If @source represents a data structure containing handles, they should * all be referenced with tp_message_ref_handle() first. * * In high-level language bindings, use tp_message_set_variant() instead. * * Since: 0.7.21 */ void tp_message_set (TpMessage *self, guint part, const gchar *key, const GValue *source) { g_return_if_fail (part < self->parts->len); g_return_if_fail (key != NULL); g_return_if_fail (source != NULL); g_return_if_fail (self->priv->mutable); g_hash_table_insert (g_ptr_array_index (self->parts, part), g_strdup (key), tp_g_value_slice_dup (source)); } /** * tp_message_set_variant: * @self: a message * @part: a part number, which must be strictly less than the number * returned by tp_message_count_parts() * @key: a key in the mapping representing the part * @value: a value * * Set @key in part @part of @self to have @value as its value. * * If @value is a floating reference (see g_variant_ref_sink()), then this * function will take ownership of it. * * Since: 0.19.10 */ void tp_message_set_variant (TpMessage *self, guint part, const gchar *key, GVariant *value) { GValue *gvalue; g_return_if_fail (part < self->parts->len); g_return_if_fail (key != NULL); g_return_if_fail (value != NULL); g_return_if_fail (self->priv->mutable); g_variant_ref_sink (value); gvalue = g_slice_new0 (GValue); dbus_g_value_parse_g_variant (value, gvalue); g_variant_unref (value); g_hash_table_insert (g_ptr_array_index (self->parts, part), g_strdup (key), gvalue); } /** * tp_message_take_message: * @self: a #TpCMMessage * @part: a part number, which must be strictly less than the number * returned by tp_message_count_parts() * @key: a key in the mapping representing the part * @message: another (distinct) message created for the same #TpBaseConnection * * Set @key in part @part of @self to have @message as an aa{sv} value (that * is, an array of Message_Part), and take ownership of @message. The caller * should not use @message after passing it to this function. All handle * references owned by @message will subsequently belong to and be released * with @self. * * Since: 0.7.21 * Deprecated: since 0.13.9. Use tp_cm_message_take_message() */ void tp_message_take_message (TpMessage *self, guint part, const gchar *key, TpMessage *message) { g_return_if_fail (TP_IS_CM_MESSAGE (self)); tp_cm_message_take_message (self, part, key, message); } static void subtract_from_hash (gpointer key, gpointer value, gpointer user_data) { DEBUG ("... removing %s", (gchar *) key); g_hash_table_remove (user_data, key); } /** * tp_message_to_text: * @message: a #TpMessage * @out_flags: (out) : if not %NULL, the #TpChannelTextMessageFlags of @message * * Concatene all the text parts contained in @message. * * Returns: (transfer full): a newly allocated string containing the * text content of #message * * Since: 0.13.9 */ gchar * tp_message_to_text (TpMessage *message, TpChannelTextMessageFlags *out_flags) { guint i; GHashTable *header = g_ptr_array_index (message->parts, 0); /* Lazily created hash tables, used as a sets: keys are borrowed * "alternative" string values from @parts, value == key. */ /* Alternative IDs for which we have already extracted an alternative */ GHashTable *alternatives_used = NULL; /* Alternative IDs for which we expect to extract text, but have not yet; * cleared if the flag Channel_Text_Message_Flag_Non_Text_Content is set. * At the end, if this contains any item not in alternatives_used, * Channel_Text_Message_Flag_Non_Text_Content must be set. */ GHashTable *alternatives_needed = NULL; GString *buffer = g_string_new (""); TpChannelTextMessageFlags flags = 0; if (tp_asv_get_boolean (header, "scrollback", NULL)) flags |= TP_CHANNEL_TEXT_MESSAGE_FLAG_SCROLLBACK; if (tp_asv_get_boolean (header, "rescued", NULL)) flags |= TP_CHANNEL_TEXT_MESSAGE_FLAG_RESCUED; /* If the message is on an extended interface, is a delivery report, or only * contains headers, definitely set the "your client is too old" flag. */ if (message->parts->len <= 1 || tp_asv_get_uint32 (header, "message-type", NULL) == TP_CHANNEL_TEXT_MESSAGE_TYPE_DELIVERY_REPORT || g_hash_table_lookup (header, "interface") != NULL) { flags |= TP_CHANNEL_TEXT_MESSAGE_FLAG_NON_TEXT_CONTENT; } for (i = 1; i < message->parts->len; i++) { GHashTable *part = g_ptr_array_index (message->parts, i); const gchar *type = tp_asv_get_string (part, "content-type"); const gchar *alternative = tp_asv_get_string (part, "alternative"); /* Renamed to "content-type" in spec 0.17.14 */ if (type == NULL) type = tp_asv_get_string (part, "type"); DEBUG ("Parsing part %u, type %s, alternative %s", i, type, alternative); if (!tp_strdiff (type, "text/plain")) { GValue *value; DEBUG ("... is text/plain"); if (alternative != NULL && alternative[0] != '\0') { if (alternatives_used == NULL) { /* We can't have seen an alternative for this part yet. * However, we need to create the hash table now */ alternatives_used = g_hash_table_new (g_str_hash, g_str_equal); } else if (g_hash_table_lookup (alternatives_used, alternative) != NULL) { /* we've seen a "better" alternative for this part already. * Skip it */ DEBUG ("... already saw a better alternative, skipping it"); continue; } g_hash_table_insert (alternatives_used, (gpointer) alternative, (gpointer) alternative); } value = g_hash_table_lookup (part, "content"); if (value != NULL && G_VALUE_HOLDS_STRING (value)) { DEBUG ("... using its text"); g_string_append (buffer, g_value_get_string (value)); value = g_hash_table_lookup (part, "truncated"); if (value != NULL && (!G_VALUE_HOLDS_BOOLEAN (value) || g_value_get_boolean (value))) { DEBUG ("... appears to have been truncated"); flags |= TP_CHANNEL_TEXT_MESSAGE_FLAG_TRUNCATED; } } else { /* There was a text/plain part we couldn't parse: * that counts as "non-text content" I think */ DEBUG ("... didn't understand it, setting NON_TEXT_CONTENT"); flags |= TP_CHANNEL_TEXT_MESSAGE_FLAG_NON_TEXT_CONTENT; tp_clear_pointer (&alternatives_needed, g_hash_table_unref); } } else if ((flags & TP_CHANNEL_TEXT_MESSAGE_FLAG_NON_TEXT_CONTENT) == 0) { DEBUG ("... wondering whether this is NON_TEXT_CONTENT?"); if (tp_str_empty (alternative)) { /* This part can't possibly have a text alternative, since it * isn't part of a multipart/alternative group * (attached image or something, perhaps) */ DEBUG ("... ... yes, no possibility of a text alternative"); flags |= TP_CHANNEL_TEXT_MESSAGE_FLAG_NON_TEXT_CONTENT; tp_clear_pointer (&alternatives_needed, g_hash_table_unref); } else if (alternatives_used != NULL && g_hash_table_lookup (alternatives_used, (gpointer) alternative) != NULL) { DEBUG ("... ... no, we already saw a text alternative"); } else { /* This part might have a text alternative later, if we're * lucky */ if (alternatives_needed == NULL) alternatives_needed = g_hash_table_new (g_str_hash, g_str_equal); DEBUG ("... ... perhaps, but might have text alternative later"); g_hash_table_insert (alternatives_needed, (gpointer) alternative, (gpointer) alternative); } } } if ((flags & TP_CHANNEL_TEXT_MESSAGE_FLAG_NON_TEXT_CONTENT) == 0 && alternatives_needed != NULL) { if (alternatives_used != NULL) g_hash_table_foreach (alternatives_used, subtract_from_hash, alternatives_needed); if (g_hash_table_size (alternatives_needed) > 0) flags |= TP_CHANNEL_TEXT_MESSAGE_FLAG_NON_TEXT_CONTENT; } if (alternatives_needed != NULL) g_hash_table_unref (alternatives_needed); if (alternatives_used != NULL) g_hash_table_unref (alternatives_used); if (out_flags != NULL) { *out_flags = flags; } return g_string_free (buffer, FALSE); } void _tp_message_set_immutable (TpMessage *self) { self->priv->mutable = FALSE; } /** * tp_message_is_mutable: * @self: a #TpMessage * * Check if @self is mutable. Only mutable messages can be modified using * functions such as tp_message_set_string(). * * Returns: %TRUE if the message is mutable. * * Since: 0.13.9 */ gboolean tp_message_is_mutable (TpMessage *self) { g_return_val_if_fail (TP_IS_MESSAGE (self), FALSE); return self->priv->mutable; } /** * tp_message_get_token: * @self: a message * * Return this message's identifier in the underlying protocol. This is * not guaranteed to be unique, even within the scope * of a single channel or contact: the only guarantee made is that two * messages with different non-empty tokens are different messages. * * If there is no suitable token, return %NULL. * * Returns: (transfer none): a non-empty opaque identifier, or %NULL if none * * Since: 0.13.9 */ const gchar * tp_message_get_token (TpMessage *self) { const gchar *token; g_return_val_if_fail (TP_IS_MESSAGE (self), NULL); token = tp_asv_get_string (tp_message_peek (self, 0), "message-token"); if (tp_str_empty (token)) return NULL; else return token; } /** * tp_message_get_message_type: * @self: a message * * * * Returns: the type of this message * * Since: 0.13.10 */ TpChannelTextMessageType tp_message_get_message_type (TpMessage *self) { g_return_val_if_fail (TP_IS_MESSAGE (self), TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL); /* if message-type is absent or invalid we just return 0, which is NORMAL */ return tp_asv_get_uint32 (tp_message_peek (self, 0), "message-type", NULL); } /** * tp_message_get_sent_timestamp: * @self: a message * * Return when this message was sent, as a number of seconds since the * beginning of 1970 in the UTC timezone (the same representation used by * g_date_time_new_from_unix_utc(), for instance), or 0 if not known. * * If this protocol does not track the time at which the message was * initially sent, this timestamp might be approximated by using the * time at which it arrived at a central server. * * Returns: a Unix timestamp, or 0 * * Since: 0.13.9 */ gint64 tp_message_get_sent_timestamp (TpMessage *self) { g_return_val_if_fail (TP_IS_MESSAGE (self), 0); return tp_asv_get_int64 (tp_message_peek (self, 0), "message-sent", NULL); } /** * tp_message_get_received_timestamp: * @self: a message * * Return when this message was received locally, as a number of seconds since * the beginning of 1970 in the UTC timezone (the same representation used by * g_date_time_new_from_unix_utc(), for instance), or 0 if not known. * * Returns: a Unix timestamp, or 0 * * Since: 0.13.9 */ gint64 tp_message_get_received_timestamp (TpMessage *self) { g_return_val_if_fail (TP_IS_MESSAGE (self), 0); return tp_asv_get_int64 (tp_message_peek (self, 0), "message-received", NULL); } /** * tp_message_is_scrollback: * @self: a message * * * * Returns: %TRUE if this message is part of a replay of message history, for * instance in an XMPP chatroom. * * Since: 0.13.9 */ gboolean tp_message_is_scrollback (TpMessage *self) { g_return_val_if_fail (TP_IS_MESSAGE (self), FALSE); return tp_asv_get_boolean (tp_message_peek (self, 0), "scrollback", NULL); } /** * tp_message_is_rescued: * @self: a message * * Returns %TRUE if this incoming message has been seen in a previous channel * during the lifetime of the Connection, but had not been acknowledged when * that channel closed, causing an identical channel (in which the message now * appears) to open. * * Loggers should check this flag to avoid duplicating messages, for instance. * * Returns: %TRUE if this message was seen in a previous Channel on this * Connection * * Since: 0.13.9 */ gboolean tp_message_is_rescued (TpMessage *self) { g_return_val_if_fail (TP_IS_MESSAGE (self), FALSE); return tp_asv_get_boolean (tp_message_peek (self, 0), "rescued", NULL); } /** * tp_message_get_supersedes: * @self: a message * * If this message replaces a previous message, return the value of * tp_message_get_token() for that previous message. Otherwise, return %NULL. * * For instance, a user interface could replace the superseded * message with this message, or grey out the superseded message. * * Returns: (transfer none): a non-empty opaque identifier, or %NULL if none * * Since: 0.13.9 */ const gchar * tp_message_get_supersedes (TpMessage *self) { const gchar *token; g_return_val_if_fail (TP_IS_MESSAGE (self), NULL); token = tp_asv_get_string (tp_message_peek (self, 0), "supersedes"); if (tp_str_empty (token)) return NULL; else return token; } /** * tp_message_get_specific_to_interface: * @self: a message * * If this message is specific to a particular D-Bus interface and should * be ignored by clients without knowledge of that interface, return the * name of the interface. * * If this message is an ordinary message or delivery report, return %NULL. * * Returns: (transfer none): a D-Bus interface name, or %NULL for ordinary * messages and delivery reports * * Since: 0.13.9 */ const gchar * tp_message_get_specific_to_interface (TpMessage *self) { g_return_val_if_fail (TP_IS_MESSAGE (self), NULL); return tp_asv_get_string (tp_message_peek (self, 0), "interface"); } /** * tp_message_is_delivery_report: * @self: a message * * If this message is a delivery report indicating success or failure of * delivering a message, return %TRUE. * * Returns: %TRUE if this is a delivery report * * Since: 0.13.9 */ gboolean tp_message_is_delivery_report (TpMessage *self) { gboolean valid; g_return_val_if_fail (TP_IS_MESSAGE (self), FALSE); tp_asv_get_uint32 (tp_message_peek (self, 0), "delivery-status", &valid); return valid; } /** * tp_message_get_pending_message_id: * @self: a message * @valid: (out): either %NULL, or a location in which to store %TRUE if @self * contains a pending message ID. * * Return the incoming message ID of @self. Only incoming messages have such * ID, for outgoing ones this function returns 0 and set @valid to %FALSE. * * Returns: the incoming message ID. * * Since: 0.15.3 */ guint32 tp_message_get_pending_message_id (TpMessage *self, gboolean *valid) { g_return_val_if_fail (TP_IS_MESSAGE (self), FALSE); return tp_asv_get_uint32 (tp_message_peek (self, 0), "pending-message-id", valid); } /* * Omitted for now: * * sender-nickname - perhaps better done in TpSignalledMessage, so we can use * the TpContact's nickname if the message doesn't specify? * * delivery reporting stuff other than "is this a report?" - later */