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 |
|