1 /* vi: set et sw=4 ts=8 cino=t0,(0: */
2 /* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 8 -*- */
3 /*
4 * This file is part of mission-control
5 *
6 * Copyright © 2007-2011 Nokia Corporation.
7 * Copyright © 2009-2011 Collabora Ltd.
8 *
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public License
11 * version 2.1 as published by the Free Software Foundation.
12 *
13 * This library is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
21 * 02110-1301 USA
22 *
23 */
24
25 /**
26 * SECTION:mcd-connection
27 * @title: McdConnection
28 * @short_description: Connection class representing Telepathy connection class
29 * @see_also:
30 * @stability: Unstable
31 * @include: mcd-connection.h
32 *
33 * FIXME
34 */
35
36 #include "config.h"
37
38 #include "mcd-connection.h"
39 #include "mcd-connection-service-points.h"
40
41 #include <string.h>
42 #include <sys/types.h>
43 #include <sched.h>
44
45 #include <glib.h>
46 #include <glib/gstdio.h>
47 #include <gio/gio.h>
48
49 #include <string.h>
50 #include <stdlib.h>
51 #include <dlfcn.h>
52
53 #include <telepathy-glib/telepathy-glib.h>
54 #include <telepathy-glib/telepathy-glib-dbus.h>
55
56 #include <telepathy-glib/proxy-subclass.h>
57
58 #include "mcd-account-priv.h"
59 #include "mcd-channel-priv.h"
60 #include "mcd-connection-priv.h"
61 #include "mcd-dispatcher-priv.h"
62 #include "mcd-channel.h"
63 #include "mcd-misc.h"
64 #include "mcd-slacker.h"
65 #include "sp_timestamp.h"
66
67 #define INITIAL_RECONNECTION_TIME 3 /* seconds */
68 #define RECONNECTION_MULTIPLIER 3
69 #define MAXIMUM_RECONNECTION_TIME 30 * 60 /* half an hour */
70
71 #define MCD_CONNECTION_PRIV(mcdconn) (MCD_CONNECTION (mcdconn)->priv)
72
73 G_DEFINE_TYPE (McdConnection, mcd_connection, MCD_TYPE_OPERATION);
74
75 /* Private */
76 struct _McdConnectionPrivate
77 {
78 /* Factory for TpConnection objects */
79 TpSimpleClientFactory *client_factory;
80
81 /* Channel dispatcher */
82 McdDispatcher *dispatcher;
83
84 /* Account */
85 McdAccount *account;
86
87 /* Telepathy connection manager */
88 TpConnectionManager *tp_conn_mgr;
89
90 /* Telepathy connection */
91 TpConnection *tp_conn;
92
93 /* Things to do before calling Connect */
94 guint tasks_before_connect;
95
96 guint reconnect_timer; /* timer for reconnection */
97 guint reconnect_interval;
98 guint probation_timer; /* for mcd_connection_probation_ended_cb */
99 guint probation_drop_count;
100
101 /* Supported presences (values are McdPresenceInfo structs) */
102 GHashTable *recognized_presences;
103
104 TpConnectionStatusReason abort_reason;
105 guint got_contact_capabilities : 1;
106 guint has_presence_if : 1;
107 guint has_capabilities_if : 1;
108 guint has_contact_capabilities_if : 1;
109 guint has_power_saving_if : 1;
110
111 /* FALSE until the dispatcher has said it's ready for us */
112 guint dispatching_started : 1;
113 /* FALSE until channels announced by NewChannel/NewChannels need to be
114 * dispatched */
115 guint dispatched_initial_channels : 1;
116
117 /* TRUE if the last status change was to CONNECTED */
118 guint connected : 1;
119
120 /* FALSE until mcd_connection_close() is called */
121 guint closed : 1;
122
123 /* FALSE until connected and the supported presence statuses retrieved */
124 guint presence_info_ready : 1;
125
126 gboolean is_disposed;
127 gboolean service_points_watched;
128
129 McdSlacker *slacker;
130
131 /* Emergency service points' handles.
132 * Set of handles, lazily-allocated. */
133 TpIntset *service_point_handles;
134 /* Emergency service points' identifiers.
135 * Set of (transfer full) (type utf8), lazily-allocated. */
136 GHashTable *service_point_ids;
137 };
138
139 typedef struct
140 {
141 TpConnectionPresenceType presence;
142 guint may_set_on_self : 1;
143 guint can_have_message : 1;
144 } McdPresenceInfo;
145
146 enum
147 {
148 PROP_0,
149 PROP_CLIENT_FACTORY,
150 PROP_TP_MANAGER,
151 PROP_TP_CONNECTION,
152 PROP_ACCOUNT,
153 PROP_DISPATCHER,
154 PROP_SLACKER,
155 };
156
157 enum
158 {
159 READY,
160 CONNECTION_STATUS_CHANGED,
161 N_SIGNALS
162 };
163
164 static guint signals[N_SIGNALS] = { 0 };
165
166 static const gchar * const _available_fb[] = { NULL };
167 static const gchar * const _away_fb[] = { "away", NULL };
168 static const gchar * const _ext_away_fb[] = { "xa", "away", NULL };
169 static const gchar * const _hidden_fb[] = { "hidden", "dnd", "busy", "away", NULL };
170 static const gchar * const _busy_fb[] = { "busy", "dnd", "away", NULL };
171 static const gchar * const *presence_fallbacks[] = {
172 _available_fb, _away_fb, _ext_away_fb, _hidden_fb, _busy_fb
173 };
174
175 static void _mcd_connection_release_tp_connection (McdConnection *connection,
176 McdInhibit *inhibit);
177 static gboolean request_channel_new_iface (McdConnection *connection,
178 McdChannel *channel);
179
180 static void
mcd_presence_info_free(McdPresenceInfo * pi)181 mcd_presence_info_free (McdPresenceInfo *pi)
182 {
183 g_slice_free (McdPresenceInfo, pi);
184 }
185
186 static void
presence_set_status_cb(TpConnection * proxy,const GError * error,gpointer user_data,GObject * weak_object)187 presence_set_status_cb (TpConnection *proxy, const GError *error,
188 gpointer user_data, GObject *weak_object)
189 {
190 McdConnectionPrivate *priv = user_data;
191
192 if (error)
193 {
194 _mcd_account_set_changing_presence (priv->account, FALSE);
195
196 g_warning ("%s: Setting presence of %s failed: %s",
197 G_STRFUNC, mcd_account_get_unique_name (priv->account),
198 error->message);
199 }
200 }
201
202 static gboolean
_check_presence(McdConnectionPrivate * priv,TpConnectionPresenceType presence,const gchar ** status)203 _check_presence (McdConnectionPrivate *priv, TpConnectionPresenceType presence,
204 const gchar **status)
205 {
206 const gchar * const *fallbacks;
207
208 if (priv->recognized_presences == NULL ||
209 g_hash_table_size (priv->recognized_presences) == 0)
210 {
211 DEBUG ("account %s: recognized presences unknown, not setting "
212 "presence yet", mcd_account_get_unique_name (priv->account));
213 return FALSE;
214 }
215
216 if (presence == TP_CONNECTION_PRESENCE_TYPE_UNSET || *status == NULL)
217 return FALSE;
218
219 if (g_hash_table_lookup (priv->recognized_presences, *status))
220 return TRUE;
221
222 if (presence < TP_CONNECTION_PRESENCE_TYPE_AVAILABLE ||
223 presence > TP_CONNECTION_PRESENCE_TYPE_BUSY)
224 return FALSE;
225
226 fallbacks =
227 presence_fallbacks[presence - TP_CONNECTION_PRESENCE_TYPE_AVAILABLE];
228
229 for (; *fallbacks != NULL; fallbacks++)
230 if (g_hash_table_lookup (priv->recognized_presences, *fallbacks))
231 break;
232
233 /* assume that "available" is always supported -- otherwise, an error will
234 * be returned by SetPresence, but it's not a big loss */
235
236 if (*fallbacks != NULL)
237 {
238 DEBUG ("account %s: presence %s not supported, setting %s",
239 mcd_account_get_unique_name (priv->account), *status,
240 *fallbacks);
241 *status = *fallbacks;
242 }
243 else
244 {
245 DEBUG ("account %s: presence %s not supported and no fallback is "
246 "supported either, trying \"available\" and hoping for the "
247 "best...", mcd_account_get_unique_name (priv->account),
248 *status);
249 *status = "available";
250 }
251
252 return TRUE;
253 }
254
255 static void
_mcd_connection_attempt(McdConnection * connection)256 _mcd_connection_attempt (McdConnection *connection)
257 {
258 g_return_if_fail (connection->priv->tp_conn_mgr != NULL);
259 g_return_if_fail (connection->priv->account != NULL);
260
261 DEBUG ("called for %p, account %s", connection,
262 mcd_account_get_unique_name (connection->priv->account));
263
264 if (connection->priv->reconnect_timer != 0)
265 {
266 g_source_remove (connection->priv->reconnect_timer);
267 connection->priv->reconnect_timer = 0;
268 }
269
270 if (mcd_account_get_connection_status (connection->priv->account) ==
271 TP_CONNECTION_STATUS_DISCONNECTED)
272 {
273 /* not user-initiated */
274 _mcd_account_connection_begin (connection->priv->account, FALSE);
275 }
276 else
277 {
278 /* Can this happen? We just don't know. */
279 DEBUG ("Not connecting because not disconnected (%i)",
280 mcd_account_get_connection_status (connection->priv->account));
281 }
282 }
283
284 static void
_mcd_connection_set_presence(McdConnection * connection,TpConnectionPresenceType presence,const gchar * status,const gchar * message)285 _mcd_connection_set_presence (McdConnection * connection,
286 TpConnectionPresenceType presence,
287 const gchar *status, const gchar *message)
288 {
289 McdConnectionPrivate *priv = connection->priv;
290 const gchar *adj_status = status;
291
292 if (!priv->tp_conn)
293 {
294 DEBUG ("tp_conn is NULL");
295 _mcd_connection_attempt (connection);
296 return;
297 }
298 g_return_if_fail (TP_IS_CONNECTION (priv->tp_conn));
299
300 if (!priv->has_presence_if)
301 {
302 DEBUG ("Presence not supported on this connection");
303 return;
304 }
305
306 if (_check_presence (priv, presence, &adj_status))
307 {
308 TpConnectionPresenceType curr_presence;
309 const gchar *curr_status;
310 const gchar *curr_message;
311
312 DEBUG ("Setting status '%s' of type %u ('%s' was requested)",
313 adj_status, presence, status);
314
315 mcd_account_get_current_presence (priv->account, &curr_presence,
316 &curr_status, &curr_message);
317 if (curr_presence == presence &&
318 tp_strdiff (curr_status, adj_status) == 0 &&
319 tp_strdiff (curr_message, message) == 0)
320 {
321 // PresencesChanged won't be emitted and Account.ChangingPresence
322 // will never go to FALSE, so forcibly set it to FALSE
323 _mcd_account_set_changing_presence (priv->account, FALSE);
324 }
325
326 tp_cli_connection_interface_simple_presence_call_set_presence
327 (priv->tp_conn, -1, adj_status, message, presence_set_status_cb,
328 priv, NULL, (GObject *)connection);
329 }
330 else
331 {
332 DEBUG ("Unable to set status '%s', or anything suitable for type %u",
333 status, presence);
334 }
335 }
336
337
338 static void
presence_get_statuses_cb(TpProxy * proxy,const GValue * v_statuses,const GError * error,gpointer user_data,GObject * weak_object)339 presence_get_statuses_cb (TpProxy *proxy, const GValue *v_statuses,
340 const GError *error, gpointer user_data,
341 GObject *weak_object)
342 {
343 McdConnectionPrivate *priv = user_data;
344 McdConnection *connection = MCD_CONNECTION (weak_object);
345 TpConnectionPresenceType presence;
346 const gchar *status, *message;
347 GHashTable *statuses;
348 GHashTableIter iter;
349 gpointer ht_key, ht_value;
350
351 if (error)
352 {
353 g_warning ("%s: Get statuses failed for account %s: %s", G_STRFUNC,
354 mcd_account_get_unique_name (priv->account),
355 error->message);
356 return;
357 }
358 else if (G_VALUE_TYPE (v_statuses) != TP_HASH_TYPE_SIMPLE_STATUS_SPEC_MAP)
359 {
360 g_warning ("%s: Get(Statuses) returned the wrong type: %s",
361 mcd_account_get_unique_name (priv->account),
362 G_VALUE_TYPE_NAME (v_statuses));
363 return;
364 }
365
366 if (!priv->recognized_presences)
367 priv->recognized_presences =
368 g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
369 (GDestroyNotify)mcd_presence_info_free);
370
371 DEBUG ("account %s:", mcd_account_get_unique_name (priv->account));
372 statuses = g_value_get_boxed (v_statuses);
373
374 g_return_if_fail (statuses != NULL);
375
376 /* This function can be called both before and after Connect() - before
377 * CONNECTED the Connection tells us the presences it believes it will
378 * probably support, and after CONNECTED it tells us the presences it
379 * *actually* supports (which might be less numerous). */
380 g_hash_table_remove_all (priv->recognized_presences);
381
382 g_hash_table_iter_init (&iter, statuses);
383
384 while (g_hash_table_iter_next (&iter, &ht_key, &ht_value))
385 {
386 GValueArray *va = ht_value;
387 McdPresenceInfo *pi;
388
389 status = ht_key;
390 DEBUG (" %s", status);
391
392 pi = g_slice_new (McdPresenceInfo);
393 pi->presence = g_value_get_uint (va->values);
394 pi->may_set_on_self = g_value_get_boolean (va->values + 1);
395 pi->can_have_message = g_value_get_boolean (va->values + 2);
396 g_hash_table_insert (priv->recognized_presences,
397 g_strdup (status), pi);
398 }
399
400 /* Now the presence info is ready. We can set the presence */
401 mcd_account_get_requested_presence (priv->account, &presence,
402 &status, &message);
403 if (priv->connected)
404 {
405 priv->presence_info_ready = TRUE;
406 }
407
408 _mcd_connection_set_presence (connection, presence, status, message);
409 }
410
411 static void
_mcd_connection_setup_presence(McdConnection * connection)412 _mcd_connection_setup_presence (McdConnection *connection)
413 {
414 McdConnectionPrivate *priv = connection->priv;
415
416 tp_cli_dbus_properties_call_get
417 (priv->tp_conn, -1, TP_IFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE,
418 "Statuses", presence_get_statuses_cb, priv, NULL,
419 (GObject *)connection);
420 }
421
422 static void
disconnect_cb(TpConnection * proxy,const GError * error,gpointer user_data,GObject * weak_object)423 disconnect_cb (TpConnection *proxy, const GError *error, gpointer user_data,
424 GObject *weak_object)
425 {
426 if (error != NULL)
427 WARNING ("Disconnect failed: %s", error->message);
428 else
429 DEBUG ("Disconnected %s", tp_proxy_get_object_path (TP_PROXY (proxy)));
430 }
431
432 static void
_mcd_connection_call_disconnect(McdConnection * connection,McdInhibit * inhibit)433 _mcd_connection_call_disconnect (McdConnection *connection,
434 McdInhibit *inhibit)
435 {
436 TpConnection *tp_conn = connection->priv->tp_conn;
437
438 if (!tp_conn || tp_proxy_get_invalidated (TP_PROXY (tp_conn)) != NULL)
439 return;
440
441 if (tp_connection_get_status (tp_conn, NULL) ==
442 TP_CONNECTION_STATUS_DISCONNECTED) return;
443 tp_cli_connection_call_disconnect (tp_conn, -1, disconnect_cb,
444 inhibit ? mcd_inhibit_hold (inhibit) : NULL,
445 inhibit ? (GDestroyNotify) mcd_inhibit_release : NULL,
446 NULL);
447
448 }
449
450 /* Update the presence of the tp_connection.
451 *
452 * Note, that the only presence transition not served by this function
453 * is getting to non-offline state since when presence is offline this object
454 * does not exist.
455 *
456 * So, here we just submit the request to the tp_connection object. The return off
457 * the operation is handled by (yet to be written) handler
458 */
459 void
_mcd_connection_request_presence(McdConnection * self,TpConnectionPresenceType presence,const gchar * status,const gchar * message)460 _mcd_connection_request_presence (McdConnection *self,
461 TpConnectionPresenceType presence,
462 const gchar *status, const gchar *message)
463 {
464 g_return_if_fail (MCD_IS_CONNECTION (self));
465
466 DEBUG ("Presence requested: %d", presence);
467 if (presence == TP_CONNECTION_PRESENCE_TYPE_UNSET) return;
468
469 if (presence == TP_CONNECTION_PRESENCE_TYPE_OFFLINE)
470 {
471 /* Connection Proxy */
472 self->priv->abort_reason = TP_CONNECTION_STATUS_REASON_REQUESTED;
473 mcd_mission_disconnect (MCD_MISSION (self));
474 _mcd_connection_call_disconnect (self, NULL);
475
476 /* if a reconnection attempt is scheduled, cancel it */
477 if (self->priv->reconnect_timer)
478 {
479 g_source_remove (self->priv->reconnect_timer);
480 self->priv->reconnect_timer = 0;
481 }
482 }
483 else
484 {
485 _mcd_connection_set_presence (self, presence, status, message);
486 }
487 }
488
489 static void
on_new_channel(TpConnection * proxy,const gchar * chan_obj_path,const gchar * chan_type,guint handle_type,guint handle,gboolean suppress_handler,gpointer user_data,GObject * weak_object)490 on_new_channel (TpConnection *proxy, const gchar *chan_obj_path,
491 const gchar *chan_type, guint handle_type, guint handle,
492 gboolean suppress_handler, gpointer user_data,
493 GObject *weak_object)
494 {
495 McdConnection *connection = MCD_CONNECTION (weak_object);
496 McdConnectionPrivate *priv = user_data;
497 McdChannel *channel;
498
499 DEBUG ("%s (t=%s, ht=%u, h=%u, suppress=%c)",
500 chan_obj_path, chan_type, handle_type, handle,
501 suppress_handler ? 'T' : 'F');
502
503 if (priv->dispatched_initial_channels)
504 {
505 channel = mcd_channel_new_from_path (proxy,
506 chan_obj_path,
507 chan_type, handle, handle_type);
508 if (G_UNLIKELY (!channel)) return;
509 mcd_operation_take_mission (MCD_OPERATION (connection),
510 MCD_MISSION (channel));
511
512 /* MC no longer calls RequestChannel. As a result, if suppress_handler
513 * is TRUE, we know that this channel was requested "behind our back",
514 * therefore we should call ObserveChannels, but refrain from calling
515 * AddDispatchOperation or HandleChannels.
516 *
517 * We assume that channels without suppress_handler are incoming. */
518 _mcd_dispatcher_add_channel (priv->dispatcher, channel,
519 suppress_handler, suppress_handler);
520 }
521 }
522
523 static void
_foreach_channel_remove(McdMission * mission,McdOperation * operation)524 _foreach_channel_remove (McdMission * mission, McdOperation * operation)
525 {
526 g_assert (MCD_IS_MISSION (mission));
527 g_assert (MCD_IS_OPERATION (operation));
528
529 mcd_operation_remove_mission (operation, mission);
530 }
531
532 static void
_mcd_connection_setup_power_saving(McdConnection * connection)533 _mcd_connection_setup_power_saving (McdConnection *connection)
534 {
535 McdConnectionPrivate *priv = connection->priv;
536
537 if (priv->slacker == NULL)
538 return;
539
540 DEBUG ("is %sactive", mcd_slacker_is_inactive (priv->slacker) ? "in" : "");
541
542 if (mcd_slacker_is_inactive (priv->slacker))
543 tp_cli_connection_interface_power_saving_call_set_power_saving (priv->tp_conn, -1,
544 TRUE, NULL, NULL, NULL, NULL);
545 }
546
547 static gboolean
mcd_connection_reconnect(McdConnection * connection)548 mcd_connection_reconnect (McdConnection *connection)
549 {
550 DEBUG ("%p", connection);
551 _mcd_connection_attempt (connection);
552 return FALSE;
553 }
554
555 /* Number of seconds after which to assume the connection is basically stable.
556 * If we have too many disconnections within this time, assume something
557 * serious is wrong, and stop reconnecting. */
558 #define PROBATION_SEC 120
559 /* Maximum number of dropped connections within PROBATION_SEC. Connections
560 * that never reached CONNECTED state don't count towards this limit, so we'll
561 * keep retrying indefinitely for those (with exponential back-off). */
562 #define PROBATION_MAX_DROPPED 3
563
564 static gboolean
mcd_connection_probation_ended_cb(gpointer user_data)565 mcd_connection_probation_ended_cb (gpointer user_data)
566 {
567 McdConnection *self = MCD_CONNECTION (user_data);
568 McdConnectionPrivate *priv = MCD_CONNECTION_PRIV (self);
569
570 /* We've been connected for PROBATION_SEC seconds. We can probably now
571 * assume that the connection is stable */
572 if (priv->tp_conn != NULL)
573 {
574 DEBUG ("probation finished, assuming connection is stable: %s",
575 tp_proxy_get_object_path (self->priv->tp_conn));
576 self->priv->probation_drop_count = 0;
577 self->priv->reconnect_interval = INITIAL_RECONNECTION_TIME;
578 }
579 else /* probation timer survived beyond its useful life */
580 {
581 g_warning ("probation error: timer should have been removed when the "
582 "TpConnection was released");
583 }
584
585 self->priv->probation_timer = 0;
586
587 return FALSE;
588 }
589
590 static void
on_connection_status_changed(TpConnection * tp_conn,GParamSpec * pspec,McdConnection * connection)591 on_connection_status_changed (TpConnection *tp_conn, GParamSpec *pspec,
592 McdConnection *connection)
593 {
594 McdConnectionPrivate *priv = MCD_CONNECTION_PRIV (connection);
595 TpConnectionStatus conn_status;
596 TpConnectionStatusReason conn_reason;
597
598 g_object_get (G_OBJECT (tp_conn),
599 "status", &conn_status,
600 "status-reason", &conn_reason,
601 NULL);
602 DEBUG ("status_changed called from tp (%d)", conn_status);
603
604 switch (conn_status)
605 {
606 case TP_CONNECTION_STATUS_CONNECTING:
607 g_signal_emit (connection, signals[CONNECTION_STATUS_CHANGED], 0,
608 conn_status, conn_reason, tp_conn, NULL, NULL);
609 priv->abort_reason = TP_CONNECTION_STATUS_REASON_NONE_SPECIFIED;
610 priv->connected = FALSE;
611 break;
612
613 case TP_CONNECTION_STATUS_CONNECTED:
614 {
615 g_signal_emit (connection, signals[CONNECTION_STATUS_CHANGED], 0,
616 conn_status, conn_reason, tp_conn, NULL, NULL);
617
618 if (priv->probation_timer == 0)
619 {
620 DEBUG ("setting probation timer (%d) seconds, for %s",
621 PROBATION_SEC, tp_proxy_get_object_path (tp_conn));
622 priv->probation_timer = g_timeout_add_seconds (PROBATION_SEC,
623 mcd_connection_probation_ended_cb, connection);
624 priv->probation_drop_count = 0;
625 }
626
627 mcd_connection_service_point_setup (connection,
628 !priv->service_points_watched);
629 priv->service_points_watched = TRUE;
630
631 priv->connected = TRUE;
632 }
633 break;
634
635 case TP_CONNECTION_STATUS_DISCONNECTED:
636 /* Connection could die during account status updated if its
637 * manager is the only one holding the reference to it (manager will
638 * remove the connection from itself). To ensure we get a chance to
639 * emit abort signal (there could be others holding a ref to it), we
640 * will hold a temporary ref to it.
641 */
642 priv->abort_reason = conn_reason;
643 /* priv->connected will be reset to FALSE in the invalidated
644 * callback */
645 break;
646
647 default:
648 g_warning ("Unknown telepathy connection status");
649 }
650 }
651
652 static gboolean
connection_should_reconnect(TpConnection * tp_conn,guint domain,gint code)653 connection_should_reconnect (TpConnection *tp_conn,
654 guint domain,
655 gint code)
656 {
657 TpConnectionStatusReason reason;
658
659 if (domain == TP_ERROR)
660 {
661 switch (code)
662 {
663 case TP_ERROR_CONNECTION_FAILED:
664 case TP_ERROR_CONNECTION_LOST:
665 case TP_ERROR_DISCONNECTED:
666 case TP_ERROR_NETWORK_ERROR:
667 DEBUG ("error code %s, reconnecting",
668 tp_error_get_dbus_name (code));
669 return TRUE;
670
671 case TP_ERROR_SOFTWARE_UPGRADE_REQUIRED:
672 case TP_ERROR_SERVICE_BUSY:
673 case TP_ERROR_CONNECTION_REPLACED:
674 case TP_ERROR_ALREADY_CONNECTED:
675 case TP_ERROR_CONNECTION_REFUSED:
676 case TP_ERROR_INVALID_ARGUMENT:
677 case TP_ERROR_INVALID_HANDLE:
678 case TP_ERROR_CANCELLED:
679 case TP_ERROR_AUTHENTICATION_FAILED:
680 case TP_ERROR_ENCRYPTION_NOT_AVAILABLE:
681 case TP_ERROR_ENCRYPTION_ERROR:
682 case TP_ERROR_CERT_NOT_PROVIDED:
683 case TP_ERROR_CERT_UNTRUSTED:
684 case TP_ERROR_CERT_EXPIRED:
685 case TP_ERROR_CERT_NOT_ACTIVATED:
686 case TP_ERROR_CERT_FINGERPRINT_MISMATCH:
687 case TP_ERROR_CERT_HOSTNAME_MISMATCH:
688 case TP_ERROR_CERT_SELF_SIGNED:
689 case TP_ERROR_CERT_INVALID:
690 case TP_ERROR_CERT_REVOKED:
691 case TP_ERROR_CERT_INSECURE:
692 case TP_ERROR_CERT_LIMIT_EXCEEDED:
693 DEBUG ("error code %s, not reconnecting",
694 tp_error_get_dbus_name (code));
695 return FALSE;
696
697 default:
698 DEBUG ("TpError code %s not handled",
699 tp_error_get_dbus_name (code));
700 }
701 }
702 else if (domain == TP_DBUS_ERRORS)
703 {
704 switch (code)
705 {
706 case TP_DBUS_ERROR_NAME_OWNER_LOST:
707 /* CM crashed */
708 DEBUG ("dbus error code: OWNER_LOST, reconnecting");
709 return TRUE;
710 }
711 }
712
713 /* not sure what the GError meant, so check the generic status code */
714 tp_connection_get_status (tp_conn, &reason);
715
716 switch (reason)
717 {
718 case TP_CONNECTION_STATUS_REASON_NETWORK_ERROR:
719 DEBUG ("StatusReason %d, reconnecting", reason);
720 return TRUE;
721 default:
722 break;
723 }
724
725 DEBUG ("not reconnecting");
726
727 return FALSE;
728 }
729
730 static void
mcd_connection_invalidated_cb(TpConnection * tp_conn,guint domain,gint code,gchar * message,McdConnection * connection)731 mcd_connection_invalidated_cb (TpConnection *tp_conn,
732 guint domain,
733 gint code,
734 gchar *message,
735 McdConnection *connection)
736 {
737 McdConnectionPrivate *priv = MCD_CONNECTION_PRIV (connection);
738
739 DEBUG ("Proxy destroyed (%s)!", message);
740
741 _mcd_connection_release_tp_connection (connection, NULL);
742
743 if (priv->connected &&
744 priv->abort_reason != TP_CONNECTION_STATUS_REASON_REQUESTED &&
745 priv->probation_timer != 0)
746 {
747 DEBUG ("connection dropped while on probation: %s",
748 tp_proxy_get_object_path (tp_conn));
749
750 if (++priv->probation_drop_count > PROBATION_MAX_DROPPED)
751 {
752 DEBUG ("connection dropped too many times, will stop "
753 "reconnecting");
754 }
755 }
756
757 priv->connected = FALSE;
758
759 if (connection_should_reconnect (tp_conn, domain, code) &&
760 priv->probation_drop_count <= PROBATION_MAX_DROPPED)
761 {
762 /* we were disconnected by a network error or by a connection manager
763 * crash (in the latter case, we get NoneSpecified as a reason): don't
764 * abort the connection but try to reconnect later */
765 if (priv->reconnect_timer == 0)
766 {
767 DEBUG ("Preparing for reconnection in %u seconds",
768 priv->reconnect_interval);
769 priv->reconnect_timer = g_timeout_add_seconds
770 (priv->reconnect_interval,
771 (GSourceFunc)mcd_connection_reconnect, connection);
772 priv->reconnect_interval *= RECONNECTION_MULTIPLIER;
773
774 if (priv->reconnect_interval >= MAXIMUM_RECONNECTION_TIME)
775 priv->reconnect_interval = MAXIMUM_RECONNECTION_TIME;
776 }
777 }
778 else
779 {
780 g_object_ref (connection);
781 /* Notify connection abort */
782 mcd_mission_abort (MCD_MISSION (connection));
783 g_object_unref (connection);
784 }
785 }
786
787 static void
connect_cb(TpConnection * tp_conn,const GError * error,gpointer user_data,GObject * weak_object)788 connect_cb (TpConnection *tp_conn, const GError *error,
789 gpointer user_data, GObject *weak_object)
790 {
791 McdConnection *connection = MCD_CONNECTION (weak_object);
792
793 DEBUG ("called for connection %p", connection);
794
795 if (error)
796 {
797 g_warning ("%s: tp_conn_connect failed: %s",
798 G_STRFUNC, error->message);
799 }
800 }
801
802 static gboolean
803 _mcd_connection_request_channel (McdConnection *connection,
804 McdChannel *channel);
805
806 static void
request_unrequested_channels(McdConnection * connection)807 request_unrequested_channels (McdConnection *connection)
808 {
809 const GList *channels;
810
811 channels = mcd_operation_get_missions ((McdOperation *)connection);
812
813 DEBUG ("called");
814 /* go through the channels that were requested while the connection was not
815 * ready, and process them */
816 while (channels)
817 {
818 McdChannel *channel = MCD_CHANNEL (channels->data);
819
820 if (mcd_channel_get_status (channel) == MCD_CHANNEL_STATUS_REQUEST)
821 {
822 DEBUG ("Requesting channel %p", channel);
823 mcd_connection_request_channel (connection, channel);
824 }
825 channels = channels->next;
826 }
827 }
828
829 McdChannel *
mcd_connection_find_channel_by_path(McdConnection * connection,const gchar * object_path)830 mcd_connection_find_channel_by_path (McdConnection *connection,
831 const gchar *object_path)
832 {
833 const GList *list = NULL;
834
835 list = mcd_operation_get_missions (MCD_OPERATION (connection));
836
837 while (list)
838 {
839 McdChannel *channel = MCD_CHANNEL (list->data);
840
841 if (_mcd_channel_is_primary_for_path (channel, object_path))
842 {
843 return channel;
844 }
845
846 list = list->next;
847 }
848 return NULL;
849 }
850
851 static gboolean mcd_connection_need_dispatch (McdConnection *connection,
852 const gchar *object_path,
853 GHashTable *props);
854
855 static void
on_new_channels(TpConnection * proxy,const GPtrArray * channels,gpointer user_data,GObject * weak_object)856 on_new_channels (TpConnection *proxy, const GPtrArray *channels,
857 gpointer user_data, GObject *weak_object)
858 {
859 McdConnection *connection = MCD_CONNECTION (weak_object);
860 McdConnectionPrivate *priv = user_data;
861 guint i;
862
863 if (DEBUGGING)
864 {
865 for (i = 0; i < channels->len; i++)
866 {
867 GValueArray *va = g_ptr_array_index (channels, i);
868 const gchar *object_path = g_value_get_boxed (va->values);
869 GHashTable *props = g_value_get_boxed (va->values + 1);
870 GHashTableIter iter;
871 gpointer k, v;
872
873 DEBUG ("%s", object_path);
874
875 g_hash_table_iter_init (&iter, props);
876
877 while (g_hash_table_iter_next (&iter, &k, &v))
878 {
879 gchar *repr = g_strdup_value_contents (v);
880
881 DEBUG(" \"%s\" => %s", (const gchar *) k, repr);
882 g_free (repr);
883 }
884 }
885 }
886
887 /* we can completely ignore the channels that arrive while this is
888 * FALSE: they'll also be in Channels in the GetAll(Requests) result */
889 if (!priv->dispatched_initial_channels) return;
890
891 sp_timestamp ("NewChannels received");
892 for (i = 0; i < channels->len; i++)
893 {
894 GValueArray *va;
895 const gchar *object_path;
896 GHashTable *props;
897 GValue *value;
898 gboolean requested = FALSE;
899 gboolean only_observe = FALSE;
900 McdChannel *channel;
901
902 va = g_ptr_array_index (channels, i);
903 object_path = g_value_get_boxed (va->values);
904 props = g_value_get_boxed (va->values + 1);
905
906 only_observe = !mcd_connection_need_dispatch (connection, object_path,
907 props);
908
909 /* Don't do anything for requested channels */
910 value = g_hash_table_lookup (props, TP_IFACE_CHANNEL ".Requested");
911 if (value && g_value_get_boolean (value))
912 requested = TRUE;
913
914 /* if the channel was a request, we already have an object for it;
915 * otherwise, create a new one */
916 channel = mcd_connection_find_channel_by_path (connection, object_path);
917 if (!channel)
918 {
919 channel = mcd_channel_new_from_properties (proxy, object_path,
920 props);
921 if (G_UNLIKELY (!channel)) continue;
922
923 mcd_operation_take_mission (MCD_OPERATION (connection),
924 MCD_MISSION (channel));
925 }
926
927 if (!requested)
928 {
929 /* we always dispatch unrequested (incoming) channels */
930 only_observe = FALSE;
931 }
932
933 _mcd_dispatcher_add_channel (priv->dispatcher, channel, requested,
934 only_observe);
935 }
936 }
937
938 static void
mcd_connection_recover_channel(McdConnection * connection,const gchar * object_path,const GHashTable * properties)939 mcd_connection_recover_channel (McdConnection *connection,
940 const gchar *object_path,
941 const GHashTable *properties)
942 {
943 McdConnectionPrivate *priv = connection->priv;
944 McdChannel *channel;
945
946 DEBUG ("called for %s", object_path);
947 channel = mcd_channel_new_from_properties (priv->tp_conn, object_path,
948 properties);
949 if (G_UNLIKELY (!channel)) return;
950
951 mcd_operation_take_mission (MCD_OPERATION (connection),
952 MCD_MISSION (channel));
953
954 _mcd_dispatcher_recover_channel (priv->dispatcher, channel,
955 mcd_account_get_object_path (priv->account));
956 }
957
958 static void
mcd_connection_found_channel(McdConnection * self,const gchar * object_path,GHashTable * channel_props)959 mcd_connection_found_channel (McdConnection *self,
960 const gchar *object_path,
961 GHashTable *channel_props)
962 {
963 const GList *list;
964 gboolean found = FALSE;
965
966 /* find the McdChannel */
967 /* NOTE: we cannot move the mcd_operation_get_missions() call out of
968 * the loop, because mcd_dispatcher_send() can cause the channel to be
969 * destroyed, at which point our list would contain a finalized channel
970 * (and a crash will happen) */
971 list = mcd_operation_get_missions ((McdOperation *) self);
972 for (; list != NULL; list = list->next)
973 {
974 McdChannel *channel = MCD_CHANNEL (list->data);
975
976 if (g_strcmp0 (object_path,
977 mcd_channel_get_object_path (channel)) == 0)
978 {
979 found = TRUE;
980 break;
981 }
982
983 if (mcd_channel_get_status (channel) !=
984 MCD_CHANNEL_STATUS_UNDISPATCHED)
985 continue;
986 }
987
988 if (!found)
989 {
990 /* We don't have a McdChannel for this channel, which most likely
991 * means that it was already present on the connection before MC
992 * started. Let's try to recover it */
993 mcd_connection_recover_channel (self, object_path, channel_props);
994 }
995 }
996
get_all_requests_cb(TpProxy * proxy,GHashTable * properties,const GError * error,gpointer user_data,GObject * weak_object)997 static void get_all_requests_cb (TpProxy *proxy, GHashTable *properties,
998 const GError *error, gpointer user_data,
999 GObject *weak_object)
1000 {
1001 McdConnection *connection = MCD_CONNECTION (weak_object);
1002 McdConnectionPrivate *priv = user_data;
1003 GPtrArray *channels;
1004 GValue *value;
1005 guint i;
1006
1007 if (error)
1008 {
1009 g_warning ("%s got error: %s", G_STRFUNC, error->message);
1010 return;
1011 }
1012
1013 value = g_hash_table_lookup (properties, "Channels");
1014
1015 if (value == NULL)
1016 {
1017 g_warning ("%s: no Channels property on %s",
1018 G_STRFUNC, tp_proxy_get_object_path (proxy));
1019 return;
1020 }
1021
1022 if (!G_VALUE_HOLDS (value, TP_ARRAY_TYPE_CHANNEL_DETAILS_LIST))
1023 {
1024 g_warning ("%s: property Channels has type %s, expecting %s",
1025 G_STRFUNC, G_VALUE_TYPE_NAME (value),
1026 g_type_name (TP_ARRAY_TYPE_CHANNEL_DETAILS_LIST));
1027 return;
1028 }
1029
1030 channels = g_value_get_boxed (value);
1031 for (i = 0; i < channels->len; i++)
1032 {
1033 GValueArray *va;
1034 const gchar *object_path;
1035 GHashTable *channel_props;
1036
1037 va = g_ptr_array_index (channels, i);
1038 object_path = g_value_get_boxed (va->values);
1039 channel_props = g_value_get_boxed (va->values + 1);
1040
1041 if (DEBUGGING)
1042 {
1043 GHashTableIter iter;
1044 gpointer k, v;
1045
1046 DEBUG ("%s", object_path);
1047
1048 g_hash_table_iter_init (&iter, channel_props);
1049
1050 while (g_hash_table_iter_next (&iter, &k, &v))
1051 {
1052 gchar *repr = g_strdup_value_contents (v);
1053
1054 DEBUG(" \"%s\" => %s", (const gchar *) k, repr);
1055 g_free (repr);
1056 }
1057 }
1058
1059 mcd_connection_found_channel (connection, object_path, channel_props);
1060 }
1061
1062 priv->dispatched_initial_channels = TRUE;
1063 }
1064
1065 static void
mcd_connection_setup_requests(McdConnection * connection)1066 mcd_connection_setup_requests (McdConnection *connection)
1067 {
1068 McdConnectionPrivate *priv = connection->priv;
1069
1070 /*
1071 * 1. connect to the NewChannels
1072 * 2. get existing channels
1073 * 3. disconnect from NewChannel
1074 * 4. dispatch the UNDISPATCHED
1075 */
1076 tp_cli_connection_interface_requests_connect_to_new_channels
1077 (priv->tp_conn, on_new_channels, priv, NULL,
1078 (GObject *)connection, NULL);
1079
1080 tp_cli_dbus_properties_call_get_all (priv->tp_conn, -1,
1081 TP_IFACE_CONNECTION_INTERFACE_REQUESTS,
1082 get_all_requests_cb, priv, NULL, (GObject *)connection);
1083 }
1084
1085 static void
list_channels_cb(TpConnection * connection,const GPtrArray * structs,const GError * error,gpointer user_data,GObject * weak_object)1086 list_channels_cb (TpConnection *connection,
1087 const GPtrArray *structs,
1088 const GError *error,
1089 gpointer user_data,
1090 GObject *weak_object)
1091 {
1092 McdConnection *self = MCD_CONNECTION (weak_object);
1093 guint i;
1094
1095 if (error)
1096 {
1097 g_warning ("ListChannels got error: %s", error->message);
1098 return;
1099 }
1100
1101 for (i = 0; i < structs->len; i++)
1102 {
1103 GValueArray *va = g_ptr_array_index (structs, i);
1104 const gchar *object_path;
1105 GHashTable *channel_props;
1106
1107 object_path = g_value_get_boxed (va->values + 0);
1108
1109 DEBUG ("%s (t=%s, ht=%u, h=%u)",
1110 object_path,
1111 g_value_get_string (va->values + 1),
1112 g_value_get_uint (va->values + 2),
1113 g_value_get_uint (va->values + 3));
1114
1115 /* this is not the most efficient thing we could possibly do, but
1116 * we're on a fallback path so it's OK to be a bit slow */
1117 channel_props = g_hash_table_new (g_str_hash, g_str_equal);
1118 g_hash_table_insert (channel_props, TP_IFACE_CHANNEL ".ChannelType",
1119 va->values + 1);
1120 g_hash_table_insert (channel_props, TP_IFACE_CHANNEL ".TargetHandleType",
1121 va->values + 2);
1122 g_hash_table_insert (channel_props, TP_IFACE_CHANNEL ".TargetHandle",
1123 va->values + 3);
1124 mcd_connection_found_channel (self, object_path, channel_props);
1125 g_hash_table_unref (channel_props);
1126 }
1127
1128 self->priv->dispatched_initial_channels = TRUE;
1129 }
1130
1131 static void
mcd_connection_setup_pre_requests(McdConnection * connection)1132 mcd_connection_setup_pre_requests (McdConnection *connection)
1133 {
1134 McdConnectionPrivate *priv = connection->priv;
1135
1136 tp_cli_connection_connect_to_new_channel
1137 (priv->tp_conn, on_new_channel, priv, NULL,
1138 (GObject *)connection, NULL);
1139
1140 tp_cli_connection_call_list_channels (priv->tp_conn, -1,
1141 list_channels_cb, priv, NULL, (GObject *) connection);
1142 }
1143
1144 static void
on_connection_ready(GObject * source_object,GAsyncResult * result,gpointer user_data)1145 on_connection_ready (GObject *source_object, GAsyncResult *result,
1146 gpointer user_data)
1147 {
1148 TpConnection *tp_conn = TP_CONNECTION (source_object);
1149 TpWeakRef *weak_ref = user_data;
1150 McdConnection *connection = tp_weak_ref_dup_object (weak_ref);
1151 McdConnectionPrivate *priv;
1152 GError *error = NULL;
1153
1154 if (!tp_proxy_prepare_finish (tp_conn, result, &error))
1155 {
1156 DEBUG ("got error: %s", error->message);
1157 g_clear_error (&error);
1158 goto finally;
1159 }
1160
1161 if (!connection)
1162 goto finally;
1163
1164 DEBUG ("connection is ready");
1165 priv = MCD_CONNECTION_PRIV (connection);
1166
1167 priv->has_presence_if = tp_proxy_has_interface_by_id
1168 (tp_conn, TP_IFACE_QUARK_CONNECTION_INTERFACE_SIMPLE_PRESENCE);
1169 priv->has_capabilities_if = tp_proxy_has_interface_by_id (tp_conn,
1170 TP_IFACE_QUARK_CONNECTION_INTERFACE_CAPABILITIES);
1171 priv->has_contact_capabilities_if = tp_proxy_has_interface_by_id (tp_conn,
1172 TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_CAPABILITIES);
1173 priv->has_power_saving_if = tp_proxy_has_interface_by_id (tp_conn,
1174 TP_IFACE_QUARK_CONNECTION_INTERFACE_POWER_SAVING);
1175
1176 if (priv->has_presence_if)
1177 _mcd_connection_setup_presence (connection);
1178
1179 if (priv->has_power_saving_if)
1180 _mcd_connection_setup_power_saving (connection);
1181
1182 if (!priv->dispatching_started)
1183 _mcd_dispatcher_add_connection (priv->dispatcher, connection);
1184
1185 request_unrequested_channels (connection);
1186
1187 g_signal_emit (connection, signals[READY], 0);
1188
1189 finally:
1190 g_clear_object (&connection);
1191 tp_weak_ref_destroy (weak_ref);
1192 }
1193
1194 void
_mcd_connection_start_dispatching(McdConnection * self,GPtrArray * client_caps)1195 _mcd_connection_start_dispatching (McdConnection *self,
1196 GPtrArray *client_caps)
1197 {
1198 g_return_if_fail (MCD_IS_CONNECTION (self));
1199 g_return_if_fail (!self->priv->dispatching_started);
1200
1201 DEBUG ("%p", self);
1202
1203 self->priv->dispatching_started = TRUE;
1204
1205 if (tp_proxy_has_interface_by_id (self->priv->tp_conn,
1206 TP_IFACE_QUARK_CONNECTION_INTERFACE_REQUESTS))
1207 mcd_connection_setup_requests (self);
1208 else
1209 mcd_connection_setup_pre_requests (self);
1210
1211 /* FIXME: why is this here? if we need to update caps before and after *
1212 * connected, it should be in the call_when_ready callback. */
1213 _mcd_connection_update_client_caps (self, client_caps);
1214 }
1215
1216 void
_mcd_connection_update_client_caps(McdConnection * self,GPtrArray * client_caps)1217 _mcd_connection_update_client_caps (McdConnection *self,
1218 GPtrArray *client_caps)
1219 {
1220 g_return_if_fail (MCD_IS_CONNECTION (self));
1221
1222 if (!self->priv->has_contact_capabilities_if)
1223 {
1224 DEBUG ("ContactCapabilities unsupported");
1225 return;
1226 }
1227
1228 DEBUG ("Sending client caps to connection");
1229 tp_cli_connection_interface_contact_capabilities_call_update_capabilities
1230 (self->priv->tp_conn, -1, client_caps, NULL, NULL, NULL, NULL);
1231 }
1232
1233 static void
mcd_connection_done_task_before_connect(McdConnection * self)1234 mcd_connection_done_task_before_connect (McdConnection *self)
1235 {
1236 if (--self->priv->tasks_before_connect == 0)
1237 {
1238 if (self->priv->tp_conn == NULL)
1239 {
1240 DEBUG ("TpConnection went away, not doing anything");
1241 }
1242
1243 if (tp_proxy_has_interface_by_id (self->priv->tp_conn,
1244 TP_IFACE_QUARK_CONNECTION_INTERFACE_REQUESTS))
1245 {
1246 _mcd_dispatcher_add_connection (self->priv->dispatcher, self);
1247 }
1248
1249 DEBUG ("%s: Calling Connect()",
1250 tp_proxy_get_object_path (self->priv->tp_conn));
1251 tp_cli_connection_call_connect (self->priv->tp_conn, -1, connect_cb,
1252 self->priv, NULL, (GObject *) self);
1253 }
1254 }
1255
1256 static void
mcd_connection_early_get_statuses_cb(TpProxy * proxy,const GValue * v_statuses,const GError * error,gpointer user_data,GObject * weak_object)1257 mcd_connection_early_get_statuses_cb (TpProxy *proxy,
1258 const GValue *v_statuses,
1259 const GError *error,
1260 gpointer user_data,
1261 GObject *weak_object)
1262 {
1263 McdConnection *self = MCD_CONNECTION (weak_object);
1264
1265 if (self->priv->tp_conn != (TpConnection *) proxy)
1266 {
1267 DEBUG ("Connection %p has been replaced with %p, stopping",
1268 proxy, self->priv->tp_conn);
1269 return;
1270 }
1271
1272 /* This is before we called Connect(), and may or may not be before
1273 * connection-ready has been signalled. */
1274
1275 if (error == NULL)
1276 {
1277 DEBUG ("%s: Early Get(Statuses) succeeded",
1278 tp_proxy_get_object_path (proxy));
1279
1280 /* This will trigger a call to SetPresence, but don't wait for that to
1281 * finish before calling Connect (there's no need to). */
1282 presence_get_statuses_cb (proxy, v_statuses, error, self->priv,
1283 weak_object);
1284 }
1285 else
1286 {
1287 DEBUG ("%s: Early Get(Statuses) failed (not a problem, will try "
1288 "again later): %s #%d: %s",
1289 tp_proxy_get_object_path (proxy),
1290 g_quark_to_string (error->domain), error->code, error->message);
1291 }
1292
1293 mcd_connection_done_task_before_connect (self);
1294 }
1295
1296 static void
mcd_connection_early_get_interfaces_cb(TpConnection * tp_conn,const gchar ** interfaces,const GError * error,gpointer user_data,GObject * weak_object)1297 mcd_connection_early_get_interfaces_cb (TpConnection *tp_conn,
1298 const gchar **interfaces,
1299 const GError *error,
1300 gpointer user_data,
1301 GObject *weak_object)
1302 {
1303 McdConnection *self = MCD_CONNECTION (weak_object);
1304 const gchar **iter;
1305
1306 if (self->priv->tp_conn != tp_conn)
1307 {
1308 DEBUG ("Connection %p has been replaced with %p, stopping",
1309 tp_conn, self->priv->tp_conn);
1310 return;
1311 }
1312
1313 if (error != NULL)
1314 {
1315 DEBUG ("%s: Early GetInterfaces failed (not a problem, will try "
1316 "again later): %s #%d: %s",
1317 tp_proxy_get_object_path (tp_conn),
1318 g_quark_to_string (error->domain), error->code, error->message);
1319 }
1320 else
1321 {
1322 for (iter = interfaces; *iter != NULL; iter++)
1323 {
1324 GQuark q = g_quark_try_string (*iter);
1325
1326 /* if the interface is not recognised, q will just be 0 */
1327
1328 if (q == TP_IFACE_QUARK_CONNECTION_INTERFACE_SIMPLE_PRESENCE)
1329 {
1330 /* nail on the interface (TpConnection will eventually know
1331 * how to do this for itself) */
1332 tp_proxy_add_interface_by_id ((TpProxy *) tp_conn, q);
1333 self->priv->has_presence_if = TRUE;
1334
1335 self->priv->tasks_before_connect++;
1336
1337 tp_cli_dbus_properties_call_get (tp_conn, -1,
1338 TP_IFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE, "Statuses",
1339 mcd_connection_early_get_statuses_cb, NULL, NULL,
1340 (GObject *) self);
1341 }
1342 else if (q == TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_CAPABILITIES)
1343 {
1344 GPtrArray *client_caps;
1345
1346 /* nail on the interface (TpConnection will eventually know
1347 * how to do this for itself) */
1348 tp_proxy_add_interface_by_id ((TpProxy *) tp_conn, q);
1349 self->priv->has_contact_capabilities_if = TRUE;
1350
1351 /* we don't need to delay Connect for this, it can be
1352 * fire-and-forget */
1353
1354 client_caps = _mcd_dispatcher_dup_client_caps (
1355 self->priv->dispatcher);
1356
1357 if (client_caps != NULL)
1358 {
1359 _mcd_connection_update_client_caps (self, client_caps);
1360 g_ptr_array_foreach (client_caps,
1361 (GFunc) g_value_array_free, NULL);
1362 g_ptr_array_unref (client_caps);
1363 }
1364 /* else the McdDispatcher hasn't sorted itself out yet, so
1365 * we can't usefully pre-load capabilities - we'll be told
1366 * the real capabilities as soon as it has worked them out */
1367 }
1368 else if (q == TP_IFACE_QUARK_CONNECTION_INTERFACE_REQUESTS)
1369 {
1370 /* If we have the Requests iface, we could start dispatching
1371 * before the connection is in CONNECTED state */
1372 tp_proxy_add_interface_by_id ((TpProxy *) tp_conn, q);
1373 }
1374 }
1375 }
1376
1377 mcd_connection_done_task_before_connect (self);
1378 }
1379
1380 static gchar *
translate_g_error(GQuark domain,gint code,const gchar * message)1381 translate_g_error (GQuark domain,
1382 gint code,
1383 const gchar *message)
1384 {
1385 if (domain == TP_ERROR)
1386 {
1387 return g_strdup (tp_error_get_dbus_name (code));
1388 }
1389 else if (domain == TP_DBUS_ERRORS)
1390 {
1391 switch (code)
1392 {
1393 case TP_DBUS_ERROR_UNKNOWN_REMOTE_ERROR:
1394 {
1395 const gchar *p = strchr (message, ':');
1396
1397 if (p != NULL)
1398 {
1399 gchar *tmp = g_strndup (message, p - message);
1400
1401 /* The syntactic restrictions for error names are the same
1402 * as for interface names. */
1403 if (g_dbus_is_interface_name (tmp))
1404 return tmp;
1405
1406 g_free (tmp);
1407 }
1408 }
1409 break;
1410
1411 case TP_DBUS_ERROR_NO_INTERFACE:
1412 return g_strdup (DBUS_ERROR_UNKNOWN_INTERFACE);
1413
1414 case TP_DBUS_ERROR_NAME_OWNER_LOST:
1415 return g_strdup (DBUS_ERROR_NAME_HAS_NO_OWNER);
1416 }
1417 }
1418
1419 /* catch-all */
1420 return g_strdup (DBUS_ERROR_FAILED);
1421 }
1422
1423 static void
request_connection_cb(TpConnectionManager * proxy,const gchar * bus_name,const gchar * obj_path,const GError * tperror,gpointer user_data,GObject * weak_object)1424 request_connection_cb (TpConnectionManager *proxy, const gchar *bus_name,
1425 const gchar *obj_path, const GError *tperror,
1426 gpointer user_data, GObject *weak_object)
1427 {
1428 TpWeakRef *weak_ref = user_data;
1429 McdConnection *connection = tp_weak_ref_dup_object (weak_ref);
1430 McdConnectionPrivate *priv;
1431 GError *error = NULL;
1432
1433 if (connection == NULL || connection->priv->closed)
1434 {
1435 DEBUG ("RequestConnection returned after we'd decided not to use this "
1436 "connection");
1437
1438 /* We no longer want this connection, in fact */
1439 if (tperror != NULL)
1440 {
1441 DEBUG ("It failed anyway: %s", tperror->message);
1442 }
1443 else
1444 {
1445 /* no point in making a TpConnection for something we're just
1446 * going to throw away */
1447 DBusGProxy *tmp_proxy = dbus_g_proxy_new_for_name
1448 (tp_proxy_get_dbus_connection (proxy),
1449 bus_name, obj_path, TP_IFACE_CONNECTION);
1450
1451 DEBUG ("Disconnecting it: %s", obj_path);
1452 dbus_g_proxy_call_no_reply (tmp_proxy, "Disconnect",
1453 G_TYPE_INVALID);
1454 g_object_unref (tmp_proxy);
1455 }
1456
1457 if (connection != NULL)
1458 {
1459 g_signal_emit (connection, signals[CONNECTION_STATUS_CHANGED], 0,
1460 TP_CONNECTION_STATUS_DISCONNECTED,
1461 TP_CONNECTION_STATUS_REASON_REQUESTED,
1462 NULL, "", NULL);
1463 }
1464
1465 goto finally;
1466 }
1467
1468 priv = connection->priv;
1469
1470 if (tperror)
1471 {
1472 gchar *dbus_error = translate_g_error (tperror->domain,
1473 tperror->code, tperror->message);
1474 GHashTable *details = tp_asv_new (
1475 "debug-message", G_TYPE_STRING, tperror->message,
1476 NULL);
1477
1478 g_warning ("%s: RequestConnection failed: %s",
1479 G_STRFUNC, tperror->message);
1480
1481 g_signal_emit (connection, signals[CONNECTION_STATUS_CHANGED], 0,
1482 TP_CONNECTION_STATUS_DISCONNECTED,
1483 TP_CONNECTION_STATUS_REASON_NONE_SPECIFIED, NULL,
1484 dbus_error, details);
1485 g_hash_table_unref (details);
1486 g_free (dbus_error);
1487 goto finally;
1488 }
1489
1490 DEBUG ("created %s", obj_path);
1491
1492 _mcd_connection_set_tp_connection (connection, bus_name, obj_path, &error);
1493 if (G_UNLIKELY (error))
1494 {
1495 g_warning ("%s: got error: %s", G_STRFUNC, error->message);
1496 g_error_free (error);
1497 goto finally;
1498 }
1499
1500 priv->tasks_before_connect = 1;
1501
1502 /* TpConnection doesn't yet know how to get this information before
1503 * the Connection goes to CONNECTED, so we'll have to do it ourselves */
1504 tp_cli_connection_call_get_interfaces (priv->tp_conn, -1,
1505 mcd_connection_early_get_interfaces_cb, NULL, NULL,
1506 (GObject *) connection);
1507
1508 finally:
1509 g_clear_object (&connection);
1510 /* weak_ref is freed by the telepathy-glib call's destructor */
1511 }
1512
1513 static void
_mcd_connection_connect_with_params(McdConnection * connection,GHashTable * params)1514 _mcd_connection_connect_with_params (McdConnection *connection,
1515 GHashTable *params)
1516 {
1517 McdConnectionPrivate *priv = connection->priv;
1518 const gchar *protocol_name;
1519
1520 protocol_name = mcd_account_get_protocol_name (priv->account);
1521
1522 DEBUG ("Trying connect account: %s",
1523 mcd_account_get_unique_name (priv->account));
1524
1525 g_signal_emit (connection, signals[CONNECTION_STATUS_CHANGED], 0,
1526 TP_CONNECTION_STATUS_CONNECTING,
1527 TP_CONNECTION_STATUS_REASON_REQUESTED, NULL, NULL, NULL);
1528
1529 /* If the McdConnection gets aborted (which results in it being freed!),
1530 * we need to kill off the Connection. So, we can't use connection as the
1531 * weak_object.
1532 *
1533 * A better design in MC 5.3.x would be for the McdConnection to have
1534 * a ref held for the duration of this call, not be freed, and signal
1535 * that it is no longer useful in some way other than getting aborted. */
1536 tp_cli_connection_manager_call_request_connection (priv->tp_conn_mgr, -1,
1537 protocol_name, params, request_connection_cb,
1538 tp_weak_ref_new (connection, NULL, NULL),
1539 (GDestroyNotify) tp_weak_ref_destroy, NULL);
1540 }
1541
1542 static void
_mcd_connection_finalize(GObject * object)1543 _mcd_connection_finalize (GObject * object)
1544 {
1545 McdConnection *connection = MCD_CONNECTION (object);
1546 McdConnectionPrivate *priv = MCD_CONNECTION_PRIV (connection);
1547
1548 if (priv->recognized_presences)
1549 g_hash_table_unref (priv->recognized_presences);
1550
1551 tp_clear_pointer (&priv->service_point_handles, tp_intset_destroy);
1552 tp_clear_pointer (&priv->service_point_ids, g_hash_table_unref);
1553
1554 G_OBJECT_CLASS (mcd_connection_parent_class)->finalize (object);
1555 }
1556
1557 static void
_mcd_connection_release_tp_connection(McdConnection * connection,McdInhibit * inhibit)1558 _mcd_connection_release_tp_connection (McdConnection *connection,
1559 McdInhibit *inhibit)
1560 {
1561 McdConnectionPrivate *priv = MCD_CONNECTION_PRIV (connection);
1562
1563 DEBUG ("%p", connection);
1564
1565 if (priv->abort_reason == TP_CONNECTION_STATUS_REASON_REQUESTED)
1566 {
1567 g_signal_emit (connection, signals[CONNECTION_STATUS_CHANGED], 0,
1568 TP_CONNECTION_STATUS_DISCONNECTED,
1569 priv->abort_reason, priv->tp_conn, "", NULL);
1570 }
1571 else
1572 {
1573 const gchar *dbus_error = NULL;
1574 const GHashTable *details = NULL;
1575
1576 if (priv->tp_conn != NULL)
1577 {
1578 dbus_error = tp_connection_get_detailed_error (priv->tp_conn,
1579 &details);
1580 }
1581
1582 g_signal_emit (connection, signals[CONNECTION_STATUS_CHANGED], 0,
1583 TP_CONNECTION_STATUS_DISCONNECTED,
1584 priv->abort_reason, priv->tp_conn, dbus_error, details);
1585 }
1586
1587 if (priv->tp_conn)
1588 {
1589 /* Disconnect signals */
1590 g_signal_handlers_disconnect_by_func (priv->tp_conn,
1591 G_CALLBACK (on_connection_status_changed),
1592 connection);
1593 g_signal_handlers_disconnect_by_func (G_OBJECT (priv->tp_conn),
1594 G_CALLBACK (mcd_connection_invalidated_cb), connection);
1595
1596 _mcd_connection_call_disconnect (connection, inhibit);
1597
1598 /* the tp_connection has gone away, so we no longer need (or want) *
1599 the probation timer to go off: there's nothing for it to check */
1600 if (priv->probation_timer > 0)
1601 {
1602 g_source_remove (priv->probation_timer);
1603 priv->probation_timer = 0;
1604 }
1605
1606 tp_clear_object (&priv->tp_conn);
1607 }
1608
1609 if (priv->recognized_presences)
1610 g_hash_table_remove_all (priv->recognized_presences);
1611
1612 priv->dispatching_started = FALSE;
1613 }
1614
1615 static void
on_account_removed(McdAccount * account,McdConnection * connection)1616 on_account_removed (McdAccount *account, McdConnection *connection)
1617 {
1618 DEBUG ("Account %s removed, aborting connection",
1619 mcd_account_get_unique_name (account));
1620 mcd_mission_abort (MCD_MISSION (connection));
1621 }
1622
1623 static void
on_inactivity_changed(McdSlacker * slacker,gboolean inactive,McdConnection * self)1624 on_inactivity_changed (McdSlacker *slacker,
1625 gboolean inactive,
1626 McdConnection *self)
1627 {
1628 McdConnectionPrivate *priv = self->priv;
1629 DEBUG ("%sactive, connection %s have power saving iface.", inactive ? "in" : "",
1630 priv->has_power_saving_if ? "does" : "doesn't");
1631
1632 if (priv->tp_conn != NULL && priv->has_power_saving_if)
1633 tp_cli_connection_interface_power_saving_call_set_power_saving (priv->tp_conn, -1,
1634 inactive, NULL, NULL, NULL, NULL);
1635 }
1636
1637 static void
_mcd_connection_constructed(GObject * object)1638 _mcd_connection_constructed (GObject * object)
1639 {
1640 McdConnection *self = MCD_CONNECTION (object);
1641 McdConnectionPrivate *priv = self->priv;
1642
1643 if (priv->slacker != NULL)
1644 g_signal_connect (priv->slacker, "inactivity-changed",
1645 G_CALLBACK (on_inactivity_changed), self);
1646 }
1647
1648 static void
_mcd_connection_dispose(GObject * object)1649 _mcd_connection_dispose (GObject * object)
1650 {
1651 McdConnection *connection = MCD_CONNECTION (object);
1652 McdConnectionPrivate *priv = MCD_CONNECTION_PRIV (connection);
1653
1654 DEBUG ("called for object %p", object);
1655
1656 if (priv->is_disposed)
1657 {
1658 return;
1659 }
1660
1661 priv->is_disposed = TRUE;
1662
1663 if (priv->probation_timer)
1664 {
1665 g_source_remove (priv->probation_timer);
1666 priv->probation_timer = 0;
1667 }
1668
1669 if (priv->reconnect_timer)
1670 {
1671 g_source_remove (priv->reconnect_timer);
1672 priv->reconnect_timer = 0;
1673 }
1674
1675 mcd_operation_foreach (MCD_OPERATION (connection),
1676 (GFunc) _foreach_channel_remove, connection);
1677
1678 _mcd_connection_release_tp_connection (connection, NULL);
1679 g_assert (priv->tp_conn == NULL);
1680
1681 if (priv->account)
1682 {
1683 g_signal_handlers_disconnect_by_func (priv->account,
1684 G_CALLBACK (on_account_removed),
1685 object);
1686 tp_clear_object (&priv->account);
1687 }
1688
1689 if (priv->slacker != NULL)
1690 {
1691 g_signal_handlers_disconnect_by_func (priv->slacker,
1692 G_CALLBACK (on_inactivity_changed),
1693 connection);
1694
1695 tp_clear_object (&priv->slacker);
1696 }
1697
1698 tp_clear_object (&priv->tp_conn_mgr);
1699 tp_clear_object (&priv->dispatcher);
1700 tp_clear_object (&priv->client_factory);
1701
1702 G_OBJECT_CLASS (mcd_connection_parent_class)->dispose (object);
1703 }
1704
1705 static void
_mcd_connection_set_property(GObject * obj,guint prop_id,const GValue * val,GParamSpec * pspec)1706 _mcd_connection_set_property (GObject * obj, guint prop_id,
1707 const GValue * val, GParamSpec * pspec)
1708 {
1709 McdDispatcher *dispatcher;
1710 McdAccount *account;
1711 TpConnectionManager *tp_conn_mgr;
1712 McdConnectionPrivate *priv = MCD_CONNECTION_PRIV (obj);
1713
1714 switch (prop_id)
1715 {
1716 case PROP_DISPATCHER:
1717 dispatcher = g_value_get_object (val);
1718 if (dispatcher)
1719 {
1720 g_return_if_fail (MCD_IS_DISPATCHER (dispatcher));
1721 g_object_ref (dispatcher);
1722 }
1723 tp_clear_object (&priv->dispatcher);
1724 priv->dispatcher = dispatcher;
1725 break;
1726
1727 case PROP_CLIENT_FACTORY:
1728 g_assert (priv->client_factory == NULL); /* construct-only */
1729 priv->client_factory = g_value_dup_object (val);
1730 break;
1731
1732 case PROP_TP_MANAGER:
1733 tp_conn_mgr = g_value_get_object (val);
1734 g_object_ref (tp_conn_mgr);
1735 tp_clear_object (&priv->tp_conn_mgr);
1736 priv->tp_conn_mgr = tp_conn_mgr;
1737 break;
1738 case PROP_ACCOUNT:
1739 account = g_value_get_object (val);
1740 g_return_if_fail (MCD_IS_ACCOUNT (account));
1741 g_object_ref (account);
1742 priv->account = account;
1743 g_signal_connect (priv->account, "removed",
1744 G_CALLBACK (on_account_removed),
1745 obj);
1746 _mcd_account_set_connection (account, MCD_CONNECTION (obj));
1747 break;
1748 case PROP_SLACKER:
1749 g_assert (priv->slacker == NULL);
1750 priv->slacker = g_value_dup_object (val);
1751 break;
1752 default:
1753 G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
1754 break;
1755 }
1756 }
1757
1758 static void
_mcd_connection_get_property(GObject * obj,guint prop_id,GValue * val,GParamSpec * pspec)1759 _mcd_connection_get_property (GObject * obj, guint prop_id,
1760 GValue * val, GParamSpec * pspec)
1761 {
1762 McdConnectionPrivate *priv = MCD_CONNECTION_PRIV (obj);
1763
1764 switch (prop_id)
1765 {
1766 case PROP_ACCOUNT:
1767 g_value_set_object (val, priv->account);
1768 break;
1769 case PROP_TP_MANAGER:
1770 g_value_set_object (val, priv->tp_conn_mgr);
1771 break;
1772 case PROP_TP_CONNECTION:
1773 g_value_set_object (val, priv->tp_conn);
1774 break;
1775 case PROP_DISPATCHER:
1776 g_value_set_object (val, priv->dispatcher);
1777 break;
1778 case PROP_SLACKER:
1779 g_value_set_object (val, priv->slacker);
1780 break;
1781 default:
1782 G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
1783 break;
1784 }
1785 }
1786
1787 /*
1788 * mcd_connection_need_dispatch:
1789 * @connection: the #McdConnection.
1790 * @object_path: the object path of the new channel (only for debugging)
1791 * @props: the properties of the new channel
1792 *
1793 * This functions must be called in response to a NewChannels signals, and is
1794 * responsible for deciding whether MC must handle the channels or not.
1795 */
1796 static gboolean
mcd_connection_need_dispatch(McdConnection * connection,const gchar * object_path,GHashTable * props)1797 mcd_connection_need_dispatch (McdConnection *connection,
1798 const gchar *object_path,
1799 GHashTable *props)
1800 {
1801 McdAccount *account = mcd_connection_get_account (connection);
1802 gboolean requested = FALSE, requested_by_us = FALSE;
1803
1804 if (_mcd_account_needs_dispatch (account))
1805 {
1806 DEBUG ("Account %s must always be dispatched, bypassing checks",
1807 mcd_account_get_object_path (account));
1808 return TRUE;
1809 }
1810
1811 /* We must _not_ handle channels that have the Requested flag set but that
1812 * have no McdChannel object associated: these are the channels directly
1813 * requested to the CM by some other application, and we must ignore them
1814 */
1815
1816 requested = tp_asv_get_boolean (props, TP_IFACE_CHANNEL ".Requested",
1817 NULL);
1818 if (requested)
1819 {
1820 if (mcd_connection_find_channel_by_path (connection, object_path))
1821 requested_by_us = TRUE;
1822 }
1823
1824 /* handle only bundles which were not requested or that were requested
1825 * through MC */
1826 return !requested || requested_by_us;
1827 }
1828
1829 gboolean
_mcd_connection_target_id_is_urgent(McdConnection * self,const gchar * name)1830 _mcd_connection_target_id_is_urgent (McdConnection *self,
1831 const gchar *name)
1832 {
1833 return self->priv->service_point_ids != NULL &&
1834 g_hash_table_contains (self->priv->service_point_ids, name);
1835 }
1836
1837 gboolean
_mcd_connection_target_handle_is_urgent(McdConnection * self,guint handle)1838 _mcd_connection_target_handle_is_urgent (McdConnection *self,
1839 guint handle)
1840 {
1841 return self->priv->service_point_handles != NULL &&
1842 tp_intset_is_member (self->priv->service_point_handles, handle);
1843 }
1844
1845 static gboolean
_mcd_connection_request_channel(McdConnection * connection,McdChannel * channel)1846 _mcd_connection_request_channel (McdConnection *connection,
1847 McdChannel *channel)
1848 {
1849 McdConnectionPrivate *priv = MCD_CONNECTION_PRIV (connection);
1850 gboolean ret;
1851
1852 g_return_val_if_fail (priv->tp_conn != NULL, FALSE);
1853 g_return_val_if_fail (TP_IS_CONNECTION (priv->tp_conn), FALSE);
1854
1855 if (!tp_proxy_is_prepared (priv->tp_conn, TP_CONNECTION_FEATURE_CONNECTED))
1856 {
1857 /* don't request any channel until the connection is ready (because we
1858 * don't know if the CM implements the Requests interface). The channel
1859 * will be processed once the connection is ready */
1860 return TRUE;
1861 }
1862
1863 if (tp_proxy_has_interface_by_id (priv->tp_conn,
1864 TP_IFACE_QUARK_CONNECTION_INTERFACE_REQUESTS))
1865 {
1866 ret = request_channel_new_iface (connection, channel);
1867 }
1868 else
1869 {
1870 mcd_channel_take_error (channel,
1871 g_error_new (TP_ERROR,
1872 TP_ERROR_NOT_IMPLEMENTED,
1873 "No Requests interface"));
1874 mcd_mission_abort ((McdMission *) channel);
1875 return TRUE;
1876 }
1877
1878 if (ret)
1879 _mcd_channel_set_status (channel, MCD_CHANNEL_STATUS_REQUESTED);
1880 return ret;
1881 }
1882
1883 static void
mcd_connection_class_init(McdConnectionClass * klass)1884 mcd_connection_class_init (McdConnectionClass * klass)
1885 {
1886 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1887 g_type_class_add_private (object_class, sizeof (McdConnectionPrivate));
1888
1889 object_class->finalize = _mcd_connection_finalize;
1890 object_class->dispose = _mcd_connection_dispose;
1891 object_class->constructed = _mcd_connection_constructed;
1892 object_class->set_property = _mcd_connection_set_property;
1893 object_class->get_property = _mcd_connection_get_property;
1894
1895 _mcd_ext_register_dbus_glib_marshallers ();
1896
1897 tp_connection_init_known_interfaces ();
1898
1899 /* Properties */
1900 g_object_class_install_property
1901 (object_class, PROP_DISPATCHER,
1902 g_param_spec_object ("dispatcher",
1903 "Dispatcher",
1904 "Dispatcher",
1905 MCD_TYPE_DISPATCHER,
1906 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1907
1908 g_object_class_install_property (object_class, PROP_CLIENT_FACTORY,
1909 g_param_spec_object ("client-factory", "Client factory",
1910 "Client factory", TP_TYPE_SIMPLE_CLIENT_FACTORY,
1911 G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
1912
1913 g_object_class_install_property
1914 (object_class, PROP_TP_MANAGER,
1915 g_param_spec_object ("tp-manager",
1916 "Telepathy Manager",
1917 "Telepathy Manager",
1918 TP_TYPE_CONNECTION_MANAGER,
1919 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1920 g_object_class_install_property
1921 (object_class, PROP_TP_CONNECTION,
1922 g_param_spec_object ("tp-connection",
1923 "Telepathy Connection",
1924 "Telepathy Connection",
1925 TP_TYPE_CONNECTION,
1926 G_PARAM_READABLE));
1927 g_object_class_install_property
1928 (object_class, PROP_ACCOUNT,
1929 g_param_spec_object ("account",
1930 "Account",
1931 "Account",
1932 MCD_TYPE_ACCOUNT,
1933 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1934 g_object_class_install_property
1935 (object_class, PROP_SLACKER,
1936 g_param_spec_object ("slacker",
1937 "MCE slacker",
1938 "Slacker object notifies us of user inactivity",
1939 MCD_TYPE_SLACKER,
1940 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1941
1942 /**
1943 * @status:
1944 * @status_reason:
1945 * @connection:
1946 * @dbus_error: a D-Bus error name, or %NULL
1947 * @details: a #GHashTable from string to #GValue, or %NULL
1948 */
1949 signals[CONNECTION_STATUS_CHANGED] = g_signal_new (
1950 "connection-status-changed", G_OBJECT_CLASS_TYPE (klass),
1951 G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, 0,
1952 NULL, NULL, NULL,
1953 G_TYPE_NONE, 5, G_TYPE_UINT, G_TYPE_UINT, TP_TYPE_CONNECTION,
1954 G_TYPE_STRING, G_TYPE_HASH_TABLE);
1955
1956 signals[READY] = g_signal_new ("ready",
1957 G_OBJECT_CLASS_TYPE (klass),
1958 G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, 0,
1959 NULL, NULL, g_cclosure_marshal_VOID__VOID,
1960 G_TYPE_NONE, 0);
1961 }
1962
1963 static void
mcd_connection_init(McdConnection * connection)1964 mcd_connection_init (McdConnection * connection)
1965 {
1966 McdConnectionPrivate *priv;
1967
1968 priv = G_TYPE_INSTANCE_GET_PRIVATE (connection, MCD_TYPE_CONNECTION,
1969 McdConnectionPrivate);
1970 connection->priv = priv;
1971
1972 priv->abort_reason = TP_CONNECTION_STATUS_REASON_NONE_SPECIFIED;
1973
1974 priv->reconnect_interval = INITIAL_RECONNECTION_TIME;
1975 }
1976
1977 /* Public methods */
1978
1979 /* Constant getters. These should probably be removed */
1980
1981 McdAccount *
mcd_connection_get_account(McdConnection * id)1982 mcd_connection_get_account (McdConnection * id)
1983 {
1984 McdConnectionPrivate *priv = MCD_CONNECTION_PRIV (id);
1985 return priv->account;
1986 }
1987
1988 static void
common_request_channel_cb(TpConnection * proxy,gboolean yours,const gchar * channel_path,GHashTable * properties,const GError * error,McdConnection * connection,McdChannel * channel)1989 common_request_channel_cb (TpConnection *proxy, gboolean yours,
1990 const gchar *channel_path, GHashTable *properties,
1991 const GError *error,
1992 McdConnection *connection, McdChannel *channel)
1993 {
1994 McdConnectionPrivate *priv = connection->priv;
1995
1996 if (error != NULL)
1997 {
1998 GError *mc_error;
1999
2000 /* No special handling of "no capabilities" error: being confident that
2001 * https://bugs.freedesktop.org/show_bug.cgi?id=15769 will be fixed
2002 * soon :-) */
2003 DEBUG ("got error: %s", error->message);
2004 mc_error = g_error_copy (error);
2005 mcd_channel_take_error (channel, mc_error);
2006 mcd_mission_abort ((McdMission *)channel);
2007 return;
2008 }
2009 DEBUG ("%p, object %s", channel, channel_path);
2010
2011 /* if this was a call to EnsureChannel, it can happen that the returned
2012 * channel was already created before; in that case we keep the McdChannel
2013 * alive only as a proxy for the status-changed signals from the "real"
2014 * McdChannel */
2015 if (_mcd_channel_get_request_use_existing (channel))
2016 {
2017 McdChannel *existing;
2018 existing = mcd_connection_find_channel_by_path (connection,
2019 channel_path);
2020 if (existing)
2021 {
2022 _mcd_dispatcher_add_channel_request (priv->dispatcher, existing,
2023 channel);
2024 return;
2025 }
2026 }
2027
2028 /* Everything here is well and fine. We can create the channel. */
2029 if (!_mcd_channel_create_proxy (channel, priv->tp_conn,
2030 channel_path, properties))
2031 {
2032 mcd_mission_abort ((McdMission *)channel);
2033 return;
2034 }
2035
2036 /* if the channel request was cancelled, abort the channel now */
2037 if (mcd_channel_get_status (channel) == MCD_CHANNEL_STATUS_FAILED)
2038 {
2039 DEBUG ("Channel %p was cancelled, aborting", channel);
2040 _mcd_channel_close (channel);
2041 mcd_mission_abort (MCD_MISSION (channel));
2042 }
2043
2044 /* No dispatching here: the channel will be dispatched upon receiving the
2045 * NewChannels signal */
2046 }
2047
2048 static void
ensure_channel_cb(TpConnection * proxy,gboolean yours,const gchar * channel_path,GHashTable * properties,const GError * error,gpointer user_data,GObject * weak_object)2049 ensure_channel_cb (TpConnection *proxy, gboolean yours,
2050 const gchar *channel_path, GHashTable *properties,
2051 const GError *error,
2052 gpointer user_data, GObject *weak_object)
2053 {
2054 common_request_channel_cb (proxy, yours, channel_path, properties, error,
2055 MCD_CONNECTION (user_data),
2056 MCD_CHANNEL (weak_object));
2057 }
2058
2059 static void
create_channel_cb(TpConnection * proxy,const gchar * channel_path,GHashTable * properties,const GError * error,gpointer user_data,GObject * weak_object)2060 create_channel_cb (TpConnection *proxy, const gchar *channel_path,
2061 GHashTable *properties, const GError *error,
2062 gpointer user_data, GObject *weak_object)
2063 {
2064 common_request_channel_cb (proxy, TRUE, channel_path, properties, error,
2065 MCD_CONNECTION (user_data),
2066 MCD_CHANNEL (weak_object));
2067 }
2068
2069 static gboolean
request_channel_new_iface(McdConnection * connection,McdChannel * channel)2070 request_channel_new_iface (McdConnection *connection, McdChannel *channel)
2071 {
2072 McdConnectionPrivate *priv = MCD_CONNECTION_PRIV (connection);
2073 GHashTable *properties;
2074
2075 properties = _mcd_channel_get_requested_properties (channel);
2076 if (_mcd_channel_get_request_use_existing (channel))
2077 {
2078 /* Timeout of 5 hours: 5 * 3600 * 1000 */
2079 tp_cli_connection_interface_requests_call_ensure_channel
2080 (priv->tp_conn, 18000000, properties, ensure_channel_cb,
2081 connection, NULL, (GObject *)channel);
2082 }
2083 else
2084 {
2085 /* Timeout of 5 hours: 5 * 3600 * 1000 */
2086 tp_cli_connection_interface_requests_call_create_channel
2087 (priv->tp_conn, 18000000, properties, create_channel_cb,
2088 connection, NULL, (GObject *)channel);
2089 }
2090 return TRUE;
2091 }
2092
2093 gboolean
mcd_connection_request_channel(McdConnection * connection,McdChannel * channel)2094 mcd_connection_request_channel (McdConnection *connection,
2095 McdChannel *channel)
2096 {
2097 g_return_val_if_fail (MCD_IS_CONNECTION (connection), FALSE);
2098 g_return_val_if_fail (MCD_IS_CHANNEL (channel), FALSE);
2099
2100 if (mcd_channel_get_status (channel) == MCD_CHANNEL_STATUS_FAILED)
2101 {
2102 DEBUG ("Channel %p failed already, never mind", channel);
2103 _mcd_channel_close (channel);
2104 mcd_mission_abort (MCD_MISSION (channel));
2105 /* FIXME: the boolean return is a decoy - everyone returns TRUE and
2106 * every caller ignores it - so it's not clear what FALSE would
2107 * mean. */
2108 return TRUE;
2109 }
2110
2111 if (!mcd_mission_get_parent ((McdMission *)channel))
2112 mcd_operation_take_mission (MCD_OPERATION (connection),
2113 MCD_MISSION (channel));
2114
2115 return _mcd_connection_request_channel (connection, channel);
2116 }
2117
2118 void
mcd_connection_close(McdConnection * connection,McdInhibit * inhibit)2119 mcd_connection_close (McdConnection *connection,
2120 McdInhibit *inhibit)
2121 {
2122 g_return_if_fail (MCD_IS_CONNECTION (connection));
2123
2124 connection->priv->closed = TRUE;
2125 connection->priv->abort_reason = TP_CONNECTION_STATUS_REASON_REQUESTED;
2126 _mcd_connection_release_tp_connection (connection, inhibit);
2127 mcd_mission_abort (MCD_MISSION (connection));
2128 }
2129
2130 /*
2131 * _mcd_connection_connect:
2132 * @connection: the #McdConnection.
2133 * @params: a #GHashTable of connection parameters
2134 *
2135 * Activate @connection.
2136 */
2137 void
_mcd_connection_connect(McdConnection * connection,GHashTable * params)2138 _mcd_connection_connect (McdConnection *connection, GHashTable *params)
2139 {
2140 McdConnectionPrivate *priv;
2141 TpConnectionStatus status = TP_CONNECTION_STATUS_DISCONNECTED;
2142
2143 g_return_if_fail (MCD_IS_CONNECTION (connection));
2144 g_return_if_fail (params != NULL);
2145 priv = connection->priv;
2146
2147 g_return_if_fail (priv->tp_conn_mgr);
2148 g_return_if_fail (priv->account);
2149 DEBUG ("called for %p, account %s", connection,
2150 mcd_account_get_unique_name (priv->account));
2151
2152 if (priv->reconnect_timer)
2153 {
2154 g_source_remove (priv->reconnect_timer);
2155 priv->reconnect_timer = 0;
2156 }
2157
2158 /* the account's status can be CONNECTING _before_ we get here, because
2159 * for the account that includes things like trying to bring up an IAP
2160 * via an McdTransport plugin. So we have to use the actual status of
2161 * the connection (or DISCONNECTED if we havan't got one yet) */
2162 if (priv->tp_conn != NULL)
2163 status = tp_connection_get_status (priv->tp_conn, NULL);
2164
2165 if (status == TP_CONNECTION_STATUS_DISCONNECTED ||
2166 status == TP_UNKNOWN_CONNECTION_STATUS)
2167 {
2168 _mcd_connection_connect_with_params (connection, params);
2169 }
2170 else
2171 {
2172 DEBUG ("Not connecting because not disconnected (%i)",
2173 mcd_account_get_connection_status (priv->account));
2174 }
2175 }
2176
2177 const gchar *
mcd_connection_get_object_path(McdConnection * connection)2178 mcd_connection_get_object_path (McdConnection *connection)
2179 {
2180 McdConnectionPrivate *priv = connection->priv;
2181
2182 if (priv->tp_conn)
2183 return tp_proxy_get_object_path (TP_PROXY (priv->tp_conn));
2184 else
2185 return NULL;
2186 }
2187
2188 const gchar *
mcd_connection_get_name(McdConnection * connection)2189 mcd_connection_get_name (McdConnection *connection)
2190 {
2191 McdConnectionPrivate *priv = connection->priv;
2192
2193 if (priv->tp_conn)
2194 return tp_proxy_get_bus_name (TP_PROXY (priv->tp_conn));
2195 else
2196 return NULL;
2197 }
2198
2199 /**
2200 * _mcd_connection_update_property:
2201 * @connection: the #McdConnection.
2202 * @name: the qualified name of the property to be updated.
2203 * @value: the new value of the property.
2204 *
2205 * Sets the property @name to @value.
2206 */
2207 void
_mcd_connection_update_property(McdConnection * connection,const gchar * name,const GValue * value)2208 _mcd_connection_update_property (McdConnection *connection, const gchar *name,
2209 const GValue *value)
2210 {
2211 McdConnectionPrivate *priv;
2212 const gchar *dot, *member;
2213 gchar *interface;
2214
2215 g_return_if_fail (MCD_IS_CONNECTION (connection));
2216 g_return_if_fail (name != NULL);
2217 priv = connection->priv;
2218
2219 if (G_UNLIKELY (!priv->tp_conn)) return;
2220
2221 dot = strrchr (name, '.');
2222 if (G_UNLIKELY (!dot)) return;
2223
2224 interface = g_strndup (name, dot - name);
2225 member = dot + 1;
2226 tp_cli_dbus_properties_call_set (priv->tp_conn, -1,
2227 interface, member, value,
2228 NULL, NULL, NULL, NULL);
2229 g_free (interface);
2230 }
2231
2232 void
_mcd_connection_set_tp_connection(McdConnection * connection,const gchar * bus_name,const gchar * obj_path,GError ** error)2233 _mcd_connection_set_tp_connection (McdConnection *connection,
2234 const gchar *bus_name,
2235 const gchar *obj_path, GError **error)
2236 {
2237 McdConnectionPrivate *priv;
2238 GQuark features[] = {
2239 TP_CONNECTION_FEATURE_CONNECTED,
2240 0
2241 };
2242 GError *inner_error = NULL;
2243
2244 g_return_if_fail (MCD_IS_CONNECTION (connection));
2245 g_return_if_fail (error != NULL);
2246 priv = connection->priv;
2247
2248 if (priv->tp_conn != NULL)
2249 {
2250 if (G_UNLIKELY (!tp_strdiff (tp_proxy_get_object_path (priv->tp_conn),
2251 obj_path)))
2252 {
2253 /* not really meant to happen */
2254 g_warning ("%s: We already have %s", G_STRFUNC,
2255 tp_proxy_get_object_path (priv->tp_conn));
2256 return;
2257 }
2258
2259 DEBUG ("releasing old connection first");
2260 _mcd_connection_release_tp_connection (connection, NULL);
2261 }
2262
2263 g_assert (priv->tp_conn == NULL);
2264 priv->tp_conn = tp_simple_client_factory_ensure_connection (
2265 priv->client_factory, obj_path, NULL, &inner_error);
2266 DEBUG ("new connection is %p", priv->tp_conn);
2267 if (!priv->tp_conn)
2268 {
2269 GHashTable *details = tp_asv_new (
2270 "debug-message", G_TYPE_STRING, inner_error->message,
2271 NULL);
2272
2273 /* Constructing a TpConnection can only fail from invalid arguments,
2274 * which would mean either MC or the connection manager is confused. */
2275 g_signal_emit (connection, signals[CONNECTION_STATUS_CHANGED], 0,
2276 TP_CONNECTION_STATUS_DISCONNECTED,
2277 TP_CONNECTION_STATUS_REASON_NONE_SPECIFIED,
2278 NULL, TP_ERROR_STR_CONFUSED, details);
2279
2280 g_hash_table_unref (details);
2281 g_propagate_error (error, inner_error);
2282 return;
2283 }
2284 /* FIXME: need some way to feed the status into the Account, but we don't
2285 * actually know it yet */
2286 _mcd_account_tp_connection_changed (priv->account, priv->tp_conn);
2287
2288 /* Setup signals */
2289 g_signal_connect (priv->tp_conn, "invalidated",
2290 G_CALLBACK (mcd_connection_invalidated_cb), connection);
2291 g_signal_connect (priv->tp_conn, "notify::status",
2292 G_CALLBACK (on_connection_status_changed),
2293 connection);
2294 /* HACK for cancelling the _call_when_ready() callback when our object gets
2295 * destroyed */
2296 tp_proxy_prepare_async (priv->tp_conn, features,
2297 on_connection_ready,
2298 tp_weak_ref_new (connection, NULL, NULL));
2299 }
2300
2301 /**
2302 * mcd_connection_get_tp_connection:
2303 * @connection: the #McdConnection.
2304 *
2305 * Returns: the #TpConnection being used, or %NULL if none.
2306 */
2307 TpConnection *
mcd_connection_get_tp_connection(McdConnection * connection)2308 mcd_connection_get_tp_connection (McdConnection *connection)
2309 {
2310 g_return_val_if_fail (MCD_IS_CONNECTION (connection), NULL);
2311 return connection->priv->tp_conn;
2312 }
2313
2314 gboolean
_mcd_connection_is_ready(McdConnection * self)2315 _mcd_connection_is_ready (McdConnection *self)
2316 {
2317 g_return_val_if_fail (MCD_IS_CONNECTION (self), FALSE);
2318
2319 return (self->priv->tp_conn != NULL) &&
2320 tp_proxy_is_prepared (self->priv->tp_conn, TP_CONNECTION_FEATURE_CONNECTED);
2321 }
2322
2323 gboolean
_mcd_connection_presence_info_is_ready(McdConnection * self)2324 _mcd_connection_presence_info_is_ready (McdConnection *self)
2325 {
2326 g_return_val_if_fail (MCD_IS_CONNECTION (self), FALSE);
2327
2328 return self->priv->presence_info_ready;
2329 }
2330
2331 void
_mcd_connection_take_emergency_numbers(McdConnection * self,GSList * numbers)2332 _mcd_connection_take_emergency_numbers (McdConnection *self,
2333 GSList *numbers)
2334 {
2335 GSList *iter;
2336
2337 if (self->priv->service_point_ids == NULL)
2338 self->priv->service_point_ids = g_hash_table_new_full (g_str_hash,
2339 g_str_equal, g_free, NULL);
2340
2341 for (iter = numbers; iter != NULL; iter = iter->next)
2342 {
2343 GStrv ids = iter->data;
2344 gchar **id_p;
2345
2346 /* We treat emergency numbers as "sticky": if a given ID has ever
2347 * been considered an emergency number, it stays an emergency number.
2348 * This seems safer than ever removing one and losing their special
2349 * treatment. */
2350 for (id_p = ids; id_p != NULL && *id_p != NULL; id_p++)
2351 g_hash_table_add (self->priv->service_point_ids, *id_p);
2352 }
2353
2354 /* GStrv members' ownership has already been transferred */
2355 g_slist_free_full (numbers, g_free);
2356 }
2357
2358 void
mcd_connection_add_emergency_handle(McdConnection * self,TpHandle handle)2359 mcd_connection_add_emergency_handle (McdConnection *self,
2360 TpHandle handle)
2361 {
2362 if (self->priv->service_point_handles == NULL)
2363 self->priv->service_point_handles = tp_intset_new ();
2364
2365 /* As above, we treat emergency numbers as "sticky". */
2366 tp_intset_add (self->priv->service_point_handles, handle);
2367 }
2368