/* * 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. * * @since 0.7.21 */ #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; }; 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-sent", "message-received", "message-token", NULL }; static const char * const headers_only_incoming[] = { "message-sender", "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))) /** * 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). */ struct _TpMessage { TpBaseConnection *connection; /* array of hash tables, allocated string => sliced GValue */ GPtrArray *parts; /* handles referenced by this message */ TpHandleSet *reffed_handles[NUM_TP_HANDLE_TYPES]; /* from here down is implementation-specific for TpMessageMixin */ /* for receiving */ guint32 incoming_id; /* for sending */ DBusGMethodInvocation *outgoing_context; TpMessageSendingFlags outgoing_flags; gboolean outgoing_text_api:1; }; /** * 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 */ TpMessage * tp_message_new (TpBaseConnection *connection, guint initial_parts, guint size_hint) { TpMessage *self; guint i; g_return_val_if_fail (connection != NULL, NULL); g_return_val_if_fail (initial_parts >= 1, NULL); g_return_val_if_fail (size_hint >= initial_parts, NULL); self = g_slice_new0 (TpMessage); self->connection = g_object_ref (connection); self->parts = g_ptr_array_sized_new (size_hint); self->incoming_id = G_MAXUINT32; self->outgoing_context = NULL; for (i = 0; i < initial_parts; i++) { 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; } /** * tp_message_destroy: * @self: a message * * Destroy @self. * * @since 0.7.21 */ void tp_message_destroy (TpMessage *self) { guint i; g_return_if_fail (TP_IS_BASE_CONNECTION (self->connection)); g_return_if_fail (self->parts != NULL); g_return_if_fail (self->parts->len >= 1); for (i = 0; i < self->parts->len; i++) { g_hash_table_destroy (g_ptr_array_index (self->parts, i)); } g_ptr_array_free (self->parts, TRUE); for (i = 0; i < NUM_TP_HANDLE_TYPES; i++) { if (self->reffed_handles[i] != NULL) tp_handle_set_destroy (self->reffed_handles[i]); } g_object_unref (self->connection); g_slice_free (TpMessage, 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: 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_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_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_hash_table_unref (g_ptr_array_remove_index (self->parts, part)); } static void _ensure_handle_set (TpMessage *self, TpHandleType handle_type) { if (self->reffed_handles[handle_type] == NULL) { TpHandleRepoIface *handles = tp_base_connection_get_handles ( self->connection, handle_type); g_return_if_fail (handles != NULL); self->reffed_handles[handle_type] = tp_handle_set_new (handles); } } /** * tp_message_ref_handle: * @self: a message * @handle_type: a handle type, greater than %TP_HANDLE_TYPE_NONE and less than * %NUM_TP_HANDLE_TYPES * @handle: a handle of the given type * * Reference the given handle until this message is destroyed. * * @since 0.7.21 */ void tp_message_ref_handle (TpMessage *self, TpHandleType handle_type, TpHandle handle) { g_return_if_fail (handle_type > TP_HANDLE_TYPE_NONE); g_return_if_fail (handle_type < NUM_TP_HANDLE_TYPES); g_return_if_fail (handle != 0); _ensure_handle_set (self, handle_type); tp_handle_set_add (self->reffed_handles[handle_type], handle); } /** * tp_message_ref_handles: * @self: a message * @handle_type: a handle type, greater than %TP_HANDLE_TYPE_NONE and less * than %NUM_TP_HANDLE_TYPES * @handles: a set of handles of the given type * * References all of the given handles until this message is destroyed. * * @since 0.7.21 */ static void tp_message_ref_handles (TpMessage *self, TpHandleType handle_type, TpIntSet *handles) { TpIntSet *updated; g_return_if_fail (handle_type > TP_HANDLE_TYPE_NONE); g_return_if_fail (handle_type < NUM_TP_HANDLE_TYPES); g_return_if_fail (!tp_intset_is_member (handles, 0)); _ensure_handle_set (self, handle_type); updated = tp_handle_set_update (self->reffed_handles[handle_type], handles); tp_intset_destroy (updated); } /** * 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); return g_hash_table_remove (g_ptr_array_index (self->parts, part), key); } /** * tp_message_set_handle: * @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 * @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.7.21 */ void tp_message_set_handle (TpMessage *self, guint part, const gchar *key, TpHandleType handle_type, TpHandle handle_or_0) { if (handle_or_0 != 0) tp_message_ref_handle (self, handle_type, handle_or_0); 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_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_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_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_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_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_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); 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_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 * * 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. * * @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_hash_table_insert (g_ptr_array_index (self->parts, part), g_strdup (key), tp_g_value_slice_dup (source)); } /** * tp_message_take_message: * @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 * @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 */ void tp_message_take_message (TpMessage *self, guint part, const gchar *key, TpMessage *message) { guint i; g_return_if_fail (self != NULL); g_return_if_fail (part < self->parts->len); g_return_if_fail (key != NULL); g_return_if_fail (message != NULL); g_return_if_fail (self != message); g_return_if_fail (self->connection == message->connection); g_hash_table_insert (g_ptr_array_index (self->parts, part), g_strdup (key), tp_g_value_slice_new_take_boxed (TP_ARRAY_TYPE_MESSAGE_PART_LIST, message->parts)); /* Now that @self has stolen @message's parts, replace them with a stub to * keep tp_message_destroy happy. */ message->parts = g_ptr_array_sized_new (1); g_ptr_array_add (message->parts, g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) tp_g_value_slice_free)); for (i = 0; i < NUM_TP_HANDLE_TYPES; i++) { if (message->reffed_handles[i] != NULL) tp_message_ref_handles (self, i, tp_handle_set_peek (message->reffed_handles[i])); } tp_message_destroy (message); } /** * 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 TpMessage *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 inline void nullify_hash (GHashTable **hash) { if (*hash == NULL) return; g_hash_table_destroy (*hash); *hash = NULL; } static void subtract_from_hash (gpointer key, gpointer value, gpointer user_data) { DEBUG ("... removing %s", (gchar *) key); g_hash_table_remove (user_data, key); } static gchar * parts_to_text (const GPtrArray *parts, TpChannelTextMessageFlags *out_flags, TpChannelTextMessageType *out_type, TpHandle *out_sender, guint *out_timestamp) { guint i; GHashTable *header = g_ptr_array_index (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 or only contains headers, * definitely set the "your client is too old" flag. */ if (parts->len <= 1 || g_hash_table_lookup (header, "interface") != NULL) { flags |= TP_CHANNEL_TEXT_MESSAGE_FLAG_NON_TEXT_CONTENT; } for (i = 1; i < parts->len; i++) { GHashTable *part = g_ptr_array_index (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; nullify_hash (&alternatives_needed); } } 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; nullify_hash (&alternatives_needed); } 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_destroy (alternatives_needed); if (alternatives_used != NULL) g_hash_table_destroy (alternatives_used); if (out_flags != NULL) { *out_flags = flags; } 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 g_string_free (buffer, FALSE); } /** * 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); } /** * 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), NUM_TP_CHANNEL_TEXT_MESSAGE_TYPES); mixin->priv->connection = g_object_ref (connection); mixin->priv->supported_content_types = g_new0 (gchar *, 1); } /** * 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_free (mixin->priv->msg_types, TRUE); g_strfreev (mixin->priv->supported_content_types); g_object_unref (mixin->priv->connection); 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); GList **nodes; TpMessage *item; guint i; nodes = g_new (GList *, ids->len); for (i = 0; i < ids->len; i++) { guint id = g_array_index (ids, guint, i); nodes[i] = g_queue_find_custom (mixin->priv->pending, GUINT_TO_POINTER (id), pending_item_id_equals_data); if (nodes[i] == NULL) { GError *error = g_error_new (TP_ERRORS, 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_free (nodes); return; } } tp_svc_channel_interface_messages_emit_pending_messages_removed (iface, ids); for (i = 0; i < ids->len; i++) { item = nodes[i]->data; DEBUG ("acknowledging message id %u", item->incoming_id); g_queue_remove (mixin->priv->pending, item); tp_message_destroy (item); } g_free (nodes); 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; GValue val = { 0, }; gchar *text; TpChannelTextMessageFlags flags; TpChannelTextMessageType type; TpHandle sender; guint timestamp; text = parts_to_text (msg->parts, &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, 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; GList *next = cur->next; i = 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_free (ids, TRUE); } tp_svc_channel_type_text_return_from_list_pending_messages (context, messages); for (i = 0; i < messages->len; i++) g_value_array_free (g_ptr_array_index (messages, i)); g_ptr_array_free (messages, TRUE); } 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_ERRORS, 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_ERRORS, 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_destroy (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; g_queue_push_tail (mixin->priv->pending, pending); text = parts_to_text (pending->parts, &flags, &type, &sender, ×tamp); tp_svc_channel_type_text_emit_received (object, pending->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; if (echo != NULL) { const GHashTable *echo_header = g_ptr_array_index (echo, 1); /* 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, NULL, &type, NULL, NULL); timestamp = tp_asv_get_uint32 (echo_header, "message-sent", NULL); } else { text = NULL; timestamp = 0; } 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); GHashTable *header; g_return_val_if_fail (message->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 */ message->incoming_id = mixin->priv->recv_id++; tp_message_set_uint32 (message, 0, "pending-message-id", message->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 message->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); time_t now = time (NULL); g_return_if_fail (mixin != NULL); g_return_if_fail (object != NULL); g_return_if_fail (message != NULL); g_return_if_fail (message != NULL); g_return_if_fail (message->parts != NULL); g_return_if_fail (message->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 (message->outgoing_context, e); g_error_free (e); } else { TpChannelTextMessageType message_type; gchar *string; GHashTable *header = g_ptr_array_index (message->parts, 0); if (tp_asv_get_uint64 (header, "message-sent", NULL) == 0) tp_message_set_uint64 (message, 0, "message-sent", time (NULL)); /* emit Sent and MessageSent */ tp_svc_channel_interface_messages_emit_message_sent (object, message->parts, flags, token); string = parts_to_text (message->parts, NULL, &message_type, NULL, NULL); tp_svc_channel_type_text_emit_sent (object, now, message_type, string); g_free (string); /* return successfully */ if (message->outgoing_text_api) { tp_svc_channel_type_text_return_from_send ( message->outgoing_context); } else { tp_svc_channel_interface_messages_return_from_send_message ( message->outgoing_context, token); } } message->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; if (mixin->priv->send_message == NULL) { tp_dbus_g_method_return_not_implemented (context); return; } message = tp_message_new (mixin->priv->connection, 2, 2); 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); message->outgoing_context = context; message->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; 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_ERRORS, 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_ERRORS, 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_ERRORS, 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_ERRORS, 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_ERRORS, TP_ERROR_INVALID_ARGUMENT, "Key '%s' not allowed in a message body", *iter); dbus_g_method_return_error (context, error); return; } } } message = tp_message_new (mixin->priv->connection, parts->len, parts->len); 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); } message->outgoing_context = context; message->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 }, { NULL } }; tp_dbus_properties_mixin_implement_interface (cls, TP_IFACE_QUARK_CHANNEL_INTERFACE_MESSAGES, tp_message_mixin_get_dbus_property, NULL, 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; 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 ("DeliveryReportingSupportFlags"); } mixin = TP_MESSAGE_MIXIN (object); g_return_if_fail (interface == TP_IFACE_QUARK_CHANNEL_INTERFACE_MESSAGES); 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); } } /** * 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 }