1 /*
2  * gabble-im-channel.c - Source for GabbleIMChannel
3  * Copyright (C) 2005 Collabora Ltd.
4  * Copyright (C) 2005 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 #include "config.h"
22 #include "im-channel.h"
23 
24 #include <string.h>
25 
26 #include <dbus/dbus-glib.h>
27 
28 #include <telepathy-glib/telepathy-glib.h>
29 #include <telepathy-glib/telepathy-glib-dbus.h>
30 
31 #define DEBUG_FLAG GABBLE_DEBUG_IM
32 #include "connection.h"
33 #include "debug.h"
34 #include "disco.h"
35 #include "message-util.h"
36 #include "namespaces.h"
37 #include "presence.h"
38 #include "presence-cache.h"
39 #include "roster.h"
40 #include "util.h"
41 
42 static void destroyable_iface_init (gpointer, gpointer);
43 
44 G_DEFINE_TYPE_WITH_CODE (GabbleIMChannel, gabble_im_channel,
45     TP_TYPE_BASE_CHANNEL,
46     G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_TYPE_TEXT,
47       tp_message_mixin_text_iface_init);
48     G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_MESSAGES,
49       tp_message_mixin_messages_iface_init);
50     G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_CHAT_STATE,
51       tp_message_mixin_chat_state_iface_init)
52     G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_DESTROYABLE,
53       destroyable_iface_init));
54 
55 static void _gabble_im_channel_send_message (GObject *object,
56     TpMessage *message, TpMessageSendingFlags flags);
57 static void gabble_im_channel_close (TpBaseChannel *base_chan);
58 static gboolean _gabble_im_channel_send_chat_state (GObject *object,
59     TpChannelChatState state,
60     GError **error);
61 
62 
63 /* private structure */
64 
65 typedef enum {
66   CHAT_STATES_UNKNOWN,
67   CHAT_STATES_SUPPORTED,
68   CHAT_STATES_NOT_SUPPORTED
69 } ChatStateSupport;
70 
71 struct _GabbleIMChannelPrivate
72 {
73   gchar *peer_jid;
74   gboolean send_nick;
75   ChatStateSupport chat_states_supported;
76 
77   gboolean dispose_has_run;
78 };
79 
80 typedef struct {
81   GabbleIMChannel *channel;
82   TpMessage *message;
83   gchar *token;
84   TpMessageSendingFlags flags;
85 } _GabbleIMSendMessageCtx;
86 
87 static GPtrArray *
gabble_im_channel_get_interfaces(TpBaseChannel * base)88 gabble_im_channel_get_interfaces (TpBaseChannel *base)
89 {
90   GPtrArray *interfaces;
91 
92   interfaces = TP_BASE_CHANNEL_CLASS (
93       gabble_im_channel_parent_class)->get_interfaces (base);
94 
95   g_ptr_array_add (interfaces, TP_IFACE_CHANNEL_INTERFACE_CHAT_STATE);
96   g_ptr_array_add (interfaces, TP_IFACE_CHANNEL_INTERFACE_MESSAGES);
97   g_ptr_array_add (interfaces, TP_IFACE_CHANNEL_INTERFACE_DESTROYABLE);
98 
99   return interfaces;
100 }
101 
102 static void
gabble_im_channel_init(GabbleIMChannel * self)103 gabble_im_channel_init (GabbleIMChannel *self)
104 {
105   GabbleIMChannelPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
106       GABBLE_TYPE_IM_CHANNEL, GabbleIMChannelPrivate);
107 
108   self->priv = priv;
109 }
110 
111 static void
gabble_im_channel_constructed(GObject * obj)112 gabble_im_channel_constructed (GObject *obj)
113 {
114   GabbleIMChannel *self = GABBLE_IM_CHANNEL (obj);
115   TpBaseChannel *base = TP_BASE_CHANNEL (self);
116   GabbleIMChannelPrivate *priv = self->priv;
117   TpBaseConnection *base_conn = tp_base_channel_get_connection (base);
118   GabbleConnection *conn = GABBLE_CONNECTION (base_conn);
119   TpHandleRepoIface *contact_handles =
120       tp_base_connection_get_handles (base_conn, TP_HANDLE_TYPE_CONTACT);
121   TpHandle target = tp_base_channel_get_target_handle (base);
122 
123   TpChannelTextMessageType types[] = {
124       TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL,
125       TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION,
126       TP_CHANNEL_TEXT_MESSAGE_TYPE_NOTICE,
127   };
128   const gchar * supported_content_types[] = {
129       "text/plain",
130       NULL
131   };
132   void (*chain_up) (GObject *) =
133     ((GObjectClass *) gabble_im_channel_parent_class)->constructed;
134 
135   if (chain_up != NULL)
136     chain_up (obj);
137 
138   priv->peer_jid = g_strdup (tp_handle_inspect (contact_handles, target));
139 
140   if (gabble_roster_handle_gets_presence_from_us (conn->roster, target))
141     priv->send_nick = FALSE;
142   else
143     priv->send_nick = TRUE;
144 
145   tp_message_mixin_init (obj, G_STRUCT_OFFSET (GabbleIMChannel, message_mixin),
146       base_conn);
147 
148   /* We deliberately do not include
149    * TP_DELIVERY_REPORTING_SUPPORT_FLAG_RECEIVE_SUCCESSES here, even though we
150    * support requesting receipts, because XEP-0184 provides no guarantees.
151    */
152   tp_message_mixin_implement_sending (obj, _gabble_im_channel_send_message,
153       G_N_ELEMENTS (types), types, 0,
154       TP_DELIVERY_REPORTING_SUPPORT_FLAG_RECEIVE_FAILURES,
155       supported_content_types);
156 
157   priv->chat_states_supported = CHAT_STATES_UNKNOWN;
158   tp_message_mixin_implement_send_chat_state (obj,
159       _gabble_im_channel_send_chat_state);
160 }
161 
162 static void gabble_im_channel_dispose (GObject *object);
163 static void gabble_im_channel_finalize (GObject *object);
164 
165 static void
gabble_im_channel_fill_immutable_properties(TpBaseChannel * chan,GHashTable * properties)166 gabble_im_channel_fill_immutable_properties (TpBaseChannel *chan,
167     GHashTable *properties)
168 {
169   TpBaseChannelClass *cls = TP_BASE_CHANNEL_CLASS (
170       gabble_im_channel_parent_class);
171 
172   cls->fill_immutable_properties (chan, properties);
173 
174   tp_dbus_properties_mixin_fill_properties_hash (
175       G_OBJECT (chan), properties,
176       TP_IFACE_CHANNEL_INTERFACE_MESSAGES, "MessagePartSupportFlags",
177       TP_IFACE_CHANNEL_INTERFACE_MESSAGES, "DeliveryReportingSupport",
178       TP_IFACE_CHANNEL_INTERFACE_MESSAGES, "SupportedContentTypes",
179       TP_IFACE_CHANNEL_INTERFACE_MESSAGES, "MessageTypes",
180       NULL);
181 }
182 
183 static gchar *
gabble_im_channel_get_object_path_suffix(TpBaseChannel * chan)184 gabble_im_channel_get_object_path_suffix (TpBaseChannel *chan)
185 {
186   return g_strdup_printf ("ImChannel%u",
187       tp_base_channel_get_target_handle (chan));
188 }
189 
190 static void
gabble_im_channel_class_init(GabbleIMChannelClass * gabble_im_channel_class)191 gabble_im_channel_class_init (GabbleIMChannelClass *gabble_im_channel_class)
192 {
193   GObjectClass *object_class = G_OBJECT_CLASS (gabble_im_channel_class);
194   TpBaseChannelClass *base_class =
195       TP_BASE_CHANNEL_CLASS (gabble_im_channel_class);
196 
197   g_type_class_add_private (gabble_im_channel_class,
198       sizeof (GabbleIMChannelPrivate));
199 
200   object_class->constructed = gabble_im_channel_constructed;
201   object_class->dispose = gabble_im_channel_dispose;
202   object_class->finalize = gabble_im_channel_finalize;
203 
204   base_class->channel_type = TP_IFACE_CHANNEL_TYPE_TEXT;
205   base_class->get_interfaces = gabble_im_channel_get_interfaces;
206   base_class->target_handle_type = TP_HANDLE_TYPE_CONTACT;
207   base_class->close = gabble_im_channel_close;
208   base_class->fill_immutable_properties =
209     gabble_im_channel_fill_immutable_properties;
210   base_class->get_object_path_suffix = gabble_im_channel_get_object_path_suffix;
211 
212   tp_message_mixin_init_dbus_properties (object_class);
213 }
214 
215 static gboolean
chat_states_supported(GabbleIMChannel * self,gboolean include_unknown)216 chat_states_supported (GabbleIMChannel *self,
217                        gboolean include_unknown)
218 {
219   GabbleIMChannelPrivate *priv = self->priv;
220   TpBaseChannel *base = (TpBaseChannel *) self;
221   GabbleConnection *conn =
222       GABBLE_CONNECTION (tp_base_channel_get_connection (base));
223   GabblePresence *presence;
224 
225   presence = gabble_presence_cache_get (conn->presence_cache,
226       tp_base_channel_get_target_handle (base));
227 
228   if (presence != NULL && gabble_presence_has_cap (presence, NS_CHAT_STATES))
229     return TRUE;
230 
231   switch (priv->chat_states_supported)
232     {
233       case CHAT_STATES_UNKNOWN:
234         return include_unknown;
235       case CHAT_STATES_SUPPORTED:
236         return TRUE;
237       case CHAT_STATES_NOT_SUPPORTED:
238         return FALSE;
239       default:
240         g_assert_not_reached ();
241         return FALSE;
242     }
243 }
244 
245 static gboolean
receipts_conceivably_supported(GabbleIMChannel * self)246 receipts_conceivably_supported (
247     GabbleIMChannel *self)
248 {
249   TpBaseChannel *base = (TpBaseChannel *) self;
250   GabbleConnection *conn =
251       GABBLE_CONNECTION (tp_base_channel_get_connection (base));
252   GabblePresence *presence;
253 
254   presence = gabble_presence_cache_get (conn->presence_cache,
255       tp_base_channel_get_target_handle (base));
256 
257   /* ...except it's never null because _parse_message_message() in
258    * presence-cache.c. I hate this exactly as much as I did when I wrote the
259    * FIXME on that function. */
260   if (presence != NULL)
261     return gabble_presence_has_cap (presence, NS_RECEIPTS);
262 
263   /* Otherwise ... who knows. Why not ask for one? */
264   return TRUE;
265 }
266 
267 static void
gabble_im_channel_dispose(GObject * object)268 gabble_im_channel_dispose (GObject *object)
269 {
270   GabbleIMChannel *self = GABBLE_IM_CHANNEL (object);
271   TpBaseChannel *base = (TpBaseChannel *) self;
272   GabbleIMChannelPrivate *priv = self->priv;
273   GabbleConnection *conn =
274       GABBLE_CONNECTION (tp_base_channel_get_connection (base));
275   TpHandle target = tp_base_channel_get_target_handle (base);
276 
277   if (priv->dispose_has_run)
278     return;
279 
280   priv->dispose_has_run = TRUE;
281 
282   if (!gabble_roster_handle_sends_presence_to_us (conn->roster, target))
283     {
284       GabblePresence *presence = gabble_presence_cache_get (
285           conn->presence_cache, target);
286 
287       if (NULL != presence)
288         {
289           presence->keep_unavailable = FALSE;
290           gabble_presence_cache_maybe_remove (conn->presence_cache, target);
291         }
292     }
293 
294   tp_message_mixin_maybe_send_gone (object);
295 
296   if (G_OBJECT_CLASS (gabble_im_channel_parent_class)->dispose)
297     G_OBJECT_CLASS (gabble_im_channel_parent_class)->dispose (object);
298 }
299 
300 static void
gabble_im_channel_finalize(GObject * object)301 gabble_im_channel_finalize (GObject *object)
302 {
303   GabbleIMChannel *self = GABBLE_IM_CHANNEL (object);
304   GabbleIMChannelPrivate *priv = self->priv;
305 
306   /* free any data held directly by the object here */
307 
308   DEBUG ("%p", object);
309 
310   g_free (priv->peer_jid);
311 
312   tp_message_mixin_finalize (object);
313 
314   G_OBJECT_CLASS (gabble_im_channel_parent_class)->finalize (object);
315 }
316 
317 static void
_gabble_im_channel_message_sent_cb(GObject * source,GAsyncResult * res,gpointer user_data)318 _gabble_im_channel_message_sent_cb (GObject *source,
319                                     GAsyncResult *res,
320                                     gpointer user_data)
321 {
322     WockyPorter *porter = WOCKY_PORTER (source);
323     GError *error = NULL;
324     _GabbleIMSendMessageCtx *context = user_data;
325     GabbleIMChannel *chan = context->channel;
326     TpMessage *message = context->message;
327 
328     if (wocky_porter_send_finish (porter, res, &error))
329       {
330         tp_message_mixin_sent ((GObject *) chan, message, context->flags,
331             context->token, NULL);
332       }
333     else
334       {
335         tp_message_mixin_sent ((GObject *) chan, context->message,
336             0, NULL, error);
337       }
338 
339     g_object_unref (context->channel);
340     g_object_unref (context->message);
341     g_free (context->token);
342     g_slice_free (_GabbleIMSendMessageCtx, context);
343 }
344 
345 static void
_gabble_im_channel_send_message(GObject * object,TpMessage * message,TpMessageSendingFlags flags)346 _gabble_im_channel_send_message (GObject *object,
347                                  TpMessage *message,
348                                  TpMessageSendingFlags flags)
349 {
350   GabbleIMChannel *self = GABBLE_IM_CHANNEL (object);
351   TpBaseChannel *base = (TpBaseChannel *) self;
352   TpBaseConnection *base_conn;
353   GabbleConnection *gabble_conn;
354   GabbleIMChannelPrivate *priv;
355   TpChannelChatState state = -1;
356   WockyStanza *stanza = NULL;
357   gchar *id = NULL;
358   GError *error = NULL;
359   WockyPorter *porter;
360   _GabbleIMSendMessageCtx *context;
361 
362   g_assert (GABBLE_IS_IM_CHANNEL (self));
363   priv = self->priv;
364 
365   base_conn = tp_base_channel_get_connection (base);
366   gabble_conn = GABBLE_CONNECTION (base_conn);
367 
368   if (chat_states_supported (self, TRUE))
369     {
370       state = TP_CHANNEL_CHAT_STATE_ACTIVE;
371       tp_message_mixin_change_chat_state (object,
372           tp_base_connection_get_self_handle (base_conn), state);
373     }
374 
375   stanza = gabble_message_util_build_stanza (message,
376       gabble_conn, 0, state, priv->peer_jid,
377       priv->send_nick, &id, &error);
378 
379   if (stanza != NULL)
380     {
381       if ((flags & TP_MESSAGE_SENDING_FLAG_REPORT_DELIVERY) &&
382           receipts_conceivably_supported (self))
383         {
384           wocky_node_add_child_ns (wocky_stanza_get_top_node (stanza),
385               "request", NS_RECEIPTS);
386           flags = TP_MESSAGE_SENDING_FLAG_REPORT_DELIVERY;
387         }
388       else
389         {
390           flags = 0;
391         }
392 
393       porter = gabble_connection_dup_porter (gabble_conn);
394       context = g_slice_new0 (_GabbleIMSendMessageCtx);
395       context->channel = g_object_ref (base);
396       context->message = g_object_ref (message);
397       context->token = id;
398       context->flags = flags;
399       wocky_porter_send_async (porter, stanza, NULL,
400           _gabble_im_channel_message_sent_cb, context);
401       g_object_unref (porter);
402       g_object_unref (stanza);
403     }
404   else
405     {
406       tp_message_mixin_sent (object, message, 0, NULL, error);
407       g_error_free (error);
408     }
409 
410 
411   if (priv->send_nick)
412     priv->send_nick = FALSE;
413 }
414 
415 static TpMessage *
build_message(GabbleIMChannel * self,TpChannelTextMessageType type,time_t timestamp,const char * text)416 build_message (
417     GabbleIMChannel *self,
418     TpChannelTextMessageType type,
419     time_t timestamp,
420     const char *text)
421 {
422   TpBaseChannel *base_chan = (TpBaseChannel *) self;
423   TpBaseConnection *base_conn = tp_base_channel_get_connection (base_chan);
424   TpMessage *msg = tp_cm_message_new (base_conn, 2);
425 
426   if (type != TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL)
427     tp_message_set_uint32 (msg, 0, "message-type", type);
428 
429   if (timestamp != 0)
430     tp_message_set_int64 (msg, 0, "message-sent", timestamp);
431 
432   /* Body */
433   tp_message_set_string (msg, 1, "content-type", "text/plain");
434   tp_message_set_string (msg, 1, "content", text);
435 
436   return msg;
437 }
438 
439 static void
maybe_send_delivery_report(GabbleIMChannel * self,WockyStanza * message,const gchar * jid,const gchar * id)440 maybe_send_delivery_report (
441     GabbleIMChannel *self,
442     WockyStanza *message,
443     const gchar *jid,
444     const gchar *id)
445 {
446   TpBaseChannel *base = TP_BASE_CHANNEL (self);
447   TpHandle target = tp_base_channel_get_target_handle (base);
448   TpBaseConnection *base_conn = tp_base_channel_get_connection (base);
449   GabbleConnection *conn = GABBLE_CONNECTION (base_conn);
450   WockyStanza *report;
451 
452   if (id == NULL)
453     return;
454 
455   if (wocky_node_get_child_ns (wocky_stanza_get_top_node (message),
456           "request", NS_RECEIPTS) == NULL)
457     return;
458 
459   if (conn->self_presence->status == GABBLE_PRESENCE_HIDDEN ||
460       !gabble_roster_handle_gets_presence_from_us (conn->roster, target))
461     return;
462 
463   report = wocky_stanza_build (
464       WOCKY_STANZA_TYPE_MESSAGE, WOCKY_STANZA_SUB_TYPE_NONE,
465       NULL, jid,
466       '(', "received", ':', NS_RECEIPTS,
467         '@', "id", id,
468       ')', NULL);
469 
470   _gabble_connection_send (conn, report, NULL);
471   g_object_unref (report);
472 }
473 
474 /*
475  * _gabble_im_channel_receive:
476  * @chan: a channel
477  * @message: the <message> stanza, from which all the following arguments were
478  *           extracted.
479  * @type: the message type
480  * @from: the full JID we received the message from
481  * @timestamp: the time at which the message was sent (not the time it was
482  *             received)
483  * @id: the id='' attribute from the <message/> stanza, if any
484  * @text: the plaintext body of the message
485  * @state: a #TpChannelChatState, or -1 if there was no chat state in the
486  *         message.
487  *
488  * Shoves an incoming message into @chan, possibly updating the chat state at
489  * the same time.
490  */
491 void
_gabble_im_channel_receive(GabbleIMChannel * chan,WockyStanza * message,TpChannelTextMessageType type,const char * from,time_t timestamp,const gchar * id,const char * text,gint state)492 _gabble_im_channel_receive (GabbleIMChannel *chan,
493                             WockyStanza *message,
494                             TpChannelTextMessageType type,
495                             const char *from,
496                             time_t timestamp,
497                             const gchar *id,
498                             const char *text,
499                             gint state)
500 {
501   GabbleIMChannelPrivate *priv;
502   TpBaseChannel *base_chan;
503   TpHandle peer;
504   TpMessage *msg;
505 
506   g_assert (GABBLE_IS_IM_CHANNEL (chan));
507   priv = chan->priv;
508   base_chan = (TpBaseChannel *) chan;
509   peer = tp_base_channel_get_target_handle (base_chan);
510 
511   /* update peer's full JID if it's changed */
512   if (tp_strdiff (from, priv->peer_jid))
513     {
514       g_free (priv->peer_jid);
515       priv->peer_jid = g_strdup (from);
516     }
517 
518   if (state == -1)
519     priv->chat_states_supported = CHAT_STATES_NOT_SUPPORTED;
520   else
521     _gabble_im_channel_state_receive (chan, state);
522 
523   msg = build_message (chan, type, timestamp, text);
524   tp_cm_message_set_sender (msg, peer);
525   tp_message_set_int64 (msg, 0, "message-received", time (NULL));
526 
527   if (id != NULL)
528     tp_message_set_string (msg, 0, "message-token", id);
529 
530   tp_message_mixin_take_received (G_OBJECT (chan), msg);
531   maybe_send_delivery_report (chan, message, from, id);
532 }
533 
534 void
_gabble_im_channel_report_delivery(GabbleIMChannel * self,TpChannelTextMessageType type,time_t timestamp,const gchar * id,const char * text,TpChannelTextSendError send_error,TpDeliveryStatus delivery_status)535 _gabble_im_channel_report_delivery (
536     GabbleIMChannel *self,
537     TpChannelTextMessageType type,
538     time_t timestamp,
539     const gchar *id,
540     const char *text,
541     TpChannelTextSendError send_error,
542     TpDeliveryStatus delivery_status)
543 {
544   GabbleIMChannelPrivate *priv;
545   TpBaseChannel *base_chan = (TpBaseChannel *) self;
546   TpBaseConnection *base_conn;
547   TpHandle peer;
548   TpMessage *delivery_report;
549   gchar *tmp;
550 
551   g_return_if_fail (GABBLE_IS_IM_CHANNEL (self));
552   priv = self->priv;
553   peer = tp_base_channel_get_target_handle (base_chan);
554   base_conn = tp_base_channel_get_connection (base_chan);
555 
556   if (send_error != GABBLE_TEXT_CHANNEL_SEND_NO_ERROR)
557     {
558       /* strip off the resource (if any), since we just failed to send to it */
559       char *slash = strchr (priv->peer_jid, '/');
560 
561       if (slash != NULL)
562         *slash = '\0';
563 
564       priv->chat_states_supported = CHAT_STATES_UNKNOWN;
565     }
566 
567   delivery_report = tp_cm_message_new (base_conn, 1);
568   tp_message_set_uint32 (delivery_report, 0, "message-type",
569       TP_CHANNEL_TEXT_MESSAGE_TYPE_DELIVERY_REPORT);
570   tp_cm_message_set_sender (delivery_report, peer);
571   tp_message_set_int64 (delivery_report, 0, "message-received",
572       time (NULL));
573 
574   tmp = gabble_generate_id ();
575   tp_message_set_string (delivery_report, 0, "message-token", tmp);
576   g_free (tmp);
577 
578   tp_message_set_uint32 (delivery_report, 0, "delivery-status",
579       delivery_status);
580   tp_message_set_uint32 (delivery_report, 0, "delivery-error", send_error);
581 
582   if (id != NULL)
583     tp_message_set_string (delivery_report, 0, "delivery-token", id);
584 
585   if (text != NULL)
586     {
587       TpMessage *msg = build_message (self, type, timestamp, text);
588       /* This is a delivery report, so the original sender of the echoed message
589        * must be us! */
590       tp_cm_message_set_sender (msg, tp_base_connection_get_self_handle (base_conn));
591 
592       /* Since this is a delivery report, we can trust the id on the message. */
593       if (id != NULL)
594         tp_message_set_string (msg, 0, "message-token", id);
595 
596       tp_cm_message_take_message (delivery_report, 0, "delivery-echo", msg);
597     }
598 
599   tp_message_mixin_take_received (G_OBJECT (self), delivery_report);
600 }
601 
602 /**
603  * _gabble_im_channel_state_receive
604  *
605  * Send the D-BUS signal ChatStateChanged
606  * on org.freedesktop.Telepathy.Channel.Interface.ChatState
607  */
608 
609 void
_gabble_im_channel_state_receive(GabbleIMChannel * chan,TpChannelChatState state)610 _gabble_im_channel_state_receive (GabbleIMChannel *chan,
611                                   TpChannelChatState state)
612 {
613   GabbleIMChannelPrivate *priv;
614   TpBaseChannel *base_chan;
615 
616   g_assert (GABBLE_IS_IM_CHANNEL (chan));
617   base_chan = (TpBaseChannel *) chan;
618   priv = chan->priv;
619 
620   priv->chat_states_supported = CHAT_STATES_SUPPORTED;
621 
622   tp_message_mixin_change_chat_state ((GObject *) chan,
623       tp_base_channel_get_target_handle (base_chan), state);
624 }
625 
626 void
gabble_im_channel_receive_receipt(GabbleIMChannel * self,const gchar * receipt_id)627 gabble_im_channel_receive_receipt (
628     GabbleIMChannel *self,
629     const gchar *receipt_id)
630 {
631   _gabble_im_channel_report_delivery (self,
632         TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL, 0, receipt_id, NULL,
633         GABBLE_TEXT_CHANNEL_SEND_NO_ERROR, TP_DELIVERY_STATUS_DELIVERED);
634 }
635 
636 static void
gabble_im_channel_close(TpBaseChannel * base_chan)637 gabble_im_channel_close (TpBaseChannel *base_chan)
638 {
639   GabbleIMChannel *self = GABBLE_IM_CHANNEL (base_chan);
640 
641   tp_message_mixin_maybe_send_gone ((GObject *) self);
642 
643   /* The IM factory will resurrect the channel if we have pending
644    * messages. When we're resurrected, we want the initiator
645    * to be the contact who sent us those messages, if it isn't already */
646   if (tp_message_mixin_has_pending_messages ((GObject *) self, NULL))
647     {
648       DEBUG ("Not really closing, I still have pending messages");
649       tp_message_mixin_set_rescued ((GObject *) self);
650       tp_base_channel_reopened (base_chan,
651           tp_base_channel_get_target_handle (base_chan));
652     }
653   else
654     {
655       DEBUG ("Actually closing, I have no pending messages");
656       tp_base_channel_destroyed (base_chan);
657     }
658 }
659 
660 /**
661  * gabble_im_channel_destroy
662  *
663  * Implements D-Bus method Destroy
664  * on interface org.freedesktop.Telepathy.Channel.Interface.Destroyable
665  */
666 static void
gabble_im_channel_destroy(TpSvcChannelInterfaceDestroyable * iface,DBusGMethodInvocation * context)667 gabble_im_channel_destroy (TpSvcChannelInterfaceDestroyable *iface,
668                            DBusGMethodInvocation *context)
669 {
670   g_assert (GABBLE_IS_IM_CHANNEL (iface));
671 
672   DEBUG ("called on %p, clearing pending messages", iface);
673   tp_message_mixin_clear ((GObject *) iface);
674   gabble_im_channel_close (TP_BASE_CHANNEL (iface));
675 
676   tp_svc_channel_interface_destroyable_return_from_destroy (context);
677 }
678 
679 static gboolean
_gabble_im_channel_send_chat_state(GObject * object,TpChannelChatState state,GError ** error)680 _gabble_im_channel_send_chat_state (GObject *object,
681     TpChannelChatState state,
682     GError **error)
683 {
684   GabbleIMChannel *self = GABBLE_IM_CHANNEL (object);
685   GabbleIMChannelPrivate *priv = self->priv;
686   TpBaseChannel *base = (TpBaseChannel *) self;
687   TpBaseConnection *base_conn = tp_base_channel_get_connection (base);
688 
689   /* Only send anything to the peer if we actually know they support chat
690    * states. */
691   if (!chat_states_supported (self, FALSE))
692     return TRUE;
693 
694   return gabble_message_util_send_chat_state (G_OBJECT (self),
695       GABBLE_CONNECTION (base_conn),
696       WOCKY_STANZA_SUB_TYPE_CHAT, state, priv->peer_jid, error);
697 }
698 
699 static void
destroyable_iface_init(gpointer g_iface,gpointer iface_data)700 destroyable_iface_init (gpointer g_iface,
701                         gpointer iface_data)
702 {
703   TpSvcChannelInterfaceDestroyableClass *klass = g_iface;
704 
705 #define IMPLEMENT(x) tp_svc_channel_interface_destroyable_implement_##x (\
706     klass, gabble_im_channel_##x)
707   IMPLEMENT(destroy);
708 #undef IMPLEMENT
709 }
710