telepathygabble/src/text-mixin.c
changeset 10 59927b2d3b75
parent 0 d0f3a028347a
equal deleted inserted replaced
0:d0f3a028347a 10:59927b2d3b75
     1 /*
       
     2  * text-mixin.c - Source for GabbleTextMixin
       
     3  * Copyright (C) 2006 Collabora Ltd.
       
     4  * 
       
     5  *   @author Ole Andre Vadla Ravnaas <ole.andre.ravnaas@collabora.co.uk>
       
     6  *   @author Robert McQueen <robert.mcqueen@collabora.co.uk>
       
     7  *   @author Senko Rasic <senko@senko.net>
       
     8  *
       
     9  * This library is free software; you can redistribute it and/or
       
    10  * modify it under the terms of the GNU Lesser General Public
       
    11  * License as published by the Free Software Foundation; either
       
    12  * version 2.1 of the License, or (at your option) any later version.
       
    13  *
       
    14  * This library is distributed in the hope that it will be useful,
       
    15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
       
    16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
       
    17  * Lesser General Public License for more details.
       
    18  *
       
    19  * You should have received a copy of the GNU Lesser General Public
       
    20  * License along with this library; if not, write to the Free Software
       
    21  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
       
    22  */
       
    23 
       
    24 
       
    25 #include "loudmouth/loudmouth.h"
       
    26 #include <dbus/dbus-glib.h>
       
    27 #include <stdio.h>
       
    28 #include <stdlib.h>
       
    29 #include <string.h>
       
    30 #include <time.h>
       
    31 
       
    32 #include "telepathy-constants.h"
       
    33 #include "telepathy-errors.h"
       
    34 
       
    35 
       
    36 #include "debug.h"
       
    37 #include "gabble-connection.h"
       
    38 #include "namespaces.h"
       
    39 #include "roster.h"
       
    40 #include "util.h"
       
    41 
       
    42 #include "text-mixin.h"
       
    43 #include "text-mixin-signals-marshal.h"
       
    44 
       
    45 #include "gabble_enums.h"
       
    46 
       
    47 #define _GNU_SOURCE /* Needed for strptime (_XOPEN_SOURCE can also be used). */
       
    48 #define DEBUG_FLAG GABBLE_DEBUG_IM
       
    49 #define TP_TYPE_PENDING_MESSAGE_STRUCT (dbus_g_type_get_struct ("GValueArray", \
       
    50       G_TYPE_UINT, \
       
    51       G_TYPE_UINT, \
       
    52       G_TYPE_UINT, \
       
    53       G_TYPE_UINT, \
       
    54       G_TYPE_UINT, \
       
    55       G_TYPE_STRING, \
       
    56       G_TYPE_INVALID))
       
    57 
       
    58 /* allocator */
       
    59 
       
    60 #ifdef DEBUG_FLAG
       
    61 //#define DEBUG(format, ...)
       
    62 #define DEBUGGING 0
       
    63 #define NODE_DEBUG(n, s)
       
    64 #endif /* DEBUG_FLAG */
       
    65 
       
    66 
       
    67 #ifdef EMULATOR
       
    68 #include "libgabble_wsd_solution.h"
       
    69 
       
    70 	GET_STATIC_VAR_FROM_TLS(offset_quark1,gabble_txt_mixin,GQuark)
       
    71 	#define offset_quark1 (*GET_WSD_VAR_NAME(offset_quark1,gabble_txt_mixin, s)())	
       
    72 	
       
    73 	GET_STATIC_VAR_FROM_TLS(offset_quark,gabble_txt_mixin,GQuark)
       
    74 	#define offset_quark (*GET_WSD_VAR_NAME(offset_quark,gabble_txt_mixin, s)())	
       
    75 	
       
    76 	GET_STATIC_VAR_FROM_TLS(alloc1,gabble_txt_mixin,GabbleAllocator)
       
    77 	#define alloc1 (*GET_WSD_VAR_NAME(alloc1,gabble_txt_mixin, s)())		
       
    78 	
       
    79 #endif
       
    80 
       
    81 /*
       
    82 Moved to gabble_enums.h
       
    83 typedef struct _GabbleAllocator GabbleAllocator;
       
    84 struct _GabbleAllocator
       
    85 {
       
    86   gulong size;
       
    87   guint limit;
       
    88   guint count;
       
    89 };*/
       
    90 
       
    91 
       
    92 
       
    93 #define ga_new0(alloc, type) \
       
    94     ((type *) gabble_allocator_alloc0 (alloc))
       
    95 
       
    96 static void
       
    97 gabble_allocator_init (GabbleAllocator *alloc, gulong size, guint limit)
       
    98 {
       
    99   g_assert (alloc != NULL);
       
   100   g_assert (size > 0);
       
   101   g_assert (limit > 0);
       
   102 
       
   103   alloc->size = size;
       
   104   alloc->limit = limit;
       
   105 }
       
   106 
       
   107 static gpointer gabble_allocator_alloc0 (GabbleAllocator *alloc)
       
   108 {
       
   109   gpointer ret;
       
   110 
       
   111   g_assert (alloc != NULL);
       
   112   g_assert (alloc->count <= alloc->limit);
       
   113 
       
   114   if (alloc->count == alloc->limit)
       
   115     {
       
   116       ret = NULL;
       
   117     }
       
   118   else
       
   119     {
       
   120       ret = g_malloc0 (alloc->size);
       
   121       alloc->count++;
       
   122     }
       
   123 
       
   124   return ret;
       
   125 }
       
   126 
       
   127 static void gabble_allocator_free (GabbleAllocator *alloc, gpointer thing)
       
   128 {
       
   129   g_assert (alloc != NULL);
       
   130   g_assert (thing != NULL);
       
   131 
       
   132   g_free (thing);
       
   133   alloc->count--;
       
   134 }
       
   135 
       
   136 /* pending message */
       
   137 #define MAX_PENDING_MESSAGES 256
       
   138 #define MAX_MESSAGE_SIZE 1024 - 1
       
   139 
       
   140 typedef struct _GabblePendingMessage GabblePendingMessage;
       
   141 struct _GabblePendingMessage
       
   142 {
       
   143   guint id;
       
   144   time_t timestamp;
       
   145   GabbleHandle sender;
       
   146   TpChannelTextMessageType type;
       
   147   char *text;
       
   148   guint flags;
       
   149 };
       
   150 
       
   151 /**
       
   152  * gabble_text_mixin_class_get_offset_quark:
       
   153  *
       
   154  * Returns: the quark used for storing mixin offset on a GObjectClass
       
   155  */
       
   156 GQuark
       
   157 gabble_text_mixin_class_get_offset_quark ()
       
   158 {
       
   159 #ifndef EMULATOR  
       
   160   static GQuark offset_quark1 = 0;
       
   161 #endif
       
   162   
       
   163   if (!offset_quark1)
       
   164     offset_quark1 = g_quark_from_static_string("TextMixinClassOffsetQuark");
       
   165   return offset_quark1;
       
   166 }
       
   167 
       
   168 /**
       
   169  * gabble_text_mixin_get_offset_quark:
       
   170  *
       
   171  * Returns: the quark used for storing mixin offset on a GObject
       
   172  */
       
   173 GQuark
       
   174 gabble_text_mixin_get_offset_quark ()
       
   175 {
       
   176 #ifndef EMULATOR  
       
   177   static GQuark offset_quark = 0;
       
   178 #endif
       
   179   
       
   180   if (!offset_quark)
       
   181     offset_quark = g_quark_from_static_string("TextMixinOffsetQuark");
       
   182   return offset_quark;
       
   183 }
       
   184 
       
   185 
       
   186 /* GabbleTextMixin */
       
   187 void
       
   188 gabble_text_mixin_class_init (GObjectClass *obj_cls, glong offset)
       
   189 {
       
   190   GabbleTextMixinClass *mixin_cls;
       
   191 
       
   192   g_assert (G_IS_OBJECT_CLASS (obj_cls));
       
   193 
       
   194   g_type_set_qdata (G_OBJECT_CLASS_TYPE (obj_cls),
       
   195       GABBLE_TEXT_MIXIN_CLASS_OFFSET_QUARK,
       
   196       GINT_TO_POINTER (offset));
       
   197 
       
   198   mixin_cls = GABBLE_TEXT_MIXIN_CLASS (obj_cls);
       
   199 
       
   200   mixin_cls->lost_message_signal_id = g_signal_new ("lost-message",
       
   201                 G_OBJECT_CLASS_TYPE (obj_cls),
       
   202                 G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
       
   203                 0,
       
   204                 NULL, NULL,
       
   205                 g_cclosure_marshal_VOID__VOID,
       
   206                 G_TYPE_NONE, 0);
       
   207 
       
   208   mixin_cls->received_signal_id = g_signal_new ("received",
       
   209                 G_OBJECT_CLASS_TYPE (obj_cls),
       
   210                 G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
       
   211                 0,
       
   212                 NULL, NULL,
       
   213                 text_mixin_marshal_VOID__UINT_UINT_UINT_UINT_UINT_STRING,
       
   214                 G_TYPE_NONE, 6, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_STRING);
       
   215 
       
   216   mixin_cls->send_error_signal_id = g_signal_new ("send-error",
       
   217                 G_OBJECT_CLASS_TYPE (obj_cls),
       
   218                 G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
       
   219                 0,
       
   220                 NULL, NULL,
       
   221                 text_mixin_marshal_VOID__UINT_UINT_UINT_STRING,
       
   222                 G_TYPE_NONE, 4, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_STRING);
       
   223 
       
   224   mixin_cls->sent_signal_id = g_signal_new ("sent",
       
   225                 G_OBJECT_CLASS_TYPE (obj_cls),
       
   226                 G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
       
   227                 0,
       
   228                 NULL, NULL,
       
   229                 text_mixin_marshal_VOID__UINT_UINT_STRING,
       
   230                 G_TYPE_NONE, 3, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_STRING);
       
   231 }
       
   232 
       
   233 void
       
   234 gabble_text_mixin_init (GObject *obj,
       
   235                         glong offset,
       
   236                         GabbleHandleRepo *handle_repo,
       
   237                         gboolean send_nick)
       
   238 {
       
   239   GabbleTextMixin *mixin;
       
   240 
       
   241   g_assert (G_IS_OBJECT (obj));
       
   242 
       
   243   g_type_set_qdata (G_OBJECT_TYPE (obj),
       
   244                     GABBLE_TEXT_MIXIN_OFFSET_QUARK,
       
   245                     GINT_TO_POINTER (offset));
       
   246 
       
   247   mixin = GABBLE_TEXT_MIXIN (obj);
       
   248 
       
   249   mixin->pending = g_queue_new ();
       
   250   mixin->handle_repo = handle_repo;
       
   251   mixin->recv_id = 0;
       
   252   mixin->msg_types = g_array_sized_new (FALSE, FALSE, sizeof (guint), 4);
       
   253 
       
   254   mixin->message_lost = FALSE;
       
   255 }
       
   256 
       
   257 void
       
   258 gabble_text_mixin_set_message_types (GObject *obj,
       
   259                                      ...)
       
   260 {
       
   261   GabbleTextMixin *mixin = GABBLE_TEXT_MIXIN (obj);
       
   262   va_list args;
       
   263   guint type;
       
   264 
       
   265   va_start (args, obj);
       
   266 
       
   267   while ((type = va_arg (args, guint)) != G_MAXUINT)
       
   268     g_array_append_val (mixin->msg_types, type);
       
   269 
       
   270   va_end (args);
       
   271 }
       
   272 
       
   273 static void _gabble_pending_free (GabblePendingMessage *msg);
       
   274 static GabbleAllocator * _gabble_pending_get_alloc ();
       
   275 
       
   276 void
       
   277 gabble_text_mixin_finalize (GObject *obj)
       
   278 {
       
   279   GabbleTextMixin *mixin = GABBLE_TEXT_MIXIN (obj);
       
   280   GabblePendingMessage *msg;
       
   281 
       
   282   /* free any data held directly by the object here */
       
   283 
       
   284   msg = g_queue_pop_head(mixin->pending);
       
   285   while (msg)
       
   286     {
       
   287       gabble_handle_unref (mixin->handle_repo, TP_HANDLE_TYPE_CONTACT, msg->sender);
       
   288       _gabble_pending_free (msg);
       
   289       msg = g_queue_pop_head(mixin->pending);
       
   290     }
       
   291 
       
   292   g_queue_free (mixin->pending);
       
   293 
       
   294   g_array_free (mixin->msg_types, TRUE);
       
   295 }
       
   296 
       
   297 /**
       
   298  * _gabble_pending_get_alloc
       
   299  *
       
   300  * Returns a GabbleAllocator for creating up to 256 pending messages, but no
       
   301  * more.
       
   302  */
       
   303 static GabbleAllocator *
       
   304 _gabble_pending_get_alloc ()
       
   305 {
       
   306 
       
   307 #ifndef EMULATOR
       
   308   static GabbleAllocator alloc1 = { 0, };
       
   309 #endif
       
   310 
       
   311   if (0 == alloc1.size)
       
   312     gabble_allocator_init (&alloc1, sizeof(GabblePendingMessage), MAX_PENDING_MESSAGES);
       
   313 
       
   314   return &alloc1;
       
   315 }
       
   316 
       
   317 #define _gabble_pending_new0() \
       
   318   (ga_new0 (_gabble_pending_get_alloc (), GabblePendingMessage))
       
   319 
       
   320 /**
       
   321  * _gabble_pending_free
       
   322  *
       
   323  * Free up a GabblePendingMessage struct.
       
   324  */
       
   325 static void _gabble_pending_free (GabblePendingMessage *msg)
       
   326 {
       
   327   g_free (msg->text);
       
   328   gabble_allocator_free (_gabble_pending_get_alloc (), msg);
       
   329 }
       
   330 
       
   331 /**
       
   332  * _gabble_text_mixin_receive
       
   333  *
       
   334  */
       
   335 gboolean gabble_text_mixin_receive (GObject *obj,
       
   336                                      TpChannelTextMessageType type,
       
   337                                      GabbleHandle sender,
       
   338                                      time_t timestamp,
       
   339                                      const char *text)
       
   340 {
       
   341   GabbleTextMixin *mixin = GABBLE_TEXT_MIXIN (obj);
       
   342   GabbleTextMixinClass *mixin_cls = GABBLE_TEXT_MIXIN_CLASS (G_OBJECT_GET_CLASS (obj));
       
   343 
       
   344   gchar *end;
       
   345   GabblePendingMessage *msg;
       
   346   gsize len;
       
   347 
       
   348   msg = _gabble_pending_new0 ();
       
   349 
       
   350   if (msg == NULL)
       
   351     {
       
   352       gabble_debug (DEBUG_FLAG, "no more pending messages available, giving up");
       
   353 
       
   354       if (!mixin->message_lost)
       
   355         {
       
   356           g_signal_emit (obj, mixin_cls->lost_message_signal_id, 0);
       
   357           mixin->message_lost = TRUE;
       
   358         }
       
   359 
       
   360       return FALSE;
       
   361     }
       
   362 
       
   363   len = strlen (text);
       
   364 
       
   365   if (len > MAX_MESSAGE_SIZE)
       
   366     {
       
   367       gabble_debug (DEBUG_FLAG, "message exceeds maximum size, truncating");
       
   368 
       
   369       msg->flags |= TP_CHANNEL_TEXT_MESSAGE_FLAG_TRUNCATED;
       
   370 
       
   371       end = g_utf8_find_prev_char (text, text+MAX_MESSAGE_SIZE);
       
   372       if (end)
       
   373         len = end-text;
       
   374       else
       
   375         len = 0;
       
   376     }
       
   377 
       
   378   msg->text = g_try_malloc (len + 1);
       
   379 
       
   380   if (msg->text == NULL)
       
   381     {
       
   382       gabble_debug (DEBUG_FLAG, "unable to allocate message, giving up");
       
   383 
       
   384       if (!mixin->message_lost)
       
   385         {
       
   386           g_signal_emit (obj, mixin_cls->lost_message_signal_id, 0);
       
   387           mixin->message_lost = TRUE;
       
   388         }
       
   389 
       
   390       _gabble_pending_free (msg);
       
   391 
       
   392       return FALSE;
       
   393     }
       
   394 
       
   395   g_strlcpy (msg->text, text, len + 1);
       
   396 
       
   397   msg->id = mixin->recv_id++;
       
   398   msg->timestamp = timestamp;
       
   399   msg->sender = sender;
       
   400   msg->type = type;
       
   401 
       
   402   gabble_handle_ref (mixin->handle_repo, TP_HANDLE_TYPE_CONTACT, msg->sender);
       
   403   g_queue_push_tail (mixin->pending, msg);
       
   404 
       
   405   g_signal_emit (obj, mixin_cls->received_signal_id, 0,
       
   406                  msg->id,
       
   407                  msg->timestamp,
       
   408                  msg->sender,
       
   409                  msg->type,
       
   410                  msg->flags,
       
   411                  msg->text);
       
   412 
       
   413   gabble_debug (DEBUG_FLAG, "queued message %u", msg->id);
       
   414 
       
   415   mixin->message_lost = FALSE;
       
   416 
       
   417   return TRUE;
       
   418 }
       
   419 
       
   420 static gint
       
   421 compare_pending_message (gconstpointer haystack,
       
   422                          gconstpointer needle)
       
   423 {
       
   424   GabblePendingMessage *msg = (GabblePendingMessage *) haystack;
       
   425   guint id = GPOINTER_TO_INT (needle);
       
   426 
       
   427   return (msg->id != id);
       
   428 }
       
   429 
       
   430 /**
       
   431  * gabble_text_mixin_acknowledge_pending_messages
       
   432  *
       
   433  * Implements D-Bus method AcknowledgePendingMessages
       
   434  * on interface org.freedesktop.Telepathy.Channel.Type.Text
       
   435  *
       
   436  * @error: Used to return a pointer to a GError detailing any error
       
   437  *         that occurred, D-Bus will throw the error only if this
       
   438  *         function returns false.
       
   439  *
       
   440  * Returns: TRUE if successful, FALSE if an error was thrown.
       
   441  */
       
   442 gboolean gabble_text_mixin_acknowledge_pending_messages (GObject *obj, const GArray * ids, GError **error)
       
   443 {
       
   444   GabbleTextMixin *mixin = GABBLE_TEXT_MIXIN (obj);
       
   445   GList **nodes;
       
   446   GabblePendingMessage *msg;
       
   447   guint i;
       
   448 
       
   449   nodes = g_new(GList *, ids->len);
       
   450 
       
   451   for (i = 0; i < ids->len; i++)
       
   452     {
       
   453       guint id = g_array_index(ids, guint, i);
       
   454 
       
   455       nodes[i] = g_queue_find_custom (mixin->pending,
       
   456                                       GINT_TO_POINTER (id),
       
   457                                       compare_pending_message);
       
   458 
       
   459       if (nodes[i] == NULL)
       
   460         {
       
   461           gabble_debug (DEBUG_FLAG, "invalid message id %u", id);
       
   462 
       
   463           g_set_error (error, TELEPATHY_ERRORS, InvalidArgument,
       
   464               "invalid message id %u", id);
       
   465 
       
   466           g_free(nodes);
       
   467           return FALSE;
       
   468         }
       
   469     }
       
   470 
       
   471   for (i = 0; i < ids->len; i++)
       
   472     {
       
   473       msg = (GabblePendingMessage *) nodes[i]->data;
       
   474 
       
   475       gabble_debug (DEBUG_FLAG, "acknowleding message id %u", msg->id);
       
   476 
       
   477       g_queue_remove (mixin->pending, msg);
       
   478 
       
   479       gabble_handle_unref (mixin->handle_repo, TP_HANDLE_TYPE_CONTACT, msg->sender);
       
   480       _gabble_pending_free (msg);
       
   481     }
       
   482 
       
   483   g_free(nodes);
       
   484   return TRUE;
       
   485 }
       
   486 
       
   487 /**
       
   488  * gabble_text_mixin_list_pending_messages
       
   489  *
       
   490  * Implements D-Bus method ListPendingMessages
       
   491  * on interface org.freedesktop.Telepathy.Channel.Type.Text
       
   492  *
       
   493  * @error: Used to return a pointer to a GError detailing any error
       
   494  *         that occurred, D-Bus will throw the error only if this
       
   495  *         function returns false.
       
   496  *
       
   497  * Returns: TRUE if successful, FALSE if an error was thrown.
       
   498  */
       
   499 gboolean gabble_text_mixin_list_pending_messages (GObject *obj, gboolean clear, GPtrArray ** ret, GError **error)
       
   500 {
       
   501   GabbleTextMixin *mixin = GABBLE_TEXT_MIXIN (obj);
       
   502   guint count;
       
   503   GPtrArray *messages;
       
   504   GList *cur;
       
   505 
       
   506   count = g_queue_get_length (mixin->pending);
       
   507   messages = g_ptr_array_sized_new (count);
       
   508 
       
   509   for (cur = (clear ? g_queue_pop_head_link(mixin->pending)
       
   510                     : g_queue_peek_head_link(mixin->pending));
       
   511        cur != NULL;
       
   512        cur = (clear ? g_queue_pop_head_link(mixin->pending)
       
   513                     : cur->next))
       
   514     {
       
   515       GabblePendingMessage *msg = (GabblePendingMessage *) cur->data;
       
   516       GValue val = { 0, };
       
   517 
       
   518       g_value_init (&val, TP_TYPE_PENDING_MESSAGE_STRUCT);
       
   519       g_value_take_boxed (&val,
       
   520           dbus_g_type_specialized_construct (TP_TYPE_PENDING_MESSAGE_STRUCT));
       
   521       dbus_g_type_struct_set (&val,
       
   522           0, msg->id,
       
   523           1, msg->timestamp,
       
   524           2, msg->sender,
       
   525           3, msg->type,
       
   526           4, msg->flags,
       
   527           5, msg->text,
       
   528           G_MAXUINT);
       
   529 
       
   530       g_ptr_array_add (messages, g_value_get_boxed (&val));
       
   531     }
       
   532 
       
   533   *ret = messages;
       
   534 
       
   535   return TRUE;
       
   536 }
       
   537 
       
   538 /**
       
   539  * gabble_text_mixin_send
       
   540  *
       
   541  * Implements D-Bus method Send
       
   542  * on interface org.freedesktop.Telepathy.Channel.Type.Text
       
   543  *
       
   544  * @error: Used to return a pointer to a GError detailing any error
       
   545  *         that occurred, D-Bus will throw the error only if this
       
   546  *         function returns false.
       
   547  *
       
   548  * Returns: TRUE if successful, FALSE if an error was thrown.
       
   549  */
       
   550 gboolean gabble_text_mixin_send (GObject *obj, guint type, guint subtype,
       
   551                                  const char *recipient, const gchar *text,
       
   552                                  GabbleConnection *conn, gboolean emit_signal,
       
   553                                  GError **error)
       
   554 {
       
   555   GabbleTextMixin *mixin = GABBLE_TEXT_MIXIN (obj);
       
   556   LmMessage *msg;
       
   557   gboolean result;
       
   558   time_t timestamp;
       
   559 
       
   560   if (type > TP_CHANNEL_TEXT_MESSAGE_TYPE_NOTICE)
       
   561     {
       
   562       gabble_debug (DEBUG_FLAG, "invalid message type %u", type);
       
   563 
       
   564       g_set_error (error, TELEPATHY_ERRORS, InvalidArgument,
       
   565           "invalid message type: %u", type);
       
   566 
       
   567       return FALSE;
       
   568     }
       
   569 
       
   570   if (!subtype)
       
   571     {
       
   572       switch (type)
       
   573         {
       
   574         case TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL:
       
   575         case TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION:
       
   576           subtype = LM_MESSAGE_SUB_TYPE_CHAT;
       
   577           break;
       
   578         case TP_CHANNEL_TEXT_MESSAGE_TYPE_NOTICE:
       
   579           subtype = LM_MESSAGE_SUB_TYPE_NORMAL;
       
   580           break;
       
   581         }
       
   582     }
       
   583 
       
   584   msg = lm_message_new_with_sub_type (recipient, LM_MESSAGE_TYPE_MESSAGE, subtype);
       
   585 
       
   586   if (mixin->send_nick)
       
   587     {
       
   588       lm_message_node_add_own_nick (msg->node, conn);
       
   589       mixin->send_nick = FALSE;
       
   590     }
       
   591 
       
   592   if (type == TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION)
       
   593     {
       
   594       gchar *tmp;
       
   595       tmp = g_strconcat ("/me ", text, NULL);
       
   596       lm_message_node_add_child (msg->node, "body", tmp);
       
   597       g_free (tmp);
       
   598     }
       
   599   else
       
   600     {
       
   601       lm_message_node_add_child (msg->node, "body", text);
       
   602     }
       
   603 
       
   604   result = _gabble_connection_send (conn, msg, error);
       
   605   lm_message_unref (msg);
       
   606 
       
   607   if (!result)
       
   608     return FALSE;
       
   609 
       
   610   if (emit_signal)
       
   611     {
       
   612       timestamp = time (NULL);
       
   613 
       
   614       gabble_text_mixin_emit_sent (obj, timestamp, type, text);
       
   615     }
       
   616 
       
   617   return TRUE;
       
   618 }
       
   619 
       
   620 void
       
   621 gabble_text_mixin_emit_sent (GObject *obj,
       
   622                              time_t timestamp,
       
   623                              guint type,
       
   624                              const char *text)
       
   625 {
       
   626   GabbleTextMixinClass *mixin_cls = GABBLE_TEXT_MIXIN_CLASS (G_OBJECT_GET_CLASS
       
   627       (obj));
       
   628 
       
   629   g_signal_emit (obj, mixin_cls->sent_signal_id, 0,
       
   630                  timestamp,
       
   631                  type,
       
   632                  text);
       
   633 }
       
   634 
       
   635 gboolean
       
   636 gabble_text_mixin_get_message_types (GObject *obj, GArray **ret, GError **error)
       
   637 {
       
   638   GabbleTextMixin *mixin = GABBLE_TEXT_MIXIN (obj);
       
   639   guint i;
       
   640 
       
   641   *ret = g_array_sized_new (FALSE, FALSE, sizeof (guint),
       
   642                             mixin->msg_types->len);
       
   643 
       
   644   for (i = 0; i < mixin->msg_types->len; i++)
       
   645     {
       
   646       g_array_append_val (*ret, g_array_index (mixin->msg_types, guint, i));
       
   647     }
       
   648 
       
   649   return TRUE;
       
   650 }
       
   651 
       
   652 
       
   653 void
       
   654 gabble_text_mixin_clear (GObject *obj)
       
   655 {
       
   656   GabbleTextMixin *mixin = GABBLE_TEXT_MIXIN (obj);
       
   657   GabblePendingMessage *msg;
       
   658 
       
   659   msg = g_queue_pop_head(mixin->pending);
       
   660   while (msg)
       
   661     {
       
   662       gabble_handle_unref (mixin->handle_repo, TP_HANDLE_TYPE_CONTACT, msg->sender);
       
   663       _gabble_pending_free (msg);
       
   664       msg = g_queue_pop_head(mixin->pending);
       
   665     }
       
   666 }
       
   667 
       
   668 gboolean
       
   669 gabble_text_mixin_parse_incoming_message (LmMessage *message,
       
   670                         const gchar **from,
       
   671                         time_t *stamp,
       
   672                         TpChannelTextMessageType *msgtype,
       
   673                         const gchar **body,
       
   674                         const gchar **body_offset,
       
   675                         GabbleTextMixinSendError *send_error)
       
   676 {
       
   677   const gchar *type;
       
   678   LmMessageNode *node;
       
   679 
       
   680   *send_error = CHANNEL_TEXT_SEND_NO_ERROR;
       
   681 
       
   682   if (lm_message_get_sub_type (message) == LM_MESSAGE_SUB_TYPE_ERROR)
       
   683     {
       
   684       LmMessageNode *error_node;
       
   685 
       
   686       error_node = lm_message_node_get_child (message->node, "error");
       
   687       if (error_node)
       
   688         {
       
   689           GabbleXmppError err = gabble_xmpp_error_from_node (error_node);
       
   690           gabble_debug (DEBUG_FLAG, "got xmpp error: %s: %s", gabble_xmpp_error_string (err),
       
   691                  gabble_xmpp_error_description (err));
       
   692 
       
   693           /* these are based on descriptions of errors, and some testing */
       
   694           switch (err)
       
   695             {
       
   696               case XMPP_ERROR_SERVICE_UNAVAILABLE:
       
   697               case XMPP_ERROR_RECIPIENT_UNAVAILABLE:
       
   698                 *send_error = CHANNEL_TEXT_SEND_ERROR_OFFLINE;
       
   699                 break;
       
   700 
       
   701               case XMPP_ERROR_ITEM_NOT_FOUND:
       
   702               case XMPP_ERROR_JID_MALFORMED:
       
   703               case XMPP_ERROR_REMOTE_SERVER_TIMEOUT:
       
   704                 *send_error = CHANNEL_TEXT_SEND_ERROR_INVALID_CONTACT;
       
   705                 break;
       
   706 
       
   707               case XMPP_ERROR_FORBIDDEN:
       
   708                 *send_error = CHANNEL_TEXT_SEND_ERROR_PERMISSION_DENIED;
       
   709                 break;
       
   710 
       
   711               case XMPP_ERROR_RESOURCE_CONSTRAINT:
       
   712                 *send_error = CHANNEL_TEXT_SEND_ERROR_TOO_LONG;
       
   713                 break;
       
   714 
       
   715               case XMPP_ERROR_FEATURE_NOT_IMPLEMENTED:
       
   716                 *send_error = CHANNEL_TEXT_SEND_ERROR_NOT_IMPLEMENTED;
       
   717                 break;
       
   718 
       
   719               default:
       
   720                 *send_error = CHANNEL_TEXT_SEND_ERROR_UNKNOWN;
       
   721             }
       
   722         }
       
   723       else
       
   724         {
       
   725           *send_error = CHANNEL_TEXT_SEND_ERROR_UNKNOWN;
       
   726         }
       
   727     }
       
   728 
       
   729   *from = lm_message_node_get_attribute (message->node, "from");
       
   730   if (*from == NULL)
       
   731     {
       
   732       NODE_DEBUG (message->node, "got a message without a from field");
       
   733       return FALSE;
       
   734     }
       
   735 
       
   736   type = lm_message_node_get_attribute (message->node, "type");
       
   737 
       
   738   /*
       
   739    * Parse timestamp of delayed messages. For non-delayed, it's
       
   740    * 0 and the channel code should set the current timestamp.
       
   741    */
       
   742   *stamp = 0;
       
   743 
       
   744   node = lm_message_node_get_child_with_namespace (message->node, "x",
       
   745       NS_X_DELAY);
       
   746   if (node != NULL)
       
   747     {
       
   748       const gchar *stamp_str, *p;
       
   749       struct tm stamp_tm = { 0, };
       
   750 
       
   751       stamp_str = lm_message_node_get_attribute (node, "stamp");
       
   752       if (stamp_str != NULL)
       
   753         {
       
   754           p = strptime (stamp_str, "%Y%m%dT%T", &stamp_tm);
       
   755           if (p == NULL || *p != '\0')
       
   756             {
       
   757               g_warning ("%s: malformed date string '%s' for jabber:x:delay",
       
   758                          G_STRFUNC, stamp_str);
       
   759             }
       
   760           else
       
   761             {
       
   762               *stamp = 0; // bsr timegm (&stamp_tm);
       
   763             }
       
   764         }
       
   765     }
       
   766 
       
   767   /*
       
   768    * Parse body if it exists.
       
   769    */
       
   770   node = lm_message_node_get_child (message->node, "body");
       
   771 
       
   772   if (node)
       
   773     {
       
   774       *body = lm_message_node_get_value (node);
       
   775     }
       
   776   else
       
   777     {
       
   778       *body = NULL;
       
   779     }
       
   780 
       
   781   /* Messages starting with /me are ACTION messages, and the /me should be
       
   782    * removed. type="chat" messages are NORMAL.  everything else is
       
   783    * something that doesn't necessarily expect a reply or ongoing
       
   784    * conversation ("normal") or has been auto-sent, so we make it NOTICE in
       
   785    * all other cases. */
       
   786 
       
   787   *msgtype = TP_CHANNEL_TEXT_MESSAGE_TYPE_NOTICE;
       
   788   *body_offset = *body;
       
   789 
       
   790   if (*body)
       
   791     {
       
   792       if (0 == strncmp (*body, "/me ", 4))
       
   793         {
       
   794           *msgtype = TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION;
       
   795           *body_offset = *body + 4;
       
   796         }
       
   797       else if (type != NULL && (0 == strcmp (type, "chat") ||
       
   798                                 0 == strcmp (type, "groupchat")))
       
   799         {
       
   800           *msgtype = TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL;
       
   801           *body_offset = *body;
       
   802         }
       
   803     }
       
   804 
       
   805   return TRUE;
       
   806 }
       
   807 
       
   808 void
       
   809 _gabble_text_mixin_send_error_signal (GObject *obj,
       
   810                                       GabbleTextMixinSendError error,
       
   811                                       time_t timestamp,
       
   812                                       TpChannelTextMessageType type,
       
   813                                       const gchar *text)
       
   814 {
       
   815   GabbleTextMixinClass *mixin_cls = GABBLE_TEXT_MIXIN_CLASS (G_OBJECT_GET_CLASS (obj));
       
   816 
       
   817   g_signal_emit (obj, mixin_cls->send_error_signal_id, 0, error, timestamp, type, text, 0);
       
   818 }
       
   819