diff -r d0f3a028347a -r 59927b2d3b75 telepathygabble/src/text-mixin.c --- a/telepathygabble/src/text-mixin.c Tue Feb 02 01:10:06 2010 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,819 +0,0 @@ -/* - * text-mixin.c - Source for GabbleTextMixin - * Copyright (C) 2006 Collabora Ltd. - * - * @author Ole Andre Vadla Ravnaas - * @author Robert McQueen - * @author Senko Rasic - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - */ - - -#include "loudmouth/loudmouth.h" -#include -#include -#include -#include -#include - -#include "telepathy-constants.h" -#include "telepathy-errors.h" - - -#include "debug.h" -#include "gabble-connection.h" -#include "namespaces.h" -#include "roster.h" -#include "util.h" - -#include "text-mixin.h" -#include "text-mixin-signals-marshal.h" - -#include "gabble_enums.h" - -#define _GNU_SOURCE /* Needed for strptime (_XOPEN_SOURCE can also be used). */ -#define DEBUG_FLAG GABBLE_DEBUG_IM -#define TP_TYPE_PENDING_MESSAGE_STRUCT (dbus_g_type_get_struct ("GValueArray", \ - G_TYPE_UINT, \ - G_TYPE_UINT, \ - G_TYPE_UINT, \ - G_TYPE_UINT, \ - G_TYPE_UINT, \ - G_TYPE_STRING, \ - G_TYPE_INVALID)) - -/* allocator */ - -#ifdef DEBUG_FLAG -//#define DEBUG(format, ...) -#define DEBUGGING 0 -#define NODE_DEBUG(n, s) -#endif /* DEBUG_FLAG */ - - -#ifdef EMULATOR -#include "libgabble_wsd_solution.h" - - GET_STATIC_VAR_FROM_TLS(offset_quark1,gabble_txt_mixin,GQuark) - #define offset_quark1 (*GET_WSD_VAR_NAME(offset_quark1,gabble_txt_mixin, s)()) - - GET_STATIC_VAR_FROM_TLS(offset_quark,gabble_txt_mixin,GQuark) - #define offset_quark (*GET_WSD_VAR_NAME(offset_quark,gabble_txt_mixin, s)()) - - GET_STATIC_VAR_FROM_TLS(alloc1,gabble_txt_mixin,GabbleAllocator) - #define alloc1 (*GET_WSD_VAR_NAME(alloc1,gabble_txt_mixin, s)()) - -#endif - -/* -Moved to gabble_enums.h -typedef struct _GabbleAllocator GabbleAllocator; -struct _GabbleAllocator -{ - gulong size; - guint limit; - guint count; -};*/ - - - -#define ga_new0(alloc, type) \ - ((type *) gabble_allocator_alloc0 (alloc)) - -static void -gabble_allocator_init (GabbleAllocator *alloc, gulong size, guint limit) -{ - g_assert (alloc != NULL); - g_assert (size > 0); - g_assert (limit > 0); - - alloc->size = size; - alloc->limit = limit; -} - -static gpointer gabble_allocator_alloc0 (GabbleAllocator *alloc) -{ - gpointer ret; - - g_assert (alloc != NULL); - g_assert (alloc->count <= alloc->limit); - - if (alloc->count == alloc->limit) - { - ret = NULL; - } - else - { - ret = g_malloc0 (alloc->size); - alloc->count++; - } - - return ret; -} - -static void gabble_allocator_free (GabbleAllocator *alloc, gpointer thing) -{ - g_assert (alloc != NULL); - g_assert (thing != NULL); - - g_free (thing); - alloc->count--; -} - -/* pending message */ -#define MAX_PENDING_MESSAGES 256 -#define MAX_MESSAGE_SIZE 1024 - 1 - -typedef struct _GabblePendingMessage GabblePendingMessage; -struct _GabblePendingMessage -{ - guint id; - time_t timestamp; - GabbleHandle sender; - TpChannelTextMessageType type; - char *text; - guint flags; -}; - -/** - * gabble_text_mixin_class_get_offset_quark: - * - * Returns: the quark used for storing mixin offset on a GObjectClass - */ -GQuark -gabble_text_mixin_class_get_offset_quark () -{ -#ifndef EMULATOR - static GQuark offset_quark1 = 0; -#endif - - if (!offset_quark1) - offset_quark1 = g_quark_from_static_string("TextMixinClassOffsetQuark"); - return offset_quark1; -} - -/** - * gabble_text_mixin_get_offset_quark: - * - * Returns: the quark used for storing mixin offset on a GObject - */ -GQuark -gabble_text_mixin_get_offset_quark () -{ -#ifndef EMULATOR - static GQuark offset_quark = 0; -#endif - - if (!offset_quark) - offset_quark = g_quark_from_static_string("TextMixinOffsetQuark"); - return offset_quark; -} - - -/* GabbleTextMixin */ -void -gabble_text_mixin_class_init (GObjectClass *obj_cls, glong offset) -{ - GabbleTextMixinClass *mixin_cls; - - g_assert (G_IS_OBJECT_CLASS (obj_cls)); - - g_type_set_qdata (G_OBJECT_CLASS_TYPE (obj_cls), - GABBLE_TEXT_MIXIN_CLASS_OFFSET_QUARK, - GINT_TO_POINTER (offset)); - - mixin_cls = GABBLE_TEXT_MIXIN_CLASS (obj_cls); - - mixin_cls->lost_message_signal_id = g_signal_new ("lost-message", - G_OBJECT_CLASS_TYPE (obj_cls), - G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, - 0, - NULL, NULL, - g_cclosure_marshal_VOID__VOID, - G_TYPE_NONE, 0); - - mixin_cls->received_signal_id = g_signal_new ("received", - G_OBJECT_CLASS_TYPE (obj_cls), - G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, - 0, - NULL, NULL, - text_mixin_marshal_VOID__UINT_UINT_UINT_UINT_UINT_STRING, - G_TYPE_NONE, 6, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_STRING); - - mixin_cls->send_error_signal_id = g_signal_new ("send-error", - G_OBJECT_CLASS_TYPE (obj_cls), - G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, - 0, - NULL, NULL, - text_mixin_marshal_VOID__UINT_UINT_UINT_STRING, - G_TYPE_NONE, 4, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_STRING); - - mixin_cls->sent_signal_id = g_signal_new ("sent", - G_OBJECT_CLASS_TYPE (obj_cls), - G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, - 0, - NULL, NULL, - text_mixin_marshal_VOID__UINT_UINT_STRING, - G_TYPE_NONE, 3, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_STRING); -} - -void -gabble_text_mixin_init (GObject *obj, - glong offset, - GabbleHandleRepo *handle_repo, - gboolean send_nick) -{ - GabbleTextMixin *mixin; - - g_assert (G_IS_OBJECT (obj)); - - g_type_set_qdata (G_OBJECT_TYPE (obj), - GABBLE_TEXT_MIXIN_OFFSET_QUARK, - GINT_TO_POINTER (offset)); - - mixin = GABBLE_TEXT_MIXIN (obj); - - mixin->pending = g_queue_new (); - mixin->handle_repo = handle_repo; - mixin->recv_id = 0; - mixin->msg_types = g_array_sized_new (FALSE, FALSE, sizeof (guint), 4); - - mixin->message_lost = FALSE; -} - -void -gabble_text_mixin_set_message_types (GObject *obj, - ...) -{ - GabbleTextMixin *mixin = GABBLE_TEXT_MIXIN (obj); - va_list args; - guint type; - - va_start (args, obj); - - while ((type = va_arg (args, guint)) != G_MAXUINT) - g_array_append_val (mixin->msg_types, type); - - va_end (args); -} - -static void _gabble_pending_free (GabblePendingMessage *msg); -static GabbleAllocator * _gabble_pending_get_alloc (); - -void -gabble_text_mixin_finalize (GObject *obj) -{ - GabbleTextMixin *mixin = GABBLE_TEXT_MIXIN (obj); - GabblePendingMessage *msg; - - /* free any data held directly by the object here */ - - msg = g_queue_pop_head(mixin->pending); - while (msg) - { - gabble_handle_unref (mixin->handle_repo, TP_HANDLE_TYPE_CONTACT, msg->sender); - _gabble_pending_free (msg); - msg = g_queue_pop_head(mixin->pending); - } - - g_queue_free (mixin->pending); - - g_array_free (mixin->msg_types, TRUE); -} - -/** - * _gabble_pending_get_alloc - * - * Returns a GabbleAllocator for creating up to 256 pending messages, but no - * more. - */ -static GabbleAllocator * -_gabble_pending_get_alloc () -{ - -#ifndef EMULATOR - static GabbleAllocator alloc1 = { 0, }; -#endif - - if (0 == alloc1.size) - gabble_allocator_init (&alloc1, sizeof(GabblePendingMessage), MAX_PENDING_MESSAGES); - - return &alloc1; -} - -#define _gabble_pending_new0() \ - (ga_new0 (_gabble_pending_get_alloc (), GabblePendingMessage)) - -/** - * _gabble_pending_free - * - * Free up a GabblePendingMessage struct. - */ -static void _gabble_pending_free (GabblePendingMessage *msg) -{ - g_free (msg->text); - gabble_allocator_free (_gabble_pending_get_alloc (), msg); -} - -/** - * _gabble_text_mixin_receive - * - */ -gboolean gabble_text_mixin_receive (GObject *obj, - TpChannelTextMessageType type, - GabbleHandle sender, - time_t timestamp, - const char *text) -{ - GabbleTextMixin *mixin = GABBLE_TEXT_MIXIN (obj); - GabbleTextMixinClass *mixin_cls = GABBLE_TEXT_MIXIN_CLASS (G_OBJECT_GET_CLASS (obj)); - - gchar *end; - GabblePendingMessage *msg; - gsize len; - - msg = _gabble_pending_new0 (); - - if (msg == NULL) - { - gabble_debug (DEBUG_FLAG, "no more pending messages available, giving up"); - - if (!mixin->message_lost) - { - g_signal_emit (obj, mixin_cls->lost_message_signal_id, 0); - mixin->message_lost = TRUE; - } - - return FALSE; - } - - len = strlen (text); - - if (len > MAX_MESSAGE_SIZE) - { - gabble_debug (DEBUG_FLAG, "message exceeds maximum size, truncating"); - - msg->flags |= TP_CHANNEL_TEXT_MESSAGE_FLAG_TRUNCATED; - - end = g_utf8_find_prev_char (text, text+MAX_MESSAGE_SIZE); - if (end) - len = end-text; - else - len = 0; - } - - msg->text = g_try_malloc (len + 1); - - if (msg->text == NULL) - { - gabble_debug (DEBUG_FLAG, "unable to allocate message, giving up"); - - if (!mixin->message_lost) - { - g_signal_emit (obj, mixin_cls->lost_message_signal_id, 0); - mixin->message_lost = TRUE; - } - - _gabble_pending_free (msg); - - return FALSE; - } - - g_strlcpy (msg->text, text, len + 1); - - msg->id = mixin->recv_id++; - msg->timestamp = timestamp; - msg->sender = sender; - msg->type = type; - - gabble_handle_ref (mixin->handle_repo, TP_HANDLE_TYPE_CONTACT, msg->sender); - g_queue_push_tail (mixin->pending, msg); - - g_signal_emit (obj, mixin_cls->received_signal_id, 0, - msg->id, - msg->timestamp, - msg->sender, - msg->type, - msg->flags, - msg->text); - - gabble_debug (DEBUG_FLAG, "queued message %u", msg->id); - - mixin->message_lost = FALSE; - - return TRUE; -} - -static gint -compare_pending_message (gconstpointer haystack, - gconstpointer needle) -{ - GabblePendingMessage *msg = (GabblePendingMessage *) haystack; - guint id = GPOINTER_TO_INT (needle); - - return (msg->id != id); -} - -/** - * gabble_text_mixin_acknowledge_pending_messages - * - * Implements D-Bus method AcknowledgePendingMessages - * on interface org.freedesktop.Telepathy.Channel.Type.Text - * - * @error: Used to return a pointer to a GError detailing any error - * that occurred, D-Bus will throw the error only if this - * function returns false. - * - * Returns: TRUE if successful, FALSE if an error was thrown. - */ -gboolean gabble_text_mixin_acknowledge_pending_messages (GObject *obj, const GArray * ids, GError **error) -{ - GabbleTextMixin *mixin = GABBLE_TEXT_MIXIN (obj); - GList **nodes; - GabblePendingMessage *msg; - 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->pending, - GINT_TO_POINTER (id), - compare_pending_message); - - if (nodes[i] == NULL) - { - gabble_debug (DEBUG_FLAG, "invalid message id %u", id); - - g_set_error (error, TELEPATHY_ERRORS, InvalidArgument, - "invalid message id %u", id); - - g_free(nodes); - return FALSE; - } - } - - for (i = 0; i < ids->len; i++) - { - msg = (GabblePendingMessage *) nodes[i]->data; - - gabble_debug (DEBUG_FLAG, "acknowleding message id %u", msg->id); - - g_queue_remove (mixin->pending, msg); - - gabble_handle_unref (mixin->handle_repo, TP_HANDLE_TYPE_CONTACT, msg->sender); - _gabble_pending_free (msg); - } - - g_free(nodes); - return TRUE; -} - -/** - * gabble_text_mixin_list_pending_messages - * - * Implements D-Bus method ListPendingMessages - * on interface org.freedesktop.Telepathy.Channel.Type.Text - * - * @error: Used to return a pointer to a GError detailing any error - * that occurred, D-Bus will throw the error only if this - * function returns false. - * - * Returns: TRUE if successful, FALSE if an error was thrown. - */ -gboolean gabble_text_mixin_list_pending_messages (GObject *obj, gboolean clear, GPtrArray ** ret, GError **error) -{ - GabbleTextMixin *mixin = GABBLE_TEXT_MIXIN (obj); - guint count; - GPtrArray *messages; - GList *cur; - - count = g_queue_get_length (mixin->pending); - messages = g_ptr_array_sized_new (count); - - for (cur = (clear ? g_queue_pop_head_link(mixin->pending) - : g_queue_peek_head_link(mixin->pending)); - cur != NULL; - cur = (clear ? g_queue_pop_head_link(mixin->pending) - : cur->next)) - { - GabblePendingMessage *msg = (GabblePendingMessage *) cur->data; - GValue val = { 0, }; - - g_value_init (&val, TP_TYPE_PENDING_MESSAGE_STRUCT); - g_value_take_boxed (&val, - dbus_g_type_specialized_construct (TP_TYPE_PENDING_MESSAGE_STRUCT)); - dbus_g_type_struct_set (&val, - 0, msg->id, - 1, msg->timestamp, - 2, msg->sender, - 3, msg->type, - 4, msg->flags, - 5, msg->text, - G_MAXUINT); - - g_ptr_array_add (messages, g_value_get_boxed (&val)); - } - - *ret = messages; - - return TRUE; -} - -/** - * gabble_text_mixin_send - * - * Implements D-Bus method Send - * on interface org.freedesktop.Telepathy.Channel.Type.Text - * - * @error: Used to return a pointer to a GError detailing any error - * that occurred, D-Bus will throw the error only if this - * function returns false. - * - * Returns: TRUE if successful, FALSE if an error was thrown. - */ -gboolean gabble_text_mixin_send (GObject *obj, guint type, guint subtype, - const char *recipient, const gchar *text, - GabbleConnection *conn, gboolean emit_signal, - GError **error) -{ - GabbleTextMixin *mixin = GABBLE_TEXT_MIXIN (obj); - LmMessage *msg; - gboolean result; - time_t timestamp; - - if (type > TP_CHANNEL_TEXT_MESSAGE_TYPE_NOTICE) - { - gabble_debug (DEBUG_FLAG, "invalid message type %u", type); - - g_set_error (error, TELEPATHY_ERRORS, InvalidArgument, - "invalid message type: %u", type); - - return FALSE; - } - - if (!subtype) - { - switch (type) - { - case TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL: - case TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION: - subtype = LM_MESSAGE_SUB_TYPE_CHAT; - break; - case TP_CHANNEL_TEXT_MESSAGE_TYPE_NOTICE: - subtype = LM_MESSAGE_SUB_TYPE_NORMAL; - break; - } - } - - msg = lm_message_new_with_sub_type (recipient, LM_MESSAGE_TYPE_MESSAGE, subtype); - - if (mixin->send_nick) - { - lm_message_node_add_own_nick (msg->node, conn); - mixin->send_nick = FALSE; - } - - if (type == TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION) - { - gchar *tmp; - tmp = g_strconcat ("/me ", text, NULL); - lm_message_node_add_child (msg->node, "body", tmp); - g_free (tmp); - } - else - { - lm_message_node_add_child (msg->node, "body", text); - } - - result = _gabble_connection_send (conn, msg, error); - lm_message_unref (msg); - - if (!result) - return FALSE; - - if (emit_signal) - { - timestamp = time (NULL); - - gabble_text_mixin_emit_sent (obj, timestamp, type, text); - } - - return TRUE; -} - -void -gabble_text_mixin_emit_sent (GObject *obj, - time_t timestamp, - guint type, - const char *text) -{ - GabbleTextMixinClass *mixin_cls = GABBLE_TEXT_MIXIN_CLASS (G_OBJECT_GET_CLASS - (obj)); - - g_signal_emit (obj, mixin_cls->sent_signal_id, 0, - timestamp, - type, - text); -} - -gboolean -gabble_text_mixin_get_message_types (GObject *obj, GArray **ret, GError **error) -{ - GabbleTextMixin *mixin = GABBLE_TEXT_MIXIN (obj); - guint i; - - *ret = g_array_sized_new (FALSE, FALSE, sizeof (guint), - mixin->msg_types->len); - - for (i = 0; i < mixin->msg_types->len; i++) - { - g_array_append_val (*ret, g_array_index (mixin->msg_types, guint, i)); - } - - return TRUE; -} - - -void -gabble_text_mixin_clear (GObject *obj) -{ - GabbleTextMixin *mixin = GABBLE_TEXT_MIXIN (obj); - GabblePendingMessage *msg; - - msg = g_queue_pop_head(mixin->pending); - while (msg) - { - gabble_handle_unref (mixin->handle_repo, TP_HANDLE_TYPE_CONTACT, msg->sender); - _gabble_pending_free (msg); - msg = g_queue_pop_head(mixin->pending); - } -} - -gboolean -gabble_text_mixin_parse_incoming_message (LmMessage *message, - const gchar **from, - time_t *stamp, - TpChannelTextMessageType *msgtype, - const gchar **body, - const gchar **body_offset, - GabbleTextMixinSendError *send_error) -{ - const gchar *type; - LmMessageNode *node; - - *send_error = CHANNEL_TEXT_SEND_NO_ERROR; - - if (lm_message_get_sub_type (message) == LM_MESSAGE_SUB_TYPE_ERROR) - { - LmMessageNode *error_node; - - error_node = lm_message_node_get_child (message->node, "error"); - if (error_node) - { - GabbleXmppError err = gabble_xmpp_error_from_node (error_node); - gabble_debug (DEBUG_FLAG, "got xmpp error: %s: %s", gabble_xmpp_error_string (err), - gabble_xmpp_error_description (err)); - - /* these are based on descriptions of errors, and some testing */ - switch (err) - { - case XMPP_ERROR_SERVICE_UNAVAILABLE: - case XMPP_ERROR_RECIPIENT_UNAVAILABLE: - *send_error = CHANNEL_TEXT_SEND_ERROR_OFFLINE; - break; - - case XMPP_ERROR_ITEM_NOT_FOUND: - case XMPP_ERROR_JID_MALFORMED: - case XMPP_ERROR_REMOTE_SERVER_TIMEOUT: - *send_error = CHANNEL_TEXT_SEND_ERROR_INVALID_CONTACT; - break; - - case XMPP_ERROR_FORBIDDEN: - *send_error = CHANNEL_TEXT_SEND_ERROR_PERMISSION_DENIED; - break; - - case XMPP_ERROR_RESOURCE_CONSTRAINT: - *send_error = CHANNEL_TEXT_SEND_ERROR_TOO_LONG; - break; - - case XMPP_ERROR_FEATURE_NOT_IMPLEMENTED: - *send_error = CHANNEL_TEXT_SEND_ERROR_NOT_IMPLEMENTED; - break; - - default: - *send_error = CHANNEL_TEXT_SEND_ERROR_UNKNOWN; - } - } - else - { - *send_error = CHANNEL_TEXT_SEND_ERROR_UNKNOWN; - } - } - - *from = lm_message_node_get_attribute (message->node, "from"); - if (*from == NULL) - { - NODE_DEBUG (message->node, "got a message without a from field"); - return FALSE; - } - - type = lm_message_node_get_attribute (message->node, "type"); - - /* - * Parse timestamp of delayed messages. For non-delayed, it's - * 0 and the channel code should set the current timestamp. - */ - *stamp = 0; - - node = lm_message_node_get_child_with_namespace (message->node, "x", - NS_X_DELAY); - if (node != NULL) - { - const gchar *stamp_str, *p; - struct tm stamp_tm = { 0, }; - - stamp_str = lm_message_node_get_attribute (node, "stamp"); - if (stamp_str != NULL) - { - p = strptime (stamp_str, "%Y%m%dT%T", &stamp_tm); - if (p == NULL || *p != '\0') - { - g_warning ("%s: malformed date string '%s' for jabber:x:delay", - G_STRFUNC, stamp_str); - } - else - { - *stamp = 0; // bsr timegm (&stamp_tm); - } - } - } - - /* - * Parse body if it exists. - */ - node = lm_message_node_get_child (message->node, "body"); - - if (node) - { - *body = lm_message_node_get_value (node); - } - else - { - *body = NULL; - } - - /* Messages starting with /me are ACTION messages, and the /me should be - * removed. type="chat" messages are NORMAL. everything else is - * something that doesn't necessarily expect a reply or ongoing - * conversation ("normal") or has been auto-sent, so we make it NOTICE in - * all other cases. */ - - *msgtype = TP_CHANNEL_TEXT_MESSAGE_TYPE_NOTICE; - *body_offset = *body; - - if (*body) - { - if (0 == strncmp (*body, "/me ", 4)) - { - *msgtype = TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION; - *body_offset = *body + 4; - } - else if (type != NULL && (0 == strcmp (type, "chat") || - 0 == strcmp (type, "groupchat"))) - { - *msgtype = TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL; - *body_offset = *body; - } - } - - return TRUE; -} - -void -_gabble_text_mixin_send_error_signal (GObject *obj, - GabbleTextMixinSendError error, - time_t timestamp, - TpChannelTextMessageType type, - const gchar *text) -{ - GabbleTextMixinClass *mixin_cls = GABBLE_TEXT_MIXIN_CLASS (G_OBJECT_GET_CLASS (obj)); - - g_signal_emit (obj, mixin_cls->send_error_signal_id, 0, error, timestamp, type, text, 0); -} -