1 /*
2 * message.c - Source for TpMessage
3 * Copyright (C) 2006-2010 Collabora Ltd.
4 * Copyright (C) 2006-2008 Nokia Corporation
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21 /**
22 * SECTION:message
23 * @title: TpMessage
24 * @short_description: a message in the Telepathy message interface
25 * @see_also: #TpCMMessage, #TpClientMessage, #TpSignalledMessage
26 *
27 * #TpMessage represent a message send or received using the Message
28 * interface.
29 *
30 * Since: 0.7.21
31 */
32
33 #include "config.h"
34
35 #include "message.h"
36 #include "message-internal.h"
37
38 #include <telepathy-glib/cm-message.h>
39 #include <telepathy-glib/dbus.h>
40 #include <telepathy-glib/gtypes.h>
41 #include <telepathy-glib/util.h>
42 #include <telepathy-glib/variant-util-internal.h>
43
44 #define DEBUG_FLAG TP_DEBUG_MISC
45 #include "telepathy-glib/debug-internal.h"
46
47 G_DEFINE_TYPE (TpMessage, tp_message, G_TYPE_OBJECT)
48
49 /**
50 * TpMessage:
51 *
52 * Opaque structure representing a message in the Telepathy messages interface
53 * (an array of at least one mapping from string to variant, where the first
54 * mapping contains message headers and subsequent mappings contain the
55 * message body).
56 *
57 * This base class provides convenience API for most of the common keys that
58 * can appear in the header. One notable exception is the sender of the
59 * message. Inside a connection manager, messages are represented by the
60 * #TpCMMessage subclass, and you should use tp_cm_message_get_sender().
61 * When composing a message in a client using #TpClientMessage, messages do
62 * not have an explicit sender (the sender is automatically the local user).
63 * When a client sees a sent or received message signalled by the connection
64 * manager (represented by #TpSignalledMessage), the message's sender (if any)
65 * can be accessed with tp_signalled_message_get_sender().
66 */
67
68 struct _TpMessagePrivate
69 {
70 gboolean mutable;
71 };
72
73 static void
tp_message_dispose(GObject * object)74 tp_message_dispose (GObject *object)
75 {
76 TpMessage *self = TP_MESSAGE (object);
77 void (*dispose) (GObject *) =
78 G_OBJECT_CLASS (tp_message_parent_class)->dispose;
79 guint i;
80
81 if (self->parts != NULL)
82 {
83 for (i = 0; i < self->parts->len; i++)
84 {
85 g_hash_table_unref (g_ptr_array_index (self->parts, i));
86 }
87
88 g_ptr_array_unref (self->parts);
89 self->parts = NULL;
90 }
91
92 if (dispose != NULL)
93 dispose (object);
94 }
95
96 static void
tp_message_class_init(TpMessageClass * klass)97 tp_message_class_init (TpMessageClass *klass)
98 {
99 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
100
101 gobject_class->dispose = tp_message_dispose;
102
103 g_type_class_add_private (gobject_class, sizeof (TpMessagePrivate));
104 }
105
106 static void
tp_message_init(TpMessage * self)107 tp_message_init (TpMessage *self)
108 {
109 self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self), TP_TYPE_MESSAGE,
110 TpMessagePrivate);
111
112 /* Message can be modified until _tp_message_set_immutable() is called */
113 self->priv->mutable = TRUE;
114
115 /* Create header part */
116 self->parts = g_ptr_array_sized_new (1);
117
118 tp_message_append_part (self);
119 }
120
121
122 /**
123 * tp_message_new:
124 * @connection: a connection on which to reference handles
125 * @initial_parts: number of parts to create (at least 1)
126 * @size_hint: preallocate space for this many parts (at least @initial_parts)
127 *
128 * <!-- nothing more to say -->
129 *
130 * Returns: a newly allocated message suitable to be passed to
131 * tp_message_mixin_take_received()
132 *
133 * Since: 0.7.21
134 * Deprecated: since 0.13.9. Use tp_cm_message_new()
135 */
136 TpMessage *
tp_message_new(TpBaseConnection * connection,guint initial_parts,guint size_hint)137 tp_message_new (TpBaseConnection *connection,
138 guint initial_parts,
139 guint size_hint)
140 {
141 g_return_val_if_fail (size_hint >= initial_parts, NULL);
142
143 return tp_cm_message_new (connection, initial_parts);
144 }
145
146
147 /**
148 * tp_message_destroy:
149 * @self: a message
150 *
151 * Since 0.13.9 this function is a simple wrapper around
152 * g_object_unref()
153 *
154 * Since: 0.7.21
155 */
156 void
tp_message_destroy(TpMessage * self)157 tp_message_destroy (TpMessage *self)
158 {
159 g_object_unref (self);
160 }
161
162 /**
163 * tp_message_count_parts:
164 * @self: a message
165 *
166 * <!-- nothing more to say -->
167 *
168 * Returns: the number of parts in the message, including the headers in
169 * part 0
170 *
171 * Since: 0.7.21
172 */
173 guint
tp_message_count_parts(TpMessage * self)174 tp_message_count_parts (TpMessage *self)
175 {
176 return self->parts->len;
177 }
178
179 /**
180 * tp_message_peek:
181 * @self: a message
182 * @part: a part number
183 *
184 * <!-- nothing more to say -->
185 *
186 * Returns: (transfer none) (element-type utf8 GObject.Value):
187 * the #GHashTable used to implement the given part, or %NULL if the
188 * part number is out of range. The hash table is only valid as long as the
189 * message is valid and the part is not deleted.
190 *
191 * Since: 0.7.21
192 */
193 const GHashTable *
tp_message_peek(TpMessage * self,guint part)194 tp_message_peek (TpMessage *self,
195 guint part)
196 {
197 if (part >= self->parts->len)
198 return NULL;
199
200 return g_ptr_array_index (self->parts, part);
201 }
202
203 /**
204 * tp_message_dup_part:
205 * @self: a message
206 * @part: a part number
207 *
208 * <!-- nothing more to say -->
209 *
210 * Returns: (transfer full):
211 * the current contents of the given part, or %NULL if the part number is
212 * out of range
213 *
214 * Since: 0.19.10
215 */
216 GVariant *
tp_message_dup_part(TpMessage * self,guint part)217 tp_message_dup_part (TpMessage *self,
218 guint part)
219 {
220 if (part >= self->parts->len)
221 return NULL;
222
223 return _tp_asv_to_vardict (g_ptr_array_index (self->parts, part));
224 }
225
226
227 /**
228 * tp_message_append_part:
229 * @self: a message
230 *
231 * Append a body part to the message.
232 *
233 * Returns: the part number
234 *
235 * Since: 0.7.21
236 */
237 guint
tp_message_append_part(TpMessage * self)238 tp_message_append_part (TpMessage *self)
239 {
240 g_return_val_if_fail (self->priv->mutable, 0);
241
242 g_ptr_array_add (self->parts, g_hash_table_new_full (g_str_hash,
243 g_str_equal, g_free, (GDestroyNotify) tp_g_value_slice_free));
244 return self->parts->len - 1;
245 }
246
247 /**
248 * tp_message_delete_part:
249 * @self: a message
250 * @part: a part number, which must be strictly greater than 0, and strictly
251 * less than the number returned by tp_message_count_parts()
252 *
253 * Delete the given body part from the message.
254 *
255 * Since: 0.7.21
256 */
257 void
tp_message_delete_part(TpMessage * self,guint part)258 tp_message_delete_part (TpMessage *self,
259 guint part)
260 {
261 g_return_if_fail (part < self->parts->len);
262 g_return_if_fail (part > 0);
263 g_return_if_fail (self->priv->mutable);
264
265 g_hash_table_unref (g_ptr_array_remove_index (self->parts, part));
266 }
267
268 /**
269 * tp_message_ref_handle:
270 * @self: a message
271 * @handle_type: a handle type, greater than %TP_HANDLE_TYPE_NONE and less than
272 * %TP_NUM_HANDLE_TYPES
273 * @handle: a handle of the given type
274 *
275 * Reference the given handle until this message is destroyed.
276 *
277 * Since: 0.7.21
278 * Deprecated: since 0.13.9. Handles are now immortal so there is
279 * no point to ref them. Furthermore, the only handle that should be stored
280 * in a TpMessage is message-sender which should be set using
281 * tp_cm_message_set_sender().
282 */
283 void
tp_message_ref_handle(TpMessage * self,TpHandleType handle_type,TpHandle handle)284 tp_message_ref_handle (TpMessage *self,
285 TpHandleType handle_type,
286 TpHandle handle)
287 {
288 g_return_if_fail (TP_IS_CM_MESSAGE (self));
289 g_return_if_fail (self->priv->mutable);
290
291 /* Handles are now immortal so we don't have to anything */
292 }
293
294 /**
295 * tp_message_delete_key:
296 * @self: a message
297 * @part: a part number, which must be strictly less than the number
298 * returned by tp_message_count_parts()
299 * @key: a key in the mapping representing the part
300 *
301 * Remove the given key and its value from the given part.
302 *
303 * Returns: %TRUE if the key previously existed
304 *
305 * Since: 0.7.21
306 */
307 gboolean
tp_message_delete_key(TpMessage * self,guint part,const gchar * key)308 tp_message_delete_key (TpMessage *self,
309 guint part,
310 const gchar *key)
311 {
312 g_return_val_if_fail (part < self->parts->len, FALSE);
313 g_return_val_if_fail (self->priv->mutable, FALSE);
314
315 return g_hash_table_remove (g_ptr_array_index (self->parts, part), key);
316 }
317
318
319 /**
320 * tp_message_set_handle:
321 * @self: a #TpCMMessage
322 * @part: a part number, which must be strictly less than the number
323 * returned by tp_message_count_parts()
324 * @key: a key in the mapping representing the part
325 * @handle_type: a handle type
326 * @handle_or_0: a handle of that type, or 0
327 *
328 * If @handle_or_0 is not zero, reference it with tp_message_ref_handle().
329 *
330 * Set @key in part @part of @self to have @handle_or_0 as an unsigned integer
331 * value.
332 *
333 * Since 0.13.9 this function has been deprecated in favor or
334 * tp_cm_message_set_sender() as 'message-sender' is the only handle
335 * you can put in a #TpCMMessage.
336 *
337 * Since: 0.7.21
338 * Deprecated: since 0.13.9. Use tp_cm_message_set_sender()
339 */
340 void
tp_message_set_handle(TpMessage * self,guint part,const gchar * key,TpHandleType handle_type,TpHandle handle_or_0)341 tp_message_set_handle (TpMessage *self,
342 guint part,
343 const gchar *key,
344 TpHandleType handle_type,
345 TpHandle handle_or_0)
346 {
347 g_return_if_fail (TP_IS_CM_MESSAGE (self));
348 g_return_if_fail (self->priv->mutable);
349
350 tp_message_set_uint32 (self, part, key, handle_or_0);
351 }
352
353
354 /**
355 * tp_message_set_boolean:
356 * @self: a message
357 * @part: a part number, which must be strictly less than the number
358 * returned by tp_message_count_parts()
359 * @key: a key in the mapping representing the part
360 * @b: a boolean value
361 *
362 * Set @key in part @part of @self to have @b as a boolean value.
363 *
364 * Since: 0.7.21
365 */
366 void
tp_message_set_boolean(TpMessage * self,guint part,const gchar * key,gboolean b)367 tp_message_set_boolean (TpMessage *self,
368 guint part,
369 const gchar *key,
370 gboolean b)
371 {
372 g_return_if_fail (part < self->parts->len);
373 g_return_if_fail (key != NULL);
374 g_return_if_fail (self->priv->mutable);
375
376 g_hash_table_insert (g_ptr_array_index (self->parts, part),
377 g_strdup (key), tp_g_value_slice_new_boolean (b));
378 }
379
380 /**
381 * tp_message_set_int16:
382 * @s: a message
383 * @p: a part number, which must be strictly less than the number
384 * returned by tp_message_count_parts()
385 * @k: a key in the mapping representing the part
386 * @i: an integer value
387 *
388 * Set @key in part @part of @self to have @i as a signed integer value.
389 *
390 * Since: 0.7.21
391 */
392
393 /**
394 * tp_message_set_int32:
395 * @self: a message
396 * @part: a part number, which must be strictly less than the number
397 * returned by tp_message_count_parts()
398 * @key: a key in the mapping representing the part
399 * @i: an integer value
400 *
401 * Set @key in part @part of @self to have @i as a signed integer value.
402 *
403 * Since: 0.7.21
404 */
405 void
tp_message_set_int32(TpMessage * self,guint part,const gchar * key,gint32 i)406 tp_message_set_int32 (TpMessage *self,
407 guint part,
408 const gchar *key,
409 gint32 i)
410 {
411 g_return_if_fail (part < self->parts->len);
412 g_return_if_fail (key != NULL);
413 g_return_if_fail (self->priv->mutable);
414
415 g_hash_table_insert (g_ptr_array_index (self->parts, part),
416 g_strdup (key), tp_g_value_slice_new_int (i));
417 }
418
419
420 /**
421 * tp_message_set_int64:
422 * @self: a message
423 * @part: a part number, which must be strictly less than the number
424 * returned by tp_message_count_parts()
425 * @key: a key in the mapping representing the part
426 * @i: an integer value
427 *
428 * Set @key in part @part of @self to have @i as a signed integer value.
429 *
430 * Since: 0.7.21
431 */
432 void
tp_message_set_int64(TpMessage * self,guint part,const gchar * key,gint64 i)433 tp_message_set_int64 (TpMessage *self,
434 guint part,
435 const gchar *key,
436 gint64 i)
437 {
438 g_return_if_fail (part < self->parts->len);
439 g_return_if_fail (key != NULL);
440 g_return_if_fail (self->priv->mutable);
441
442 g_hash_table_insert (g_ptr_array_index (self->parts, part),
443 g_strdup (key), tp_g_value_slice_new_int64 (i));
444 }
445
446
447 /**
448 * tp_message_set_uint16:
449 * @s: a message
450 * @p: a part number, which must be strictly less than the number
451 * returned by tp_message_count_parts()
452 * @k: a key in the mapping representing the part
453 * @u: an unsigned integer value
454 *
455 * Set @key in part @part of @self to have @u as an unsigned integer value.
456 *
457 * Since: 0.7.21
458 */
459
460 /**
461 * tp_message_set_uint32:
462 * @self: a message
463 * @part: a part number, which must be strictly less than the number
464 * returned by tp_message_count_parts()
465 * @key: a key in the mapping representing the part
466 * @u: an unsigned integer value
467 *
468 * Set @key in part @part of @self to have @u as an unsigned integer value.
469 *
470 * Since: 0.7.21
471 */
472 void
tp_message_set_uint32(TpMessage * self,guint part,const gchar * key,guint32 u)473 tp_message_set_uint32 (TpMessage *self,
474 guint part,
475 const gchar *key,
476 guint32 u)
477 {
478 g_return_if_fail (part < self->parts->len);
479 g_return_if_fail (key != NULL);
480 g_return_if_fail (self->priv->mutable);
481
482 g_hash_table_insert (g_ptr_array_index (self->parts, part),
483 g_strdup (key), tp_g_value_slice_new_uint (u));
484 }
485
486
487 /**
488 * tp_message_set_uint64:
489 * @self: a message
490 * @part: a part number, which must be strictly less than the number
491 * returned by tp_message_count_parts()
492 * @key: a key in the mapping representing the part
493 * @u: an unsigned integer value
494 *
495 * Set @key in part @part of @self to have @u as an unsigned integer value.
496 *
497 * Since: 0.7.21
498 */
499 void
tp_message_set_uint64(TpMessage * self,guint part,const gchar * key,guint64 u)500 tp_message_set_uint64 (TpMessage *self,
501 guint part,
502 const gchar *key,
503 guint64 u)
504 {
505 g_return_if_fail (part < self->parts->len);
506 g_return_if_fail (key != NULL);
507 g_return_if_fail (self->priv->mutable);
508
509 g_hash_table_insert (g_ptr_array_index (self->parts, part),
510 g_strdup (key), tp_g_value_slice_new_uint64 (u));
511 }
512
513
514 /**
515 * tp_message_set_string:
516 * @self: a message
517 * @part: a part number, which must be strictly less than the number
518 * returned by tp_message_count_parts()
519 * @key: a key in the mapping representing the part
520 * @s: a string value
521 *
522 * Set @key in part @part of @self to have @s as a string value.
523 *
524 * Since: 0.7.21
525 */
526 void
tp_message_set_string(TpMessage * self,guint part,const gchar * key,const gchar * s)527 tp_message_set_string (TpMessage *self,
528 guint part,
529 const gchar *key,
530 const gchar *s)
531 {
532 g_return_if_fail (part < self->parts->len);
533 g_return_if_fail (key != NULL);
534 g_return_if_fail (s != NULL);
535 g_return_if_fail (self->priv->mutable);
536
537 g_hash_table_insert (g_ptr_array_index (self->parts, part),
538 g_strdup (key), tp_g_value_slice_new_string (s));
539 }
540
541
542 /**
543 * tp_message_set_string_printf:
544 * @self: a message
545 * @part: a part number, which must be strictly less than the number
546 * returned by tp_message_count_parts()
547 * @key: a key in the mapping representing the part
548 * @fmt: a printf-style format string for the string value
549 * @...: arguments for the format string
550 *
551 * Set @key in part @part of @self to have a string value constructed from a
552 * printf-style format string.
553 *
554 * Since: 0.7.21
555 */
556 void
tp_message_set_string_printf(TpMessage * self,guint part,const gchar * key,const gchar * fmt,...)557 tp_message_set_string_printf (TpMessage *self,
558 guint part,
559 const gchar *key,
560 const gchar *fmt,
561 ...)
562 {
563 va_list va;
564 gchar *s;
565
566 g_return_if_fail (part < self->parts->len);
567 g_return_if_fail (key != NULL);
568 g_return_if_fail (fmt != NULL);
569 g_return_if_fail (self->priv->mutable);
570
571 va_start (va, fmt);
572 s = g_strdup_vprintf (fmt, va);
573 va_end (va);
574
575 g_hash_table_insert (g_ptr_array_index (self->parts, part),
576 g_strdup (key), tp_g_value_slice_new_take_string (s));
577 }
578
579
580 /**
581 * tp_message_set_bytes:
582 * @self: a message
583 * @part: a part number, which must be strictly less than the number
584 * returned by tp_message_count_parts()
585 * @key: a key in the mapping representing the part
586 * @len: a number of bytes
587 * @bytes: an array of @len bytes
588 *
589 * Set @key in part @part of @self to have @bytes as a byte-array value.
590 *
591 * Since: 0.7.21
592 */
593 void
tp_message_set_bytes(TpMessage * self,guint part,const gchar * key,guint len,gconstpointer bytes)594 tp_message_set_bytes (TpMessage *self,
595 guint part,
596 const gchar *key,
597 guint len,
598 gconstpointer bytes)
599 {
600 g_return_if_fail (part < self->parts->len);
601 g_return_if_fail (key != NULL);
602 g_return_if_fail (bytes != NULL);
603 g_return_if_fail (self->priv->mutable);
604
605 g_hash_table_insert (g_ptr_array_index (self->parts, part),
606 g_strdup (key),
607 tp_g_value_slice_new_bytes (len, bytes));
608 }
609
610
611 /**
612 * tp_message_set:
613 * @self: a message
614 * @part: a part number, which must be strictly less than the number
615 * returned by tp_message_count_parts()
616 * @key: a key in the mapping representing the part
617 * @source: a value, encoded as dbus-glib would
618 *
619 * Set @key in part @part of @self to have a copy of @source as its value.
620 *
621 * If @source represents a data structure containing handles, they should
622 * all be referenced with tp_message_ref_handle() first.
623 *
624 * In high-level language bindings, use tp_message_set_variant() instead.
625 *
626 * Since: 0.7.21
627 */
628 void
tp_message_set(TpMessage * self,guint part,const gchar * key,const GValue * source)629 tp_message_set (TpMessage *self,
630 guint part,
631 const gchar *key,
632 const GValue *source)
633 {
634 g_return_if_fail (part < self->parts->len);
635 g_return_if_fail (key != NULL);
636 g_return_if_fail (source != NULL);
637 g_return_if_fail (self->priv->mutable);
638
639 g_hash_table_insert (g_ptr_array_index (self->parts, part),
640 g_strdup (key), tp_g_value_slice_dup (source));
641 }
642
643 /**
644 * tp_message_set_variant:
645 * @self: a message
646 * @part: a part number, which must be strictly less than the number
647 * returned by tp_message_count_parts()
648 * @key: a key in the mapping representing the part
649 * @value: a value
650 *
651 * Set @key in part @part of @self to have @value as its value.
652 *
653 * If @value is a floating reference (see g_variant_ref_sink()), then this
654 * function will take ownership of it.
655 *
656 * Since: 0.19.10
657 */
658 void
tp_message_set_variant(TpMessage * self,guint part,const gchar * key,GVariant * value)659 tp_message_set_variant (TpMessage *self,
660 guint part,
661 const gchar *key,
662 GVariant *value)
663 {
664 GValue *gvalue;
665
666 g_return_if_fail (part < self->parts->len);
667 g_return_if_fail (key != NULL);
668 g_return_if_fail (value != NULL);
669 g_return_if_fail (self->priv->mutable);
670
671 g_variant_ref_sink (value);
672 gvalue = g_slice_new0 (GValue);
673 dbus_g_value_parse_g_variant (value, gvalue);
674 g_variant_unref (value);
675
676 g_hash_table_insert (g_ptr_array_index (self->parts, part),
677 g_strdup (key), gvalue);
678 }
679
680 /**
681 * tp_message_take_message:
682 * @self: a #TpCMMessage
683 * @part: a part number, which must be strictly less than the number
684 * returned by tp_message_count_parts()
685 * @key: a key in the mapping representing the part
686 * @message: another (distinct) message created for the same #TpBaseConnection
687 *
688 * Set @key in part @part of @self to have @message as an aa{sv} value (that
689 * is, an array of Message_Part), and take ownership of @message. The caller
690 * should not use @message after passing it to this function. All handle
691 * references owned by @message will subsequently belong to and be released
692 * with @self.
693 *
694 * Since: 0.7.21
695 * Deprecated: since 0.13.9. Use tp_cm_message_take_message()
696 */
697 void
tp_message_take_message(TpMessage * self,guint part,const gchar * key,TpMessage * message)698 tp_message_take_message (TpMessage *self,
699 guint part,
700 const gchar *key,
701 TpMessage *message)
702 {
703 g_return_if_fail (TP_IS_CM_MESSAGE (self));
704
705 tp_cm_message_take_message (self, part, key, message);
706 }
707
708 static void
subtract_from_hash(gpointer key,gpointer value,gpointer user_data)709 subtract_from_hash (gpointer key,
710 gpointer value,
711 gpointer user_data)
712 {
713 DEBUG ("... removing %s", (gchar *) key);
714 g_hash_table_remove (user_data, key);
715 }
716
717 /**
718 * tp_message_to_text:
719 * @message: a #TpMessage
720 * @out_flags: (out) : if not %NULL, the #TpChannelTextMessageFlags of @message
721 *
722 * Concatene all the text parts contained in @message.
723 *
724 * Returns: (transfer full): a newly allocated string containing the
725 * text content of #message
726 *
727 * Since: 0.13.9
728 */
729 gchar *
tp_message_to_text(TpMessage * message,TpChannelTextMessageFlags * out_flags)730 tp_message_to_text (TpMessage *message,
731 TpChannelTextMessageFlags *out_flags)
732 {
733 guint i;
734 GHashTable *header = g_ptr_array_index (message->parts, 0);
735 /* Lazily created hash tables, used as a sets: keys are borrowed
736 * "alternative" string values from @parts, value == key. */
737 /* Alternative IDs for which we have already extracted an alternative */
738 GHashTable *alternatives_used = NULL;
739 /* Alternative IDs for which we expect to extract text, but have not yet;
740 * cleared if the flag Channel_Text_Message_Flag_Non_Text_Content is set.
741 * At the end, if this contains any item not in alternatives_used,
742 * Channel_Text_Message_Flag_Non_Text_Content must be set. */
743 GHashTable *alternatives_needed = NULL;
744 GString *buffer = g_string_new ("");
745 TpChannelTextMessageFlags flags = 0;
746
747 if (tp_asv_get_boolean (header, "scrollback", NULL))
748 flags |= TP_CHANNEL_TEXT_MESSAGE_FLAG_SCROLLBACK;
749
750 if (tp_asv_get_boolean (header, "rescued", NULL))
751 flags |= TP_CHANNEL_TEXT_MESSAGE_FLAG_RESCUED;
752
753 /* If the message is on an extended interface, is a delivery report, or only
754 * contains headers, definitely set the "your client is too old" flag. */
755 if (message->parts->len <= 1 ||
756 tp_asv_get_uint32 (header, "message-type", NULL)
757 == TP_CHANNEL_TEXT_MESSAGE_TYPE_DELIVERY_REPORT ||
758 g_hash_table_lookup (header, "interface") != NULL)
759 {
760 flags |= TP_CHANNEL_TEXT_MESSAGE_FLAG_NON_TEXT_CONTENT;
761 }
762
763 for (i = 1; i < message->parts->len; i++)
764 {
765 GHashTable *part = g_ptr_array_index (message->parts, i);
766 const gchar *type = tp_asv_get_string (part, "content-type");
767 const gchar *alternative = tp_asv_get_string (part, "alternative");
768
769 /* Renamed to "content-type" in spec 0.17.14 */
770 if (type == NULL)
771 type = tp_asv_get_string (part, "type");
772
773 DEBUG ("Parsing part %u, type %s, alternative %s", i, type, alternative);
774
775 if (!tp_strdiff (type, "text/plain"))
776 {
777 GValue *value;
778
779 DEBUG ("... is text/plain");
780
781 if (alternative != NULL && alternative[0] != '\0')
782 {
783 if (alternatives_used == NULL)
784 {
785 /* We can't have seen an alternative for this part yet.
786 * However, we need to create the hash table now */
787 alternatives_used = g_hash_table_new (g_str_hash,
788 g_str_equal);
789 }
790 else if (g_hash_table_lookup (alternatives_used,
791 alternative) != NULL)
792 {
793 /* we've seen a "better" alternative for this part already.
794 * Skip it */
795 DEBUG ("... already saw a better alternative, skipping it");
796 continue;
797 }
798
799 g_hash_table_insert (alternatives_used, (gpointer) alternative,
800 (gpointer) alternative);
801 }
802
803 value = g_hash_table_lookup (part, "content");
804
805 if (value != NULL && G_VALUE_HOLDS_STRING (value))
806 {
807 DEBUG ("... using its text");
808 g_string_append (buffer, g_value_get_string (value));
809
810 value = g_hash_table_lookup (part, "truncated");
811
812 if (value != NULL && (!G_VALUE_HOLDS_BOOLEAN (value) ||
813 g_value_get_boolean (value)))
814 {
815 DEBUG ("... appears to have been truncated");
816 flags |= TP_CHANNEL_TEXT_MESSAGE_FLAG_TRUNCATED;
817 }
818 }
819 else
820 {
821 /* There was a text/plain part we couldn't parse:
822 * that counts as "non-text content" I think */
823 DEBUG ("... didn't understand it, setting NON_TEXT_CONTENT");
824 flags |= TP_CHANNEL_TEXT_MESSAGE_FLAG_NON_TEXT_CONTENT;
825 tp_clear_pointer (&alternatives_needed, g_hash_table_unref);
826 }
827 }
828 else if ((flags & TP_CHANNEL_TEXT_MESSAGE_FLAG_NON_TEXT_CONTENT) == 0)
829 {
830 DEBUG ("... wondering whether this is NON_TEXT_CONTENT?");
831
832 if (tp_str_empty (alternative))
833 {
834 /* This part can't possibly have a text alternative, since it
835 * isn't part of a multipart/alternative group
836 * (attached image or something, perhaps) */
837 DEBUG ("... ... yes, no possibility of a text alternative");
838 flags |= TP_CHANNEL_TEXT_MESSAGE_FLAG_NON_TEXT_CONTENT;
839 tp_clear_pointer (&alternatives_needed, g_hash_table_unref);
840 }
841 else if (alternatives_used != NULL &&
842 g_hash_table_lookup (alternatives_used, (gpointer) alternative)
843 != NULL)
844 {
845 DEBUG ("... ... no, we already saw a text alternative");
846 }
847 else
848 {
849 /* This part might have a text alternative later, if we're
850 * lucky */
851 if (alternatives_needed == NULL)
852 alternatives_needed = g_hash_table_new (g_str_hash,
853 g_str_equal);
854
855 DEBUG ("... ... perhaps, but might have text alternative later");
856 g_hash_table_insert (alternatives_needed, (gpointer) alternative,
857 (gpointer) alternative);
858 }
859 }
860 }
861
862 if ((flags & TP_CHANNEL_TEXT_MESSAGE_FLAG_NON_TEXT_CONTENT) == 0 &&
863 alternatives_needed != NULL)
864 {
865 if (alternatives_used != NULL)
866 g_hash_table_foreach (alternatives_used, subtract_from_hash,
867 alternatives_needed);
868
869 if (g_hash_table_size (alternatives_needed) > 0)
870 flags |= TP_CHANNEL_TEXT_MESSAGE_FLAG_NON_TEXT_CONTENT;
871 }
872
873 if (alternatives_needed != NULL)
874 g_hash_table_unref (alternatives_needed);
875
876 if (alternatives_used != NULL)
877 g_hash_table_unref (alternatives_used);
878
879 if (out_flags != NULL)
880 {
881 *out_flags = flags;
882 }
883
884 return g_string_free (buffer, FALSE);
885 }
886
887 void
_tp_message_set_immutable(TpMessage * self)888 _tp_message_set_immutable (TpMessage *self)
889 {
890 self->priv->mutable = FALSE;
891 }
892
893 /**
894 * tp_message_is_mutable:
895 * @self: a #TpMessage
896 *
897 * Check if @self is mutable. Only mutable messages can be modified using
898 * functions such as tp_message_set_string().
899 *
900 * Returns: %TRUE if the message is mutable.
901 *
902 * Since: 0.13.9
903 */
904 gboolean
tp_message_is_mutable(TpMessage * self)905 tp_message_is_mutable (TpMessage *self)
906 {
907 g_return_val_if_fail (TP_IS_MESSAGE (self), FALSE);
908
909 return self->priv->mutable;
910 }
911
912 /**
913 * tp_message_get_token:
914 * @self: a message
915 *
916 * Return this message's identifier in the underlying protocol. This is
917 * <emphasis>not</emphasis> guaranteed to be unique, even within the scope
918 * of a single channel or contact: the only guarantee made is that two
919 * messages with different non-empty tokens are different messages.
920 *
921 * If there is no suitable token, return %NULL.
922 *
923 * Returns: (transfer none): a non-empty opaque identifier, or %NULL if none
924 *
925 * Since: 0.13.9
926 */
927 const gchar *
tp_message_get_token(TpMessage * self)928 tp_message_get_token (TpMessage *self)
929 {
930 const gchar *token;
931
932 g_return_val_if_fail (TP_IS_MESSAGE (self), NULL);
933
934 token = tp_asv_get_string (tp_message_peek (self, 0), "message-token");
935
936 if (tp_str_empty (token))
937 return NULL;
938 else
939 return token;
940 }
941
942 /**
943 * tp_message_get_message_type:
944 * @self: a message
945 *
946 * <!-- -->
947 *
948 * Returns: the type of this message
949 *
950 * Since: 0.13.10
951 */
952 TpChannelTextMessageType
tp_message_get_message_type(TpMessage * self)953 tp_message_get_message_type (TpMessage *self)
954 {
955 g_return_val_if_fail (TP_IS_MESSAGE (self),
956 TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL);
957 /* if message-type is absent or invalid we just return 0, which is NORMAL */
958 return tp_asv_get_uint32 (tp_message_peek (self, 0), "message-type", NULL);
959 }
960
961 /**
962 * tp_message_get_sent_timestamp:
963 * @self: a message
964 *
965 * Return when this message was sent, as a number of seconds since the
966 * beginning of 1970 in the UTC timezone (the same representation used by
967 * g_date_time_new_from_unix_utc(), for instance), or 0 if not known.
968 *
969 * If this protocol does not track the time at which the message was
970 * initially sent, this timestamp might be approximated by using the
971 * time at which it arrived at a central server.
972 *
973 * Returns: a Unix timestamp, or 0
974 *
975 * Since: 0.13.9
976 */
977 gint64
tp_message_get_sent_timestamp(TpMessage * self)978 tp_message_get_sent_timestamp (TpMessage *self)
979 {
980 g_return_val_if_fail (TP_IS_MESSAGE (self), 0);
981 return tp_asv_get_int64 (tp_message_peek (self, 0), "message-sent", NULL);
982 }
983
984 /**
985 * tp_message_get_received_timestamp:
986 * @self: a message
987 *
988 * Return when this message was received locally, as a number of seconds since
989 * the beginning of 1970 in the UTC timezone (the same representation used by
990 * g_date_time_new_from_unix_utc(), for instance), or 0 if not known.
991 *
992 * Returns: a Unix timestamp, or 0
993 *
994 * Since: 0.13.9
995 */
996 gint64
tp_message_get_received_timestamp(TpMessage * self)997 tp_message_get_received_timestamp (TpMessage *self)
998 {
999 g_return_val_if_fail (TP_IS_MESSAGE (self), 0);
1000 return tp_asv_get_int64 (tp_message_peek (self, 0), "message-received",
1001 NULL);
1002 }
1003
1004 /**
1005 * tp_message_is_scrollback:
1006 * @self: a message
1007 *
1008 * <!-- no more to say -->
1009 *
1010 * Returns: %TRUE if this message is part of a replay of message history, for
1011 * instance in an XMPP chatroom.
1012 *
1013 * Since: 0.13.9
1014 */
1015 gboolean
tp_message_is_scrollback(TpMessage * self)1016 tp_message_is_scrollback (TpMessage *self)
1017 {
1018 g_return_val_if_fail (TP_IS_MESSAGE (self), FALSE);
1019 return tp_asv_get_boolean (tp_message_peek (self, 0), "scrollback", NULL);
1020 }
1021
1022 /**
1023 * tp_message_is_rescued:
1024 * @self: a message
1025 *
1026 * Returns %TRUE if this incoming message has been seen in a previous channel
1027 * during the lifetime of the Connection, but had not been acknowledged when
1028 * that channel closed, causing an identical channel (in which the message now
1029 * appears) to open.
1030 *
1031 * Loggers should check this flag to avoid duplicating messages, for instance.
1032 *
1033 * Returns: %TRUE if this message was seen in a previous Channel on this
1034 * Connection
1035 *
1036 * Since: 0.13.9
1037 */
1038 gboolean
tp_message_is_rescued(TpMessage * self)1039 tp_message_is_rescued (TpMessage *self)
1040 {
1041 g_return_val_if_fail (TP_IS_MESSAGE (self), FALSE);
1042 return tp_asv_get_boolean (tp_message_peek (self, 0), "rescued", NULL);
1043 }
1044
1045 /**
1046 * tp_message_get_supersedes:
1047 * @self: a message
1048 *
1049 * If this message replaces a previous message, return the value of
1050 * tp_message_get_token() for that previous message. Otherwise, return %NULL.
1051 *
1052 * For instance, a user interface could replace the superseded
1053 * message with this message, or grey out the superseded message.
1054 *
1055 * Returns: (transfer none): a non-empty opaque identifier, or %NULL if none
1056 *
1057 * Since: 0.13.9
1058 */
1059 const gchar *
tp_message_get_supersedes(TpMessage * self)1060 tp_message_get_supersedes (TpMessage *self)
1061 {
1062 const gchar *token;
1063
1064 g_return_val_if_fail (TP_IS_MESSAGE (self), NULL);
1065
1066 token = tp_asv_get_string (tp_message_peek (self, 0), "supersedes");
1067
1068 if (tp_str_empty (token))
1069 return NULL;
1070 else
1071 return token;
1072 }
1073
1074 /**
1075 * tp_message_get_specific_to_interface:
1076 * @self: a message
1077 *
1078 * If this message is specific to a particular D-Bus interface and should
1079 * be ignored by clients without knowledge of that interface, return the
1080 * name of the interface.
1081 *
1082 * If this message is an ordinary message or delivery report, return %NULL.
1083 *
1084 * Returns: (transfer none): a D-Bus interface name, or %NULL for ordinary
1085 * messages and delivery reports
1086 *
1087 * Since: 0.13.9
1088 */
1089 const gchar *
tp_message_get_specific_to_interface(TpMessage * self)1090 tp_message_get_specific_to_interface (TpMessage *self)
1091 {
1092 g_return_val_if_fail (TP_IS_MESSAGE (self), NULL);
1093 return tp_asv_get_string (tp_message_peek (self, 0), "interface");
1094 }
1095
1096 /**
1097 * tp_message_is_delivery_report:
1098 * @self: a message
1099 *
1100 * If this message is a delivery report indicating success or failure of
1101 * delivering a message, return %TRUE.
1102 *
1103 * Returns: %TRUE if this is a delivery report
1104 *
1105 * Since: 0.13.9
1106 */
1107 gboolean
tp_message_is_delivery_report(TpMessage * self)1108 tp_message_is_delivery_report (TpMessage *self)
1109 {
1110 gboolean valid;
1111
1112 g_return_val_if_fail (TP_IS_MESSAGE (self), FALSE);
1113
1114 tp_asv_get_uint32 (tp_message_peek (self, 0), "delivery-status", &valid);
1115 return valid;
1116 }
1117
1118 /**
1119 * tp_message_get_pending_message_id:
1120 * @self: a message
1121 * @valid: (out): either %NULL, or a location in which to store %TRUE if @self
1122 * contains a pending message ID.
1123 *
1124 * Return the incoming message ID of @self. Only incoming messages have such
1125 * ID, for outgoing ones this function returns 0 and set @valid to %FALSE.
1126 *
1127 * Returns: the incoming message ID.
1128 *
1129 * Since: 0.15.3
1130 */
1131 guint32
tp_message_get_pending_message_id(TpMessage * self,gboolean * valid)1132 tp_message_get_pending_message_id (TpMessage *self,
1133 gboolean *valid)
1134 {
1135 g_return_val_if_fail (TP_IS_MESSAGE (self), FALSE);
1136
1137 return tp_asv_get_uint32 (tp_message_peek (self, 0),
1138 "pending-message-id", valid);
1139 }
1140
1141 /*
1142 * Omitted for now:
1143 *
1144 * sender-nickname - perhaps better done in TpSignalledMessage, so we can use
1145 * the TpContact's nickname if the message doesn't specify?
1146 *
1147 * delivery reporting stuff other than "is this a report?" - later
1148 */
1149