1 /*
2  * text-channel.h - high level API for Text channels
3  *
4  * Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
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:text-channel
23  * @title: TpTextChannel
24  * @short_description: proxy object for a text channel
25  *
26  * #TpTextChannel is a sub-class of #TpChannel providing convenient API
27  * to send and receive #TpMessage.
28  */
29 
30 /**
31  * TpTextChannel:
32  *
33  * Data structure representing a #TpTextChannel.
34  *
35  * Since: 0.13.10
36  */
37 
38 /**
39  * TpTextChannelClass:
40  *
41  * The class of a #TpTextChannel.
42  *
43  * Since: 0.13.10
44  */
45 
46 #include "config.h"
47 
48 #include "telepathy-glib/text-channel.h"
49 
50 #include <telepathy-glib/contact.h>
51 #include <telepathy-glib/dbus.h>
52 #include <telepathy-glib/enums.h>
53 #include <telepathy-glib/gnio-util.h>
54 #include <telepathy-glib/gtypes.h>
55 #include <telepathy-glib/interfaces.h>
56 #include <telepathy-glib/message-internal.h>
57 #include <telepathy-glib/proxy-internal.h>
58 #include <telepathy-glib/proxy-subclass.h>
59 #include <telepathy-glib/signalled-message-internal.h>
60 #include <telepathy-glib/util-internal.h>
61 #include <telepathy-glib/util.h>
62 
63 #define DEBUG_FLAG TP_DEBUG_CHANNEL
64 #include "telepathy-glib/debug-internal.h"
65 #include "telepathy-glib/automatic-client-factory-internal.h"
66 #include "telepathy-glib/channel-internal.h"
67 
68 #include <stdio.h>
69 #include <glib/gstdio.h>
70 
71 G_DEFINE_TYPE (TpTextChannel, tp_text_channel, TP_TYPE_CHANNEL)
72 
73 struct _TpTextChannelPrivate
74 {
75   GStrv supported_content_types;
76   TpMessagePartSupportFlags message_part_support_flags;
77   TpDeliveryReportingSupportFlags delivery_reporting_support;
78   GArray *message_types;
79 
80   GSimpleAsyncResult *pending_messages_result;
81   guint n_preparing_pending_messages;
82 
83   /* queue of owned TpSignalledMessage */
84   GQueue *pending_messages;
85   gboolean got_initial_messages;
86 
87   gboolean is_sms_channel;
88   gboolean sms_flash;
89 };
90 
91 enum
92 {
93   PROP_SUPPORTED_CONTENT_TYPES = 1,
94   PROP_MESSAGE_PART_SUPPORT_FLAGS,
95   PROP_DELIVERY_REPORTING_SUPPORT,
96   PROP_MESSAGE_TYPES,
97   PROP_IS_SMS_CHANNEL,
98   PROP_SMS_FLASH,
99 };
100 
101 enum /* signals */
102 {
103   SIG_MESSAGE_RECEIVED,
104   SIG_PENDING_MESSAGE_REMOVED,
105   SIG_MESSAGE_SENT,
106   SIG_CONTACT_CHAT_STATE_CHANGED,
107   LAST_SIGNAL
108 };
109 
110 static guint signals[LAST_SIGNAL] = { 0, };
111 
112 static void
tp_text_channel_dispose(GObject * obj)113 tp_text_channel_dispose (GObject *obj)
114 {
115   TpTextChannel *self = (TpTextChannel *) obj;
116 
117   tp_clear_pointer (&self->priv->supported_content_types, g_strfreev);
118   tp_clear_pointer (&self->priv->message_types, g_array_unref);
119 
120   g_queue_foreach (self->priv->pending_messages, (GFunc) g_object_unref, NULL);
121   tp_clear_pointer (&self->priv->pending_messages, g_queue_free);
122 
123   G_OBJECT_CLASS (tp_text_channel_parent_class)->dispose (obj);
124 }
125 
126 static void
tp_text_channel_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)127 tp_text_channel_get_property (GObject *object,
128     guint property_id,
129     GValue *value,
130     GParamSpec *pspec)
131 {
132   TpTextChannel *self = (TpTextChannel *) object;
133 
134   switch (property_id)
135     {
136       case PROP_SUPPORTED_CONTENT_TYPES:
137         g_value_set_boxed (value,
138             tp_text_channel_get_supported_content_types (self));
139         break;
140 
141       case PROP_MESSAGE_PART_SUPPORT_FLAGS:
142         g_value_set_uint (value,
143             tp_text_channel_get_message_part_support_flags (self));
144         break;
145 
146       case PROP_DELIVERY_REPORTING_SUPPORT:
147         g_value_set_uint (value,
148             tp_text_channel_get_delivery_reporting_support (self));
149         break;
150 
151       case PROP_MESSAGE_TYPES:
152         g_value_set_boxed (value,
153             tp_text_channel_get_message_types (self));
154         break;
155 
156       case PROP_IS_SMS_CHANNEL:
157         g_value_set_boolean (value, tp_text_channel_is_sms_channel (self));
158         break;
159 
160       case PROP_SMS_FLASH:
161         g_value_set_boolean (value, tp_text_channel_get_sms_flash (self));
162         break;
163 
164       default:
165         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
166         break;
167     }
168 }
169 
170 static TpHandle
get_sender(TpTextChannel * self,const GPtrArray * message,TpContact ** contact,const gchar ** out_sender_id)171 get_sender (TpTextChannel *self,
172     const GPtrArray *message,
173     TpContact **contact,
174     const gchar **out_sender_id)
175 {
176   const GHashTable *header;
177   TpHandle handle;
178   const gchar *sender_id = NULL;
179   TpConnection *conn;
180 
181   g_assert (contact != NULL);
182 
183   header = g_ptr_array_index (message, 0);
184   handle = tp_asv_get_uint32 (header, "message-sender", NULL);
185   if (handle == 0)
186     {
187       DEBUG ("Message received on Channel %s doesn't have message-sender",
188           tp_proxy_get_object_path (self));
189 
190       *contact = NULL;
191       goto out;
192     }
193 
194   sender_id = tp_asv_get_string (header, "message-sender-id");
195 
196   conn = tp_channel_get_connection ((TpChannel *) self);
197   *contact = tp_connection_dup_contact_if_possible (conn, handle, sender_id);
198 
199   if (*contact == NULL)
200     {
201       if (!tp_connection_has_immortal_handles (conn))
202         DEBUG ("Connection %s don't have immortal handles, please fix CM",
203             tp_proxy_get_object_path (conn));
204       else if (tp_str_empty (sender_id))
205         DEBUG ("Message received on %s doesn't include message-sender-id, "
206             "please fix CM", tp_proxy_get_object_path (self));
207     }
208 
209 out:
210   if (out_sender_id != NULL)
211     *out_sender_id = sender_id;
212 
213   return handle;
214 }
215 
216 static void
prepare_sender_async(TpTextChannel * self,const GPtrArray * message,gboolean fallback_to_self,GAsyncReadyCallback callback,gpointer user_data)217 prepare_sender_async (TpTextChannel *self,
218     const GPtrArray *message,
219     gboolean fallback_to_self,
220     GAsyncReadyCallback callback,
221     gpointer user_data)
222 {
223   TpChannel *channel = (TpChannel *) self;
224   TpContact *contact;
225   TpHandle handle;
226   const gchar *id;
227 
228   handle = get_sender (self, message, &contact, &id);
229   if (contact == NULL && fallback_to_self)
230     {
231       TpConnection *conn;
232 
233       conn = tp_channel_get_connection ((TpChannel *) self);
234 
235       DEBUG ("Failed to get our self contact, please fix CM (%s)",
236           tp_proxy_get_object_path (conn));
237 
238       /* Use the connection self contact as a fallback */
239       contact = tp_connection_get_self_contact (conn);
240       if (contact != NULL)
241         g_object_ref (contact);
242     }
243 
244   if (contact != NULL)
245     {
246       GPtrArray *contacts = g_ptr_array_new_with_free_func (g_object_unref);
247 
248       /* get_sender() refs the contact, we give that ref to the ptr-array */
249       g_ptr_array_add (contacts, contact);
250       _tp_channel_contacts_queue_prepare_async (channel,
251           contacts, callback, user_data);
252       g_ptr_array_unref (contacts);
253     }
254   else if (id != NULL)
255     {
256       GPtrArray *ids = g_ptr_array_new_with_free_func (g_free);
257 
258       g_ptr_array_add (ids, g_strdup (id));
259       _tp_channel_contacts_queue_prepare_by_id_async (channel,
260           ids, callback, user_data);
261       g_ptr_array_unref (ids);
262     }
263   else if (handle != 0)
264     {
265       GArray *handles = g_array_new (FALSE, FALSE, sizeof (TpHandle));
266 
267       g_array_append_val (handles, handle);
268       _tp_channel_contacts_queue_prepare_by_handle_async (channel,
269           handles, callback, user_data);
270       g_array_unref (handles);
271     }
272   else
273     {
274       /* No sender. Still need to go through the queue to prevent reordering */
275       _tp_channel_contacts_queue_prepare_async (channel,
276           NULL, callback, user_data);
277     }
278 }
279 
280 static TpContact *
prepare_sender_finish(TpTextChannel * self,GAsyncResult * result,GError ** error)281 prepare_sender_finish (TpTextChannel *self,
282     GAsyncResult *result,
283     GError **error)
284 {
285   GPtrArray *contacts;
286   TpContact *sender = NULL;
287 
288   _tp_channel_contacts_queue_prepare_finish ((TpChannel *) self, result,
289       &contacts, error);
290 
291   if (contacts != NULL && contacts->len > 0)
292     sender = g_object_ref (g_ptr_array_index (contacts, 0));
293 
294   tp_clear_pointer (&contacts, g_ptr_array_unref);
295 
296   return sender;
297 }
298 
299 static GPtrArray *
copy_parts(const GPtrArray * parts)300 copy_parts (const GPtrArray *parts)
301 {
302   return g_boxed_copy (TP_ARRAY_TYPE_MESSAGE_PART_LIST, parts);
303 }
304 
305 static void
free_parts(GPtrArray * parts)306 free_parts (GPtrArray *parts)
307 {
308   g_boxed_free (TP_ARRAY_TYPE_MESSAGE_PART_LIST, parts);
309 }
310 
311 typedef struct
312 {
313   GPtrArray *parts;
314   guint flags;
315   gchar *token;
316 } MessageSentData;
317 
318 static void
message_sent_sender_ready_cb(GObject * object,GAsyncResult * result,gpointer user_data)319 message_sent_sender_ready_cb (GObject *object,
320     GAsyncResult *result,
321     gpointer user_data)
322 {
323   TpTextChannel *self = (TpTextChannel *) object;
324   MessageSentData *data = user_data;
325   TpContact *sender;
326   TpMessage *msg;
327 
328   sender = prepare_sender_finish (self, result, NULL);
329   msg = _tp_signalled_message_new (data->parts, sender);
330 
331   g_signal_emit (self, signals[SIG_MESSAGE_SENT], 0, msg, data->flags,
332       data->token);
333 
334   g_object_unref (msg);
335   free_parts (data->parts);
336   g_free (data->token);
337   g_slice_free (MessageSentData, data);
338 }
339 
340 static void
message_sent_cb(TpChannel * channel,const GPtrArray * parts,guint flags,const gchar * token,gpointer user_data,GObject * weak_object)341 message_sent_cb (TpChannel *channel,
342     const GPtrArray *parts,
343     guint flags,
344     const gchar *token,
345     gpointer user_data,
346     GObject *weak_object)
347 {
348   TpTextChannel *self = (TpTextChannel *) channel;
349   MessageSentData *data;
350 
351   DEBUG ("New message sent");
352 
353   data = g_slice_new (MessageSentData);
354   data->parts = copy_parts (parts);
355   data->flags = flags;
356   data->token = tp_str_empty (token) ? NULL : g_strdup (token);
357 
358   prepare_sender_async (self, parts, TRUE,
359       message_sent_sender_ready_cb, data);
360 }
361 
362 static void
chat_state_changed_cb(TpTextChannel * self,TpHandle handle,TpChannelChatState state)363 chat_state_changed_cb (TpTextChannel *self,
364     TpHandle handle,
365     TpChannelChatState state)
366 {
367   TpConnection *conn;
368   TpContact *contact;
369 
370   /* We have only an handle, but since we guarantee "contact-chat-state-changed"
371    * to be emitted only if TP_CHANNEL_FEATURE_GROUP and
372    * TP_CHANNEL_FEATURE_CONTACTS has been prepared, we should already have its
373    * TpContact. If the TpContact does not exist, telling its chat state is
374    * useless anyway. */
375   conn = tp_channel_get_connection ((TpChannel *) self);
376   contact = tp_connection_dup_contact_if_possible (conn, handle, NULL);
377   if (contact == NULL)
378     return;
379 
380   g_signal_emit (self, signals[SIG_CONTACT_CHAT_STATE_CHANGED], 0,
381       contact, state);
382 
383   g_object_unref (contact);
384 }
385 
386 static void
tp_text_channel_prepare_chat_states_async(TpProxy * proxy,const TpProxyFeature * feature,GAsyncReadyCallback callback,gpointer user_data)387 tp_text_channel_prepare_chat_states_async (TpProxy *proxy,
388     const TpProxyFeature *feature,
389     GAsyncReadyCallback callback,
390     gpointer user_data)
391 {
392   /* This feature depends on TP_CHANNEL_FEATURE_CHAT_STATES so it's already
393    * prepared. */
394   tp_simple_async_report_success_in_idle ((GObject *) proxy,
395       callback, user_data, tp_text_channel_prepare_chat_states_async);
396 }
397 
398 static void
tp_text_channel_constructed(GObject * obj)399 tp_text_channel_constructed (GObject *obj)
400 {
401   TpTextChannel *self = (TpTextChannel *) obj;
402   void (*chain_up) (GObject *) =
403     ((GObjectClass *) tp_text_channel_parent_class)->constructed;
404   TpChannel *chan = (TpChannel *) obj;
405   GHashTable *props;
406   gboolean valid;
407   GError *err = NULL;
408 
409   if (chain_up != NULL)
410     chain_up (obj);
411 
412   if (tp_channel_get_channel_type_id (chan) !=
413       TP_IFACE_QUARK_CHANNEL_TYPE_TEXT)
414     {
415       GError error = { TP_DBUS_ERRORS, TP_DBUS_ERROR_INCONSISTENT,
416           "Channel is not of type Text" };
417 
418       DEBUG ("Channel %s is not of type Text: %s",
419           tp_proxy_get_object_path (self), tp_channel_get_channel_type (chan));
420 
421       tp_proxy_invalidate (TP_PROXY (self), &error);
422       return;
423     }
424 
425   if (!tp_proxy_has_interface_by_id (self,
426         TP_IFACE_QUARK_CHANNEL_INTERFACE_MESSAGES))
427     {
428       GError error = { TP_DBUS_ERRORS, TP_DBUS_ERROR_INCONSISTENT,
429           "Channel does not implement the Messages interface" };
430 
431       DEBUG ("Channel %s does not implement the Messages interface",
432           tp_proxy_get_object_path (self));
433 
434       tp_proxy_invalidate (TP_PROXY (self), &error);
435       return;
436 
437     }
438 
439   /* Forward TpChannel::chat-state-changed as
440    * TpTextChannel::contact-chat-state-changed */
441   g_signal_connect (self, "chat-state-changed",
442       G_CALLBACK (chat_state_changed_cb), NULL);
443 
444   props = _tp_channel_get_immutable_properties (TP_CHANNEL (self));
445 
446   self->priv->supported_content_types = (GStrv) tp_asv_get_strv (props,
447       TP_PROP_CHANNEL_INTERFACE_MESSAGES_SUPPORTED_CONTENT_TYPES);
448   if (self->priv->supported_content_types == NULL)
449     {
450       const gchar * const plain[] = { "text/plain", NULL };
451 
452       DEBUG ("Channel %s doesn't have Messages.SupportedContentTypes in its "
453           "immutable properties", tp_proxy_get_object_path (self));
454 
455       /* spec mandates that plain text is always allowed. */
456       self->priv->supported_content_types = g_strdupv ((GStrv) plain);
457     }
458   else
459     {
460       self->priv->supported_content_types = g_strdupv (
461           self->priv->supported_content_types);
462     }
463 
464   self->priv->message_part_support_flags = tp_asv_get_uint32 (props,
465       TP_PROP_CHANNEL_INTERFACE_MESSAGES_MESSAGE_PART_SUPPORT_FLAGS, &valid);
466   if (!valid)
467     {
468       DEBUG ("Channel %s doesn't have Messages.MessagePartSupportFlags in its "
469           "immutable properties", tp_proxy_get_object_path (self));
470     }
471 
472   self->priv->delivery_reporting_support = tp_asv_get_uint32 (props,
473       TP_PROP_CHANNEL_INTERFACE_MESSAGES_DELIVERY_REPORTING_SUPPORT, &valid);
474   if (!valid)
475     {
476       DEBUG ("Channel %s doesn't have Messages.DeliveryReportingSupport in its "
477           "immutable properties", tp_proxy_get_object_path (self));
478     }
479 
480   self->priv->message_types = tp_asv_get_boxed (props,
481       TP_PROP_CHANNEL_INTERFACE_MESSAGES_MESSAGE_TYPES, DBUS_TYPE_G_UINT_ARRAY);
482   if (self->priv->message_types != NULL)
483     {
484       self->priv->message_types = g_boxed_copy (DBUS_TYPE_G_UINT_ARRAY,
485           self->priv->message_types);
486     }
487   else
488     {
489       self->priv->message_types = g_array_new (FALSE, FALSE,
490           sizeof (TpChannelTextMessageType));
491 
492       DEBUG ("Channel %s doesn't have Messages.MessageTypes in its "
493           "immutable properties", tp_proxy_get_object_path (self));
494     }
495 
496   tp_cli_channel_interface_messages_connect_to_message_sent (chan,
497       message_sent_cb, NULL, NULL, NULL, &err);
498   if (err != NULL)
499     {
500       WARNING ("Failed to connect to MessageSent on %s: %s",
501           tp_proxy_get_object_path (self), err->message);
502       g_error_free (err);
503     }
504 
505   /* SMS */
506   self->priv->sms_flash = tp_asv_get_boolean (props,
507       TP_PROP_CHANNEL_INTERFACE_SMS_FLASH, NULL);
508 }
509 
510 static void
add_message_received(TpTextChannel * self,const GPtrArray * parts,TpContact * sender,gboolean fire_received)511 add_message_received (TpTextChannel *self,
512     const GPtrArray *parts,
513     TpContact *sender,
514     gboolean fire_received)
515 {
516   TpMessage *msg;
517 
518   msg = _tp_signalled_message_new (parts, sender);
519 
520   g_queue_push_tail (self->priv->pending_messages, msg);
521 
522   if (fire_received)
523     g_signal_emit (self, signals[SIG_MESSAGE_RECEIVED], 0, msg);
524 }
525 
526 static void
message_received_sender_ready_cb(GObject * object,GAsyncResult * result,gpointer user_data)527 message_received_sender_ready_cb (GObject *object,
528     GAsyncResult *result,
529     gpointer user_data)
530 {
531   TpTextChannel *self = (TpTextChannel *) object;
532   GPtrArray *parts = user_data;
533   TpContact *sender;
534 
535   sender = prepare_sender_finish (self, result, NULL);
536   add_message_received (self, parts, sender, TRUE);
537 
538   free_parts (parts);
539 }
540 
541 static void
message_received_cb(TpChannel * proxy,const GPtrArray * message,gpointer user_data,GObject * weak_object)542 message_received_cb (TpChannel *proxy,
543     const GPtrArray *message,
544     gpointer user_data,
545     GObject *weak_object)
546 {
547   TpTextChannel *self = user_data;
548 
549   /* If we are still retrieving pending messages, no need to add the message,
550    * it will be in the initial set of messages retrieved. */
551   if (!self->priv->got_initial_messages)
552     return;
553 
554   DEBUG ("New message received");
555 
556   prepare_sender_async (self, message, FALSE,
557       message_received_sender_ready_cb,
558       copy_parts (message));
559 }
560 
561 static gint
find_msg_by_id(gconstpointer a,gconstpointer b)562 find_msg_by_id (gconstpointer a,
563     gconstpointer b)
564 {
565   TpMessage *msg = TP_MESSAGE (a);
566   guint id = GPOINTER_TO_UINT (b);
567   gboolean valid;
568   guint msg_id;
569 
570   msg_id = _tp_signalled_message_get_pending_message_id (msg, &valid);
571   if (!valid)
572     return 1;
573 
574   return msg_id != id;
575 }
576 
577 static void
pending_messages_removed_ready_cb(GObject * object,GAsyncResult * result,gpointer user_data)578 pending_messages_removed_ready_cb (GObject *object,
579     GAsyncResult *result,
580     gpointer user_data)
581 {
582   TpTextChannel *self = (TpTextChannel *) object;
583   TpChannel *channel = (TpChannel *) self;
584   GArray *ids = user_data;
585   guint i;
586 
587   _tp_channel_contacts_queue_prepare_finish (channel, result, NULL, NULL);
588 
589   for (i = 0; i < ids->len; i++)
590     {
591       guint id = g_array_index (ids, guint, i);
592       GList *link_;
593       TpMessage *msg;
594 
595       link_ = g_queue_find_custom (self->priv->pending_messages,
596           GUINT_TO_POINTER (id), find_msg_by_id);
597 
598       if (link_ == NULL)
599         {
600           DEBUG ("Unable to find pending message having id %d", id);
601           continue;
602         }
603 
604       msg = link_->data;
605 
606       g_queue_delete_link (self->priv->pending_messages, link_);
607 
608       g_signal_emit (self, signals[SIG_PENDING_MESSAGE_REMOVED], 0, msg);
609 
610       g_object_unref (msg);
611     }
612 
613   g_array_unref (ids);
614 }
615 
616 static void
pending_messages_removed_cb(TpChannel * channel,const GArray * ids,gpointer user_data,GObject * weak_object)617 pending_messages_removed_cb (TpChannel *channel,
618     const GArray *ids,
619     gpointer user_data,
620     GObject *weak_object)
621 {
622   TpTextChannel *self = (TpTextChannel *) channel;
623   GArray *ids_dup;
624 
625   if (!self->priv->got_initial_messages)
626     return;
627 
628   /* We have nothing to prepare, but still we have to hook into the queue
629    * to preserve order. Messages removed here could still be in that queue. */
630   ids_dup = g_array_sized_new (FALSE, FALSE, sizeof (guint), ids->len);
631   g_array_append_vals (ids_dup, ids->data, ids->len);
632 
633   _tp_channel_contacts_queue_prepare_async (channel, NULL,
634       pending_messages_removed_ready_cb, ids_dup);
635 }
636 
637 static void
pending_message_sender_ready_cb(GObject * object,GAsyncResult * result,gpointer user_data)638 pending_message_sender_ready_cb (GObject *object,
639     GAsyncResult *result,
640     gpointer user_data)
641 {
642   TpTextChannel *self = (TpTextChannel *) object;
643   GPtrArray *parts = user_data;
644   TpContact *sender;
645 
646   sender = prepare_sender_finish (self, result, NULL);
647   add_message_received (self, parts, sender, FALSE);
648 
649   self->priv->n_preparing_pending_messages--;
650   if (self->priv->n_preparing_pending_messages == 0)
651     {
652       g_simple_async_result_complete (self->priv->pending_messages_result);
653       g_clear_object (&self->priv->pending_messages_result);
654     }
655 
656   free_parts (parts);
657 }
658 
659 /* There is no TP_ARRAY_TYPE_PENDING_TEXT_MESSAGE_LIST_LIST (fdo #32433) */
660 #define ARRAY_TYPE_PENDING_TEXT_MESSAGE_LIST_LIST dbus_g_type_get_collection (\
661     "GPtrArray", TP_ARRAY_TYPE_MESSAGE_PART_LIST)
662 
663 static void
get_pending_messages_cb(TpProxy * proxy,const GValue * value,const GError * error,gpointer user_data,GObject * weak_object)664 get_pending_messages_cb (TpProxy *proxy,
665     const GValue *value,
666     const GError *error,
667     gpointer user_data,
668     GObject *weak_object)
669 {
670   TpTextChannel *self = (TpTextChannel *) proxy;
671   GPtrArray *messages;
672   guint i;
673 
674   self->priv->got_initial_messages = TRUE;
675 
676   if (error != NULL)
677     {
678       DEBUG ("Failed to get PendingMessages property: %s", error->message);
679 
680       g_simple_async_result_set_error (self->priv->pending_messages_result,
681           error->domain, error->code,
682           "Failed to get PendingMessages property: %s", error->message);
683 
684       g_simple_async_result_complete_in_idle (self->priv->pending_messages_result);
685       g_clear_object (&self->priv->pending_messages_result);
686       return;
687     }
688 
689   if (!G_VALUE_HOLDS (value, ARRAY_TYPE_PENDING_TEXT_MESSAGE_LIST_LIST))
690     {
691       DEBUG ("PendingMessages property is of the wrong type");
692 
693       g_simple_async_result_set_error (self->priv->pending_messages_result,
694           TP_ERROR, TP_ERROR_CONFUSED,
695           "PendingMessages property is of the wrong type");
696 
697       g_simple_async_result_complete_in_idle (self->priv->pending_messages_result);
698       g_clear_object (&self->priv->pending_messages_result);
699       return;
700     }
701 
702   messages = g_value_get_boxed (value);
703 
704   if (messages->len == 0)
705     {
706       g_simple_async_result_complete_in_idle (self->priv->pending_messages_result);
707       g_clear_object (&self->priv->pending_messages_result);
708       return;
709     }
710 
711   self->priv->n_preparing_pending_messages = messages->len;
712   for (i = 0; i < messages->len; i++)
713     {
714       GPtrArray *parts = g_ptr_array_index (messages, i);
715 
716       prepare_sender_async (self, parts, FALSE,
717           pending_message_sender_ready_cb,
718           copy_parts (parts));
719     }
720 }
721 
722 static void
tp_text_channel_prepare_incoming_messages_async(TpProxy * proxy,const TpProxyFeature * feature,GAsyncReadyCallback callback,gpointer user_data)723 tp_text_channel_prepare_incoming_messages_async (TpProxy *proxy,
724     const TpProxyFeature *feature,
725     GAsyncReadyCallback callback,
726     gpointer user_data)
727 {
728   TpTextChannel *self = (TpTextChannel *) proxy;
729   TpChannel *channel = (TpChannel *) self;
730   GError *error = NULL;
731 
732   tp_cli_channel_interface_messages_connect_to_message_received (channel,
733       message_received_cb, proxy, NULL, G_OBJECT (proxy), &error);
734   if (error != NULL)
735     {
736       g_simple_async_report_take_gerror_in_idle ((GObject *) self,
737           callback, user_data, error);
738       return;
739     }
740 
741   tp_cli_channel_interface_messages_connect_to_pending_messages_removed (
742       channel, pending_messages_removed_cb, proxy, NULL, G_OBJECT (proxy),
743       &error);
744   if (error != NULL)
745     {
746       g_simple_async_report_take_gerror_in_idle ((GObject *) self,
747           callback, user_data, error);
748       return;
749     }
750 
751   g_assert (self->priv->pending_messages_result == NULL);
752   self->priv->pending_messages_result = g_simple_async_result_new (
753       (GObject *) proxy, callback, user_data,
754       tp_text_channel_prepare_incoming_messages_async);
755 
756 
757   tp_cli_dbus_properties_call_get (proxy, -1,
758       TP_IFACE_CHANNEL_INTERFACE_MESSAGES, "PendingMessages",
759       get_pending_messages_cb, NULL, NULL, G_OBJECT (proxy));
760 }
761 
762 static void
get_sms_channel_cb(TpProxy * proxy,const GValue * value,const GError * error,gpointer user_data,GObject * weak_object)763 get_sms_channel_cb (TpProxy *proxy,
764     const GValue *value,
765     const GError *error,
766     gpointer user_data,
767     GObject *weak_object)
768 {
769   TpTextChannel *self = (TpTextChannel *) proxy;
770   GSimpleAsyncResult *result = user_data;
771 
772   if (error != NULL)
773     {
774       DEBUG ("Failed to get SMSChannel property: %s", error->message);
775 
776       g_simple_async_result_set_error (result, error->domain, error->code,
777           "Failed to get SMSChannel property: %s", error->message);
778       goto out;
779     }
780 
781   if (!G_VALUE_HOLDS (value, G_TYPE_BOOLEAN))
782     {
783       DEBUG ("SMSChannel property is of the wrong type");
784 
785       g_simple_async_result_set_error (result, TP_ERROR, TP_ERROR_CONFUSED,
786           "SMSChannel property is of the wrong type");
787       goto out;
788     }
789 
790   self->priv->is_sms_channel = g_value_get_boolean (value);
791 
792   /* self->priv->is_sms_channel is set to FALSE by default, so only notify the
793    * property change is it is now set to TRUE. */
794   if (self->priv->is_sms_channel)
795     g_object_notify (G_OBJECT (self), "is-sms-channel");
796 
797 out:
798   g_simple_async_result_complete_in_idle (result);
799   g_object_unref (result);
800 }
801 
802 static void
sms_channel_changed_cb(TpChannel * proxy,gboolean sms,gpointer user_data,GObject * weak_object)803 sms_channel_changed_cb (TpChannel *proxy,
804     gboolean sms,
805     gpointer user_data,
806     GObject *weak_object)
807 {
808   TpTextChannel *self = (TpTextChannel *) proxy;
809 
810   if (self->priv->is_sms_channel == sms)
811     return;
812 
813   self->priv->is_sms_channel = sms;
814   g_object_notify (weak_object, "is-sms-channel");
815 }
816 
817 static void
tp_text_channel_prepare_sms_async(TpProxy * proxy,const TpProxyFeature * feature,GAsyncReadyCallback callback,gpointer user_data)818 tp_text_channel_prepare_sms_async (TpProxy *proxy,
819     const TpProxyFeature *feature,
820     GAsyncReadyCallback callback,
821     gpointer user_data)
822 {
823   GSimpleAsyncResult *result;
824   GError *error = NULL;
825 
826   result = g_simple_async_result_new ((GObject *) proxy, callback, user_data,
827       tp_text_channel_prepare_sms_async);
828 
829   tp_cli_channel_interface_sms_connect_to_sms_channel_changed (
830       (TpChannel *) proxy, sms_channel_changed_cb, NULL, NULL,
831       G_OBJECT (proxy), &error);
832   if (error != NULL)
833     {
834       WARNING ("Failed to connect to SMS.SMSChannelChanged: %s",
835           error->message);
836       g_error_free (error);
837     }
838 
839   tp_cli_dbus_properties_call_get (proxy, -1,
840       TP_IFACE_CHANNEL_INTERFACE_SMS, "SMSChannel",
841       get_sms_channel_cb, result, NULL, G_OBJECT (proxy));
842 }
843 
844 enum {
845     FEAT_INCOMING_MESSAGES,
846     FEAT_SMS,
847     FEAT_CHAT_STATES,
848     N_FEAT
849 };
850 
851 static const TpProxyFeature *
tp_text_channel_list_features(TpProxyClass * cls G_GNUC_UNUSED)852 tp_text_channel_list_features (TpProxyClass *cls G_GNUC_UNUSED)
853 {
854   static TpProxyFeature features[N_FEAT + 1] = { { 0 } };
855   static GQuark need_sms[2] = {0, 0};
856   static GQuark depends_chat_state[2] = {0, 0};
857 
858   if (G_LIKELY (features[0].name != 0))
859     return features;
860 
861   features[FEAT_INCOMING_MESSAGES].name =
862     TP_TEXT_CHANNEL_FEATURE_INCOMING_MESSAGES;
863   features[FEAT_INCOMING_MESSAGES].prepare_async =
864     tp_text_channel_prepare_incoming_messages_async;
865 
866   features[FEAT_SMS].name =
867     TP_TEXT_CHANNEL_FEATURE_SMS;
868   features[FEAT_SMS].prepare_async =
869     tp_text_channel_prepare_sms_async;
870   need_sms[0] = TP_IFACE_QUARK_CHANNEL_INTERFACE_SMS;
871   features[FEAT_SMS].interfaces_needed = need_sms;
872 
873   G_GNUC_BEGIN_IGNORE_DEPRECATIONS
874   features[FEAT_CHAT_STATES].name =
875     TP_TEXT_CHANNEL_FEATURE_CHAT_STATES;
876   features[FEAT_CHAT_STATES].prepare_async =
877     tp_text_channel_prepare_chat_states_async;
878   depends_chat_state[0] = TP_CHANNEL_FEATURE_CHAT_STATES;
879   features[FEAT_CHAT_STATES].depends_on = depends_chat_state;
880   G_GNUC_END_IGNORE_DEPRECATIONS
881 
882   /* assert that the terminator at the end is there */
883   g_assert (features[N_FEAT].name == 0);
884 
885   return features;
886 }
887 
888 static void
tp_text_channel_class_init(TpTextChannelClass * klass)889 tp_text_channel_class_init (TpTextChannelClass *klass)
890 {
891   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
892   TpProxyClass *proxy_class = (TpProxyClass *) klass;
893   GParamSpec *param_spec;
894 
895   gobject_class->constructed = tp_text_channel_constructed;
896   gobject_class->get_property = tp_text_channel_get_property;
897   gobject_class->dispose = tp_text_channel_dispose;
898 
899   proxy_class->list_features = tp_text_channel_list_features;
900 
901   /**
902    * TpTextChannel:supported-content-types:
903    *
904    * A #GStrv containing the MIME types supported by this channel, with more
905    * preferred MIME types appearing earlier in the array.
906    *
907    * Since: 0.13.10
908    */
909   param_spec = g_param_spec_boxed ("supported-content-types",
910       "SupportedContentTypes",
911       "The Messages.SupportedContentTypes property of the channel",
912       G_TYPE_STRV,
913       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
914   g_object_class_install_property (gobject_class, PROP_SUPPORTED_CONTENT_TYPES,
915       param_spec);
916 
917   /**
918    * TpTextChannel:message-part-support-flags:
919    *
920    * A #TpMessagePartSupportFlags indicating the level of support for
921    * message parts on this channel.
922    *
923    * Since: 0.13.10
924    */
925   param_spec = g_param_spec_uint ("message-part-support-flags",
926       "MessagePartSupportFlags",
927       "The Messages.MessagePartSupportFlags property of the channel",
928       0, G_MAXUINT32, 0,
929       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
930   g_object_class_install_property (gobject_class,
931       PROP_MESSAGE_PART_SUPPORT_FLAGS, param_spec);
932 
933   /**
934    * TpTextChannel:delivery-reporting-support:
935    *
936    * A #TpDeliveryReportingSupportFlags indicating features supported
937    * by this channel.
938    *
939    * Since: 0.13.10
940    */
941   param_spec = g_param_spec_uint ("delivery-reporting-support",
942       "DeliveryReportingSupport",
943       "The Messages.DeliveryReportingSupport property of the channel",
944       0, G_MAXUINT32, 0,
945       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
946   g_object_class_install_property (gobject_class,
947       PROP_DELIVERY_REPORTING_SUPPORT, param_spec);
948 
949   /**
950    * TpTextChannel:message-types:
951    *
952    * A #GArray containing the #TpChannelTextMessageType which may be sent on
953    * this channel.
954    *
955    * Since: 0.13.16
956    */
957   param_spec = g_param_spec_boxed ("message-types",
958       "MessageTypes",
959       "The Messages.MessageTypes property of the channel",
960       DBUS_TYPE_G_UINT_ARRAY,
961       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
962   g_object_class_install_property (gobject_class,
963       PROP_MESSAGE_TYPES, param_spec);
964 
965  /**
966    * TpTextChannel:is-sms-channel:
967    *
968    * %TRUE if messages sent and received on this channel are transmitted
969    * via SMS.
970    *
971    * This property is not guaranteed to have a meaningful value until
972    * TP_TEXT_CHANNEL_FEATURE_SMS has been prepared.
973    *
974    * Since: 0.15.1
975    */
976   param_spec = g_param_spec_boolean ("is-sms-channel",
977       "is SMS channel",
978       "The SMS.SMSChannel property of the channel",
979       FALSE,
980       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
981   g_object_class_install_property (gobject_class, PROP_IS_SMS_CHANNEL,
982       param_spec);
983 
984  /**
985    * TpTextChannel:sms-flash:
986    *
987    * %TRUE if this channel is exclusively for receiving class 0 SMSes
988    * (and no SMSes can be sent using tp_text_channel_send_message_async()
989    * on this channel). If %FALSE, no incoming class 0 SMSes will appear
990    * on this channel.
991    *
992    * Since: 0.15.1
993    */
994   param_spec = g_param_spec_boolean ("sms-flash",
995       "SMS flash",
996       "The SMS.Flash property of the channel",
997       FALSE,
998       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
999   g_object_class_install_property (gobject_class, PROP_SMS_FLASH, param_spec);
1000 
1001   /**
1002    * TpTextChannel::message-received:
1003    * @self: the #TpTextChannel
1004    * @message: a #TpSignalledMessage
1005    *
1006    * The ::message-received signal is emitted when a new message has been
1007    * received on @self.
1008    *
1009    * The same @message object will be used by the
1010    * #TpTextChannel::pending-message-removed signal once @message has been
1011    * acked so you can simply compare pointers to identify the message.
1012    *
1013    * Note that this signal is only fired once the
1014    * #TP_TEXT_CHANNEL_FEATURE_INCOMING_MESSAGES has been prepared.
1015    *
1016    * It is guaranteed that @message's #TpSignalledMessage:sender has all of the
1017    * features previously passed to
1018    * tp_simple_client_factory_add_contact_features() prepared.
1019    *
1020    * Since: 0.13.10
1021    */
1022   signals[SIG_MESSAGE_RECEIVED] = g_signal_new ("message-received",
1023       G_OBJECT_CLASS_TYPE (klass),
1024       G_SIGNAL_RUN_LAST,
1025       0, NULL, NULL, NULL,
1026       G_TYPE_NONE,
1027       1, TP_TYPE_SIGNALLED_MESSAGE);
1028 
1029   /**
1030    * TpTextChannel::pending-message-removed:
1031    * @self: the #TpTextChannel
1032    * @message: a #TpSignalledMessage
1033    *
1034    * The ::pending-message-removed signal is emitted when @message
1035    * has been acked and so removed from the pending messages list.
1036    *
1037    * Note that this signal is only fired once the
1038    * #TP_TEXT_CHANNEL_FEATURE_INCOMING_MESSAGES has been prepared.
1039    *
1040    * It is guaranteed that @message's #TpSignalledMessage:sender has all of the
1041    * features previously passed to
1042    * tp_simple_client_factory_add_contact_features() prepared.
1043    *
1044    * Since: 0.13.10
1045    */
1046   signals[SIG_PENDING_MESSAGE_REMOVED] = g_signal_new (
1047       "pending-message-removed",
1048       G_OBJECT_CLASS_TYPE (klass),
1049       G_SIGNAL_RUN_LAST,
1050       0, NULL, NULL, NULL,
1051       G_TYPE_NONE,
1052       1, TP_TYPE_SIGNALLED_MESSAGE);
1053 
1054   /**
1055    * TpTextChannel::message-sent:
1056    * @self: the #TpTextChannel
1057    * @message: a #TpSignalledMessage
1058    * @flags: the #TpMessageSendingFlags affecting how the message was sent
1059    * @token: an opaque token used to match any incoming delivery or failure
1060    * reports against this message, or %NULL if the message is not
1061    * readily identifiable.
1062    *
1063    * The ::message-sent signal is emitted when @message
1064    * has been submitted for sending.
1065    *
1066    * It is guaranteed that @message's #TpSignalledMessage:sender has all of the
1067    * features previously passed to
1068    * tp_simple_client_factory_add_contact_features() prepared.
1069    *
1070    * Since: 0.13.10
1071    */
1072   signals[SIG_MESSAGE_SENT] = g_signal_new (
1073       "message-sent",
1074       G_OBJECT_CLASS_TYPE (klass),
1075       G_SIGNAL_RUN_LAST,
1076       0, NULL, NULL, NULL,
1077       G_TYPE_NONE,
1078       3, TP_TYPE_SIGNALLED_MESSAGE, G_TYPE_UINT, G_TYPE_STRING);
1079 
1080   g_type_class_add_private (gobject_class, sizeof (TpTextChannelPrivate));
1081 
1082   /**
1083    * TpTextChannel::contact-chat-state-changed:
1084    * @self: a channel
1085    * @contact: a #TpContact for the local user or another contact
1086    * @state: the new #TpChannelChatState for the contact
1087    *
1088    * Emitted when a contact's chat state changes after tp_proxy_prepare_async()
1089    * has finished preparing features %TP_TEXT_CHANNEL_FEATURE_CHAT_STATES,
1090    * %TP_CHANNEL_FEATURE_GROUP and %TP_CHANNEL_FEATURE_CONTACTS.
1091    *
1092    * Since: 0.19.0
1093    */
1094   signals[SIG_CONTACT_CHAT_STATE_CHANGED] = g_signal_new (
1095       "contact-chat-state-changed",
1096       G_OBJECT_CLASS_TYPE (klass),
1097       G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
1098       0,
1099       NULL, NULL, NULL,
1100       G_TYPE_NONE, 2, TP_TYPE_CONTACT, G_TYPE_UINT);
1101 }
1102 
1103 static void
tp_text_channel_init(TpTextChannel * self)1104 tp_text_channel_init (TpTextChannel *self)
1105 {
1106   self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self), TP_TYPE_TEXT_CHANNEL,
1107       TpTextChannelPrivate);
1108 
1109   self->priv->pending_messages = g_queue_new ();
1110 }
1111 
1112 
1113 /**
1114  * tp_text_channel_new:
1115  * @conn: a #TpConnection; may not be %NULL
1116  * @object_path: the object path of the channel; may not be %NULL
1117  * @immutable_properties: (transfer none) (element-type utf8 GObject.Value):
1118  *  the immutable properties of the channel,
1119  *  as signalled by the NewChannel D-Bus signal or returned by the
1120  *  CreateChannel and EnsureChannel D-Bus methods: a mapping from
1121  *  strings (D-Bus interface name + "." + property name) to #GValue instances
1122  * @error: used to indicate the error if %NULL is returned
1123  *
1124  * Convenient function to create a new #TpTextChannel
1125  *
1126  * Returns: (transfer full): a newly created #TpTextChannel
1127  *
1128  * Since: 0.13.10
1129  * Deprecated: Use tp_simple_client_factory_ensure_channel() instead.
1130  */
1131 TpTextChannel *
tp_text_channel_new(TpConnection * conn,const gchar * object_path,const GHashTable * immutable_properties,GError ** error)1132 tp_text_channel_new (TpConnection *conn,
1133     const gchar *object_path,
1134     const GHashTable *immutable_properties,
1135     GError **error)
1136 {
1137   return _tp_text_channel_new_with_factory (NULL, conn, object_path,
1138       immutable_properties, error);
1139 }
1140 
1141 TpTextChannel *
_tp_text_channel_new_with_factory(TpSimpleClientFactory * factory,TpConnection * conn,const gchar * object_path,const GHashTable * immutable_properties,GError ** error)1142 _tp_text_channel_new_with_factory (
1143     TpSimpleClientFactory *factory,
1144     TpConnection *conn,
1145     const gchar *object_path,
1146     const GHashTable *immutable_properties,
1147     GError **error)
1148 {
1149   TpProxy *conn_proxy = (TpProxy *) conn;
1150 
1151   g_return_val_if_fail (TP_IS_CONNECTION (conn), NULL);
1152   g_return_val_if_fail (object_path != NULL, NULL);
1153   g_return_val_if_fail (immutable_properties != NULL, NULL);
1154 
1155   if (!tp_dbus_check_valid_object_path (object_path, error))
1156     return NULL;
1157 
1158   return g_object_new (TP_TYPE_TEXT_CHANNEL,
1159       "connection", conn,
1160        "dbus-daemon", conn_proxy->dbus_daemon,
1161        "bus-name", conn_proxy->bus_name,
1162        "object-path", object_path,
1163        "handle-type", (guint) TP_UNKNOWN_HANDLE_TYPE,
1164        "channel-properties", immutable_properties,
1165        "factory", factory,
1166        NULL);
1167 }
1168 
1169 /**
1170  * tp_text_channel_get_supported_content_types:
1171  * @self: a #TpTextChannel
1172  *
1173  * Return the #TpTextChannel:supported-content-types property
1174  *
1175  * Returns: (transfer none) :
1176  * the value of #TpTextChannel:supported-content-types
1177  *
1178  * Since: 0.13.10
1179  */
1180 const gchar * const *
tp_text_channel_get_supported_content_types(TpTextChannel * self)1181 tp_text_channel_get_supported_content_types (TpTextChannel *self)
1182 {
1183   g_return_val_if_fail (TP_IS_TEXT_CHANNEL (self), NULL);
1184 
1185   return (const gchar * const *) self->priv->supported_content_types;
1186 }
1187 
1188 /**
1189  * tp_text_channel_get_message_part_support_flags:
1190  * @self: a #TpTextChannel
1191  *
1192  * Return the #TpTextChannel:message-part-support-flags property
1193  *
1194  * Returns: the value of #TpTextChannel:message-part-support-flags
1195  *
1196  * Since: 0.13.10
1197  */
1198 TpMessagePartSupportFlags
tp_text_channel_get_message_part_support_flags(TpTextChannel * self)1199 tp_text_channel_get_message_part_support_flags (
1200     TpTextChannel *self)
1201 {
1202   g_return_val_if_fail (TP_IS_TEXT_CHANNEL (self), 0);
1203 
1204   return self->priv->message_part_support_flags;
1205 }
1206 
1207 /**
1208  * tp_text_channel_get_delivery_reporting_support:
1209  * @self: a #TpTextChannel
1210  *
1211  * Return the #TpTextChannel:delivery-reporting-support property
1212  *
1213  * Returns: the value of #TpTextChannel:delivery-reporting-support property
1214  *
1215  * Since: 0.13.10
1216  */
1217 TpDeliveryReportingSupportFlags
tp_text_channel_get_delivery_reporting_support(TpTextChannel * self)1218 tp_text_channel_get_delivery_reporting_support (
1219     TpTextChannel *self)
1220 {
1221   g_return_val_if_fail (TP_IS_TEXT_CHANNEL (self), 0);
1222 
1223   return self->priv->delivery_reporting_support;
1224 }
1225 
1226 /**
1227  * TP_TEXT_CHANNEL_FEATURE_INCOMING_MESSAGES:
1228  *
1229  * Expands to a call to a function that returns a quark representing the
1230  * incoming messages features of a #TpTextChannel.
1231  *
1232  * When this feature is prepared, tp_text_channel_dup_pending_messages() will
1233  * return a non-empty list if any unacknowledged messages are waiting, and the
1234  * #TpTextChannel::message-received and #TpTextChannel::pending-message-removed
1235  * signals will be emitted.
1236  *
1237  * One can ask for a feature to be prepared using the
1238  * tp_proxy_prepare_async() function, and waiting for it to callback.
1239  *
1240  * Since: 0.13.10
1241  */
1242 GQuark
tp_text_channel_get_feature_quark_incoming_messages(void)1243 tp_text_channel_get_feature_quark_incoming_messages (void)
1244 {
1245   return g_quark_from_static_string (
1246       "tp-text-channel-feature-incoming-messages");
1247 }
1248 
1249 /**
1250  * tp_text_channel_get_pending_messages:
1251  * @self: a #TpTextChannel
1252  *
1253  * Return a newly allocated list of unacknowledged #TpSignalledMessage
1254  * objects.
1255  *
1256  * It is guaranteed that the #TpSignalledMessage:sender of each
1257  * #TpSignalledMessage has all of the features previously passed to
1258  * tp_simple_client_factory_add_contact_features() prepared.
1259  *
1260  * Returns: (transfer container) (element-type TelepathyGLib.SignalledMessage):
1261  * a #GList of borrowed #TpSignalledMessage
1262  *
1263  * Since: 0.13.10
1264  * Deprecated: Since 0.19.9. New code should use
1265  *  tp_text_channel_dup_pending_messages() instead.
1266  */
1267 GList *
tp_text_channel_get_pending_messages(TpTextChannel * self)1268 tp_text_channel_get_pending_messages (TpTextChannel *self)
1269 {
1270   return g_list_copy (g_queue_peek_head_link (self->priv->pending_messages));
1271 }
1272 
1273 /**
1274  * tp_text_channel_dup_pending_messages:
1275  * @self: a #TpTextChannel
1276  *
1277  * Return a newly allocated list of unacknowledged #TpSignalledMessage
1278  * objects.
1279  *
1280  * It is guaranteed that the #TpSignalledMessage:sender of each
1281  * #TpSignalledMessage has all of the features previously passed to
1282  * tp_simple_client_factory_add_contact_features() prepared.
1283  *
1284  * Returns: (transfer full) (element-type TelepathyGLib.SignalledMessage):
1285  * a #GList of reffed #TpSignalledMessage
1286  *
1287  * Since: 0.19.9
1288  */
1289 GList *
tp_text_channel_dup_pending_messages(TpTextChannel * self)1290 tp_text_channel_dup_pending_messages (TpTextChannel *self)
1291 {
1292   return _tp_g_list_copy_deep (
1293       g_queue_peek_head_link (self->priv->pending_messages),
1294       (GCopyFunc) g_object_ref, NULL);
1295 }
1296 
1297 static void
send_message_cb(TpChannel * proxy,const gchar * token,const GError * error,gpointer user_data,GObject * weak_object)1298 send_message_cb (TpChannel *proxy,
1299     const gchar *token,
1300     const GError *error,
1301     gpointer user_data,
1302     GObject *weak_object)
1303 {
1304   GSimpleAsyncResult *result = user_data;
1305 
1306   if (error != NULL)
1307     {
1308       DEBUG ("Failed to send message: %s", error->message);
1309 
1310       g_simple_async_result_set_from_error (result, error);
1311     }
1312 
1313   g_simple_async_result_set_op_res_gpointer (result,
1314       tp_str_empty (token) ? NULL : g_strdup (token), g_free);
1315 
1316   g_simple_async_result_complete_in_idle (result);
1317   g_object_unref (result);
1318 }
1319 
1320 /**
1321  * tp_text_channel_send_message_async:
1322  * @self: a #TpTextChannel
1323  * @message: a #TpClientMessage
1324  * @flags: flags affecting how the message is sent
1325  * @callback: a callback to call when the message has been submitted to the
1326  * server
1327  * @user_data: data to pass to @callback
1328  *
1329  * Submit a message to the server for sending. Once the message has been
1330  * submitted to the sever, @callback will be called. You can then call
1331  * tp_text_channel_send_message_finish() to get the result of the operation.
1332  *
1333  * Since: 0.13.10
1334  */
1335 void
tp_text_channel_send_message_async(TpTextChannel * self,TpMessage * message,TpMessageSendingFlags flags,GAsyncReadyCallback callback,gpointer user_data)1336 tp_text_channel_send_message_async (TpTextChannel *self,
1337     TpMessage *message,
1338     TpMessageSendingFlags flags,
1339     GAsyncReadyCallback callback,
1340     gpointer user_data)
1341 {
1342   GSimpleAsyncResult *result;
1343 
1344   g_return_if_fail (TP_IS_TEXT_CHANNEL (self));
1345   g_return_if_fail (TP_IS_CLIENT_MESSAGE (message));
1346 
1347   result = g_simple_async_result_new (G_OBJECT (self), callback,
1348       user_data, tp_text_channel_send_message_async);
1349 
1350   tp_cli_channel_interface_messages_call_send_message (TP_CHANNEL (self),
1351     -1, message->parts, flags, send_message_cb, result, NULL, NULL);
1352 }
1353 
1354 /**
1355  * tp_text_channel_send_message_finish:
1356  * @self: a #TpTextChannel
1357  * @result: a #GAsyncResult passed to the callback for tp_text_channel_send_message_async()
1358  * @token: (out) (transfer full): if not %NULL, used to return the
1359  * token of the sent message
1360  * @error: a #GError to fill
1361  *
1362  * Completes a call to tp_text_channel_send_message_async().
1363  *
1364  * @token can be used to match any incoming delivery or failure reports
1365  * against the sent message. If this function returns true but the returned
1366  * token is %NULL, the message was sent successfully but the protocol does not
1367  * provide a way to identify it later.
1368  *
1369  * Returns: %TRUE if the message has been submitted to the server, %FALSE
1370  * otherwise.
1371  *
1372  * Since: 0.13.10
1373  */
1374 gboolean
tp_text_channel_send_message_finish(TpTextChannel * self,GAsyncResult * result,gchar ** token,GError ** error)1375 tp_text_channel_send_message_finish (TpTextChannel *self,
1376     GAsyncResult *result,
1377     gchar **token,
1378     GError **error)
1379 {
1380   _tp_implement_finish_copy_pointer (self, tp_text_channel_send_message_async,
1381       g_strdup, token);
1382 }
1383 
1384 static void
acknowledge_pending_messages_ready_cb(GObject * object,GAsyncResult * res,gpointer user_data)1385 acknowledge_pending_messages_ready_cb (GObject *object,
1386     GAsyncResult *res,
1387     gpointer user_data)
1388 {
1389   TpChannel *channel = (TpChannel *) object;
1390   GSimpleAsyncResult *result = user_data;
1391 
1392   _tp_channel_contacts_queue_prepare_finish (channel, res, NULL, NULL);
1393 
1394   g_simple_async_result_complete_in_idle (result);
1395   g_object_unref (result);
1396 }
1397 
1398 static void
acknowledge_pending_messages_cb(TpChannel * channel,const GError * error,gpointer user_data,GObject * weak_object)1399 acknowledge_pending_messages_cb (TpChannel *channel,
1400     const GError *error,
1401     gpointer user_data,
1402     GObject *weak_object)
1403 {
1404   GSimpleAsyncResult *result = user_data;
1405 
1406   if (error != NULL)
1407     {
1408       DEBUG ("Failed to ack messages: %s", error->message);
1409 
1410       g_simple_async_result_set_from_error (result, error);
1411       g_simple_async_result_complete_in_idle (result);
1412       g_object_unref (result);
1413       return;
1414     }
1415 
1416   /* We should have already got MessagesRemoved signal, but it could still be
1417    * in the queue. So we have to hook into that queue before complete to be
1418    * sure messages are removed from pending_messages */
1419   _tp_channel_contacts_queue_prepare_async (channel, NULL,
1420       acknowledge_pending_messages_ready_cb, result);
1421 }
1422 
1423 /**
1424  * tp_text_channel_ack_messages_async:
1425  * @self: a #TpTextChannel
1426  * @messages: (element-type TelepathyGLib.SignalledMessage): a #GList of
1427  * #TpSignalledMessage
1428  * @callback: a callback to call when the message have been acked
1429  * @user_data: data to pass to @callback
1430  *
1431  * Acknowledge all the messages in @messages.
1432  * Once the messages have been acked, @callback will be called.
1433  * You can then call tp_text_channel_ack_messages_finish() to get the
1434  * result of the operation.
1435  *
1436  * You should use the #TpSignalledMessage received from
1437  * tp_text_channel_dup_pending_messages() or the
1438  * #TpTextChannel::message-received signal.
1439  *
1440  * See tp_text_channel_ack_message_async() about acknowledging messages.
1441  *
1442  * Since: 0.13.10
1443  */
1444 void
tp_text_channel_ack_messages_async(TpTextChannel * self,const GList * messages,GAsyncReadyCallback callback,gpointer user_data)1445 tp_text_channel_ack_messages_async (TpTextChannel *self,
1446     const GList *messages,
1447     GAsyncReadyCallback callback,
1448     gpointer user_data)
1449 {
1450   TpChannel *chan = (TpChannel *) self;
1451   GArray *ids;
1452   GList *l;
1453   GSimpleAsyncResult *result;
1454 
1455   g_return_if_fail (TP_IS_TEXT_CHANNEL (self));
1456 
1457   result = g_simple_async_result_new (G_OBJECT (self), callback,
1458       user_data, tp_text_channel_ack_messages_async);
1459 
1460   if (messages == NULL)
1461     {
1462       /* Nothing to ack, succeed immediately */
1463       g_simple_async_result_complete_in_idle (result);
1464 
1465       g_object_unref (result);
1466       return;
1467     }
1468 
1469   ids = g_array_sized_new (FALSE, FALSE, sizeof (guint),
1470       g_list_length ((GList *) messages));
1471 
1472   for (l = (GList *) messages; l != NULL; l = g_list_next (l))
1473     {
1474       TpMessage *msg = l->data;
1475       guint id;
1476       gboolean valid;
1477 
1478       g_return_if_fail (TP_IS_SIGNALLED_MESSAGE (msg));
1479 
1480       id = _tp_signalled_message_get_pending_message_id (msg, &valid);
1481       if (!valid)
1482         {
1483           DEBUG ("Message doesn't have pending-message-id ?!");
1484           continue;
1485         }
1486 
1487       g_array_append_val (ids, id);
1488     }
1489 
1490   tp_cli_channel_type_text_call_acknowledge_pending_messages (chan, -1, ids,
1491       acknowledge_pending_messages_cb, result, NULL, G_OBJECT (self));
1492 
1493   g_array_unref (ids);
1494 }
1495 
1496 /**
1497  * tp_text_channel_ack_messages_finish:
1498  * @self: a #TpTextChannel
1499  * @result: a #GAsyncResult passed to the callback for tp_text_channel_ack_messages_async()
1500  * @error: a #GError to fill
1501  *
1502  * Finishes acknowledging a list of messages.
1503  *
1504  * Returns: %TRUE if the messages have been acked, %FALSE otherwise.
1505  *
1506  * Since: 0.13.10
1507  */
1508 gboolean
tp_text_channel_ack_messages_finish(TpTextChannel * self,GAsyncResult * result,GError ** error)1509 tp_text_channel_ack_messages_finish (TpTextChannel *self,
1510     GAsyncResult *result,
1511     GError **error)
1512 {
1513   _tp_implement_finish_void (self, tp_text_channel_ack_messages_async)
1514 }
1515 
1516 /**
1517  * tp_text_channel_ack_message_async:
1518  * @self: a #TpTextChannel
1519  * @message: a #TpSignalledMessage
1520  * @callback: a callback to call when the message have been acked
1521  * @user_data: data to pass to @callback
1522  *
1523  * Acknowledge @message. Once the message has been acked, @callback will be
1524  * called. You can then call tp_text_channel_ack_message_finish() to get the
1525  * result of the operation.
1526  *
1527  * A message should be acknowledged once it has been shown to the user by the
1528  * Handler of the channel. So Observers and Approvers should NOT acknowledge
1529  * messages themselves.
1530  * Once a message has been acknowledged, it is removed from the
1531  * pending-message queue and so the #TpTextChannel::pending-message-removed
1532  * signal is fired.
1533  *
1534  * You should use the #TpSignalledMessage received from
1535  * tp_text_channel_dup_pending_messages() or the
1536  * #TpTextChannel::message-received signal.
1537  *
1538  * Since: 0.13.10
1539  */
1540 void
tp_text_channel_ack_message_async(TpTextChannel * self,TpMessage * message,GAsyncReadyCallback callback,gpointer user_data)1541 tp_text_channel_ack_message_async (TpTextChannel *self,
1542     TpMessage *message,
1543     GAsyncReadyCallback callback,
1544     gpointer user_data)
1545 {
1546   TpChannel *chan = (TpChannel *) self;
1547   GSimpleAsyncResult *result;
1548   GArray *ids;
1549   guint id;
1550   gboolean valid;
1551 
1552   g_return_if_fail (TP_IS_TEXT_CHANNEL (self));
1553   g_return_if_fail (TP_IS_SIGNALLED_MESSAGE (message));
1554 
1555   id = _tp_signalled_message_get_pending_message_id (message, &valid);
1556   if (!valid)
1557     {
1558       g_simple_async_report_error_in_idle (G_OBJECT (self), callback, user_data,
1559           TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
1560           "Message doesn't have a pending-message-id");
1561 
1562       return;
1563     }
1564 
1565   result = g_simple_async_result_new (G_OBJECT (self), callback,
1566       user_data, tp_text_channel_ack_message_async);
1567 
1568   ids = g_array_sized_new (FALSE, FALSE, sizeof (guint), 1);
1569   g_array_append_val (ids, id);
1570 
1571   tp_cli_channel_type_text_call_acknowledge_pending_messages (chan, -1, ids,
1572       acknowledge_pending_messages_cb, result, NULL, G_OBJECT (self));
1573 
1574   g_array_unref (ids);
1575 }
1576 
1577 /**
1578  * tp_text_channel_ack_message_finish:
1579  * @self: a #TpTextChannel
1580  * @result: a #GAsyncResult passed to the callback for tp_text_channel_ack_message_async()
1581  * @error: a #GError to fill
1582  *
1583  * Finishes acknowledging a message.
1584  *
1585  * Returns: %TRUE if the message has been acked, %FALSE otherwise.
1586  *
1587  * Since: 0.13.10
1588  */
1589 gboolean
tp_text_channel_ack_message_finish(TpTextChannel * self,GAsyncResult * result,GError ** error)1590 tp_text_channel_ack_message_finish (TpTextChannel *self,
1591     GAsyncResult *result,
1592     GError **error)
1593 {
1594   _tp_implement_finish_void (self, tp_text_channel_ack_message_async)
1595 }
1596 
1597 /**
1598  * TP_TEXT_CHANNEL_FEATURE_CHAT_STATES:
1599  *
1600  * Expands to a call to a function that returns a quark representing the
1601  * chat states feature on a #TpTextChannel.
1602  *
1603  * When this feature is prepared, tp_text_channel_get_chat_state() and the
1604  * #TpTextChannel::contact-chat-state-changed signal become useful.
1605  *
1606  * One can ask for a feature to be prepared using the
1607  * tp_proxy_prepare_async() function, and waiting for it to callback.
1608  *
1609  * Since: 0.19.0
1610  */
1611 
1612 GQuark
tp_text_channel_get_feature_quark_chat_states(void)1613 tp_text_channel_get_feature_quark_chat_states (void)
1614 {
1615   return g_quark_from_static_string ("tp-text-channel-feature-chat-states");
1616 }
1617 
1618 /**
1619  * tp_text_channel_get_chat_state:
1620  * @self: a channel
1621  * @contact: a #TpContact
1622  *
1623  * Return the chat state for the given contact. If tp_proxy_is_prepared()
1624  * would return %FALSE for the feature %TP_TEXT_CHANNEL_FEATURE_CHAT_STATES,
1625  * the result will always be %TP_CHANNEL_CHAT_STATE_INACTIVE.
1626  *
1627  * Returns: the chat state for @contact, or %TP_CHANNEL_CHAT_STATE_INACTIVE
1628  *  if their chat state is not known
1629  * Since: 0.19.0
1630  */
1631 TpChannelChatState
tp_text_channel_get_chat_state(TpTextChannel * self,TpContact * contact)1632 tp_text_channel_get_chat_state (TpTextChannel *self,
1633     TpContact *contact)
1634 {
1635   g_return_val_if_fail (TP_IS_TEXT_CHANNEL (self), 0);
1636 
1637   /* Use the deprecated function internally to avoid duplicated introspection */
1638   G_GNUC_BEGIN_IGNORE_DEPRECATIONS
1639   return tp_channel_get_chat_state ((TpChannel *) self,
1640       tp_contact_get_handle (contact));
1641   G_GNUC_END_IGNORE_DEPRECATIONS
1642 }
1643 
1644 static void
set_chat_state_cb(TpChannel * proxy,const GError * error,gpointer user_data,GObject * weak_object)1645 set_chat_state_cb (TpChannel *proxy,
1646       const GError *error,
1647       gpointer user_data,
1648       GObject *weak_object)
1649 {
1650   GSimpleAsyncResult *result = user_data;
1651 
1652   if (error != NULL)
1653     {
1654       DEBUG ("SetChatState failed: %s", error->message);
1655 
1656       g_simple_async_result_set_from_error (result, error);
1657     }
1658 
1659   g_simple_async_result_complete_in_idle (result);
1660   g_object_unref (result);
1661 }
1662 
1663 /**
1664  * tp_text_channel_set_chat_state_async:
1665  * @self: a #TpTextChannel
1666  * @state: a #TpChannelChatState to set
1667  * @callback: a callback to call when the chat state has been set
1668  * @user_data: data to pass to @callback
1669  *
1670  * Set the local state on channel @self to @state.
1671  * Once the state has been set, @callback will be called.
1672  * You can then call tp_text_channel_set_chat_state_finish() to get the
1673  * result of the operation.
1674  *
1675  * Since: 0.13.10
1676  */
1677 void
tp_text_channel_set_chat_state_async(TpTextChannel * self,TpChannelChatState state,GAsyncReadyCallback callback,gpointer user_data)1678 tp_text_channel_set_chat_state_async (TpTextChannel *self,
1679     TpChannelChatState state,
1680     GAsyncReadyCallback callback,
1681     gpointer user_data)
1682 {
1683   GSimpleAsyncResult *result;
1684 
1685   result = g_simple_async_result_new (G_OBJECT (self), callback,
1686       user_data, tp_text_channel_set_chat_state_async);
1687 
1688   tp_cli_channel_interface_chat_state_call_set_chat_state (TP_CHANNEL (self),
1689       -1, state, set_chat_state_cb, result, NULL, G_OBJECT (self));
1690 }
1691 
1692 /**
1693  * tp_text_channel_set_chat_state_finish:
1694  * @self: a #TpTextChannel
1695  * @result: a #GAsyncResult passed to the callback for tp_text_channel_set_chat_state_async()
1696  * @error: a #GError to fill
1697  *
1698  * Completes a call to tp_text_channel_set_chat_state_async().
1699  *
1700  * Returns: %TRUE if the chat state has been changed, %FALSE otherwise.
1701  *
1702  * Since: 0.13.10
1703  */
1704 gboolean
tp_text_channel_set_chat_state_finish(TpTextChannel * self,GAsyncResult * result,GError ** error)1705 tp_text_channel_set_chat_state_finish (TpTextChannel *self,
1706     GAsyncResult *result,
1707     GError **error)
1708 {
1709   _tp_implement_finish_void (self, tp_text_channel_set_chat_state_async)
1710 }
1711 
1712 /**
1713  * tp_text_channel_get_message_types:
1714  * @self: a #TpTextChannel
1715  *
1716  * Return the #TpTextChannel:message-types property
1717  *
1718  * Returns: (transfer none) (element-type TelepathyGLib.ChannelTextMessageType):
1719  * the value of #TpTextChannel:message-types
1720  *
1721  * Since: 0.13.16
1722  */
1723 GArray *
tp_text_channel_get_message_types(TpTextChannel * self)1724 tp_text_channel_get_message_types (TpTextChannel *self)
1725 {
1726   g_return_val_if_fail (TP_IS_TEXT_CHANNEL (self), NULL);
1727 
1728   return self->priv->message_types;
1729 }
1730 
1731 /**
1732  * tp_text_channel_supports_message_type:
1733  * @self: a #TpTextChannel
1734  * @message_type: a #TpChannelTextMessageType
1735  *
1736  * Check if message of type @message_type can be sent on this channel.
1737  *
1738  * Returns: %TRUE if message of type @message_type can be sent on @self, %FALSE
1739  * otherwise
1740  *
1741  * Since: 0.13.16
1742  */
1743 gboolean
tp_text_channel_supports_message_type(TpTextChannel * self,TpChannelTextMessageType message_type)1744 tp_text_channel_supports_message_type (TpTextChannel *self,
1745     TpChannelTextMessageType message_type)
1746 {
1747   guint i;
1748 
1749   for (i = 0; i < self->priv->message_types->len; i++)
1750     {
1751       TpChannelTextMessageType tmp = g_array_index (self->priv->message_types,
1752           TpChannelTextMessageType, i);
1753 
1754       if (tmp == message_type)
1755         return TRUE;
1756     }
1757 
1758   return FALSE;
1759 }
1760 
1761 /**
1762  * TP_TEXT_CHANNEL_FEATURE_SMS:
1763  *
1764  * Expands to a call to a function that returns a quark representing the
1765  * SMS feature of a #TpTextChannel.
1766  *
1767  * When this feature is prepared, the TpTextChannel:is-sms-channel property
1768  * will have a meaningful value and will be updated when needed.
1769  *
1770  * One can ask for a feature to be prepared using the
1771  * tp_proxy_prepare_async() function, and waiting for it to callback.
1772  *
1773  * Since: 0.15.1
1774  */
1775 GQuark
tp_text_channel_get_feature_quark_sms(void)1776 tp_text_channel_get_feature_quark_sms (void)
1777 {
1778   return g_quark_from_static_string ("tp-text-channel-feature-sms");
1779 }
1780 
1781 /**
1782  * tp_text_channel_is_sms_channel:
1783  * @self: a #TpTextChannel
1784  *
1785  * Return the #TpTextChannel:is-sms-channel property
1786  *
1787  * Returns: the value of #TpTextChannel:is-sms-channel property
1788  *
1789  * Since: 0.15.1
1790  */
1791 gboolean
tp_text_channel_is_sms_channel(TpTextChannel * self)1792 tp_text_channel_is_sms_channel (TpTextChannel *self)
1793 {
1794   g_return_val_if_fail (TP_IS_TEXT_CHANNEL (self), FALSE);
1795 
1796   return self->priv->is_sms_channel;
1797 }
1798 
1799 /**
1800  * tp_text_channel_get_sms_flash:
1801  * @self: a #TpTextChannel
1802  *
1803  * Return the #TpTextChannel:sms-flash property
1804  *
1805  * Returns: the value of #TpTextChannel:sms-flash property
1806  *
1807  * Since: 0.15.1
1808  */
1809 gboolean
tp_text_channel_get_sms_flash(TpTextChannel * self)1810 tp_text_channel_get_sms_flash (TpTextChannel *self)
1811 {
1812   g_return_val_if_fail (TP_IS_TEXT_CHANNEL (self), FALSE);
1813 
1814   return self->priv->sms_flash;
1815 }
1816 
1817 typedef struct
1818 {
1819   guint chunks_required;
1820   gint remaining_characters;
1821   gint estimated_cost;
1822 } GetSmsLengthReturn;
1823 
1824 static GetSmsLengthReturn *
get_sms_length_return_new(guint chunks_required,gint remaining_characters,gint estimated_cost)1825 get_sms_length_return_new (guint chunks_required,
1826     gint remaining_characters,
1827     gint estimated_cost)
1828 {
1829   GetSmsLengthReturn *result = g_slice_new (GetSmsLengthReturn);
1830 
1831   result->chunks_required = chunks_required;
1832   result->remaining_characters = remaining_characters;
1833   result->estimated_cost = estimated_cost;
1834 
1835   return result;
1836 }
1837 
1838 static void
get_sms_length_return_free(GetSmsLengthReturn * r)1839 get_sms_length_return_free (GetSmsLengthReturn *r)
1840 {
1841   g_slice_free (GetSmsLengthReturn, r);
1842 }
1843 
1844 static void
get_sms_length_cb(TpChannel * proxy,guint chunks_required,gint remaining_characters,gint estimated_cost,const GError * error,gpointer user_data,GObject * weak_object)1845 get_sms_length_cb (TpChannel *proxy,
1846     guint chunks_required,
1847     gint remaining_characters,
1848     gint estimated_cost,
1849     const GError *error,
1850     gpointer user_data,
1851     GObject *weak_object)
1852 {
1853   GSimpleAsyncResult *result = user_data;
1854   GetSmsLengthReturn *r;
1855 
1856   if (error != NULL)
1857     {
1858       DEBUG ("Failed to get SMS length: %s", error->message);
1859 
1860       g_simple_async_result_set_from_error (result, error);
1861       goto out;
1862     }
1863 
1864   r = get_sms_length_return_new (chunks_required, remaining_characters,
1865       estimated_cost);
1866 
1867   g_simple_async_result_set_op_res_gpointer (result, r,
1868       (GDestroyNotify) get_sms_length_return_free);
1869 
1870 out:
1871   g_simple_async_result_complete_in_idle (result);
1872 }
1873 
1874 /**
1875  * tp_text_channel_get_sms_length_async:
1876  * @self: a #TpTextChannel
1877  * @message: a #TpClientMessage
1878  * @callback: a callback to call when the request has been satisfied
1879  * @user_data: data to pass to @callback
1880  *
1881  * Starts an async call to get the number of 140 octet chunks required to
1882  * send a #message via SMS on #self, as well as the number of remaining
1883  * characters available in the final chunk and, if possible,
1884  * an estimate of the cost.
1885  *
1886  * Once the request has been satisfied, @callback will be called.
1887  * You can then call tp_text_channel_get_sms_length_finish() to get the
1888  * result of the operation.
1889  *
1890  * Since: 0.15.1
1891  */
1892 void
tp_text_channel_get_sms_length_async(TpTextChannel * self,TpMessage * message,GAsyncReadyCallback callback,gpointer user_data)1893 tp_text_channel_get_sms_length_async (TpTextChannel *self,
1894     TpMessage *message,
1895     GAsyncReadyCallback callback,
1896     gpointer user_data)
1897 {
1898   GSimpleAsyncResult *result;
1899 
1900   result = g_simple_async_result_new ((GObject *) self, callback, user_data,
1901       tp_text_channel_get_sms_length_async);
1902 
1903   tp_cli_channel_interface_sms_call_get_sms_length ((TpChannel *) self, -1,
1904       message->parts, get_sms_length_cb, result, g_object_unref,
1905       G_OBJECT (self));
1906 }
1907 
1908 
1909 /**
1910  * tp_text_channel_get_sms_length_finish:
1911  * @self: a #TpTextChannel
1912  * @result: a #GAsyncResult
1913  * @chunks_required: (out): if not %NULL used to return
1914  * the number of 140 octet chunks required to send the message.
1915  * @remaining_characters: (out): if not %NULL used to return
1916  * the number of further characters that can be fit in the final chunk.
1917  * A negative value indicates that the message will be truncated by
1918  * abs(@remaining_characters).
1919  * The value #G_MININT32 indicates the message will be truncated by
1920  * an unknown amount.
1921  * @estimated_cost: (out): if not %NULL used to return
1922  * the estimated cost of sending this message.
1923  * The currency and scale of this value are the same as the
1924  * values of the #TpConnection:balance-scale and
1925  * #TpConnection:balance-currency properties.
1926  * A value of -1 indicates the cost could not be estimated.
1927  * @error: a #GError to fill
1928  *
1929  * Finishes an async SMS length request.
1930  *
1931  * Returns: %TRUE if the number of 140 octet chunks required to send
1932  * the message has been retrieved, %FALSE otherwise.
1933  *
1934  * Since: 0.15.1
1935  */
1936 gboolean
tp_text_channel_get_sms_length_finish(TpTextChannel * self,GAsyncResult * result,guint * chunks_required,gint * remaining_characters,gint * estimated_cost,GError ** error)1937 tp_text_channel_get_sms_length_finish (TpTextChannel *self,
1938     GAsyncResult *result,
1939     guint *chunks_required,
1940     gint *remaining_characters,
1941     gint *estimated_cost,
1942     GError **error)
1943 {
1944   GSimpleAsyncResult *simple = (GSimpleAsyncResult *) result;
1945   GetSmsLengthReturn *r;
1946 
1947   if (g_simple_async_result_propagate_error (simple, error))
1948     return FALSE;
1949 
1950   g_return_val_if_fail (g_simple_async_result_is_valid (result,
1951         G_OBJECT (self), tp_text_channel_get_sms_length_async), FALSE);
1952 
1953   r = g_simple_async_result_get_op_res_gpointer (simple);
1954 
1955   if (chunks_required != NULL)
1956     *chunks_required = r->chunks_required;
1957 
1958   if (remaining_characters != NULL)
1959     *remaining_characters = r->remaining_characters;
1960 
1961   if (estimated_cost != NULL)
1962     *estimated_cost = r->estimated_cost;
1963 
1964   return TRUE;
1965 }
1966 
1967 /**
1968  * tp_text_channel_ack_all_pending_messages_async:
1969  * @self: a #TpTextChannel
1970  * @callback: a callback to call when the messages have been acked
1971  * @user_data: data to pass to @callback
1972  *
1973  * Acknowledge all the pending messages. This is equivalent of calling
1974  * tp_text_channel_ack_messages_async() with the list of #TpSignalledMessage
1975  * returned by tp_text_channel_dup_pending_messages().
1976  *
1977  * Once the messages have been acked, @callback will be called.
1978  * You can then call tp_text_channel_ack_all_pending_messages_finish() to get
1979  * the result of the operation.
1980  *
1981  * See tp_text_channel_ack_message_async() about acknowledging messages.
1982  *
1983  * Since: 0.15.3
1984  */
1985 void
tp_text_channel_ack_all_pending_messages_async(TpTextChannel * self,GAsyncReadyCallback callback,gpointer user_data)1986 tp_text_channel_ack_all_pending_messages_async (TpTextChannel *self,
1987     GAsyncReadyCallback callback,
1988     gpointer user_data)
1989 {
1990   GList *messages;
1991 
1992   messages = g_queue_peek_head_link (self->priv->pending_messages);
1993 
1994   tp_text_channel_ack_messages_async (self, messages,
1995       callback, user_data);
1996 }
1997 
1998 /**
1999  * tp_text_channel_ack_all_pending_messages_finish:
2000  * @self: a #TpTextChannel
2001  * @result: a #GAsyncResult
2002  * @error: a #GError to fill
2003  *
2004  * Finish an asynchronous acknowledgement operation of all messages.
2005  *
2006  * Returns: %TRUE if the messages have been acked, %FALSE otherwise.
2007  *
2008  * Since: 0.15.3
2009  */
2010 gboolean
tp_text_channel_ack_all_pending_messages_finish(TpTextChannel * self,GAsyncResult * result,GError ** error)2011 tp_text_channel_ack_all_pending_messages_finish (TpTextChannel *self,
2012     GAsyncResult *result,
2013     GError **error)
2014 {
2015   return tp_text_channel_ack_messages_finish (self, result, error);
2016 }
2017