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