--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/telepathygabble/src/text-mixin.c Tue Feb 02 01:10:06 2010 +0200
@@ -0,0 +1,819 @@
+/*
+ * text-mixin.c - Source for GabbleTextMixin
+ * Copyright (C) 2006 Collabora Ltd.
+ *
+ * @author Ole Andre Vadla Ravnaas <ole.andre.ravnaas@collabora.co.uk>
+ * @author Robert McQueen <robert.mcqueen@collabora.co.uk>
+ * @author Senko Rasic <senko@senko.net>
+ *
+ * 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 <dbus/dbus-glib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#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);
+}
+