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