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 © 2008–2010 Nokia Corporation.
7  * Copyright © 2009–2013 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 #include "config.h"
25 #include "mcd-account.h"
26 
27 #include <errno.h>
28 #include <stdio.h>
29 #include <string.h>
30 
31 #include <dbus/dbus.h>
32 #include <glib/gstdio.h>
33 #include <telepathy-glib/telepathy-glib.h>
34 #include <telepathy-glib/telepathy-glib-dbus.h>
35 
36 #include "mcd-account-priv.h"
37 #include "mcd-account-conditions.h"
38 #include "mcd-account-manager-priv.h"
39 #include "mcd-account-addressing.h"
40 #include "mcd-connection-priv.h"
41 #include "mcd-misc.h"
42 #include "mcd-manager.h"
43 #include "mcd-manager-priv.h"
44 #include "mcd-master.h"
45 #include "mcd-master-priv.h"
46 #include "mcd-dbusprop.h"
47 
48 #include "_gen/interfaces.h"
49 #include "_gen/enums.h"
50 #include "_gen/gtypes.h"
51 #include "_gen/cli-Connection_Manager_Interface_Account_Storage-body.h"
52 
53 #define MC_OLD_AVATAR_FILENAME	"avatar.bin"
54 
55 #define MCD_ACCOUNT_PRIV(account) (MCD_ACCOUNT (account)->priv)
56 
57 static void account_iface_init (TpSvcAccountClass *iface,
58 			       	gpointer iface_data);
59 static void properties_iface_init (TpSvcDBusPropertiesClass *iface,
60 				   gpointer iface_data);
61 static void account_avatar_iface_init (TpSvcAccountInterfaceAvatarClass *iface,
62 				       gpointer iface_data);
63 static void account_storage_iface_init (
64     TpSvcAccountInterfaceStorageClass *iface,
65     gpointer iface_data);
66 static void account_hidden_iface_init (
67     McSvcAccountInterfaceHiddenClass *iface,
68     gpointer iface_data);
69 static void account_external_password_storage_iface_init (
70     McSvcAccountInterfaceExternalPasswordStorageClass *iface,
71     gpointer iface_data);
72 
73 static const McdDBusProp account_properties[];
74 static const McdDBusProp account_avatar_properties[];
75 static const McdDBusProp account_storage_properties[];
76 static const McdDBusProp account_hidden_properties[];
77 static const McdDBusProp account_external_password_storage_properties[];
78 
79 static const McdInterfaceData account_interfaces[] = {
80     MCD_IMPLEMENT_IFACE (tp_svc_account_get_type, account, TP_IFACE_ACCOUNT),
81     MCD_IMPLEMENT_IFACE (tp_svc_account_interface_avatar_get_type,
82 			 account_avatar,
83 			 TP_IFACE_ACCOUNT_INTERFACE_AVATAR),
84     MCD_IMPLEMENT_IFACE (mc_svc_account_interface_conditions_get_type,
85 			 account_conditions,
86 			 MC_IFACE_ACCOUNT_INTERFACE_CONDITIONS),
87     MCD_IMPLEMENT_IFACE (tp_svc_account_interface_storage_get_type,
88                          account_storage,
89                          TP_IFACE_ACCOUNT_INTERFACE_STORAGE),
90     MCD_IMPLEMENT_IFACE (tp_svc_account_interface_addressing_get_type,
91         account_addressing,
92         TP_IFACE_ACCOUNT_INTERFACE_ADDRESSING),
93     MCD_IMPLEMENT_IFACE (mc_svc_account_interface_hidden_get_type,
94                          account_hidden,
95                          MC_IFACE_ACCOUNT_INTERFACE_HIDDEN),
96     MCD_IMPLEMENT_OPTIONAL_IFACE (
97         mc_svc_account_interface_external_password_storage_get_type,
98         account_external_password_storage,
99         MC_IFACE_ACCOUNT_INTERFACE_EXTERNAL_PASSWORD_STORAGE),
100 
101     { NULL, }
102 };
103 
104 G_DEFINE_TYPE_WITH_CODE (McdAccount, mcd_account, G_TYPE_OBJECT,
105 			 MCD_DBUS_INIT_INTERFACES (account_interfaces);
106 			 G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
107 						properties_iface_init);
108 			)
109 
110 typedef struct {
111     McdOnlineRequestCb callback;
112     gpointer user_data;
113 } McdOnlineRequestData;
114 
115 struct _McdAccountPrivate
116 {
117     gchar *unique_name;
118     gchar *object_path;
119     gchar *manager_name;
120     gchar *protocol_name;
121 
122     TpConnection *tp_connection;
123     TpContact *self_contact;
124     McdConnection *connection;
125     McdManager *manager;
126 
127     McdStorage *storage;
128     TpDBusDaemon *dbus_daemon;
129     gboolean registered;
130     McdConnectivityMonitor *connectivity;
131 
132     McdAccountConnectionContext *connection_context;
133     GKeyFile *keyfile;		/* configuration file */
134     McpAccountStorage *storage_plugin;
135     GPtrArray *supersedes;
136 
137     /* connection status */
138     TpConnectionStatus conn_status;
139     TpConnectionStatusReason conn_reason;
140     gchar *conn_dbus_error;
141     GHashTable *conn_error_details;
142 
143     /* current presence fields */
144     TpConnectionPresenceType curr_presence_type;
145     gchar *curr_presence_status;
146     gchar *curr_presence_message;
147 
148     /* requested presence fields */
149     TpConnectionPresenceType req_presence_type;
150     gchar *req_presence_status;
151     gchar *req_presence_message;
152 
153     /* automatic presence fields */
154     TpConnectionPresenceType auto_presence_type;
155     gchar *auto_presence_status; /* TODO: consider loading these from the
156 				    configuration file as needed */
157     gchar *auto_presence_message;
158 
159     GList *online_requests; /* list of McdOnlineRequestData structures
160                                (callback with user data) to be called when the
161                                account will be online */
162 
163     /* %NULL if the account is valid; a valid error for reporting over the
164      * D-Bus if the account is invalid.
165      */
166     GError *invalid_reason;
167 
168     gboolean connect_automatically;
169     gboolean enabled;
170     gboolean loaded;
171     gboolean has_been_online;
172     gboolean removed;
173     gboolean always_on;
174     gboolean changing_presence;
175     gboolean setting_avatar;
176     gboolean waiting_for_initial_avatar;
177     gboolean waiting_for_connectivity;
178 
179     gboolean hidden;
180     /* In addition to affecting dispatching, this flag also makes this
181      * account bypass connectivity checks. */
182     gboolean always_dispatch;
183 
184     /* These fields are used to cache the changed properties */
185     gboolean properties_frozen;
186     GHashTable *changed_properties;
187     guint properties_source;
188 
189     gboolean password_saved;
190 };
191 
192 enum
193 {
194     PROP_0,
195     PROP_DBUS_DAEMON,
196     PROP_CONNECTIVITY_MONITOR,
197     PROP_STORAGE,
198     PROP_NAME,
199     PROP_ALWAYS_ON,
200     PROP_HIDDEN,
201 };
202 
203 enum
204 {
205     VALIDITY_CHANGED,
206     CONNECTION_PATH_CHANGED,
207     LAST_SIGNAL
208 };
209 
210 static guint _mcd_account_signals[LAST_SIGNAL] = { 0 };
211 static GQuark account_ready_quark = 0;
212 
213 GQuark
mcd_account_error_quark(void)214 mcd_account_error_quark (void)
215 {
216     static GQuark quark = 0;
217 
218     if (quark == 0)
219         quark = g_quark_from_static_string ("mcd-account-error");
220 
221     return quark;
222 }
223 
224 /*
225  * _mcd_account_maybe_autoconnect:
226  * @account: the #McdAccount.
227  *
228  * Check whether automatic connection should happen (and attempt it if needed).
229  */
230 void
_mcd_account_maybe_autoconnect(McdAccount * account)231 _mcd_account_maybe_autoconnect (McdAccount *account)
232 {
233     McdAccountPrivate *priv;
234 
235     g_return_if_fail (MCD_IS_ACCOUNT (account));
236     priv = account->priv;
237 
238     if (!mcd_account_would_like_to_connect (account))
239     {
240         return;
241     }
242 
243     if (_mcd_account_needs_dispatch (account))
244     {
245         DEBUG ("Always-dispatchable account %s needs no transport",
246             priv->unique_name);
247     }
248     else if (mcd_connectivity_monitor_is_online (priv->connectivity))
249     {
250         DEBUG ("Account %s has connectivity, connecting",
251             priv->unique_name);
252     }
253     else
254     {
255         DEBUG ("Account %s needs connectivity, not connecting",
256             priv->unique_name);
257     }
258 
259     DEBUG ("connecting account %s", priv->unique_name);
260     _mcd_account_connect_with_auto_presence (account, FALSE);
261 }
262 
263 static gboolean
value_is_same(const GValue * val1,const GValue * val2)264 value_is_same (const GValue *val1, const GValue *val2)
265 {
266     g_return_val_if_fail (val1 != NULL && val2 != NULL, FALSE);
267     switch (G_VALUE_TYPE (val1))
268     {
269     case G_TYPE_STRING:
270         return g_strcmp0 (g_value_get_string (val1),
271                           g_value_get_string (val2)) == 0;
272     case G_TYPE_CHAR:
273     case G_TYPE_UCHAR:
274     case G_TYPE_INT:
275     case G_TYPE_UINT:
276     case G_TYPE_BOOLEAN:
277         return val1->data[0].v_uint == val2->data[0].v_uint;
278 
279     case G_TYPE_INT64:
280         return g_value_get_int64 (val1) == g_value_get_int64 (val2);
281     case G_TYPE_UINT64:
282         return g_value_get_uint64 (val1) == g_value_get_uint64 (val2);
283 
284     case G_TYPE_DOUBLE:
285         return g_value_get_double (val1) == g_value_get_double (val2);
286 
287     default:
288         if (G_VALUE_TYPE (val1) == DBUS_TYPE_G_OBJECT_PATH)
289         {
290             return !tp_strdiff (g_value_get_boxed (val1),
291                                 g_value_get_boxed (val2));
292         }
293         else if (G_VALUE_TYPE (val1) == G_TYPE_STRV)
294         {
295             gchar **left = g_value_get_boxed (val1);
296             gchar **right = g_value_get_boxed (val2);
297 
298             if (left == NULL || right == NULL ||
299                 *left == NULL || *right == NULL)
300             {
301                 return ((left == NULL || *left == NULL) &&
302                         (right == NULL || *right == NULL));
303             }
304 
305             while (*left != NULL || *right != NULL)
306             {
307                 if (tp_strdiff (*left, *right))
308                 {
309                     return FALSE;
310                 }
311 
312                 left++;
313                 right++;
314             }
315 
316             return TRUE;
317         }
318         else
319         {
320             g_warning ("%s: unexpected type %s",
321                        G_STRFUNC, G_VALUE_TYPE_NAME (val1));
322             return FALSE;
323         }
324     }
325 }
326 
327 static void
mcd_account_loaded(McdAccount * account)328 mcd_account_loaded (McdAccount *account)
329 {
330     g_return_if_fail (!account->priv->loaded);
331     account->priv->loaded = TRUE;
332 
333     /* invoke all the callbacks */
334     g_object_ref (account);
335 
336     _mcd_object_ready (account, account_ready_quark, NULL);
337 
338     if (account->priv->online_requests != NULL)
339     {
340         /* if we have established that the account is not valid or is
341          * disabled, cancel all requests */
342         if (!mcd_account_is_valid (account) || !account->priv->enabled)
343         {
344             /* FIXME: pick better errors and put them in telepathy-spec? */
345             GError e = { TP_ERROR, TP_ERROR_NOT_AVAILABLE,
346                 "account isn't Valid (not enough information to put it "
347                     "online)" };
348             GList *list;
349 
350             if (mcd_account_is_valid (account))
351             {
352                 e.message = "account isn't Enabled";
353             }
354 
355             list = account->priv->online_requests;
356             account->priv->online_requests = NULL;
357 
358             for (/* already initialized */ ;
359                  list != NULL;
360                  list = g_list_delete_link (list, list))
361             {
362                 McdOnlineRequestData *data = list->data;
363 
364                 data->callback (account, data->user_data, &e);
365                 g_slice_free (McdOnlineRequestData, data);
366             }
367         }
368 
369         /* otherwise, we want to go online now */
370         if (account->priv->conn_status == TP_CONNECTION_STATUS_DISCONNECTED)
371         {
372             _mcd_account_connect_with_auto_presence (account, TRUE);
373         }
374     }
375 
376     _mcd_account_maybe_autoconnect (account);
377 
378     g_object_unref (account);
379 }
380 
381 /*
382  * _mcd_account_set_parameter:
383  * @account: the #McdAccount.
384  * @name: the parameter name.
385  * @value: a #GValue with the value to set, or %NULL.
386  *
387  * Sets the parameter @name to the value in @value. If @value, is %NULL, the
388  * parameter is unset.
389  */
390 static void
_mcd_account_set_parameter(McdAccount * account,const gchar * name,const GValue * value)391 _mcd_account_set_parameter (McdAccount *account, const gchar *name,
392                             const GValue *value)
393 {
394     McdAccountPrivate *priv = account->priv;
395     McdStorage *storage = priv->storage;
396     const gchar *account_name = mcd_account_get_unique_name (account);
397     gboolean secret = mcd_account_parameter_is_secret (account, name);
398 
399     mcd_storage_set_parameter (storage, account_name, name, value, secret);
400 }
401 
402 static GType mc_param_type (const TpConnectionManagerParam *param);
403 
404 /**
405  * mcd_account_get_parameter:
406  * @account: the #McdAccount.
407  * @name: the parameter name.
408  * @parameter: location at which to store the parameter's current value, or
409  *  %NULL if you don't actually care about the parameter's value.
410  * @error: location at which to store an error if the parameter cannot be
411  *  retrieved.
412  *
413  * Get the @name parameter for @account.
414  *
415  * Returns: %TRUE if the parameter could be retrieved; %FALSE otherwise
416  */
417 gboolean
mcd_account_get_parameter(McdAccount * account,const gchar * name,GValue * parameter,GError ** error)418 mcd_account_get_parameter (McdAccount *account, const gchar *name,
419                            GValue *parameter,
420                            GError **error)
421 {
422     McdAccountPrivate *priv = account->priv;
423     const TpConnectionManagerParam *param;
424     GType type;
425 
426     param = mcd_manager_get_protocol_param (priv->manager,
427                                             priv->protocol_name, name);
428     type = mc_param_type (param);
429 
430     return mcd_account_get_parameter_of_known_type (account, name,
431                                                     type, parameter, error);
432 }
433 
434 gboolean
mcd_account_get_parameter_of_known_type(McdAccount * account,const gchar * name,GType type,GValue * parameter,GError ** error)435 mcd_account_get_parameter_of_known_type (McdAccount *account,
436                                          const gchar *name,
437                                          GType type,
438                                          GValue *parameter,
439                                          GError **error)
440 {
441     const gchar *account_name = mcd_account_get_unique_name (account);
442     McdStorage *storage = account->priv->storage;
443     GValue tmp = G_VALUE_INIT;
444 
445     g_value_init (&tmp, type);
446 
447     if (mcd_storage_get_parameter (storage, account_name, name, &tmp, error))
448     {
449         if (parameter != NULL)
450         {
451             memcpy (parameter, &tmp, sizeof (tmp));
452         }
453         else
454         {
455             g_value_unset (&tmp);
456         }
457 
458         return TRUE;
459     }
460 
461     g_value_unset (&tmp);
462     return FALSE;
463 }
464 
465 typedef void (*CheckParametersCb) (
466     McdAccount *account,
467     const GError *invalid_reason,
468     gpointer user_data);
469 static void mcd_account_check_parameters (McdAccount *account,
470     CheckParametersCb callback, gpointer user_data);
471 
472 static void
manager_ready_check_params_cb(McdAccount * account,const GError * invalid_reason,gpointer user_data)473 manager_ready_check_params_cb (McdAccount *account,
474     const GError *invalid_reason,
475     gpointer user_data)
476 {
477     McdAccountPrivate *priv = account->priv;
478 
479     g_clear_error (&priv->invalid_reason);
480     if (invalid_reason != NULL)
481     {
482         priv->invalid_reason = g_error_copy (invalid_reason);
483     }
484 
485     mcd_account_loaded (account);
486 }
487 
488 static void
account_external_password_storage_get_accounts_cb(TpProxy * cm,const GValue * value,const GError * in_error,gpointer user_data,GObject * self)489 account_external_password_storage_get_accounts_cb (TpProxy *cm,
490     const GValue *value,
491     const GError *in_error,
492     gpointer user_data,
493     GObject *self)
494 {
495   McdAccount *account = MCD_ACCOUNT (self);
496   const char *account_id = user_data;
497   GHashTable *map, *props;
498 
499   if (in_error != NULL)
500     {
501       DEBUG ("Failed to get Account property: %s", in_error->message);
502       return;
503     }
504 
505   g_return_if_fail (G_VALUE_HOLDS (value, MC_HASH_TYPE_ACCOUNT_FLAGS_MAP));
506 
507   map = g_value_get_boxed (value);
508 
509   account->priv->password_saved =
510     GPOINTER_TO_UINT (g_hash_table_lookup (map, account_id)) &
511       MC_ACCOUNT_FLAG_CREDENTIALS_STORED;
512 
513   DEBUG ("PasswordSaved = %u", account->priv->password_saved);
514 
515   /* emit the changed signal */
516   props = tp_asv_new (
517       "PasswordSaved", G_TYPE_BOOLEAN, account->priv->password_saved,
518       NULL);
519 
520   tp_svc_dbus_properties_emit_properties_changed (account,
521       MC_IFACE_ACCOUNT_INTERFACE_EXTERNAL_PASSWORD_STORAGE,
522       props,
523       NULL);
524 
525   g_hash_table_unref (props);
526 }
527 
528 static void
account_setup_identify_account_cb(TpProxy * protocol,const char * account_id,const GError * in_error,gpointer user_data,GObject * self)529 account_setup_identify_account_cb (TpProxy *protocol,
530     const char *account_id,
531     const GError *in_error,
532     gpointer user_data,
533     GObject *self)
534 {
535   McdAccount *account = MCD_ACCOUNT (self);
536   TpConnectionManager *cm = mcd_account_get_cm (account);
537 
538   if (in_error != NULL)
539     {
540       DEBUG ("Error identifying account: %s", in_error->message);
541       return;
542     }
543 
544   DEBUG ("Identified account as %s", account_id);
545 
546   /* look up the current value of the CM.I.AS.Accounts property
547    * and monitor future changes */
548   tp_cli_dbus_properties_call_get (cm, -1,
549       MC_IFACE_CONNECTION_MANAGER_INTERFACE_ACCOUNT_STORAGE,
550       "Accounts",
551       account_external_password_storage_get_accounts_cb,
552       g_strdup (account_id), g_free, G_OBJECT (account));
553 }
554 
555 static void
account_external_password_storage_properties_changed_cb(TpProxy * cm,const char * iface,GHashTable * changed_properties,const char ** invalidated_properties,gpointer user_data,GObject * self)556 account_external_password_storage_properties_changed_cb (TpProxy *cm,
557     const char *iface,
558     GHashTable *changed_properties,
559     const char **invalidated_properties,
560     gpointer user_data,
561     GObject *self)
562 {
563   McdAccount *account = MCD_ACCOUNT (self);
564   TpProtocol *protocol = tp_connection_manager_get_protocol_object (
565       TP_CONNECTION_MANAGER (cm), account->priv->protocol_name);
566   GHashTable *params;
567 
568   if (tp_strdiff (iface,
569         MC_IFACE_CONNECTION_MANAGER_INTERFACE_ACCOUNT_STORAGE))
570     return;
571 
572   /* look up account identity so we can look up our value in
573    * the Accounts map */
574   params = _mcd_account_dup_parameters (account);
575   tp_cli_protocol_call_identify_account (protocol, -1, params,
576       account_setup_identify_account_cb,
577       NULL, NULL, G_OBJECT (account));
578 
579   g_hash_table_unref (params);
580 }
581 
on_manager_ready(McdManager * manager,const GError * error,gpointer user_data)582 static void on_manager_ready (McdManager *manager, const GError *error,
583                               gpointer user_data)
584 {
585     McdAccount *account = MCD_ACCOUNT (user_data);
586 
587     if (error)
588     {
589         DEBUG ("got error: %s", error->message);
590         mcd_account_loaded (account);
591     }
592     else
593     {
594         TpConnectionManager *cm = mcd_manager_get_tp_proxy (manager);
595 
596         mcd_account_check_parameters (account, manager_ready_check_params_cb,
597                                       NULL);
598 
599         /* determine if we support Acct.I.ExternalPasswordStorage */
600         if (tp_proxy_has_interface_by_id (cm,
601                 MC_IFACE_QUARK_CONNECTION_MANAGER_INTERFACE_ACCOUNT_STORAGE))
602         {
603             TpProtocol *protocol = tp_connection_manager_get_protocol_object (
604                 cm, account->priv->protocol_name);
605             GHashTable *params;
606 
607             DEBUG ("CM %s has CM.I.AccountStorage iface",
608                    mcd_manager_get_name (manager));
609 
610             mcd_dbus_activate_optional_interface (
611                 TP_SVC_DBUS_PROPERTIES (account),
612                 MC_TYPE_SVC_ACCOUNT_INTERFACE_EXTERNAL_PASSWORD_STORAGE);
613 
614             /* look up account identity so we can look up our value in
615              * the Accounts map */
616             params = _mcd_account_dup_parameters (account);
617             tp_cli_protocol_call_identify_account (protocol, -1, params,
618                 account_setup_identify_account_cb,
619                 NULL, NULL, G_OBJECT (account));
620 
621             tp_cli_dbus_properties_connect_to_properties_changed (cm,
622                 account_external_password_storage_properties_changed_cb,
623                 NULL, NULL, G_OBJECT (account), NULL);
624 
625             g_hash_table_unref (params);
626         }
627     }
628 }
629 
630 static gboolean
load_manager(McdAccount * account)631 load_manager (McdAccount *account)
632 {
633     McdAccountPrivate *priv = account->priv;
634     McdMaster *master;
635 
636     if (G_UNLIKELY (!priv->manager_name)) return FALSE;
637     master = mcd_master_get_default ();
638     priv->manager = _mcd_master_lookup_manager (master, priv->manager_name);
639     if (priv->manager)
640     {
641 	g_object_ref (priv->manager);
642         mcd_manager_call_when_ready (priv->manager, on_manager_ready, account);
643 	return TRUE;
644     }
645     else
646 	return FALSE;
647 }
648 
649 /* Returns the data dir for the given account name.
650  * Returned string must be freed by caller. */
651 static gchar *
get_old_account_data_path(McdAccountPrivate * priv)652 get_old_account_data_path (McdAccountPrivate *priv)
653 {
654     const gchar *base;
655 
656     base = g_getenv ("MC_ACCOUNT_DIR");
657     if (!base)
658 	base = ACCOUNTS_DIR;
659     if (!base)
660 	return NULL;
661 
662     if (base[0] == '~')
663 	return g_build_filename (g_get_home_dir(), base + 1,
664 				 priv->unique_name, NULL);
665     else
666 	return g_build_filename (base, priv->unique_name, NULL);
667 }
668 
669 static void
account_delete_identify_account_cb(TpProxy * protocol,const char * account_id,const GError * in_error,gpointer user_data,GObject * self)670 account_delete_identify_account_cb (TpProxy *protocol,
671     const char *account_id,
672     const GError *in_error,
673     gpointer user_data,
674     GObject *self)
675 {
676   McdAccount *account = MCD_ACCOUNT (self);
677   TpConnectionManager *cm = mcd_account_get_cm (account);
678 
679   if (in_error != NULL)
680     {
681       DEBUG ("Error identifying account: %s", in_error->message);
682     }
683   else
684     {
685       DEBUG ("Identified account as %s", account_id);
686 
687       mc_cli_connection_manager_interface_account_storage_call_remove_account (
688           cm, -1, account_id,
689           NULL, NULL, NULL, NULL);
690     }
691 
692   g_object_unref (account);
693 }
694 
695 static void
unregister_dbus_service(McdAccount * self)696 unregister_dbus_service (McdAccount *self)
697 {
698     DBusGConnection *dbus_connection;
699 
700     g_return_if_fail (MCD_IS_ACCOUNT (self));
701 
702     if (!self->priv->registered)
703         return;
704 
705     dbus_connection = tp_proxy_get_dbus_connection (self->priv->dbus_daemon);
706     dbus_g_connection_unregister_g_object (dbus_connection, (GObject *) self);
707 
708     self->priv->registered = FALSE;
709 }
710 
711 void
mcd_account_delete(McdAccount * account,McdAccountDeleteCb callback,gpointer user_data)712 mcd_account_delete (McdAccount *account,
713                      McdAccountDeleteCb callback,
714                      gpointer user_data)
715 {
716     McdAccountPrivate *priv = account->priv;
717     gchar *data_dir_str;
718     GError *error = NULL;
719     const gchar *name = mcd_account_get_unique_name (account);
720     TpConnectionManager *cm = mcd_account_get_cm (account);
721 
722     /* if the CM implements CM.I.AccountStorage, we need to tell the CM
723      * to forget any account credentials it knows */
724     if (tp_proxy_has_interface_by_id (cm,
725             MC_IFACE_QUARK_CONNECTION_MANAGER_INTERFACE_ACCOUNT_STORAGE))
726     {
727         TpProtocol *protocol;
728         GHashTable *params;
729 
730         /* identify the account */
731         protocol = tp_connection_manager_get_protocol_object (cm,
732             account->priv->protocol_name);
733         params = _mcd_account_dup_parameters (account);
734 
735         tp_cli_protocol_call_identify_account (protocol, -1, params,
736             account_delete_identify_account_cb,
737             NULL, NULL, g_object_ref (account));
738 
739         g_hash_table_unref (params);
740     }
741 
742     /* got to turn the account off before removing it, otherwise we can *
743      * end up with an orphaned CM holding the account online            */
744     if (!_mcd_account_set_enabled (account, FALSE, FALSE,
745                                    MCD_DBUS_PROP_SET_FLAG_NONE, &error))
746     {
747         g_warning ("could not disable account %s (%s)", name, error->message);
748         callback (account, error, user_data);
749         g_error_free (error);
750         return;
751     }
752 
753     mcd_storage_delete_account (priv->storage, name);
754 
755     data_dir_str = get_old_account_data_path (priv);
756 
757     if (data_dir_str != NULL)
758     {
759         GDir *data_dir = g_dir_open (data_dir_str, 0, NULL);
760 
761         if (data_dir)
762         {
763             const gchar *filename;
764 
765             while ((filename = g_dir_read_name (data_dir)) != NULL)
766             {
767                 gchar *path = g_build_filename (data_dir_str, filename, NULL);
768 
769                 g_remove (path);
770                 g_free (path);
771             }
772 
773             g_dir_close (data_dir);
774             g_rmdir (data_dir_str);
775         }
776 
777         g_free (data_dir_str);
778     }
779 
780     mcd_storage_commit (priv->storage, name);
781 
782     /* The callback may drop the latest ref on @account so make sure it stays
783      * alive while we still need it. */
784     g_object_ref (account);
785 
786     if (callback != NULL)
787         callback (account, NULL, user_data);
788 
789     /* If the account was not removed via the DBus Account interface code     *
790      * path and something is holding a ref to it so it does not get disposed, *
791      * then this signal may not get fired, so we make sure it _does_ here     */
792     if (!priv->removed)
793     {
794         DEBUG ("Forcing Account.Removed for %s", name);
795         priv->removed = TRUE;
796         tp_svc_account_emit_removed (account);
797     }
798 
799     unregister_dbus_service (account);
800     g_object_unref (account);
801 }
802 
803 void
_mcd_account_load(McdAccount * account,McdAccountLoadCb callback,gpointer user_data)804 _mcd_account_load (McdAccount *account, McdAccountLoadCb callback,
805                    gpointer user_data)
806 {
807     if (account->priv->loaded)
808         callback (account, NULL, user_data);
809     else
810         _mcd_object_call_when_ready (account, account_ready_quark,
811                                      (McdReadyCb)callback, user_data);
812 }
813 
814 static void
on_connection_abort(McdConnection * connection,McdAccount * account)815 on_connection_abort (McdConnection *connection, McdAccount *account)
816 {
817     McdAccountPrivate *priv = MCD_ACCOUNT_PRIV (account);
818 
819     DEBUG ("called (%p, account %s)", connection, priv->unique_name);
820     _mcd_account_set_connection (account, NULL);
821 }
822 
823 static void mcd_account_changed_property (McdAccount *account,
824     const gchar *key, const GValue *value);
825 
826 static void
mcd_account_request_presence_int(McdAccount * account,TpConnectionPresenceType type,const gchar * status,const gchar * message,gboolean user_initiated)827 mcd_account_request_presence_int (McdAccount *account,
828                                   TpConnectionPresenceType type,
829                                   const gchar *status,
830                                   const gchar *message,
831                                   gboolean user_initiated)
832 {
833     McdAccountPrivate *priv = account->priv;
834     gboolean changed = FALSE;
835 
836     if (priv->req_presence_type != type)
837     {
838         priv->req_presence_type = type;
839         changed = TRUE;
840     }
841 
842     if (tp_strdiff (priv->req_presence_status, status))
843     {
844         g_free (priv->req_presence_status);
845         priv->req_presence_status = g_strdup (status);
846         changed = TRUE;
847     }
848 
849     if (tp_strdiff (priv->req_presence_message, message))
850     {
851         g_free (priv->req_presence_message);
852         priv->req_presence_message = g_strdup (message);
853         changed = TRUE;
854     }
855 
856     if (changed)
857     {
858         GValue value = G_VALUE_INIT;
859 
860         g_value_init (&value, TP_STRUCT_TYPE_SIMPLE_PRESENCE);
861         g_value_take_boxed (&value,
862                             tp_value_array_build (3,
863                                 G_TYPE_UINT, type,
864                                 G_TYPE_STRING, status,
865                                 G_TYPE_STRING, message,
866                                 G_TYPE_INVALID));
867         mcd_account_changed_property (account, "RequestedPresence", &value);
868         g_value_unset (&value);
869     }
870 
871     DEBUG ("Requested presence: %u %s %s",
872         priv->req_presence_type,
873         priv->req_presence_status,
874         priv->req_presence_message);
875 
876     if (type >= TP_CONNECTION_PRESENCE_TYPE_AVAILABLE)
877     {
878         if (!priv->enabled)
879         {
880             DEBUG ("%s not Enabled", priv->unique_name);
881             return;
882         }
883 
884         if (!mcd_account_is_valid (account))
885         {
886             DEBUG ("%s not Valid", priv->unique_name);
887             return;
888         }
889     }
890 
891     if (priv->connection == NULL)
892     {
893         if (type >= TP_CONNECTION_PRESENCE_TYPE_AVAILABLE)
894         {
895             if (changed)
896             {
897                 _mcd_account_set_changing_presence (account, TRUE);
898             }
899 
900             _mcd_account_connection_begin (account, user_initiated);
901         }
902     }
903     else
904     {
905         if (changed)
906         {
907             _mcd_account_set_changing_presence (account, TRUE);
908         }
909 
910         _mcd_connection_request_presence (priv->connection,
911                                           priv->req_presence_type,
912 					  priv->req_presence_status,
913 					  priv->req_presence_message);
914     }
915 }
916 
917 /*
918  * mcd_account_rerequest_presence:
919  *
920  * Re-requests the account's current RequestedPresence, possibly triggering a
921  * new connection attempt.
922  */
923 static void
mcd_account_rerequest_presence(McdAccount * account,gboolean user_initiated)924 mcd_account_rerequest_presence (McdAccount *account,
925                                 gboolean user_initiated)
926 {
927     McdAccountPrivate *priv = account->priv;
928 
929     mcd_account_request_presence_int (account,
930                                       priv->req_presence_type,
931                                       priv->req_presence_status,
932                                       priv->req_presence_message,
933                                       user_initiated);
934 }
935 
936 void
_mcd_account_connect(McdAccount * account,GHashTable * params)937 _mcd_account_connect (McdAccount *account, GHashTable *params)
938 {
939     McdAccountPrivate *priv = account->priv;
940 
941     g_assert (params != NULL);
942 
943     if (!priv->connection)
944     {
945         McdConnection *connection;
946 
947 	if (!priv->manager && !load_manager (account))
948 	{
949 	    g_warning ("%s: Could not find manager `%s'",
950 		       G_STRFUNC, priv->manager_name);
951 	    return;
952 	}
953 
954         connection = mcd_manager_create_connection (priv->manager, account);
955         _mcd_account_set_connection (account, connection);
956     }
957     _mcd_connection_connect (priv->connection, params);
958 }
959 
960 static gboolean
emit_property_changed(gpointer userdata)961 emit_property_changed (gpointer userdata)
962 {
963     McdAccount *account = MCD_ACCOUNT (userdata);
964     McdAccountPrivate *priv = account->priv;
965 
966     DEBUG ("called");
967 
968     if (g_hash_table_size (priv->changed_properties) > 0)
969     {
970         tp_svc_account_emit_account_property_changed (account,
971             priv->changed_properties);
972         g_hash_table_remove_all (priv->changed_properties);
973     }
974 
975     if (priv->properties_source != 0)
976     {
977       g_source_remove (priv->properties_source);
978       priv->properties_source = 0;
979     }
980     return FALSE;
981 }
982 
983 static void
mcd_account_freeze_properties(McdAccount * self)984 mcd_account_freeze_properties (McdAccount *self)
985 {
986     g_return_if_fail (!self->priv->properties_frozen);
987     DEBUG ("%s", self->priv->unique_name);
988     self->priv->properties_frozen = TRUE;
989 }
990 
991 static void
mcd_account_thaw_properties(McdAccount * self)992 mcd_account_thaw_properties (McdAccount *self)
993 {
994     g_return_if_fail (self->priv->properties_frozen);
995     DEBUG ("%s", self->priv->unique_name);
996     self->priv->properties_frozen = FALSE;
997 
998     if (g_hash_table_size (self->priv->changed_properties) != 0)
999     {
1000         emit_property_changed (self);
1001     }
1002 }
1003 
1004 /*
1005  * This function is responsible of emitting the AccountPropertyChanged signal.
1006  * One possible improvement would be to save the HashTable and have the signal
1007  * emitted in an idle function (or a timeout function with a very small delay)
1008  * to group together several property changes that occur at the same time.
1009  */
1010 static void
mcd_account_changed_property(McdAccount * account,const gchar * key,const GValue * value)1011 mcd_account_changed_property (McdAccount *account, const gchar *key,
1012 			      const GValue *value)
1013 {
1014     McdAccountPrivate *priv = account->priv;
1015 
1016     DEBUG ("called: %s", key);
1017     if (priv->changed_properties &&
1018 	g_hash_table_lookup (priv->changed_properties, key))
1019     {
1020 	/* the changed property was also changed before; then let's force the
1021 	 * emission of the signal now, so that the property will appear in two
1022 	 * separate signals */
1023         DEBUG ("Forcibly emit PropertiesChanged now");
1024 	emit_property_changed (account);
1025     }
1026 
1027     if (priv->properties_source == 0)
1028     {
1029         DEBUG ("First changed property");
1030         priv->properties_source = g_timeout_add_full (G_PRIORITY_DEFAULT, 10,
1031                                                       emit_property_changed,
1032                                                       g_object_ref (account),
1033                                                       g_object_unref);
1034     }
1035     g_hash_table_insert (priv->changed_properties, (gpointer) key,
1036                          tp_g_value_slice_dup (value));
1037 }
1038 
1039 typedef enum {
1040     SET_RESULT_ERROR,
1041     SET_RESULT_UNCHANGED,
1042     SET_RESULT_CHANGED
1043 } SetResult;
1044 
1045 /*
1046  * mcd_account_set_string_val:
1047  * @account: an account
1048  * @key: a D-Bus property name that is a string
1049  * @value: the new value for that property
1050  * @error: set to an error if %SET_RESULT_ERROR is returned
1051  *
1052  * Returns: %SET_RESULT_CHANGED or %SET_RESULT_UNCHANGED on success,
1053  *  %SET_RESULT_ERROR on error
1054  */
1055 static SetResult
mcd_account_set_string_val(McdAccount * account,const gchar * key,const GValue * value,McdDBusPropSetFlags flags,GError ** error)1056 mcd_account_set_string_val (McdAccount *account,
1057                             const gchar *key,
1058                             const GValue *value,
1059                             McdDBusPropSetFlags flags,
1060                             GError **error)
1061 {
1062     McdAccountPrivate *priv = account->priv;
1063     McdStorage *storage = priv->storage;
1064     const gchar *name = mcd_account_get_unique_name (account);
1065     const gchar *new_string;
1066 
1067     if (!G_VALUE_HOLDS_STRING (value))
1068     {
1069         g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
1070                      "Expected string for %s, but got %s", key,
1071                      G_VALUE_TYPE_NAME (value));
1072         return SET_RESULT_ERROR;
1073     }
1074 
1075     new_string = g_value_get_string (value);
1076 
1077     if (tp_str_empty (new_string)) {
1078         new_string = NULL;
1079     }
1080 
1081     if (flags & MCD_DBUS_PROP_SET_FLAG_ALREADY_IN_STORAGE)
1082     {
1083         mcd_account_changed_property (account, key, value);
1084         return SET_RESULT_CHANGED;
1085     }
1086     else if (mcd_storage_set_string (storage, name, key, new_string))
1087     {
1088         mcd_storage_commit (storage, name);
1089         mcd_account_changed_property (account, key, value);
1090         return SET_RESULT_CHANGED;
1091     }
1092     else
1093     {
1094         return SET_RESULT_UNCHANGED;
1095     }
1096 }
1097 
1098 static void
mcd_account_get_string_val(McdAccount * account,const gchar * key,GValue * value)1099 mcd_account_get_string_val (McdAccount *account, const gchar *key,
1100 			    GValue *value)
1101 {
1102     McdAccountPrivate *priv = account->priv;
1103     const gchar *name = mcd_account_get_unique_name (account);
1104 
1105     g_value_init (value, G_TYPE_STRING);
1106 
1107     if (!mcd_storage_get_attribute (priv->storage, name, key, value, NULL))
1108     {
1109         g_value_set_static_string (value, NULL);
1110     }
1111 }
1112 
1113 static gboolean
set_display_name(TpSvcDBusProperties * self,const gchar * name,const GValue * value,McdDBusPropSetFlags flags,GError ** error)1114 set_display_name (TpSvcDBusProperties *self,
1115                   const gchar *name,
1116                   const GValue *value,
1117                   McdDBusPropSetFlags flags,
1118                   GError **error)
1119 {
1120     McdAccount *account = MCD_ACCOUNT (self);
1121     McdAccountPrivate *priv = account->priv;
1122 
1123     DEBUG ("called for %s", priv->unique_name);
1124     return (mcd_account_set_string_val (account,
1125         MC_ACCOUNTS_KEY_DISPLAY_NAME, value, flags,
1126         error) != SET_RESULT_ERROR);
1127 }
1128 
1129 static void
get_display_name(TpSvcDBusProperties * self,const gchar * name,GValue * value)1130 get_display_name (TpSvcDBusProperties *self, const gchar *name, GValue *value)
1131 {
1132     McdAccount *account = MCD_ACCOUNT (self);
1133 
1134     mcd_account_get_string_val (account, MC_ACCOUNTS_KEY_DISPLAY_NAME, value);
1135 }
1136 
1137 static gboolean
set_icon(TpSvcDBusProperties * self,const gchar * name,const GValue * value,McdDBusPropSetFlags flags,GError ** error)1138 set_icon (TpSvcDBusProperties *self,
1139           const gchar *name,
1140           const GValue *value,
1141           McdDBusPropSetFlags flags,
1142           GError **error)
1143 {
1144     McdAccount *account = MCD_ACCOUNT (self);
1145     McdAccountPrivate *priv = account->priv;
1146 
1147     DEBUG ("called for %s", priv->unique_name);
1148     return (mcd_account_set_string_val (account,
1149         MC_ACCOUNTS_KEY_ICON, value, flags, error) != SET_RESULT_ERROR);
1150 }
1151 
1152 static void
get_icon(TpSvcDBusProperties * self,const gchar * name,GValue * value)1153 get_icon (TpSvcDBusProperties *self, const gchar *name, GValue *value)
1154 {
1155     McdAccount *account = MCD_ACCOUNT (self);
1156 
1157     mcd_account_get_string_val (account, MC_ACCOUNTS_KEY_ICON, value);
1158 }
1159 
1160 static void
get_valid(TpSvcDBusProperties * self,const gchar * name,GValue * value)1161 get_valid (TpSvcDBusProperties *self, const gchar *name, GValue *value)
1162 {
1163     McdAccount *account = MCD_ACCOUNT (self);
1164 
1165     g_value_init (value, G_TYPE_BOOLEAN);
1166     g_value_set_boolean (value, mcd_account_is_valid (account));
1167 }
1168 
1169 static void
get_has_been_online(TpSvcDBusProperties * self,const gchar * name,GValue * value)1170 get_has_been_online (TpSvcDBusProperties *self, const gchar *name,
1171                      GValue *value)
1172 {
1173     McdAccount *account = MCD_ACCOUNT (self);
1174     McdAccountPrivate *priv = account->priv;
1175 
1176     g_value_init (value, G_TYPE_BOOLEAN);
1177     g_value_set_boolean (value, priv->has_been_online);
1178 }
1179 
1180 /**
1181  * mcd_account_set_enabled:
1182  * @account: the #McdAccount
1183  * @enabled: %TRUE if the account is to be enabled
1184  * @write_out: %TRUE if this should be written to the keyfile
1185  * @error: return location for an error condition
1186  *
1187  * Returns: %TRUE on success
1188  */
1189 gboolean
_mcd_account_set_enabled(McdAccount * account,gboolean enabled,gboolean write_out,McdDBusPropSetFlags flags,GError ** error)1190 _mcd_account_set_enabled (McdAccount *account,
1191                           gboolean enabled,
1192                           gboolean write_out,
1193                           McdDBusPropSetFlags flags,
1194                           GError **error)
1195 {
1196     McdAccountPrivate *priv = account->priv;
1197 
1198     if (priv->always_on && !enabled)
1199     {
1200         g_set_error (error, TP_ERROR, TP_ERROR_PERMISSION_DENIED,
1201                      "Account %s cannot be disabled",
1202                      priv->unique_name);
1203         return FALSE;
1204     }
1205 
1206     if (priv->enabled != enabled)
1207     {
1208         GValue value = G_VALUE_INIT;
1209         const gchar *name = mcd_account_get_unique_name (account);
1210 
1211         if (!enabled && priv->connection != NULL)
1212             _mcd_connection_request_presence (priv->connection,
1213                                               TP_CONNECTION_PRESENCE_TYPE_OFFLINE,
1214                                               "offline",
1215                                               NULL);
1216 
1217         priv->enabled = enabled;
1218 
1219         g_value_init (&value, G_TYPE_BOOLEAN);
1220         g_value_set_boolean (&value, enabled);
1221 
1222         if (!(flags & MCD_DBUS_PROP_SET_FLAG_ALREADY_IN_STORAGE))
1223         {
1224             mcd_storage_set_attribute (priv->storage, name,
1225                                        MC_ACCOUNTS_KEY_ENABLED, &value);
1226 
1227             if (write_out)
1228                 mcd_storage_commit (priv->storage, name);
1229         }
1230 
1231         mcd_account_changed_property (account, "Enabled", &value);
1232 
1233         g_value_unset (&value);
1234 
1235         if (enabled)
1236         {
1237             mcd_account_rerequest_presence (account, TRUE);
1238             _mcd_account_maybe_autoconnect (account);
1239         }
1240     }
1241 
1242     return TRUE;
1243 }
1244 
1245 static gboolean
set_enabled(TpSvcDBusProperties * self,const gchar * name,const GValue * value,McdDBusPropSetFlags flags,GError ** error)1246 set_enabled (TpSvcDBusProperties *self,
1247              const gchar *name,
1248              const GValue *value,
1249              McdDBusPropSetFlags flags,
1250              GError **error)
1251 {
1252     McdAccount *account = MCD_ACCOUNT (self);
1253     McdAccountPrivate *priv = account->priv;
1254     gboolean enabled;
1255 
1256     DEBUG ("called for %s", priv->unique_name);
1257 
1258     if (!G_VALUE_HOLDS_BOOLEAN (value))
1259     {
1260         g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
1261                      "Expected boolean for Enabled, but got %s",
1262                      G_VALUE_TYPE_NAME (value));
1263         return FALSE;
1264     }
1265 
1266     enabled = g_value_get_boolean (value);
1267 
1268     return _mcd_account_set_enabled (account, enabled, TRUE, flags, error);
1269 }
1270 
1271 static void
get_enabled(TpSvcDBusProperties * self,const gchar * name,GValue * value)1272 get_enabled (TpSvcDBusProperties *self, const gchar *name, GValue *value)
1273 {
1274     McdAccount *account = MCD_ACCOUNT (self);
1275     McdAccountPrivate *priv = account->priv;
1276 
1277     g_value_init (value, G_TYPE_BOOLEAN);
1278     g_value_set_boolean (value, priv->enabled);
1279 }
1280 
1281 static gboolean
set_service(TpSvcDBusProperties * self,const gchar * name,const GValue * value,McdDBusPropSetFlags flags,GError ** error)1282 set_service (TpSvcDBusProperties *self, const gchar *name,
1283              const GValue *value,
1284              McdDBusPropSetFlags flags,
1285              GError **error)
1286 {
1287     McdAccount *account = MCD_ACCOUNT (self);
1288     SetResult ret = SET_RESULT_ERROR;
1289     gboolean proceed = TRUE;
1290     static GRegex *rule = NULL;
1291     static gsize service_re_init = 0;
1292 
1293     if (g_once_init_enter (&service_re_init))
1294     {
1295         GError *regex_error = NULL;
1296         rule = g_regex_new ("^(?:[a-z][a-z0-9_-]*)?$",
1297                             G_REGEX_CASELESS|G_REGEX_DOLLAR_ENDONLY,
1298                             0, &regex_error);
1299         g_assert_no_error (regex_error);
1300         g_once_init_leave (&service_re_init, 1);
1301     }
1302 
1303     if (G_VALUE_HOLDS_STRING (value))
1304       proceed = g_regex_match (rule, g_value_get_string (value), 0, NULL);
1305 
1306     /* if value is not a string, mcd_account_set_string_val will set *
1307      * the appropriate error for us: don't duplicate that logic here */
1308     if (proceed)
1309     {
1310         ret = mcd_account_set_string_val (account, MC_ACCOUNTS_KEY_SERVICE,
1311                                           value, flags, error);
1312     }
1313     else
1314     {
1315         g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
1316                      "Invalid service '%s': Must consist of ASCII alphanumeric "
1317                      "characters, underscores (_) and hyphens (-) only, and "
1318                      "start with a letter",
1319                      g_value_get_string (value));
1320     }
1321 
1322     return (ret != SET_RESULT_ERROR);
1323 }
1324 
1325 static void
get_service(TpSvcDBusProperties * self,const gchar * name,GValue * value)1326 get_service (TpSvcDBusProperties *self, const gchar *name, GValue *value)
1327 {
1328     McdAccount *account = MCD_ACCOUNT (self);
1329 
1330     mcd_account_get_string_val (account, MC_ACCOUNTS_KEY_SERVICE, value);
1331 }
1332 
1333 static void
mcd_account_set_self_alias_cb(TpConnection * tp_connection,const GError * error,gpointer user_data,GObject * weak_object)1334 mcd_account_set_self_alias_cb (TpConnection *tp_connection,
1335     const GError *error,
1336     gpointer user_data,
1337     GObject *weak_object)
1338 {
1339   if (error)
1340     DEBUG ("%s", error->message);
1341 }
1342 
1343 static void
mcd_account_send_nickname_to_connection(McdAccount * self,const gchar * nickname)1344 mcd_account_send_nickname_to_connection (McdAccount *self,
1345     const gchar *nickname)
1346 {
1347   if (self->priv->tp_connection == NULL)
1348     return;
1349 
1350   if (self->priv->self_contact == NULL)
1351     return;
1352 
1353   DEBUG ("%s: '%s'", self->priv->unique_name, nickname);
1354 
1355   if (tp_proxy_has_interface_by_id (self->priv->tp_connection,
1356           TP_IFACE_QUARK_CONNECTION_INTERFACE_ALIASING))
1357     {
1358       GHashTable *aliases = g_hash_table_new (NULL, NULL);
1359 
1360       g_hash_table_insert (aliases,
1361           GUINT_TO_POINTER (tp_contact_get_handle (self->priv->self_contact)),
1362           (gchar *) nickname);
1363       tp_cli_connection_interface_aliasing_call_set_aliases (
1364           self->priv->tp_connection, -1, aliases,
1365           mcd_account_set_self_alias_cb, NULL, NULL, NULL);
1366       g_hash_table_unref (aliases);
1367     }
1368 }
1369 
1370 
1371 static gboolean
set_nickname(TpSvcDBusProperties * self,const gchar * name,const GValue * value,McdDBusPropSetFlags flags,GError ** error)1372 set_nickname (TpSvcDBusProperties *self, const gchar *name,
1373               const GValue *value,
1374               McdDBusPropSetFlags flags,
1375               GError **error)
1376 {
1377     McdAccount *account = MCD_ACCOUNT (self);
1378     McdAccountPrivate *priv = account->priv;
1379     SetResult ret;
1380     GValue replacement = G_VALUE_INIT;
1381 
1382     DEBUG ("called for %s", priv->unique_name);
1383 
1384     /* If we're asked to set Nickname = "", set it to our identifier
1385      * (NormalizedName) instead, so that we always have some sort of nickname.
1386      * This matches what we do when connecting an account.
1387      *
1388      * Exception: if we're not fully connected yet (and hence have no
1389      * self-contact), rely on the corresponding special-case
1390      * when we do become connected.
1391      */
1392     if (G_VALUE_HOLDS_STRING (value) &&
1393         tp_str_empty (g_value_get_string (value)) &&
1394         priv->self_contact != NULL)
1395     {
1396         g_value_init (&replacement, G_TYPE_STRING);
1397         g_value_set_string (&replacement,
1398             tp_contact_get_identifier (priv->self_contact));
1399         value = &replacement;
1400     }
1401 
1402     ret = mcd_account_set_string_val (account, MC_ACCOUNTS_KEY_NICKNAME,
1403                                       value, flags, error);
1404 
1405     if (ret != SET_RESULT_ERROR)
1406     {
1407         mcd_account_send_nickname_to_connection (account,
1408             g_value_get_string (value));
1409     }
1410 
1411     if (value == &replacement)
1412         g_value_unset (&replacement);
1413 
1414     return (ret != SET_RESULT_ERROR);
1415 }
1416 
1417 static void
get_nickname(TpSvcDBusProperties * self,const gchar * name,GValue * value)1418 get_nickname (TpSvcDBusProperties *self, const gchar *name, GValue *value)
1419 {
1420     McdAccount *account = MCD_ACCOUNT (self);
1421 
1422     mcd_account_get_string_val (account, MC_ACCOUNTS_KEY_NICKNAME, value);
1423 }
1424 
1425 static void
mcd_account_self_contact_notify_avatar_file_cb(McdAccount * self,GParamSpec * unused_param_spec G_GNUC_UNUSED,TpContact * self_contact)1426 mcd_account_self_contact_notify_avatar_file_cb (McdAccount *self,
1427     GParamSpec *unused_param_spec G_GNUC_UNUSED,
1428     TpContact *self_contact)
1429 {
1430   const gchar *token;
1431   gchar *prev_token;
1432   GFile *file;
1433   GError *error = NULL;
1434   gboolean changed;
1435 
1436   if (self_contact != self->priv->self_contact)
1437     return;
1438 
1439   file = tp_contact_get_avatar_file (self_contact);
1440   token = tp_contact_get_avatar_token (self_contact);
1441 
1442   if (self->priv->setting_avatar)
1443     {
1444       DEBUG ("Ignoring avatar change notification: we are setting ours");
1445       return;
1446     }
1447 
1448   if (self->priv->waiting_for_initial_avatar)
1449     {
1450       DEBUG ("Ignoring avatar change notification: we are waiting for the "
1451           "initial value");
1452       return;
1453     }
1454 
1455   prev_token = _mcd_account_get_avatar_token (self);
1456   changed = tp_strdiff (prev_token, token);
1457   g_free (prev_token);
1458 
1459   if (!changed)
1460     {
1461       DEBUG ("Avatar unchanged: '%s'", token);
1462       return;
1463     }
1464 
1465   if (file == NULL)
1466     {
1467       if (!_mcd_account_set_avatar (self, NULL, "", "", &error))
1468         {
1469           DEBUG ("Attempt to clear avatar failed: %s", error->message);
1470           g_clear_error (&error);
1471         }
1472     }
1473   else
1474     {
1475       gchar *contents = NULL;
1476       gsize len = 0;
1477       GArray *arr;
1478 
1479       if (!g_file_load_contents (file, NULL, &contents, &len, NULL, &error))
1480         {
1481           gchar *uri = g_file_get_uri (file);
1482 
1483           WARNING ("Unable to read avatar file %s: %s", uri, error->message);
1484           g_clear_error (&error);
1485           g_free (uri);
1486           return;
1487         }
1488 
1489       if (G_UNLIKELY (len > G_MAXUINT))
1490         {
1491           gchar *uri = g_file_get_uri (file);
1492 
1493           WARNING ("Avatar file %s was ludicrously huge", uri);
1494           g_free (uri);
1495           g_free (contents);
1496           return;
1497         }
1498 
1499       arr = g_array_sized_new (TRUE, FALSE, 1, (guint) len);
1500       g_array_append_vals (arr, contents, (guint) len);
1501       g_free (contents);
1502 
1503       if (!_mcd_account_set_avatar (self, arr,
1504               tp_contact_get_avatar_mime_type (self_contact),
1505               tp_contact_get_avatar_token (self_contact), &error))
1506         {
1507           DEBUG ("Attempt to save avatar failed: %s", error->message);
1508           g_clear_error (&error);
1509         }
1510 
1511       g_array_unref (arr);
1512     }
1513 }
1514 
1515 static void
avatars_set_avatar_cb(TpConnection * tp_connection,const gchar * token,const GError * error,gpointer user_data,GObject * weak_object)1516 avatars_set_avatar_cb (TpConnection *tp_connection,
1517     const gchar *token,
1518     const GError *error,
1519     gpointer user_data,
1520     GObject *weak_object)
1521 {
1522   McdAccount *self = MCD_ACCOUNT (weak_object);
1523 
1524   self->priv->setting_avatar = FALSE;
1525 
1526   if (error != NULL)
1527     {
1528       DEBUG ("%s: %s", self->priv->unique_name, error->message);
1529     }
1530   else
1531     {
1532       DEBUG ("%s: new token %s", self->priv->unique_name, token);
1533       _mcd_account_set_avatar_token (self, token);
1534     }
1535 }
1536 
1537 static void
avatars_clear_avatar_cb(TpConnection * tp_connection,const GError * error,gpointer user_data,GObject * weak_object)1538 avatars_clear_avatar_cb (TpConnection *tp_connection,
1539     const GError *error,
1540     gpointer user_data,
1541     GObject *weak_object)
1542 {
1543   McdAccount *self = MCD_ACCOUNT (weak_object);
1544 
1545   self->priv->setting_avatar = FALSE;
1546 
1547   if (error != NULL)
1548     {
1549       DEBUG ("%s: %s", self->priv->unique_name, error->message);
1550     }
1551   else
1552     {
1553       DEBUG ("%s: success", self->priv->unique_name);
1554       _mcd_account_set_avatar_token (self, "");
1555     }
1556 }
1557 
1558 static void
mcd_account_send_avatar_to_connection(McdAccount * self,const GArray * avatar,const gchar * mime_type)1559 mcd_account_send_avatar_to_connection (McdAccount *self,
1560     const GArray *avatar,
1561     const gchar *mime_type)
1562 {
1563   if (self->priv->tp_connection == NULL)
1564     return;
1565 
1566   if (self->priv->self_contact == NULL)
1567     return;
1568 
1569   DEBUG ("%s: %u bytes", self->priv->unique_name, avatar->len);
1570 
1571   if (tp_proxy_has_interface_by_id (self->priv->tp_connection,
1572           TP_IFACE_QUARK_CONNECTION_INTERFACE_AVATARS))
1573     {
1574       self->priv->setting_avatar = TRUE;
1575 
1576       if (avatar->len > 0 && avatar->len < G_MAXUINT)
1577         {
1578           tp_cli_connection_interface_avatars_call_set_avatar (
1579             self->priv->tp_connection, -1, avatar, mime_type,
1580             avatars_set_avatar_cb, NULL, NULL, (GObject *) self);
1581         }
1582       else
1583         {
1584           tp_cli_connection_interface_avatars_call_clear_avatar (
1585               self->priv->tp_connection, -1, avatars_clear_avatar_cb,
1586               NULL, NULL, (GObject *) self);
1587         }
1588     }
1589   else
1590     {
1591       DEBUG ("unsupported, ignoring");
1592     }
1593 }
1594 
1595 static gboolean
set_avatar(TpSvcDBusProperties * self,const gchar * name,const GValue * value,McdDBusPropSetFlags flags,GError ** error)1596 set_avatar (TpSvcDBusProperties *self, const gchar *name, const GValue *value,
1597             McdDBusPropSetFlags flags,
1598             GError **error)
1599 {
1600     McdAccount *account = MCD_ACCOUNT (self);
1601     McdAccountPrivate *priv = account->priv;
1602     const gchar *mime_type;
1603     const GArray *avatar;
1604     GValueArray *va;
1605 
1606     DEBUG ("called for %s", priv->unique_name);
1607 
1608     if (!G_VALUE_HOLDS (value, TP_STRUCT_TYPE_AVATAR))
1609     {
1610         g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
1611                      "Unexpected type for Avatar: wanted (ay,s), got %s",
1612                      G_VALUE_TYPE_NAME (value));
1613         return FALSE;
1614     }
1615 
1616     va = g_value_get_boxed (value);
1617     avatar = g_value_get_boxed (va->values);
1618     mime_type = g_value_get_string (va->values + 1);
1619 
1620     if (!_mcd_account_set_avatar (account, avatar, mime_type, NULL, error))
1621     {
1622         return FALSE;
1623     }
1624 
1625     tp_svc_account_interface_avatar_emit_avatar_changed (account);
1626     return TRUE;
1627 }
1628 
1629 static void
get_avatar(TpSvcDBusProperties * self,const gchar * name,GValue * value)1630 get_avatar (TpSvcDBusProperties *self, const gchar *name, GValue *value)
1631 {
1632     McdAccount *account = MCD_ACCOUNT (self);
1633     gchar *mime_type;
1634     GArray *avatar = NULL;
1635     GType type = TP_STRUCT_TYPE_AVATAR;
1636     GValueArray *va;
1637 
1638     _mcd_account_get_avatar (account, &avatar, &mime_type);
1639     if (!avatar)
1640         avatar = g_array_new (FALSE, FALSE, 1);
1641 
1642     g_value_init (value, type);
1643     g_value_take_boxed (value, dbus_g_type_specialized_construct (type));
1644     va = (GValueArray *) g_value_get_boxed (value);
1645     g_value_take_boxed (va->values, avatar);
1646     g_value_take_string (va->values + 1, mime_type);
1647 }
1648 
1649 static void
get_parameters(TpSvcDBusProperties * self,const gchar * name,GValue * value)1650 get_parameters (TpSvcDBusProperties *self, const gchar *name,
1651                 GValue *value)
1652 {
1653     McdAccount *account = MCD_ACCOUNT (self);
1654     GHashTable *params = _mcd_account_dup_parameters (account);
1655 
1656     if (params == NULL)
1657     {
1658         if (mcd_account_is_valid (account))
1659             g_warning ("%s is supposedly valid, but _dup_parameters() failed!",
1660                 mcd_account_get_unique_name (account));
1661 
1662         params = tp_asv_new (NULL, NULL);
1663     }
1664 
1665     g_value_init (value, TP_HASH_TYPE_STRING_VARIANT_MAP);
1666     g_value_take_boxed (value, params);
1667 }
1668 
1669 gboolean
_mcd_account_presence_type_is_settable(TpConnectionPresenceType type)1670 _mcd_account_presence_type_is_settable (TpConnectionPresenceType type)
1671 {
1672     switch (type)
1673     {
1674         case TP_CONNECTION_PRESENCE_TYPE_UNSET:
1675         case TP_CONNECTION_PRESENCE_TYPE_UNKNOWN:
1676         case TP_CONNECTION_PRESENCE_TYPE_ERROR:
1677             return FALSE;
1678 
1679         default:
1680             return TRUE;
1681     }
1682 }
1683 
1684 static gboolean
_presence_type_is_online(TpConnectionPresenceType type)1685 _presence_type_is_online (TpConnectionPresenceType type)
1686 {
1687     switch (type)
1688     {
1689         case TP_CONNECTION_PRESENCE_TYPE_UNSET:
1690         case TP_CONNECTION_PRESENCE_TYPE_OFFLINE:
1691         case TP_CONNECTION_PRESENCE_TYPE_UNKNOWN:
1692         case TP_CONNECTION_PRESENCE_TYPE_ERROR:
1693             return FALSE;
1694 
1695         default:
1696             return TRUE;
1697     }
1698 }
1699 
1700 static gboolean
set_automatic_presence(TpSvcDBusProperties * self,const gchar * name,const GValue * value,McdDBusPropSetFlags flags,GError ** error)1701 set_automatic_presence (TpSvcDBusProperties *self,
1702                         const gchar *name,
1703                         const GValue *value,
1704                         McdDBusPropSetFlags flags,
1705                         GError **error)
1706 {
1707     McdAccount *account = MCD_ACCOUNT (self);
1708     McdAccountPrivate *priv = account->priv;
1709     const gchar *status, *message;
1710     TpConnectionPresenceType type;
1711     gboolean changed = FALSE;
1712     GValueArray *va;
1713     const gchar *account_name = mcd_account_get_unique_name (account);
1714 
1715     DEBUG ("called for %s", account_name);
1716 
1717     if (!G_VALUE_HOLDS (value, TP_STRUCT_TYPE_SIMPLE_PRESENCE))
1718     {
1719         g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
1720                      "Unexpected type for AutomaticPresence: wanted (u,s,s), "
1721                      "got %s", G_VALUE_TYPE_NAME (value));
1722         return FALSE;
1723     }
1724 
1725     va = g_value_get_boxed (value);
1726     type = g_value_get_uint (va->values);
1727     status = g_value_get_string (va->values + 1);
1728     message = g_value_get_string (va->values + 2);
1729 
1730     if (!_presence_type_is_online (type))
1731     {
1732         g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
1733                      "AutomaticPresence must be an online presence, not %d",
1734                      type);
1735         return FALSE;
1736     }
1737 
1738     DEBUG ("setting automatic presence: %d, %s, %s", type, status, message);
1739 
1740     if (priv->auto_presence_type != type)
1741     {
1742         priv->auto_presence_type = type;
1743         changed = TRUE;
1744     }
1745 
1746     if (tp_strdiff (priv->auto_presence_status, status))
1747     {
1748         g_free (priv->auto_presence_status);
1749         priv->auto_presence_status = g_strdup (status);
1750         changed = TRUE;
1751     }
1752 
1753     if (tp_strdiff (priv->auto_presence_message, message))
1754     {
1755         g_free (priv->auto_presence_message);
1756         priv->auto_presence_message = g_strdup (message);
1757         changed = TRUE;
1758     }
1759 
1760     if (changed)
1761     {
1762         mcd_storage_set_attribute (priv->storage, account_name,
1763                                    MC_ACCOUNTS_KEY_AUTOMATIC_PRESENCE,
1764                                    value);
1765         mcd_storage_commit (priv->storage, account_name);
1766         mcd_account_changed_property (account, name, value);
1767     }
1768 
1769     return TRUE;
1770 }
1771 
1772 static void
get_automatic_presence(TpSvcDBusProperties * self,const gchar * name,GValue * value)1773 get_automatic_presence (TpSvcDBusProperties *self,
1774 			const gchar *name, GValue *value)
1775 {
1776     McdAccount *account = MCD_ACCOUNT (self);
1777     McdAccountPrivate *priv = account->priv;
1778     gchar *presence, *message;
1779     gint presence_type;
1780     GType type;
1781     GValueArray *va;
1782 
1783     presence_type = priv->auto_presence_type;
1784     presence = priv->auto_presence_status;
1785     message = priv->auto_presence_message;
1786 
1787     type = TP_STRUCT_TYPE_SIMPLE_PRESENCE;
1788     g_value_init (value, type);
1789     g_value_take_boxed (value, dbus_g_type_specialized_construct (type));
1790     va = (GValueArray *) g_value_get_boxed (value);
1791     g_value_set_uint (va->values, presence_type);
1792     g_value_set_static_string (va->values + 1, presence);
1793     g_value_set_static_string (va->values + 2, message);
1794 }
1795 
1796 static gboolean
set_connect_automatically(TpSvcDBusProperties * self,const gchar * name,const GValue * value,McdDBusPropSetFlags flags,GError ** error)1797 set_connect_automatically (TpSvcDBusProperties *self,
1798                            const gchar *name,
1799                            const GValue *value,
1800                            McdDBusPropSetFlags flags,
1801                            GError **error)
1802 {
1803     McdAccount *account = MCD_ACCOUNT (self);
1804     McdAccountPrivate *priv = account->priv;
1805     gboolean connect_automatically;
1806 
1807     DEBUG ("called for %s", priv->unique_name);
1808 
1809     if (!G_VALUE_HOLDS_BOOLEAN (value))
1810     {
1811         g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
1812                      "Expected boolean for ConnectAutomatically, but got %s",
1813                      G_VALUE_TYPE_NAME (value));
1814         return FALSE;
1815     }
1816 
1817     connect_automatically = g_value_get_boolean (value);
1818 
1819     if (priv->always_on && !connect_automatically)
1820     {
1821         g_set_error (error, TP_ERROR, TP_ERROR_PERMISSION_DENIED,
1822                      "Account %s always connects automatically",
1823                      priv->unique_name);
1824         return FALSE;
1825     }
1826 
1827     if (priv->connect_automatically != connect_automatically)
1828     {
1829         const gchar *account_name = mcd_account_get_unique_name (account);
1830 
1831         if (!(flags & MCD_DBUS_PROP_SET_FLAG_ALREADY_IN_STORAGE))
1832         {
1833             mcd_storage_set_attribute (priv->storage, account_name,
1834                                        MC_ACCOUNTS_KEY_CONNECT_AUTOMATICALLY,
1835                                        value);
1836             mcd_storage_commit (priv->storage, account_name);
1837         }
1838 
1839         priv->connect_automatically = connect_automatically;
1840         mcd_account_changed_property (account, name, value);
1841 
1842         if (connect_automatically)
1843         {
1844             _mcd_account_maybe_autoconnect (account);
1845         }
1846     }
1847 
1848     return TRUE;
1849 }
1850 
1851 static void
get_connect_automatically(TpSvcDBusProperties * self,const gchar * name,GValue * value)1852 get_connect_automatically (TpSvcDBusProperties *self,
1853 			   const gchar *name, GValue *value)
1854 {
1855     McdAccount *account = MCD_ACCOUNT (self);
1856     McdAccountPrivate *priv = account->priv;
1857 
1858     DEBUG ("called for %s", priv->unique_name);
1859     g_value_init (value, G_TYPE_BOOLEAN);
1860     g_value_set_boolean (value, priv->connect_automatically);
1861 }
1862 
1863 static void
get_connection(TpSvcDBusProperties * self,const gchar * name,GValue * value)1864 get_connection (TpSvcDBusProperties *self, const gchar *name, GValue *value)
1865 {
1866     McdAccount *account = MCD_ACCOUNT (self);
1867     McdAccountPrivate *priv = account->priv;
1868     const gchar *object_path;
1869 
1870     g_value_init (value, DBUS_TYPE_G_OBJECT_PATH);
1871     if (priv->connection &&
1872 	(object_path = mcd_connection_get_object_path (priv->connection)))
1873 	g_value_set_boxed (value, object_path);
1874     else
1875 	g_value_set_static_boxed (value, "/");
1876 }
1877 
1878 static void
get_connection_status(TpSvcDBusProperties * self,const gchar * name,GValue * value)1879 get_connection_status (TpSvcDBusProperties *self,
1880 		       const gchar *name, GValue *value)
1881 {
1882     McdAccount *account = MCD_ACCOUNT (self);
1883 
1884     g_value_init (value, G_TYPE_UINT);
1885     g_value_set_uint (value, account->priv->conn_status);
1886 }
1887 
1888 static void
get_connection_status_reason(TpSvcDBusProperties * self,const gchar * name,GValue * value)1889 get_connection_status_reason (TpSvcDBusProperties *self,
1890 			      const gchar *name, GValue *value)
1891 {
1892     McdAccount *account = MCD_ACCOUNT (self);
1893 
1894     g_value_init (value, G_TYPE_UINT);
1895     g_value_set_uint (value, account->priv->conn_reason);
1896 }
1897 
1898 static void
get_connection_error(TpSvcDBusProperties * self,const gchar * name,GValue * value)1899 get_connection_error (TpSvcDBusProperties *self,
1900                       const gchar *name,
1901                       GValue *value)
1902 {
1903     McdAccount *account = MCD_ACCOUNT (self);
1904 
1905     g_value_init (value, G_TYPE_STRING);
1906     g_value_set_string (value, account->priv->conn_dbus_error);
1907 }
1908 
1909 static void
get_connection_error_details(TpSvcDBusProperties * self,const gchar * name,GValue * value)1910 get_connection_error_details (TpSvcDBusProperties *self,
1911                               const gchar *name,
1912                               GValue *value)
1913 {
1914     McdAccount *account = MCD_ACCOUNT (self);
1915 
1916     g_value_init (value, TP_HASH_TYPE_STRING_VARIANT_MAP);
1917     g_value_set_boxed (value, account->priv->conn_error_details);
1918 }
1919 
1920 static void
get_current_presence(TpSvcDBusProperties * self,const gchar * name,GValue * value)1921 get_current_presence (TpSvcDBusProperties *self, const gchar *name,
1922 		      GValue *value)
1923 {
1924     McdAccount *account = MCD_ACCOUNT (self);
1925     McdAccountPrivate *priv = account->priv;
1926     gchar *status, *message;
1927     gint presence_type;
1928     GType type;
1929     GValueArray *va;
1930 
1931     presence_type = priv->curr_presence_type;
1932     status = priv->curr_presence_status;
1933     message = priv->curr_presence_message;
1934 
1935     type = TP_STRUCT_TYPE_SIMPLE_PRESENCE;
1936     g_value_init (value, type);
1937     g_value_take_boxed (value, dbus_g_type_specialized_construct (type));
1938     va = (GValueArray *) g_value_get_boxed (value);
1939     g_value_set_uint (va->values, presence_type);
1940     g_value_set_static_string (va->values + 1, status);
1941     g_value_set_static_string (va->values + 2, message);
1942 }
1943 
1944 static gboolean
set_requested_presence(TpSvcDBusProperties * self,const gchar * name,const GValue * value,McdDBusPropSetFlags flags,GError ** error)1945 set_requested_presence (TpSvcDBusProperties *self,
1946                         const gchar *name,
1947                         const GValue *value,
1948                         McdDBusPropSetFlags flags,
1949                         GError **error)
1950 {
1951     McdAccount *account = MCD_ACCOUNT (self);
1952     McdAccountPrivate *priv = account->priv;
1953     const gchar *status, *message;
1954     gint type;
1955     GValueArray *va;
1956 
1957     DEBUG ("called for %s", priv->unique_name);
1958 
1959     if (!G_VALUE_HOLDS (value, TP_STRUCT_TYPE_SIMPLE_PRESENCE))
1960     {
1961         g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
1962                      "Unexpected type for RequestedPresence: wanted (u,s,s), "
1963                      "got %s", G_VALUE_TYPE_NAME (value));
1964         return FALSE;
1965     }
1966 
1967     va = g_value_get_boxed (value);
1968     type = (gint)g_value_get_uint (va->values);
1969     status = g_value_get_string (va->values + 1);
1970     message = g_value_get_string (va->values + 2);
1971 
1972     if (priv->always_on && !_presence_type_is_online (type))
1973     {
1974         g_set_error (error, TP_ERROR, TP_ERROR_PERMISSION_DENIED,
1975                      "Account %s cannot be taken offline", priv->unique_name);
1976         return FALSE;
1977     }
1978 
1979     if (!_mcd_account_presence_type_is_settable (type))
1980     {
1981         g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
1982                      "RequestedPresence %d cannot be set on yourself", type);
1983         return FALSE;
1984     }
1985 
1986     DEBUG ("setting requested presence: %d, %s, %s", type, status, message);
1987 
1988     mcd_account_request_presence_int (account, type, status, message, TRUE);
1989     return TRUE;
1990 }
1991 
1992 static void
get_requested_presence(TpSvcDBusProperties * self,const gchar * name,GValue * value)1993 get_requested_presence (TpSvcDBusProperties *self,
1994 			const gchar *name, GValue *value)
1995 {
1996     McdAccount *account = MCD_ACCOUNT (self);
1997     McdAccountPrivate *priv = account->priv;
1998     gchar *presence, *message;
1999     gint presence_type;
2000     GType type;
2001     GValueArray *va;
2002 
2003     presence_type = priv->req_presence_type;
2004     presence = priv->req_presence_status;
2005     message = priv->req_presence_message;
2006 
2007     type = TP_STRUCT_TYPE_SIMPLE_PRESENCE;
2008     g_value_init (value, type);
2009     g_value_take_boxed (value, dbus_g_type_specialized_construct (type));
2010     va = (GValueArray *) g_value_get_boxed (value);
2011     g_value_set_uint (va->values, presence_type);
2012     g_value_set_static_string (va->values + 1, presence);
2013     g_value_set_static_string (va->values + 2, message);
2014 }
2015 
2016 static void
get_changing_presence(TpSvcDBusProperties * self,const gchar * name,GValue * value)2017 get_changing_presence (TpSvcDBusProperties *self,
2018                        const gchar *name, GValue *value)
2019 {
2020     McdAccount *account = MCD_ACCOUNT (self);
2021     McdAccountPrivate *priv = account->priv;
2022 
2023     g_value_init (value, G_TYPE_BOOLEAN);
2024     g_value_set_boolean (value, priv->changing_presence);
2025 }
2026 
2027 static void
get_normalized_name(TpSvcDBusProperties * self,const gchar * name,GValue * value)2028 get_normalized_name (TpSvcDBusProperties *self,
2029 		     const gchar *name, GValue *value)
2030 {
2031     McdAccount *account = MCD_ACCOUNT (self);
2032 
2033     mcd_account_get_string_val (account, MC_ACCOUNTS_KEY_NORMALIZED_NAME,
2034                                 value);
2035 }
2036 
2037 static gboolean
set_supersedes(TpSvcDBusProperties * svc,const gchar * name,const GValue * value,McdDBusPropSetFlags flags,GError ** error)2038 set_supersedes (TpSvcDBusProperties *svc,
2039     const gchar *name,
2040     const GValue *value,
2041     McdDBusPropSetFlags flags,
2042     GError **error)
2043 {
2044   McdAccount *self = MCD_ACCOUNT (svc);
2045 
2046   if (!G_VALUE_HOLDS (value, TP_ARRAY_TYPE_OBJECT_PATH_LIST))
2047     {
2048       g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
2049           "Unexpected type for Supersedes: wanted 'ao', got %s",
2050           G_VALUE_TYPE_NAME (value));
2051       return FALSE;
2052     }
2053 
2054   if (self->priv->supersedes != NULL)
2055     g_ptr_array_unref (self->priv->supersedes);
2056 
2057   self->priv->supersedes = g_value_dup_boxed (value);
2058   mcd_account_changed_property (self, name, value);
2059 
2060   mcd_storage_set_attribute (self->priv->storage, self->priv->unique_name,
2061       MC_ACCOUNTS_KEY_SUPERSEDES, value);
2062   mcd_storage_commit (self->priv->storage, self->priv->unique_name);
2063 
2064   return TRUE;
2065 }
2066 
2067 static void
get_supersedes(TpSvcDBusProperties * svc,const gchar * name,GValue * value)2068 get_supersedes (TpSvcDBusProperties *svc,
2069     const gchar *name,
2070     GValue *value)
2071 {
2072   McdAccount *self = MCD_ACCOUNT (svc);
2073 
2074   if (self->priv->supersedes == NULL)
2075     self->priv->supersedes = g_ptr_array_new ();
2076 
2077   g_value_init (value, TP_ARRAY_TYPE_OBJECT_PATH_LIST);
2078   g_value_set_boxed (value, self->priv->supersedes);
2079 }
2080 
2081 static McpAccountStorage *
get_storage_plugin(McdAccount * account)2082 get_storage_plugin (McdAccount *account)
2083 {
2084   McdAccountPrivate *priv = account->priv;
2085   const gchar *account_name = mcd_account_get_unique_name (account);
2086 
2087   if (priv->storage_plugin != NULL)
2088     return priv->storage_plugin;
2089 
2090   priv->storage_plugin = mcd_storage_get_plugin (priv->storage, account_name);
2091 
2092   if (priv->storage_plugin != NULL)
2093       g_object_ref (priv->storage_plugin);
2094 
2095    return priv->storage_plugin;
2096 }
2097 
2098 static void
get_storage_provider(TpSvcDBusProperties * self,const gchar * name,GValue * value)2099 get_storage_provider (TpSvcDBusProperties *self,
2100     const gchar *name, GValue *value)
2101 {
2102   McdAccount *account = MCD_ACCOUNT (self);
2103   McpAccountStorage *storage_plugin = get_storage_plugin (account);
2104 
2105   g_value_init (value, G_TYPE_STRING);
2106 
2107   if (storage_plugin != NULL)
2108     g_value_set_string (value, mcp_account_storage_provider (storage_plugin));
2109   else
2110     g_value_set_static_string (value, "");
2111 }
2112 
2113 static gboolean
set_storage_provider(TpSvcDBusProperties * self,const gchar * name,const GValue * value,McdDBusPropSetFlags flags,GError ** error)2114 set_storage_provider (TpSvcDBusProperties *self,
2115     const gchar *name,
2116     const GValue *value,
2117     McdDBusPropSetFlags flags,
2118     GError **error)
2119 {
2120   McdAccount *account = MCD_ACCOUNT (self);
2121   McpAccountStorage *storage_plugin = get_storage_plugin (account);
2122   const gchar *current_provider = mcp_account_storage_provider (storage_plugin);
2123 
2124   if (!G_VALUE_HOLDS_STRING (value) ||
2125       tp_strdiff (g_value_get_string (value), current_provider))
2126     {
2127       g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
2128           "Cannot change provider, it is defined at account creation only");
2129       return FALSE;
2130     }
2131 
2132   return TRUE;
2133 }
2134 
2135 static void
get_storage_identifier(TpSvcDBusProperties * self,const gchar * name,GValue * value)2136 get_storage_identifier (TpSvcDBusProperties *self,
2137     const gchar *name, GValue *value)
2138 {
2139 
2140   McdAccount *account = MCD_ACCOUNT (self);
2141   McpAccountStorage *storage_plugin = get_storage_plugin (account);
2142   GValue identifier = G_VALUE_INIT;
2143 
2144   g_value_init (value, G_TYPE_VALUE);
2145 
2146   if (storage_plugin != NULL)
2147     {
2148       mcp_account_storage_get_identifier (
2149           storage_plugin, account->priv->unique_name, &identifier);
2150     }
2151   else
2152     {
2153       g_value_init (&identifier, G_TYPE_UINT);
2154 
2155       g_value_set_uint (&identifier, 0);
2156     }
2157 
2158   g_value_set_boxed (value, &identifier);
2159 
2160   g_value_unset (&identifier);
2161 }
2162 
2163 static void
get_storage_specific_info(TpSvcDBusProperties * self,const gchar * name,GValue * value)2164 get_storage_specific_info (TpSvcDBusProperties *self,
2165     const gchar *name, GValue *value)
2166 {
2167   GHashTable *storage_specific_info;
2168   McdAccount *account = MCD_ACCOUNT (self);
2169   McpAccountStorage *storage_plugin = get_storage_plugin (account);
2170 
2171   g_value_init (value, TP_HASH_TYPE_STRING_VARIANT_MAP);
2172 
2173   if (storage_plugin != NULL)
2174     storage_specific_info = mcp_account_storage_get_additional_info (
2175         storage_plugin, account->priv->unique_name);
2176   else
2177     storage_specific_info = g_hash_table_new (g_str_hash, g_str_equal);
2178 
2179   g_value_take_boxed (value, storage_specific_info);
2180 }
2181 
2182 static void
get_storage_restrictions(TpSvcDBusProperties * self,const gchar * name,GValue * value)2183 get_storage_restrictions (TpSvcDBusProperties *self,
2184     const gchar *name, GValue *value)
2185 {
2186   TpStorageRestrictionFlags flags;
2187   McdAccount *account = MCD_ACCOUNT (self);
2188   McpAccountStorage *storage_plugin = get_storage_plugin (account);
2189 
2190   g_value_init (value, G_TYPE_UINT);
2191 
2192   g_return_if_fail (storage_plugin != NULL);
2193 
2194   flags = mcp_account_storage_get_restrictions (storage_plugin,
2195       account->priv->unique_name);
2196 
2197   g_value_set_uint (value, flags);
2198 }
2199 
2200 static const McdDBusProp account_properties[] = {
2201     { "Interfaces", NULL, mcd_dbus_get_interfaces },
2202     { "DisplayName", set_display_name, get_display_name },
2203     { "Icon", set_icon, get_icon },
2204     { "Valid", NULL, get_valid },
2205     { "Enabled", set_enabled, get_enabled },
2206     { "Nickname", set_nickname, get_nickname },
2207     { "Service", set_service, get_service  },
2208     { "Parameters", NULL, get_parameters },
2209     { "AutomaticPresence", set_automatic_presence, get_automatic_presence },
2210     { "ConnectAutomatically", set_connect_automatically, get_connect_automatically },
2211     { "Connection", NULL, get_connection },
2212     { "ConnectionStatus", NULL, get_connection_status },
2213     { "ConnectionStatusReason", NULL, get_connection_status_reason },
2214     { "ConnectionError", NULL, get_connection_error },
2215     { "ConnectionErrorDetails", NULL, get_connection_error_details },
2216     { "CurrentPresence", NULL, get_current_presence },
2217     { "RequestedPresence", set_requested_presence, get_requested_presence },
2218     { "ChangingPresence", NULL, get_changing_presence },
2219     { "NormalizedName", NULL, get_normalized_name },
2220     { "HasBeenOnline", NULL, get_has_been_online },
2221     { "Supersedes", set_supersedes, get_supersedes },
2222     { 0 },
2223 };
2224 
2225 static const McdDBusProp account_avatar_properties[] = {
2226     { "Avatar", set_avatar, get_avatar },
2227     { 0 },
2228 };
2229 
2230 static const McdDBusProp account_storage_properties[] = {
2231     { "StorageProvider", set_storage_provider, get_storage_provider },
2232     { "StorageIdentifier", NULL, get_storage_identifier },
2233     { "StorageSpecificInformation", NULL, get_storage_specific_info },
2234     { "StorageRestrictions", NULL, get_storage_restrictions },
2235     { 0 },
2236 };
2237 
2238 static void
account_avatar_iface_init(TpSvcAccountInterfaceAvatarClass * iface,gpointer iface_data)2239 account_avatar_iface_init (TpSvcAccountInterfaceAvatarClass *iface,
2240 			   gpointer iface_data)
2241 {
2242 }
2243 
2244 static void
account_storage_iface_init(TpSvcAccountInterfaceStorageClass * iface,gpointer iface_data)2245 account_storage_iface_init (TpSvcAccountInterfaceStorageClass *iface,
2246                              gpointer iface_data)
2247 {
2248 }
2249 
2250 static void
get_hidden(TpSvcDBusProperties * self,const gchar * name,GValue * value)2251 get_hidden (TpSvcDBusProperties *self,
2252     const gchar *name, GValue *value)
2253 {
2254   g_value_init (value, G_TYPE_BOOLEAN);
2255   g_object_get_property (G_OBJECT (self), "hidden", value);
2256 }
2257 
2258 static gboolean
set_hidden(TpSvcDBusProperties * self,const gchar * name,const GValue * value,McdDBusPropSetFlags flags,GError ** error)2259 set_hidden (TpSvcDBusProperties *self,
2260     const gchar *name,
2261     const GValue *value,
2262     McdDBusPropSetFlags flags,
2263     GError **error)
2264 {
2265   McdAccount *account = MCD_ACCOUNT (self);
2266   McdAccountPrivate *priv = account->priv;
2267   const gchar *account_name = mcd_account_get_unique_name (account);
2268 
2269   if (!G_VALUE_HOLDS_BOOLEAN (value))
2270     {
2271       g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
2272           "Hidden must be set to a boolean, not a %s",
2273           G_VALUE_TYPE_NAME (value));
2274       return FALSE;
2275     }
2276 
2277   /* Technically this property is immutable after the account's been created,
2278    * but currently it's not easy for this code to tell whether or not this is
2279    * a create-time property. It would probably be better if the create-time
2280    * properties were passed into us as a construct-time GObject property. But
2281    * that's a job for another month.
2282    *
2283    * So for now we check whether the value has changed, and violate the spec
2284    * by making this property mutable (at least with the keyfile backend).
2285    */
2286   if (mcd_storage_set_attribute (priv->storage, account_name,
2287           MC_ACCOUNTS_KEY_HIDDEN, value))
2288     {
2289       mcd_storage_commit (priv->storage, account_name);
2290       mcd_account_changed_property (account, MC_ACCOUNTS_KEY_HIDDEN, value);
2291       g_object_set_property (G_OBJECT (self), "hidden", value);
2292     }
2293 
2294   return TRUE;
2295 }
2296 
2297 static const McdDBusProp account_hidden_properties[] = {
2298     { "Hidden", set_hidden, get_hidden },
2299     { 0 },
2300 };
2301 
2302 static void
account_hidden_iface_init(McSvcAccountInterfaceHiddenClass * iface,gpointer iface_data)2303 account_hidden_iface_init (
2304     McSvcAccountInterfaceHiddenClass *iface,
2305     gpointer iface_data)
2306 {
2307   /* wow, it's pretty crap that I need this. */
2308 }
2309 
2310 static void
get_password_saved(TpSvcDBusProperties * self,const gchar * name,GValue * value)2311 get_password_saved (TpSvcDBusProperties *self,
2312     const gchar *name,
2313     GValue *value)
2314 {
2315   McdAccount *account = MCD_ACCOUNT (self);
2316 
2317   g_assert_cmpstr (name, ==, "PasswordSaved");
2318 
2319   g_value_init (value, G_TYPE_BOOLEAN);
2320   g_value_set_boolean (value, account->priv->password_saved);
2321 }
2322 
2323 static const McdDBusProp account_external_password_storage_properties[] = {
2324     { "PasswordSaved", NULL, get_password_saved },
2325     { 0 },
2326 };
2327 
2328 static void
account_external_password_storage_forget_credentials_cb(TpProxy * cm,const GError * in_error,gpointer user_data,GObject * self)2329 account_external_password_storage_forget_credentials_cb (TpProxy *cm,
2330     const GError *in_error,
2331     gpointer user_data,
2332     GObject *self)
2333 {
2334   DBusGMethodInvocation *context = user_data;
2335 
2336   if (in_error != NULL)
2337     {
2338       dbus_g_method_return_error (context, in_error);
2339       return;
2340     }
2341 
2342   mc_svc_account_interface_external_password_storage_return_from_forget_password (context);
2343 }
2344 
2345 static void
account_external_password_storage_identify_account_cb(TpProxy * protocol,const char * account_id,const GError * in_error,gpointer user_data,GObject * self)2346 account_external_password_storage_identify_account_cb (TpProxy *protocol,
2347     const char *account_id,
2348     const GError *in_error,
2349     gpointer user_data,
2350     GObject *self)
2351 {
2352   McdAccount *account = MCD_ACCOUNT (self);
2353   DBusGMethodInvocation *context = user_data;
2354   TpConnectionManager *cm = mcd_account_get_cm (account);
2355 
2356   if (in_error != NULL)
2357     {
2358       dbus_g_method_return_error (context, in_error);
2359       return;
2360     }
2361 
2362   DEBUG ("Identified account as %s", account_id);
2363 
2364   mc_cli_connection_manager_interface_account_storage_call_forget_credentials (
2365       cm, -1, account_id,
2366       account_external_password_storage_forget_credentials_cb,
2367       context, NULL, self);
2368 }
2369 
2370 static void
account_external_password_storage_forget_password(McSvcAccountInterfaceExternalPasswordStorage * self,DBusGMethodInvocation * context)2371 account_external_password_storage_forget_password (
2372     McSvcAccountInterfaceExternalPasswordStorage *self,
2373     DBusGMethodInvocation *context)
2374 {
2375   McdAccount *account = MCD_ACCOUNT (self);
2376   TpConnectionManager *cm = mcd_account_get_cm (account);
2377   TpProtocol *protocol;
2378   GHashTable *params;
2379 
2380   /* do we support the interface */
2381   if (!tp_proxy_has_interface_by_id (cm,
2382           MC_IFACE_QUARK_CONNECTION_MANAGER_INTERFACE_ACCOUNT_STORAGE))
2383     {
2384       GError *error = g_error_new (TP_ERROR, TP_ERROR_NOT_IMPLEMENTED,
2385           "CM for this Account does not implement AccountStorage iface");
2386 
2387       dbus_g_method_return_error (context, error);
2388       g_error_free (error);
2389 
2390       return;
2391     }
2392 
2393   /* identify the account */
2394   protocol = tp_connection_manager_get_protocol_object (cm,
2395       account->priv->protocol_name);
2396   params = _mcd_account_dup_parameters (account);
2397 
2398   tp_cli_protocol_call_identify_account (protocol, -1, params,
2399       account_external_password_storage_identify_account_cb,
2400       context, NULL, G_OBJECT (self));
2401 
2402   g_hash_table_unref (params);
2403 }
2404 
2405 static void
account_external_password_storage_iface_init(McSvcAccountInterfaceExternalPasswordStorageClass * iface,gpointer iface_data)2406 account_external_password_storage_iface_init (
2407     McSvcAccountInterfaceExternalPasswordStorageClass *iface,
2408     gpointer iface_data)
2409 {
2410 #define IMPLEMENT(x) \
2411   mc_svc_account_interface_external_password_storage_implement_##x (\
2412       iface, account_external_password_storage_##x)
2413   IMPLEMENT (forget_password);
2414 #undef IMPLEMENT
2415 }
2416 
2417 static void
properties_iface_init(TpSvcDBusPropertiesClass * iface,gpointer iface_data)2418 properties_iface_init (TpSvcDBusPropertiesClass *iface, gpointer iface_data)
2419 {
2420 #define IMPLEMENT(x) tp_svc_dbus_properties_implement_##x (\
2421     iface, dbusprop_##x)
2422     IMPLEMENT(set);
2423     IMPLEMENT(get);
2424     IMPLEMENT(get_all);
2425 #undef IMPLEMENT
2426 }
2427 
2428 static GType
mc_param_type(const TpConnectionManagerParam * param)2429 mc_param_type (const TpConnectionManagerParam *param)
2430 {
2431     const gchar *dbus_signature;
2432 
2433     if (G_UNLIKELY (param == NULL))
2434         return G_TYPE_INVALID;
2435 
2436     dbus_signature = tp_connection_manager_param_get_dbus_signature (param);
2437 
2438     if (G_UNLIKELY (!dbus_signature))
2439         return G_TYPE_INVALID;
2440 
2441     switch (dbus_signature[0])
2442     {
2443     case DBUS_TYPE_STRING:
2444 	return G_TYPE_STRING;
2445 
2446     case DBUS_TYPE_BYTE:
2447         return G_TYPE_UCHAR;
2448 
2449     case DBUS_TYPE_INT16:
2450     case DBUS_TYPE_INT32:
2451 	return G_TYPE_INT;
2452 
2453     case DBUS_TYPE_UINT16:
2454     case DBUS_TYPE_UINT32:
2455 	return G_TYPE_UINT;
2456 
2457     case DBUS_TYPE_BOOLEAN:
2458 	return G_TYPE_BOOLEAN;
2459 
2460     case DBUS_TYPE_DOUBLE:
2461         return G_TYPE_DOUBLE;
2462 
2463     case DBUS_TYPE_OBJECT_PATH:
2464         return DBUS_TYPE_G_OBJECT_PATH;
2465 
2466     case DBUS_TYPE_INT64:
2467         return G_TYPE_INT64;
2468 
2469     case DBUS_TYPE_UINT64:
2470         return G_TYPE_UINT64;
2471 
2472     case DBUS_TYPE_ARRAY:
2473         if (dbus_signature[1] == DBUS_TYPE_STRING)
2474             return G_TYPE_STRV;
2475         /* other array types are not supported:
2476          * fall through the default case */
2477     default:
2478         g_warning ("skipping parameter %s, unknown type %s",
2479             tp_connection_manager_param_get_name (param), dbus_signature);
2480     }
2481     return G_TYPE_INVALID;
2482 }
2483 
2484 typedef struct
2485 {
2486     McdAccount *self;
2487     DBusGMethodInvocation *context;
2488 } RemoveMethodData;
2489 
2490 static void
account_remove_delete_cb(McdAccount * account,const GError * error,gpointer user_data)2491 account_remove_delete_cb (McdAccount *account, const GError *error,
2492                           gpointer user_data)
2493 {
2494     RemoveMethodData *data = (RemoveMethodData *) user_data;
2495 
2496     if (error != NULL)
2497     {
2498         dbus_g_method_return_error (data->context, (GError *) error);
2499         return;
2500     }
2501 
2502     if (!data->self->priv->removed)
2503     {
2504         data->self->priv->removed = TRUE;
2505         tp_svc_account_emit_removed (data->self);
2506     }
2507 
2508     tp_svc_account_return_from_remove (data->context);
2509 
2510     g_slice_free (RemoveMethodData, data);
2511 }
2512 
2513 static void
account_remove(TpSvcAccount * svc,DBusGMethodInvocation * context)2514 account_remove (TpSvcAccount *svc, DBusGMethodInvocation *context)
2515 {
2516     McdAccount *self = MCD_ACCOUNT (svc);
2517     RemoveMethodData *data;
2518 
2519     data = g_slice_new0 (RemoveMethodData);
2520     data->self = self;
2521     data->context = context;
2522 
2523     DEBUG ("called");
2524     mcd_account_delete (self, account_remove_delete_cb, data);
2525 }
2526 
2527 /*
2528  * @account: the account
2529  * @name: an attribute name, or "param-" + a parameter name
2530  *
2531  * Tell the account that one of its attributes or parameters has changed
2532  * behind its back (as opposed to an external change triggered by DBus,
2533  * for example). This occurs when a storage plugin wishes to notify us
2534  * that something has changed. This will trigger an update when the
2535  * callback receives the new value. */
2536 void
mcd_account_altered_by_plugin(McdAccount * account,const gchar * name)2537 mcd_account_altered_by_plugin (McdAccount *account,
2538                                const gchar *name)
2539 {
2540     /* parameters are handled en bloc, reinvoke self with bloc key: */
2541     if (g_str_has_prefix (name, "param-"))
2542     {
2543         mcd_account_altered_by_plugin (account, "Parameters");
2544     }
2545     else
2546     {
2547         guint i = 0;
2548         const McdDBusProp *prop = NULL;
2549         GValue value = G_VALUE_INIT;
2550         GError *error = NULL;
2551 
2552         DEBUG ("%s", name);
2553 
2554         if (tp_strdiff (name, "Parameters") &&
2555             !mcd_storage_init_value_for_attribute (&value, name))
2556         {
2557             WARNING ("plugin wants to alter %s but I don't know what "
2558                      "type that ought to be", name);
2559             return;
2560         }
2561 
2562         if (!tp_strdiff (name, "Parameters"))
2563         {
2564             get_parameters (TP_SVC_DBUS_PROPERTIES (account), name, &value);
2565         }
2566         else if (!mcd_storage_get_attribute (account->priv->storage,
2567                                              account->priv->unique_name,
2568                                              name, &value, &error))
2569         {
2570             WARNING ("cannot get new value of %s: %s", name, error->message);
2571             g_error_free (error);
2572             return;
2573         }
2574 
2575         /* find the property update handler */
2576         for (; prop == NULL && account_properties[i].name != NULL; i++)
2577         {
2578             if (g_str_equal (name, account_properties[i].name))
2579                 prop = &account_properties[i];
2580         }
2581 
2582         /* is a known property: invoke the getter method for it (if any): *
2583          * then issue the change notification (DBus signals etc) for it   */
2584         if (prop != NULL)
2585         {
2586             /* poke the value back into itself with the setter: this      *
2587              * extra round-trip may trigger extra actions like notifying  *
2588              * the connection manager of the change, even though our own  *
2589              * internal storage already has this value and needn't change */
2590             if (prop->setprop != NULL)
2591             {
2592                 DEBUG ("Calling property setter for %s", name);
2593                 if (!prop->setprop (TP_SVC_DBUS_PROPERTIES (account),
2594                                     prop->name, &value,
2595                                     MCD_DBUS_PROP_SET_FLAG_ALREADY_IN_STORAGE,
2596                                     &error))
2597                 {
2598                     WARNING ("Unable to set %s: %s", name, error->message);
2599                     g_error_free (error);
2600                 }
2601             }
2602             else
2603             {
2604                 DEBUG ("Emitting signal directly for %s", name);
2605                 mcd_account_changed_property (account, prop->name, &value);
2606             }
2607         }
2608         else
2609         {
2610             DEBUG ("%s does not appear to be an Account property", name);
2611         }
2612 
2613         g_value_unset (&value);
2614     }
2615 }
2616 
2617 
2618 static void
mcd_account_check_parameters(McdAccount * account,CheckParametersCb callback,gpointer user_data)2619 mcd_account_check_parameters (McdAccount *account,
2620                               CheckParametersCb callback,
2621                               gpointer user_data)
2622 {
2623     McdAccountPrivate *priv = account->priv;
2624     TpProtocol *protocol;
2625     GList *params = NULL;
2626     GList *iter;
2627     GError *error = NULL;
2628 
2629     g_return_if_fail (callback != NULL);
2630 
2631     DEBUG ("called for %s", priv->unique_name);
2632     protocol = _mcd_manager_dup_protocol (priv->manager, priv->protocol_name);
2633 
2634     if (protocol == NULL)
2635     {
2636         g_set_error (&error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
2637             "CM '%s' doesn't implement protocol '%s'", priv->manager_name,
2638             priv->protocol_name);
2639         goto out;
2640     }
2641 
2642     params = tp_protocol_dup_params (protocol);
2643 
2644     for (iter = params; iter != NULL; iter = iter->next)
2645     {
2646         TpConnectionManagerParam *param = iter->data;
2647         const gchar *param_name = tp_connection_manager_param_get_name (param);
2648 
2649         if (!tp_connection_manager_param_is_required ((param)))
2650             continue;
2651 
2652         if (!mcd_account_get_parameter (account, param_name, NULL, NULL))
2653         {
2654             g_set_error (&error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
2655                 "missing required parameter '%s'", param_name);
2656             goto out;
2657         }
2658     }
2659 
2660 out:
2661     if (error != NULL)
2662     {
2663         DEBUG ("%s", error->message);
2664     }
2665 
2666     callback (account, error, user_data);
2667     g_clear_error (&error);
2668     g_list_free_full (params,
2669                       (GDestroyNotify) tp_connection_manager_param_free);
2670     g_clear_object (&protocol);
2671 }
2672 
2673 static void
set_parameters_maybe_autoconnect_cb(McdAccount * account,const GError * invalid_reason,gpointer user_data G_GNUC_UNUSED)2674 set_parameters_maybe_autoconnect_cb (McdAccount *account,
2675                                      const GError *invalid_reason,
2676                                      gpointer user_data G_GNUC_UNUSED)
2677 {
2678     /* Strictly speaking this doesn't need to be called unless invalid_reason
2679      * is NULL, but calling it in all cases gives us clearer debug output */
2680     _mcd_account_maybe_autoconnect (account);
2681 }
2682 
2683 static void
apply_parameter_updates(McdAccount * account,GHashTable * params,const gchar ** unset,GHashTable * dbus_properties)2684 apply_parameter_updates (McdAccount *account,
2685                          GHashTable *params,
2686                          const gchar **unset,
2687                          GHashTable *dbus_properties)
2688 {
2689     McdAccountPrivate *priv = account->priv;
2690     GHashTableIter iter;
2691     gpointer name, value;
2692     const gchar **unset_iter;
2693 
2694     g_hash_table_iter_init (&iter, params);
2695     while (g_hash_table_iter_next (&iter, &name, &value))
2696     {
2697         _mcd_account_set_parameter (account, name, value);
2698     }
2699 
2700     for (unset_iter = unset;
2701          unset_iter != NULL && *unset_iter != NULL;
2702          unset_iter++)
2703     {
2704         _mcd_account_set_parameter (account, *unset_iter, NULL);
2705     }
2706 
2707     if (mcd_account_get_connection_status (account) ==
2708         TP_CONNECTION_STATUS_CONNECTED)
2709     {
2710         g_hash_table_iter_init (&iter, dbus_properties);
2711         while (g_hash_table_iter_next (&iter, &name, &value))
2712         {
2713             DEBUG ("updating parameter %s", (const gchar *) name);
2714             _mcd_connection_update_property (priv->connection, name, value);
2715         }
2716     }
2717 
2718     mcd_account_check_validity (account,
2719                                 set_parameters_maybe_autoconnect_cb, NULL);
2720 }
2721 
2722 static void
set_parameter_changed(GHashTable * dbus_properties,GPtrArray * not_yet,const TpConnectionManagerParam * param,const GValue * new_value)2723 set_parameter_changed (GHashTable *dbus_properties,
2724                        GPtrArray *not_yet,
2725                        const TpConnectionManagerParam *param,
2726                        const GValue *new_value)
2727 {
2728     const gchar *name = tp_connection_manager_param_get_name (param);
2729 
2730     DEBUG ("Parameter %s changed", name);
2731 
2732     /* can the param be updated on the fly? If yes, prepare to do so; and if
2733      * not, prepare to reset the connection */
2734     if (tp_connection_manager_param_is_dbus_property (param))
2735     {
2736         g_hash_table_insert (dbus_properties, g_strdup (name),
2737             tp_g_value_slice_dup (new_value));
2738     }
2739     else
2740     {
2741         g_ptr_array_add (not_yet, g_strdup (name));
2742     }
2743 }
2744 
2745 static gboolean
check_one_parameter_update(McdAccount * account,TpProtocol * protocol,GHashTable * dbus_properties,GPtrArray * not_yet,const gchar * name,const GValue * new_value,GError ** error)2746 check_one_parameter_update (McdAccount *account,
2747                             TpProtocol *protocol,
2748                             GHashTable *dbus_properties,
2749                             GPtrArray *not_yet,
2750                             const gchar *name,
2751                             const GValue *new_value,
2752                             GError **error)
2753 {
2754     const TpConnectionManagerParam *param =
2755         tp_protocol_get_param (protocol, name);
2756     GType type;
2757 
2758     if (param == NULL)
2759     {
2760         g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
2761                      "Protocol '%s' does not have parameter '%s'",
2762                      tp_protocol_get_name (protocol), name);
2763         return FALSE;
2764     }
2765 
2766     type = mc_param_type (param);
2767 
2768     if (G_VALUE_TYPE (new_value) != type)
2769     {
2770         /* FIXME: use D-Bus type names, not GType names. */
2771         g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
2772                      "parameter '%s' must be of type %s, not %s",
2773                      tp_connection_manager_param_get_name (param),
2774                      g_type_name (type), G_VALUE_TYPE_NAME (new_value));
2775         return FALSE;
2776     }
2777 
2778     if (mcd_account_get_connection_status (account) ==
2779         TP_CONNECTION_STATUS_CONNECTED)
2780     {
2781         GValue current_value = G_VALUE_INIT;
2782 
2783         /* Check if the parameter's current value (or its default, if it has
2784          * one and it's not set to anything) matches the new value.
2785          */
2786         if (mcd_account_get_parameter (account, tp_connection_manager_param_get_name (param),
2787                 &current_value, NULL) ||
2788             tp_connection_manager_param_get_default (param, &current_value))
2789         {
2790             if (!value_is_same (&current_value, new_value))
2791                 set_parameter_changed (dbus_properties, not_yet, param,
2792                                        new_value);
2793 
2794             g_value_unset (&current_value);
2795         }
2796         else
2797         {
2798             /* The parameter wasn't previously set, and has no default value;
2799              * this update must be a change.
2800              */
2801             set_parameter_changed (dbus_properties, not_yet, param, new_value);
2802         }
2803     }
2804 
2805     return TRUE;
2806 }
2807 
2808 static gboolean
check_one_parameter_unset(McdAccount * account,TpProtocol * protocol,GHashTable * dbus_properties,GPtrArray * not_yet,const gchar * name,GError ** error)2809 check_one_parameter_unset (McdAccount *account,
2810                            TpProtocol *protocol,
2811                            GHashTable *dbus_properties,
2812                            GPtrArray *not_yet,
2813                            const gchar *name,
2814                            GError **error)
2815 {
2816     const TpConnectionManagerParam *param =
2817         tp_protocol_get_param (protocol, name);
2818 
2819     /* The spec decrees that “If the given parameters […] do not exist at all,
2820      * the account manager MUST accept this without error.”. Thus this function
2821      * is a no-op if @name doesn't actually exist.
2822      */
2823     if (param != NULL &&
2824         mcd_account_get_connection_status (account) ==
2825             TP_CONNECTION_STATUS_CONNECTED)
2826     {
2827         GValue current_value = G_VALUE_INIT;
2828 
2829         if (mcd_account_get_parameter (account, tp_connection_manager_param_get_name (param),
2830                                        &current_value, NULL))
2831         {
2832             /* There's an existing value; let's see if it's the same as the
2833              * default, if any.
2834              */
2835             GValue default_value = G_VALUE_INIT;
2836 
2837             if (tp_connection_manager_param_get_default (param, &default_value))
2838             {
2839                 if (!value_is_same (&current_value, &default_value))
2840                     set_parameter_changed (dbus_properties, not_yet, param,
2841                                            &default_value);
2842 
2843                 g_value_unset (&default_value);
2844             }
2845             else
2846             {
2847                 /* It has no default; we're gonna have to reconnect to make
2848                  * this take effect.
2849                  */
2850                 g_ptr_array_add (not_yet, g_strdup (tp_connection_manager_param_get_name (param)));
2851             }
2852 
2853             g_value_unset (&current_value);
2854         }
2855     }
2856 
2857     return TRUE;
2858 }
2859 
2860 static gboolean
check_parameters(McdAccount * account,TpProtocol * protocol,GHashTable * params,const gchar ** unset,GHashTable * dbus_properties,GPtrArray * not_yet,GError ** error)2861 check_parameters (McdAccount *account,
2862                   TpProtocol *protocol,
2863                   GHashTable *params,
2864                   const gchar **unset,
2865                   GHashTable *dbus_properties,
2866                   GPtrArray *not_yet,
2867                   GError **error)
2868 {
2869     GHashTableIter iter;
2870     gpointer key, value;
2871     const gchar **unset_iter;
2872 
2873     g_hash_table_iter_init (&iter, params);
2874     while (g_hash_table_iter_next (&iter, &key, &value))
2875     {
2876         if (!check_one_parameter_update (account, protocol, dbus_properties,
2877                                          not_yet, key, value, error))
2878             return FALSE;
2879     }
2880 
2881     for (unset_iter = unset;
2882          unset_iter != NULL && *unset_iter != NULL;
2883          unset_iter++)
2884     {
2885         if (!check_one_parameter_unset (account, protocol, dbus_properties,
2886                                         not_yet, *unset_iter, error))
2887             return FALSE;
2888     }
2889 
2890     return TRUE;
2891 }
2892 
2893 /*
2894  * _mcd_account_set_parameters:
2895  * @account: the #McdAccount.
2896  * @name: the parameter name.
2897  * @params: names and values of parameters to set
2898  * @unset: names of parameters to unset
2899  * @callback: function to be called when finished
2900  * @user_data: data to be passed to @callback
2901  *
2902  * Alter the account parameters.
2903  *
2904  */
2905 void
_mcd_account_set_parameters(McdAccount * account,GHashTable * params,const gchar ** unset,McdAccountSetParametersCb callback,gpointer user_data)2906 _mcd_account_set_parameters (McdAccount *account, GHashTable *params,
2907                              const gchar **unset,
2908                              McdAccountSetParametersCb callback,
2909                              gpointer user_data)
2910 {
2911     McdAccountPrivate *priv = account->priv;
2912     GHashTable *dbus_properties = NULL;
2913     GPtrArray *not_yet = NULL;
2914     GError *error = NULL;
2915     TpProtocol *protocol = NULL;
2916 
2917     DEBUG ("called");
2918     if (G_UNLIKELY (!priv->manager && !load_manager (account)))
2919     {
2920         /* FIXME: this branch is never reached, even if the specified CM
2921          * doesn't actually exist: load_manager essentially always succeeds,
2922          * but of course the TpCM hasn't prepared (or failed, as it will if we
2923          * would like to hit this path) yet. So in practice we hit the next
2924          * block for nonexistant CMs too.
2925          */
2926         g_set_error (&error, TP_ERROR, TP_ERROR_NOT_IMPLEMENTED,
2927                      "Manager '%s' not found", priv->manager_name);
2928         goto out;
2929     }
2930 
2931     protocol = _mcd_manager_dup_protocol (priv->manager, priv->protocol_name);
2932 
2933     if (G_UNLIKELY (protocol == NULL))
2934     {
2935         g_set_error (&error, TP_ERROR, TP_ERROR_NOT_IMPLEMENTED,
2936                      "Protocol '%s' not found on CM '%s'", priv->protocol_name,
2937                      priv->manager_name);
2938         goto out;
2939     }
2940 
2941     /* An a{sv} of DBus_Property parameters we should set on the connection. */
2942     dbus_properties = g_hash_table_new_full (g_str_hash, g_str_equal,
2943           g_free, (GDestroyNotify) tp_g_value_slice_free);
2944     not_yet = g_ptr_array_new_with_free_func (g_free);
2945 
2946     if (check_parameters (account, protocol, params, unset, dbus_properties,
2947                            not_yet, &error))
2948         apply_parameter_updates (account, params, unset, dbus_properties);
2949 
2950 out:
2951     if (callback != NULL)
2952     {
2953         if (error == NULL)
2954             callback (account, not_yet, NULL, user_data);
2955         else
2956             callback (account, NULL, error, user_data);
2957     }
2958 
2959     g_clear_error (&error);
2960     tp_clear_pointer (&dbus_properties, g_hash_table_unref);
2961     tp_clear_pointer (&not_yet, g_ptr_array_unref);
2962     g_clear_object (&protocol);
2963 }
2964 
2965 static void
account_update_parameters_cb(McdAccount * account,GPtrArray * not_yet,const GError * error,gpointer user_data)2966 account_update_parameters_cb (McdAccount *account, GPtrArray *not_yet,
2967                               const GError *error, gpointer user_data)
2968 {
2969     McdAccountPrivate *priv = account->priv;
2970     DBusGMethodInvocation *context = (DBusGMethodInvocation *) user_data;
2971     const gchar *account_name = mcd_account_get_unique_name (account);
2972     GHashTable *params;
2973     GValue value = G_VALUE_INIT;
2974 
2975     if (error != NULL)
2976     {
2977         dbus_g_method_return_error (context, (GError *) error);
2978         return;
2979     }
2980 
2981     /* Emit the PropertiesChanged signal */
2982     params = _mcd_account_dup_parameters (account);
2983     g_return_if_fail (params != NULL);
2984 
2985     g_value_init (&value, TP_HASH_TYPE_STRING_VARIANT_MAP);
2986     g_value_take_boxed (&value, params);
2987     mcd_account_changed_property (account, "Parameters", &value);
2988     g_value_unset (&value);
2989 
2990     /* Commit the changes to disk */
2991     mcd_storage_commit (priv->storage, account_name);
2992 
2993     /* And finally, return from UpdateParameters() */
2994     g_ptr_array_add (not_yet, NULL);
2995     tp_svc_account_return_from_update_parameters (context,
2996         (const gchar **) not_yet->pdata);
2997 }
2998 
2999 static void
account_update_parameters(TpSvcAccount * self,GHashTable * set,const gchar ** unset,DBusGMethodInvocation * context)3000 account_update_parameters (TpSvcAccount *self, GHashTable *set,
3001 			   const gchar **unset, DBusGMethodInvocation *context)
3002 {
3003     McdAccount *account = MCD_ACCOUNT (self);
3004     McdAccountPrivate *priv = account->priv;
3005 
3006     DEBUG ("called for %s", priv->unique_name);
3007 
3008     _mcd_account_set_parameters (account, set, unset,
3009                                  account_update_parameters_cb, context);
3010 }
3011 
3012 void
_mcd_account_reconnect(McdAccount * self,gboolean user_initiated)3013 _mcd_account_reconnect (McdAccount *self,
3014     gboolean user_initiated)
3015 {
3016     /* FIXME: this isn't quite right. If we've just called RequestConnection
3017      * (possibly with out of date parameters) but we haven't got a Connection
3018      * back from the CM yet, the old parameters will still be used, I think
3019      * (I can't quite make out what actually happens). */
3020     if (self->priv->connection)
3021         mcd_connection_close (self->priv->connection, NULL);
3022 
3023     _mcd_account_connection_begin (self, user_initiated);
3024 }
3025 
3026 static void
account_reconnect(TpSvcAccount * service,DBusGMethodInvocation * context)3027 account_reconnect (TpSvcAccount *service,
3028                    DBusGMethodInvocation *context)
3029 {
3030     McdAccount *self = MCD_ACCOUNT (service);
3031     McdAccountPrivate *priv = self->priv;
3032 
3033     DEBUG ("%s", mcd_account_get_unique_name (self));
3034 
3035     /* if we can't, or don't want to, connect this method is a no-op */
3036     if (!priv->enabled ||
3037         !mcd_account_is_valid (self) ||
3038         priv->req_presence_type == TP_CONNECTION_PRESENCE_TYPE_OFFLINE)
3039     {
3040         DEBUG ("doing nothing (enabled=%c, valid=%c and "
3041                "combined presence=%i)",
3042                self->priv->enabled ? 'T' : 'F',
3043                mcd_account_is_valid (self) ? 'T' : 'F',
3044                self->priv->req_presence_type);
3045         tp_svc_account_return_from_reconnect (context);
3046         return;
3047     }
3048 
3049     /* Reconnect() counts as user-initiated */
3050     _mcd_account_reconnect (self, TRUE);
3051 
3052     /* FIXME: we shouldn't really return from this method until the
3053      * reconnection has actually happened, but that would require less tangled
3054      * integration between Account and Connection */
3055     tp_svc_account_return_from_reconnect (context);
3056 }
3057 
3058 static void
account_iface_init(TpSvcAccountClass * iface,gpointer iface_data)3059 account_iface_init (TpSvcAccountClass *iface, gpointer iface_data)
3060 {
3061 #define IMPLEMENT(x) tp_svc_account_implement_##x (\
3062     iface, account_##x)
3063     IMPLEMENT(remove);
3064     IMPLEMENT(update_parameters);
3065     IMPLEMENT(reconnect);
3066 #undef IMPLEMENT
3067 }
3068 
3069 static void
register_dbus_service(McdAccount * self,const GError * error,gpointer unused G_GNUC_UNUSED)3070 register_dbus_service (McdAccount *self,
3071                        const GError *error,
3072                        gpointer unused G_GNUC_UNUSED)
3073 {
3074     DBusGConnection *dbus_connection;
3075     TpDBusDaemon *dbus_daemon;
3076 
3077     if (error != NULL)
3078     {
3079         /* due to some tangled error handling, the McdAccount might already
3080          * have been freed by the time we get here, so it's no longer safe to
3081          * dereference self here! */
3082         DEBUG ("%p failed to load: %s code %d: %s", self,
3083                g_quark_to_string (error->domain), error->code, error->message);
3084         return;
3085     }
3086 
3087     g_assert (MCD_IS_ACCOUNT (self));
3088     /* these are invariants - the storage is set at construct-time
3089      * and the object path is set in mcd_account_setup, both of which are
3090      * run before this callback can possibly be invoked */
3091     g_assert (self->priv->storage != NULL);
3092     g_assert (self->priv->object_path != NULL);
3093 
3094     dbus_daemon = self->priv->dbus_daemon;
3095     g_return_if_fail (dbus_daemon != NULL);
3096 
3097     dbus_connection = tp_proxy_get_dbus_connection (TP_PROXY (dbus_daemon));
3098 
3099     if (G_LIKELY (dbus_connection)) {
3100         dbus_g_connection_register_g_object (dbus_connection,
3101                                              self->priv->object_path,
3102                                              (GObject *) self);
3103 
3104         self->priv->registered = TRUE;
3105     }
3106 }
3107 
3108 /*
3109  * @account: (allow-none):
3110  * @dir_out: (out): e.g. ~/.local/share/telepathy/mission-control
3111  * @basename_out: (out): e.g. gabble-jabber-fred_40example_2ecom.avatar
3112  * @file_out: (out): @dir_out + "/" + @basename_out
3113  */
3114 static void
get_avatar_paths(McdAccount * account,gchar ** dir_out,gchar ** basename_out,gchar ** file_out)3115 get_avatar_paths (McdAccount *account,
3116                   gchar **dir_out,
3117                   gchar **basename_out,
3118                   gchar **file_out)
3119 {
3120     gchar *dir = NULL;
3121 
3122     dir = g_build_filename (g_get_user_data_dir (),
3123                             "telepathy", "mission-control", NULL);
3124 
3125     if (account == NULL)
3126     {
3127         if (file_out != NULL)
3128             *file_out = NULL;
3129 
3130         if (basename_out != NULL)
3131             *basename_out = NULL;
3132     }
3133     else if (basename_out != NULL || file_out != NULL)
3134     {
3135         gchar *basename = NULL;
3136 
3137         basename = g_strdup_printf ("%s.avatar", account->priv->unique_name);
3138         g_strdelimit (basename, "/", '-');
3139 
3140         if (file_out != NULL)
3141             *file_out = g_build_filename (dir, basename, NULL);
3142 
3143         if (basename_out != NULL)
3144             *basename_out = basename;
3145         else
3146             g_free (basename);
3147     }
3148 
3149     if (dir_out != NULL)
3150         *dir_out = dir;
3151     else
3152         g_free (dir);
3153 }
3154 
3155 static gboolean
save_avatar(McdAccount * self,gpointer data,gssize len,GError ** error)3156 save_avatar (McdAccount *self,
3157              gpointer data,
3158              gssize len,
3159              GError **error)
3160 {
3161     gchar *dir = NULL;
3162     gchar *file = NULL;
3163     gboolean ret = FALSE;
3164 
3165     get_avatar_paths (self, &dir, NULL, &file);
3166 
3167     if (mcd_ensure_directory (dir, error) &&
3168         g_file_set_contents (file, data, len, error))
3169     {
3170         DEBUG ("Saved avatar to %s", file);
3171         ret = TRUE;
3172     }
3173     else if (len == 0)
3174     {
3175         GArray *avatar = NULL;
3176 
3177         /* It failed, but maybe that's OK, since we didn't really want
3178          * an avatar anyway. */
3179         _mcd_account_get_avatar (self, &avatar, NULL);
3180 
3181         if (avatar == NULL)
3182         {
3183             /* Creating the empty file failed, but it's fine, since what's
3184              * on disk correctly indicates that we have no avatar. */
3185             DEBUG ("Ignoring failure to write empty avatar");
3186 
3187             if (error != NULL)
3188                 g_clear_error (error);
3189         }
3190         else
3191         {
3192             /* Continue to raise the error: we failed to write a 0-byte
3193              * file into the highest-priority avatar directory, and we do
3194              * need it, since there is a non-empty avatar in either that
3195              * directory or a lower-priority directory */
3196             g_array_free (avatar, TRUE);
3197         }
3198     }
3199 
3200     g_free (dir);
3201     g_free (file);
3202     return ret;
3203 }
3204 
3205 static gchar *_mcd_account_get_old_avatar_filename (McdAccount *account,
3206                                                     gchar **old_dir);
3207 
3208 static void
mcd_account_migrate_avatar(McdAccount * account)3209 mcd_account_migrate_avatar (McdAccount *account)
3210 {
3211     GError *error = NULL;
3212     gchar *old_file;
3213     gchar *old_dir = NULL;
3214     gchar *new_dir = NULL;
3215     gchar *basename = NULL;
3216     gchar *new_file = NULL;
3217     gchar *contents = NULL;
3218     guint i;
3219 
3220     /* Try to migrate the avatar to a better location */
3221     old_file = _mcd_account_get_old_avatar_filename (account, &old_dir);
3222 
3223     if (!g_file_test (old_file, G_FILE_TEST_EXISTS))
3224     {
3225         /* nothing to do */
3226         goto finally;
3227     }
3228 
3229     DEBUG ("Migrating avatar from %s", old_file);
3230 
3231     get_avatar_paths (account, &new_dir, &basename, &new_file);
3232 
3233     if (g_file_test (new_file, G_FILE_TEST_IS_REGULAR))
3234     {
3235         DEBUG ("... already migrated to %s", new_file);
3236 
3237         if (g_unlink (old_file) != 0)
3238         {
3239             DEBUG ("Failed to unlink %s: %s", old_file,
3240                    g_strerror (errno));
3241         }
3242 
3243         goto finally;
3244     }
3245 
3246     if (!mcd_ensure_directory (new_dir, &error))
3247     {
3248         DEBUG ("%s", error->message);
3249         goto finally;
3250     }
3251 
3252     if (g_rename (old_file, new_file) == 0)
3253     {
3254         DEBUG ("Renamed %s to %s", old_file, new_file);
3255     }
3256     else
3257     {
3258         gsize len;
3259 
3260         DEBUG ("Unable to rename %s to %s, will try copy+delete: %s",
3261                old_file, new_file, g_strerror (errno));
3262 
3263         if (!g_file_get_contents (old_file, &contents, &len, &error))
3264         {
3265             DEBUG ("Unable to load old avatar %s: %s", old_file,
3266                    error->message);
3267             goto finally;
3268         }
3269 
3270         if (g_file_set_contents (new_file, contents, len, &error))
3271         {
3272             DEBUG ("Copied old avatar from %s to %s", old_file, new_file);
3273         }
3274         else
3275         {
3276             DEBUG ("Unable to save new avatar %s: %s", new_file,
3277                    error->message);
3278             goto finally;
3279         }
3280 
3281         if (g_unlink (old_file) != 0)
3282         {
3283             DEBUG ("Failed to unlink %s: %s", old_file, g_strerror (errno));
3284             goto finally;
3285         }
3286     }
3287 
3288     /* old_dir is typically ~/.mission-control/accounts/gabble/jabber/badger0.
3289      * We want to delete badger0, jabber, gabble, accounts if they are empty.
3290      * If they are not, we'll just get ENOTEMPTY and stop. */
3291     for (i = 0; i < 4; i++)
3292     {
3293         gchar *tmp;
3294 
3295         if (g_rmdir (old_dir) != 0)
3296         {
3297             DEBUG ("Failed to rmdir %s: %s", old_dir, g_strerror (errno));
3298             goto finally;
3299         }
3300 
3301         tmp = g_path_get_dirname (old_dir);
3302         g_free (old_dir);
3303         old_dir = tmp;
3304     }
3305 
3306 finally:
3307     g_clear_error (&error);
3308     g_free (basename);
3309     g_free (new_file);
3310     g_free (new_dir);
3311     g_free (contents);
3312     g_free (old_file);
3313     g_free (old_dir);
3314 }
3315 
3316 static gboolean
mcd_account_setup(McdAccount * account)3317 mcd_account_setup (McdAccount *account)
3318 {
3319     McdAccountPrivate *priv = account->priv;
3320     McdStorage *storage = priv->storage;
3321     const gchar *name = mcd_account_get_unique_name (account);
3322     GValue value = G_VALUE_INIT;
3323 
3324     priv->manager_name =
3325       mcd_storage_dup_string (storage, name, MC_ACCOUNTS_KEY_MANAGER);
3326 
3327     if (priv->manager_name == NULL)
3328     {
3329         g_warning ("Account '%s' has no manager", name);
3330         goto broken_account;
3331     }
3332 
3333     priv->protocol_name =
3334       mcd_storage_dup_string (storage, name, MC_ACCOUNTS_KEY_PROTOCOL);
3335 
3336     if (priv->protocol_name == NULL)
3337     {
3338         g_warning ("Account has no protocol");
3339         goto broken_account;
3340     }
3341 
3342     priv->object_path = g_strconcat (TP_ACCOUNT_OBJECT_PATH_BASE, name, NULL);
3343 
3344     if (!priv->always_on)
3345     {
3346         priv->enabled =
3347           mcd_storage_get_boolean (storage, name, MC_ACCOUNTS_KEY_ENABLED);
3348 
3349         priv->connect_automatically =
3350           mcd_storage_get_boolean (storage, name,
3351                                    MC_ACCOUNTS_KEY_CONNECT_AUTOMATICALLY);
3352     }
3353 
3354     priv->has_been_online =
3355       mcd_storage_get_boolean (storage, name, MC_ACCOUNTS_KEY_HAS_BEEN_ONLINE);
3356     priv->hidden =
3357       mcd_storage_get_boolean (storage, name, MC_ACCOUNTS_KEY_HIDDEN);
3358 
3359     /* special case flag (for ring accounts, so far) */
3360     priv->always_dispatch =
3361       mcd_storage_get_boolean (storage, name, MC_ACCOUNTS_KEY_ALWAYS_DISPATCH);
3362 
3363     g_value_init (&value, TP_STRUCT_TYPE_SIMPLE_PRESENCE);
3364 
3365     g_free (priv->auto_presence_status);
3366     g_free (priv->auto_presence_message);
3367 
3368     if (mcd_storage_get_attribute (storage, name,
3369                                    MC_ACCOUNTS_KEY_AUTOMATIC_PRESENCE, &value,
3370                                    NULL))
3371     {
3372         GValueArray *va = g_value_get_boxed (&value);
3373 
3374         priv->auto_presence_type = g_value_get_uint (va->values + 0);
3375         priv->auto_presence_status = g_value_dup_string (va->values + 1);
3376         priv->auto_presence_message = g_value_dup_string (va->values + 2);
3377 
3378         if (priv->auto_presence_status == NULL)
3379             priv->auto_presence_status = g_strdup ("");
3380         if (priv->auto_presence_message == NULL)
3381             priv->auto_presence_message = g_strdup ("");
3382     }
3383     else
3384     {
3385         /* try the old versions */
3386         priv->auto_presence_type =
3387           mcd_storage_get_integer (storage, name,
3388                                    MC_ACCOUNTS_KEY_AUTO_PRESENCE_TYPE);
3389         priv->auto_presence_status =
3390           mcd_storage_dup_string (storage, name,
3391                                   MC_ACCOUNTS_KEY_AUTO_PRESENCE_STATUS);
3392         priv->auto_presence_message =
3393           mcd_storage_dup_string (storage, name,
3394                                   MC_ACCOUNTS_KEY_AUTO_PRESENCE_MESSAGE);
3395 
3396         if (priv->auto_presence_status == NULL)
3397             priv->auto_presence_status = g_strdup ("");
3398         if (priv->auto_presence_message == NULL)
3399             priv->auto_presence_message = g_strdup ("");
3400 
3401         /* migrate to a more sensible storage format */
3402         g_value_take_boxed (&value, tp_value_array_build (3,
3403                 G_TYPE_UINT, (guint) priv->auto_presence_type,
3404                 G_TYPE_STRING, priv->auto_presence_status,
3405                 G_TYPE_STRING, priv->auto_presence_message,
3406                 G_TYPE_INVALID));
3407 
3408         if (mcd_storage_set_attribute (storage, name,
3409                                        MC_ACCOUNTS_KEY_AUTOMATIC_PRESENCE,
3410                                        &value))
3411         {
3412             mcd_storage_set_attribute (storage, name,
3413                                        MC_ACCOUNTS_KEY_AUTO_PRESENCE_TYPE,
3414                                        NULL);
3415             mcd_storage_set_attribute (storage, name,
3416                                        MC_ACCOUNTS_KEY_AUTO_PRESENCE_STATUS,
3417                                        NULL);
3418             mcd_storage_set_attribute (storage, name,
3419                                        MC_ACCOUNTS_KEY_AUTO_PRESENCE_MESSAGE,
3420                                        NULL);
3421             mcd_storage_commit (storage, name);
3422         }
3423     }
3424 
3425     /* If invalid or something, force it to AVAILABLE - we want the auto
3426      * presence type to be an online status */
3427     if (!_presence_type_is_online (priv->auto_presence_type))
3428     {
3429         priv->auto_presence_type = TP_CONNECTION_PRESENCE_TYPE_AVAILABLE;
3430         g_free (priv->auto_presence_status);
3431         priv->auto_presence_status = g_strdup ("available");
3432     }
3433 
3434     g_value_unset (&value);
3435     g_value_init (&value, TP_ARRAY_TYPE_OBJECT_PATH_LIST);
3436 
3437     if (priv->supersedes != NULL)
3438         g_ptr_array_unref (priv->supersedes);
3439 
3440     if (mcd_storage_get_attribute (storage, name,
3441                                    MC_ACCOUNTS_KEY_SUPERSEDES, &value, NULL))
3442     {
3443         priv->supersedes = g_value_dup_boxed (&value);
3444     }
3445     else
3446     {
3447         priv->supersedes = g_ptr_array_new ();
3448     }
3449 
3450     g_value_unset (&value);
3451 
3452     /* check the manager */
3453     if (!priv->manager && !load_manager (account))
3454     {
3455 	g_warning ("Could not find manager `%s'", priv->manager_name);
3456         mcd_account_loaded (account);
3457     }
3458 
3459     /* even though the manager is absent or unusable, we still register *
3460      * the accounts dbus name as it is otherwise acceptably configured  */
3461 
3462     _mcd_account_load (account, register_dbus_service, NULL);
3463     return TRUE;
3464 
3465 broken_account:
3466     /* normally, various callbacks would release locks when the manager      *
3467      * became ready: however, this cannot happen for an incomplete account   *
3468      * as it never gets a manager: We therefore invoke the account callbacks *
3469      * right now so the account manager doesn't hang around forever waiting  *
3470      * for an event that cannot happen (at least until the account is fixed) */
3471     mcd_account_loaded (account);
3472     return FALSE;
3473 }
3474 
3475 static void
set_property(GObject * obj,guint prop_id,const GValue * val,GParamSpec * pspec)3476 set_property (GObject *obj, guint prop_id,
3477 	      const GValue *val, GParamSpec *pspec)
3478 {
3479     McdAccount *account = MCD_ACCOUNT (obj);
3480     McdAccountPrivate *priv = account->priv;
3481 
3482     switch (prop_id)
3483     {
3484     case PROP_STORAGE:
3485         g_assert (priv->storage == NULL);
3486         priv->storage = g_value_dup_object (val);
3487 	break;
3488 
3489       case PROP_DBUS_DAEMON:
3490         g_assert (priv->dbus_daemon == NULL);
3491         priv->dbus_daemon = g_value_dup_object (val);
3492         break;
3493 
3494       case PROP_CONNECTIVITY_MONITOR:
3495         g_assert (priv->connectivity == NULL);
3496         priv->connectivity = g_value_dup_object (val);
3497         break;
3498 
3499     case PROP_NAME:
3500 	g_assert (priv->unique_name == NULL);
3501 	priv->unique_name = g_value_dup_string (val);
3502 	break;
3503 
3504     case PROP_ALWAYS_ON:
3505         priv->always_on = g_value_get_boolean (val);
3506 
3507         if (priv->always_on)
3508         {
3509             priv->enabled = TRUE;
3510             priv->connect_automatically = TRUE;
3511             priv->req_presence_type = priv->auto_presence_type;
3512             priv->req_presence_status = g_strdup (priv->auto_presence_status);
3513             priv->req_presence_message = g_strdup (priv->auto_presence_message);
3514         }
3515 
3516         break;
3517     case PROP_HIDDEN:
3518         priv->hidden = g_value_get_boolean (val);
3519         break;
3520     default:
3521 	G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
3522 	break;
3523     }
3524 }
3525 
3526 static void
get_property(GObject * obj,guint prop_id,GValue * val,GParamSpec * pspec)3527 get_property (GObject *obj, guint prop_id,
3528 	      GValue *val, GParamSpec *pspec)
3529 {
3530     McdAccountPrivate *priv = MCD_ACCOUNT_PRIV (obj);
3531 
3532     switch (prop_id)
3533     {
3534     case PROP_DBUS_DAEMON:
3535         g_value_set_object (val, priv->dbus_daemon);
3536 	break;
3537 
3538     case PROP_CONNECTIVITY_MONITOR:
3539         g_value_set_object (val, priv->connectivity);
3540         break;
3541 
3542     case PROP_NAME:
3543 	g_value_set_string (val, priv->unique_name);
3544 	break;
3545     case PROP_HIDDEN:
3546         g_value_set_boolean (val, priv->hidden);
3547         break;
3548     default:
3549 	G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
3550 	break;
3551     }
3552 }
3553 
3554 static void
_mcd_account_finalize(GObject * object)3555 _mcd_account_finalize (GObject *object)
3556 {
3557     McdAccount *account = MCD_ACCOUNT (object);
3558     McdAccountPrivate *priv = MCD_ACCOUNT_PRIV (account);
3559 
3560     DEBUG ("%p (%s)", object, priv->unique_name);
3561 
3562     if (priv->changed_properties)
3563 	g_hash_table_unref (priv->changed_properties);
3564     if (priv->properties_source != 0)
3565 	g_source_remove (priv->properties_source);
3566 
3567     tp_clear_pointer (&priv->curr_presence_status, g_free);
3568     tp_clear_pointer (&priv->curr_presence_message, g_free);
3569 
3570     tp_clear_pointer (&priv->req_presence_status, g_free);
3571     tp_clear_pointer (&priv->req_presence_message, g_free);
3572 
3573     tp_clear_pointer (&priv->auto_presence_status, g_free);
3574     tp_clear_pointer (&priv->auto_presence_message, g_free);
3575 
3576     tp_clear_pointer (&priv->manager_name, g_free);
3577     tp_clear_pointer (&priv->protocol_name, g_free);
3578     tp_clear_pointer (&priv->unique_name, g_free);
3579     tp_clear_pointer (&priv->object_path, g_free);
3580 
3581     G_OBJECT_CLASS (mcd_account_parent_class)->finalize (object);
3582 }
3583 
3584 static void
_mcd_account_dispose(GObject * object)3585 _mcd_account_dispose (GObject *object)
3586 {
3587     McdAccount *self = MCD_ACCOUNT (object);
3588     McdAccountPrivate *priv = self->priv;
3589 
3590     DEBUG ("%p (%s)", object, priv->unique_name);
3591 
3592     if (!self->priv->removed)
3593     {
3594         self->priv->removed = TRUE;
3595         tp_svc_account_emit_removed (self);
3596     }
3597 
3598     if (priv->online_requests)
3599     {
3600         GError *error;
3601         GList *list = priv->online_requests;
3602 
3603         error = g_error_new (TP_ERROR, TP_ERROR_DISCONNECTED,
3604                              "Disposing account %s", priv->unique_name);
3605         while (list)
3606         {
3607             McdOnlineRequestData *data = list->data;
3608 
3609             data->callback (MCD_ACCOUNT (object), data->user_data, error);
3610             g_slice_free (McdOnlineRequestData, data);
3611             list = g_list_delete_link (list, list);
3612         }
3613         g_error_free (error);
3614 	priv->online_requests = NULL;
3615     }
3616 
3617     tp_clear_object (&priv->manager);
3618     tp_clear_object (&priv->storage_plugin);
3619     tp_clear_object (&priv->storage);
3620     tp_clear_object (&priv->dbus_daemon);
3621     tp_clear_object (&priv->self_contact);
3622     tp_clear_object (&priv->connectivity);
3623 
3624     _mcd_account_set_connection_context (self, NULL);
3625     _mcd_account_set_connection (self, NULL);
3626 
3627     G_OBJECT_CLASS (mcd_account_parent_class)->dispose (object);
3628 }
3629 
3630 static GObject *
_mcd_account_constructor(GType type,guint n_params,GObjectConstructParam * params)3631 _mcd_account_constructor (GType type, guint n_params,
3632                           GObjectConstructParam *params)
3633 {
3634     GObjectClass *object_class = (GObjectClass *)mcd_account_parent_class;
3635     McdAccount *account;
3636     McdAccountPrivate *priv;
3637 
3638     account = MCD_ACCOUNT (object_class->constructor (type, n_params, params));
3639     priv = account->priv;
3640 
3641     g_return_val_if_fail (account != NULL, NULL);
3642 
3643     if (G_UNLIKELY (!priv->storage || !priv->unique_name))
3644     {
3645         g_object_unref (account);
3646         return NULL;
3647     }
3648 
3649     return (GObject *) account;
3650 }
3651 
3652 static void
monitor_state_changed_cb(McdConnectivityMonitor * monitor,gboolean connected,McdInhibit * inhibit,gpointer user_data)3653 monitor_state_changed_cb (
3654     McdConnectivityMonitor *monitor,
3655     gboolean connected,
3656     McdInhibit *inhibit,
3657     gpointer user_data)
3658 {
3659   McdAccount *self = MCD_ACCOUNT (user_data);
3660 
3661   if (connected)
3662     {
3663       if (mcd_account_would_like_to_connect (self))
3664         {
3665           DEBUG ("account %s would like to connect",
3666               self->priv->unique_name);
3667           _mcd_account_connect_with_auto_presence (self, FALSE);
3668         }
3669     }
3670   else
3671     {
3672       if (_mcd_account_needs_dispatch (self))
3673         {
3674           /* special treatment for cellular accounts */
3675           DEBUG ("account %s is always dispatched and does not need a "
3676               "transport", self->priv->unique_name);
3677         }
3678       else
3679         {
3680           McdConnection *connection;
3681 
3682           DEBUG ("account %s must disconnect", self->priv->unique_name);
3683           connection = mcd_account_get_connection (self);
3684 
3685           if (connection != NULL)
3686             mcd_connection_close (connection, inhibit);
3687         }
3688     }
3689 
3690   if (!self->priv->waiting_for_connectivity)
3691     return;
3692 
3693   /* If we've gone online, allow the account to actually try to connect;
3694    * if we've fallen offline, say as much. (I don't actually think this
3695    * code will be reached if !connected, but.)
3696    */
3697   DEBUG ("telling %s to %s", self->priv->unique_name,
3698       connected ? "proceed" : "give up");
3699   mcd_account_connection_proceed_with_reason (self, connected,
3700       connected ? TP_CONNECTION_STATUS_REASON_NONE_SPECIFIED
3701                 : TP_CONNECTION_STATUS_REASON_NETWORK_ERROR);
3702   self->priv->waiting_for_connectivity = FALSE;
3703 }
3704 
3705 static void
_mcd_account_constructed(GObject * object)3706 _mcd_account_constructed (GObject *object)
3707 {
3708     GObjectClass *object_class = (GObjectClass *)mcd_account_parent_class;
3709     McdAccount *account = MCD_ACCOUNT (object);
3710 
3711     if (object_class->constructed)
3712         object_class->constructed (object);
3713 
3714     DEBUG ("%p (%s)", object, account->priv->unique_name);
3715 
3716     mcd_account_migrate_avatar (account);
3717     mcd_account_setup (account);
3718 
3719     tp_g_signal_connect_object (account->priv->connectivity, "state-change",
3720         (GCallback) monitor_state_changed_cb, account, 0);
3721 }
3722 
3723 static void
mcd_account_add_signals(TpProxy * self,guint quark,DBusGProxy * proxy,gpointer data)3724 mcd_account_add_signals (TpProxy *self,
3725     guint quark,
3726     DBusGProxy *proxy,
3727     gpointer data)
3728 {
3729   mc_cli_Connection_Manager_Interface_Account_Storage_add_signals (self,
3730       quark, proxy, data);
3731 }
3732 
3733 static void
mcd_account_class_init(McdAccountClass * klass)3734 mcd_account_class_init (McdAccountClass * klass)
3735 {
3736     GObjectClass *object_class = G_OBJECT_CLASS (klass);
3737     g_type_class_add_private (object_class, sizeof (McdAccountPrivate));
3738 
3739     object_class->constructor = _mcd_account_constructor;
3740     object_class->constructed = _mcd_account_constructed;
3741     object_class->dispose = _mcd_account_dispose;
3742     object_class->finalize = _mcd_account_finalize;
3743     object_class->set_property = set_property;
3744     object_class->get_property = get_property;
3745 
3746     klass->check_request = _mcd_account_check_request_real;
3747 
3748     g_object_class_install_property
3749         (object_class, PROP_DBUS_DAEMON,
3750          g_param_spec_object ("dbus-daemon", "DBus daemon", "DBus daemon",
3751                               TP_TYPE_DBUS_DAEMON,
3752                               G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
3753 
3754     g_object_class_install_property
3755         (object_class, PROP_CONNECTIVITY_MONITOR,
3756          g_param_spec_object ("connectivity-monitor",
3757                               "Connectivity monitor",
3758                               "Connectivity monitor",
3759                               MCD_TYPE_CONNECTIVITY_MONITOR,
3760                               G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
3761 
3762     g_object_class_install_property
3763         (object_class, PROP_STORAGE,
3764          g_param_spec_object ("storage", "storage",
3765                                "storage", MCD_TYPE_STORAGE,
3766                                G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
3767 
3768     g_object_class_install_property
3769         (object_class, PROP_NAME,
3770          g_param_spec_string ("name", "Unique name", "Unique name",
3771                               NULL,
3772                               G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
3773 
3774     g_object_class_install_property
3775         (object_class, PROP_ALWAYS_ON,
3776          g_param_spec_boolean ("always-on", "Always on?", "Always on?",
3777                               FALSE,
3778                               G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
3779                               G_PARAM_STATIC_STRINGS));
3780 
3781     g_object_class_install_property
3782         (object_class, PROP_HIDDEN,
3783          g_param_spec_boolean ("hidden", "Hidden?", "Is this account hidden?",
3784                                FALSE,
3785                                G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
3786     /* Signals */
3787     _mcd_account_signals[VALIDITY_CHANGED] =
3788 	g_signal_new ("validity-changed",
3789 		      G_OBJECT_CLASS_TYPE (klass),
3790 		      G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
3791 		      0,
3792 		      NULL, NULL, g_cclosure_marshal_VOID__BOOLEAN,
3793 		      G_TYPE_NONE, 1,
3794 		      G_TYPE_BOOLEAN);
3795     _mcd_account_signals[CONNECTION_PATH_CHANGED] =
3796         g_signal_new ("connection-path-changed",
3797                       G_OBJECT_CLASS_TYPE (klass),
3798                       G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
3799                       0,
3800                       NULL, NULL, g_cclosure_marshal_VOID__STRING,
3801                       G_TYPE_NONE, 1, G_TYPE_STRING);
3802 
3803     account_ready_quark = g_quark_from_static_string ("mcd_account_load");
3804 
3805     tp_proxy_or_subclass_hook_on_interface_add (TP_TYPE_CONNECTION_MANAGER,
3806         mcd_account_add_signals);
3807 }
3808 
3809 static void
mcd_account_init(McdAccount * account)3810 mcd_account_init (McdAccount *account)
3811 {
3812     McdAccountPrivate *priv;
3813 
3814     priv = G_TYPE_INSTANCE_GET_PRIVATE ((account),
3815 					MCD_TYPE_ACCOUNT,
3816 					McdAccountPrivate);
3817     account->priv = priv;
3818 
3819     priv->req_presence_type = TP_CONNECTION_PRESENCE_TYPE_OFFLINE;
3820     priv->req_presence_status = g_strdup ("offline");
3821     priv->req_presence_message = g_strdup ("");
3822 
3823     priv->curr_presence_type = TP_CONNECTION_PRESENCE_TYPE_OFFLINE;
3824     priv->curr_presence_status = g_strdup ("offline");
3825     priv->curr_presence_message = g_strdup ("");
3826 
3827     priv->always_on = FALSE;
3828     priv->always_dispatch = FALSE;
3829     priv->enabled = FALSE;
3830     priv->connect_automatically = FALSE;
3831 
3832     priv->changing_presence = FALSE;
3833 
3834     priv->auto_presence_type = TP_CONNECTION_PRESENCE_TYPE_AVAILABLE;
3835     priv->auto_presence_status = g_strdup ("available");
3836     priv->auto_presence_message = g_strdup ("");
3837 
3838     /* initializes the interfaces */
3839     mcd_dbus_init_interfaces_instances (account);
3840 
3841     priv->conn_status = TP_CONNECTION_STATUS_DISCONNECTED;
3842     priv->conn_reason = TP_CONNECTION_STATUS_REASON_REQUESTED;
3843     priv->conn_dbus_error = g_strdup ("");
3844     priv->conn_error_details = g_hash_table_new_full (g_str_hash, g_str_equal,
3845         g_free, (GDestroyNotify) tp_g_value_slice_free);
3846 
3847     priv->changed_properties = g_hash_table_new_full (g_str_hash, g_str_equal,
3848         NULL, (GDestroyNotify) tp_g_value_slice_free);
3849 
3850     g_set_error (&priv->invalid_reason, TP_ERROR, TP_ERROR_NOT_YET,
3851         "This account is not yet fully loaded");
3852 }
3853 
3854 McdAccount *
mcd_account_new(McdAccountManager * account_manager,const gchar * name,McdConnectivityMonitor * connectivity)3855 mcd_account_new (McdAccountManager *account_manager,
3856     const gchar *name,
3857     McdConnectivityMonitor *connectivity)
3858 {
3859     gpointer *obj;
3860     McdStorage *storage = mcd_account_manager_get_storage (account_manager);
3861     TpDBusDaemon *dbus = mcd_account_manager_get_dbus_daemon (account_manager);
3862 
3863     obj = g_object_new (MCD_TYPE_ACCOUNT,
3864                         "storage", storage,
3865                         "dbus-daemon", dbus,
3866                         "connectivity-monitor", connectivity,
3867 			"name", name,
3868 			NULL);
3869     return MCD_ACCOUNT (obj);
3870 }
3871 
3872 McdStorage *
_mcd_account_get_storage(McdAccount * account)3873 _mcd_account_get_storage (McdAccount *account)
3874 {
3875     return account->priv->storage;
3876 }
3877 
3878 /*
3879  * mcd_account_is_valid:
3880  * @account: the #McdAccount.
3881  *
3882  * Checks that the account is usable:
3883  * - Manager, protocol and TODO presets (if specified) must exist
3884  * - All required parameters for the protocol must be set
3885  *
3886  * Returns: %TRUE if the account is valid, false otherwise.
3887  */
3888 gboolean
mcd_account_is_valid(McdAccount * account)3889 mcd_account_is_valid (McdAccount *account)
3890 {
3891     McdAccountPrivate *priv = MCD_ACCOUNT_PRIV (account);
3892     return priv->invalid_reason == NULL;
3893 }
3894 
3895 /**
3896  * mcd_account_is_enabled:
3897  * @account: the #McdAccount.
3898  *
3899  * Checks if the account is enabled:
3900  *
3901  * Returns: %TRUE if the account is enabled, false otherwise.
3902  */
3903 gboolean
mcd_account_is_enabled(McdAccount * account)3904 mcd_account_is_enabled (McdAccount *account)
3905 {
3906     McdAccountPrivate *priv = MCD_ACCOUNT_PRIV (account);
3907     return priv->enabled;
3908 }
3909 
3910 gboolean
_mcd_account_is_hidden(McdAccount * account)3911 _mcd_account_is_hidden (McdAccount *account)
3912 {
3913     g_return_val_if_fail (MCD_IS_ACCOUNT (account), FALSE);
3914 
3915     return account->priv->hidden;
3916 }
3917 
3918 const gchar *
mcd_account_get_unique_name(McdAccount * account)3919 mcd_account_get_unique_name (McdAccount *account)
3920 {
3921     return account->priv->unique_name;
3922 }
3923 
3924 const gchar *
mcd_account_get_object_path(McdAccount * account)3925 mcd_account_get_object_path (McdAccount *account)
3926 {
3927     return account->priv->object_path;
3928 }
3929 
3930 /**
3931  * _mcd_account_dup_parameters:
3932  * @account: the #McdAccount.
3933  *
3934  * Get the parameters set for this account. The resulting #GHashTable will be
3935  * newly allocated and must be g_hash_table_unref()'d after use.
3936  *
3937  * Returns: @account's current parameters, or %NULL if they could not be
3938  *          retrieved.
3939  */
3940 GHashTable *
_mcd_account_dup_parameters(McdAccount * account)3941 _mcd_account_dup_parameters (McdAccount *account)
3942 {
3943     McdAccountPrivate *priv;
3944     TpProtocol *protocol;
3945     GList *protocol_params;
3946     GList *iter;
3947     GHashTable *params;
3948 
3949     g_return_val_if_fail (MCD_IS_ACCOUNT (account), NULL);
3950 
3951     priv = account->priv;
3952 
3953     DEBUG ("called");
3954 
3955     /* FIXME: this is ridiculous. MC stores the parameters for the account, so
3956      * it should be able to expose them on D-Bus even if the CM is uninstalled.
3957      * It shouldn't need to iterate across the parameters supported by the CM.
3958      * But it does, because MC doesn't store the types of parameters. So it
3959      * needs the CM (or .manager file) to be around to tell it whether "true"
3960      * is a string or a boolean…
3961      */
3962     if (!priv->manager && !load_manager (account))
3963     {
3964         DEBUG ("unable to load manager for account %s", priv->unique_name);
3965         return NULL;
3966     }
3967 
3968     protocol = _mcd_manager_dup_protocol (priv->manager,
3969                                           priv->protocol_name);
3970 
3971     if (G_UNLIKELY (protocol == NULL))
3972     {
3973         DEBUG ("unable to get protocol for %s account %s", priv->protocol_name,
3974                priv->unique_name);
3975         return NULL;
3976     }
3977 
3978     params = g_hash_table_new_full (g_str_hash, g_str_equal,
3979                                     g_free,
3980                                     (GDestroyNotify) tp_g_value_slice_free);
3981 
3982     protocol_params = tp_protocol_dup_params (protocol);
3983 
3984     for (iter = protocol_params; iter != NULL; iter = iter->next)
3985     {
3986         TpConnectionManagerParam *param = iter->data;
3987         const gchar *name = tp_connection_manager_param_get_name (param);
3988         GValue v = G_VALUE_INIT;
3989 
3990         if (mcd_account_get_parameter (account, name, &v, NULL))
3991         {
3992             g_hash_table_insert (params, g_strdup (name),
3993                                  tp_g_value_slice_dup (&v));
3994             g_value_unset (&v);
3995         }
3996     }
3997 
3998     g_list_free_full (protocol_params,
3999                       (GDestroyNotify) tp_connection_manager_param_free);
4000     g_object_unref (protocol);
4001     return params;
4002 }
4003 
4004 /**
4005  * mcd_account_request_presence:
4006  * @account: the #McdAccount.
4007  * @presence: a #TpConnectionPresenceType.
4008  * @status: presence status.
4009  * @message: presence status message.
4010  *
4011  * Request a presence status on the account, initiated by some other part of
4012  * MC (i.e. not by user request).
4013  */
4014 void
mcd_account_request_presence(McdAccount * account,TpConnectionPresenceType presence,const gchar * status,const gchar * message)4015 mcd_account_request_presence (McdAccount *account,
4016 			      TpConnectionPresenceType presence,
4017 			      const gchar *status, const gchar *message)
4018 {
4019     mcd_account_request_presence_int (account, presence, status, message,
4020                                       FALSE);
4021 }
4022 
4023 static void
mcd_account_update_self_presence(McdAccount * account,guint presence,const gchar * status,const gchar * message,TpContact * self_contact)4024 mcd_account_update_self_presence (McdAccount *account,
4025                                   guint presence,
4026                                   const gchar *status,
4027                                   const gchar *message,
4028                                   TpContact *self_contact)
4029 {
4030     McdAccountPrivate *priv = account->priv;
4031     gboolean changed = FALSE;
4032     GValue value = G_VALUE_INIT;
4033 
4034     if (self_contact != account->priv->self_contact)
4035         return;
4036 
4037     if (priv->curr_presence_type != presence)
4038     {
4039 	priv->curr_presence_type = presence;
4040 	changed = TRUE;
4041     }
4042     if (tp_strdiff (priv->curr_presence_status, status))
4043     {
4044 	g_free (priv->curr_presence_status);
4045 	priv->curr_presence_status = g_strdup (status);
4046 	changed = TRUE;
4047     }
4048     if (tp_strdiff (priv->curr_presence_message, message))
4049     {
4050 	g_free (priv->curr_presence_message);
4051 	priv->curr_presence_message = g_strdup (message);
4052 	changed = TRUE;
4053     }
4054 
4055     if (_mcd_connection_presence_info_is_ready (priv->connection))
4056     {
4057         _mcd_account_set_changing_presence (account, FALSE);
4058     }
4059 
4060     if (!changed) return;
4061 
4062     g_value_init (&value, TP_STRUCT_TYPE_SIMPLE_PRESENCE);
4063     g_value_take_boxed (&value,
4064                         tp_value_array_build (3,
4065                                               G_TYPE_UINT, presence,
4066                                               G_TYPE_STRING, status,
4067                                               G_TYPE_STRING, message,
4068                                               G_TYPE_INVALID));
4069     mcd_account_changed_property (account, "CurrentPresence", &value);
4070     g_value_unset (&value);
4071 }
4072 
4073 /* TODO: remove when the relative members will become public */
4074 void
mcd_account_get_requested_presence(McdAccount * account,TpConnectionPresenceType * presence,const gchar ** status,const gchar ** message)4075 mcd_account_get_requested_presence (McdAccount *account,
4076 				    TpConnectionPresenceType *presence,
4077 				    const gchar **status,
4078 				    const gchar **message)
4079 {
4080     McdAccountPrivate *priv = account->priv;
4081 
4082     if (presence != NULL)
4083         *presence = priv->req_presence_type;
4084 
4085     if (status != NULL)
4086         *status = priv->req_presence_status;
4087 
4088     if (message != NULL)
4089         *message = priv->req_presence_message;
4090 }
4091 
4092 /* TODO: remove when the relative members will become public */
4093 void
mcd_account_get_current_presence(McdAccount * account,TpConnectionPresenceType * presence,const gchar ** status,const gchar ** message)4094 mcd_account_get_current_presence (McdAccount *account,
4095 				  TpConnectionPresenceType *presence,
4096 				  const gchar **status,
4097 				  const gchar **message)
4098 {
4099     McdAccountPrivate *priv = account->priv;
4100 
4101     if (presence != NULL)
4102         *presence = priv->curr_presence_type;
4103 
4104     if (status != NULL)
4105         *status = priv->curr_presence_status;
4106 
4107     if (message != NULL)
4108         *message = priv->curr_presence_message;
4109 }
4110 
4111 /*
4112  * mcd_account_would_like_to_connect:
4113  * @account: an account
4114  *
4115  * Returns: %TRUE if @account is not currently in the process of trying to
4116  *          connect, but would like to be, in a perfect world.
4117  */
4118 gboolean
mcd_account_would_like_to_connect(McdAccount * account)4119 mcd_account_would_like_to_connect (McdAccount *account)
4120 {
4121     McdAccountPrivate *priv;
4122 
4123     g_return_val_if_fail (MCD_IS_ACCOUNT (account), FALSE);
4124     priv = account->priv;
4125 
4126     if (!priv->enabled)
4127     {
4128         DEBUG ("%s not Enabled", priv->unique_name);
4129         return FALSE;
4130     }
4131 
4132     if (!mcd_account_is_valid (account))
4133     {
4134         DEBUG ("%s not Valid", priv->unique_name);
4135         return FALSE;
4136     }
4137 
4138     if (priv->conn_status != TP_CONNECTION_STATUS_DISCONNECTED)
4139     {
4140         DEBUG ("%s already connecting/connected", priv->unique_name);
4141         return FALSE;
4142     }
4143 
4144     if (!priv->connect_automatically &&
4145         !_presence_type_is_online (priv->req_presence_type))
4146     {
4147         DEBUG ("%s does not ConnectAutomatically, and its RequestedPresence "
4148             "(%u, '%s', '%s') doesn't indicate the user wants to be online",
4149             priv->unique_name, priv->req_presence_type,
4150             priv->req_presence_status, priv->req_presence_message);
4151         return FALSE;
4152     }
4153 
4154     return TRUE;
4155 }
4156 
4157 /* TODO: remove when the relative members will become public */
4158 const gchar *
mcd_account_get_manager_name(McdAccount * account)4159 mcd_account_get_manager_name (McdAccount *account)
4160 {
4161     McdAccountPrivate *priv = account->priv;
4162 
4163     return priv->manager_name;
4164 }
4165 
4166 /* TODO: remove when the relative members will become public */
4167 const gchar *
mcd_account_get_protocol_name(McdAccount * account)4168 mcd_account_get_protocol_name (McdAccount *account)
4169 {
4170     McdAccountPrivate *priv = account->priv;
4171 
4172     return priv->protocol_name;
4173 }
4174 
4175 /**
4176  * mcd_account_get_cm:
4177  * @account: an account
4178  *
4179  * Fetches the connection manager through which @account connects. If @account
4180  * is not ready, or is invalid (perhaps because the connection manager is
4181  * missing), this may be %NULL.
4182  *
4183  * Returns: the connection manager through which @account connects, or %NULL.
4184  */
4185 TpConnectionManager *
mcd_account_get_cm(McdAccount * account)4186 mcd_account_get_cm (McdAccount *account)
4187 {
4188     g_return_val_if_fail (account != NULL, NULL);
4189     g_return_val_if_fail (MCD_IS_ACCOUNT (account), NULL);
4190 
4191     return mcd_manager_get_tp_proxy (account->priv->manager);
4192 }
4193 
4194 void
_mcd_account_set_normalized_name(McdAccount * account,const gchar * name)4195 _mcd_account_set_normalized_name (McdAccount *account, const gchar *name)
4196 {
4197     McdAccountPrivate *priv = account->priv;
4198     GValue value = G_VALUE_INIT;
4199     const gchar *account_name = mcd_account_get_unique_name (account);
4200 
4201     DEBUG ("called (%s)", name);
4202 
4203     g_value_init (&value, G_TYPE_STRING);
4204     g_value_set_static_string (&value, name);
4205 
4206     mcd_storage_set_attribute (priv->storage, account_name,
4207                                MC_ACCOUNTS_KEY_NORMALIZED_NAME, &value);
4208     mcd_storage_commit (priv->storage, account_name);
4209     mcd_account_changed_property (account, MC_ACCOUNTS_KEY_NORMALIZED_NAME,
4210                                   &value);
4211 
4212     g_value_unset (&value);
4213 }
4214 
4215 void
_mcd_account_set_avatar_token(McdAccount * account,const gchar * token)4216 _mcd_account_set_avatar_token (McdAccount *account, const gchar *token)
4217 {
4218     McdAccountPrivate *priv = account->priv;
4219     const gchar *account_name = mcd_account_get_unique_name (account);
4220 
4221     DEBUG ("called (%s)", token);
4222     mcd_storage_set_string (priv->storage,
4223                             account_name,
4224                             MC_ACCOUNTS_KEY_AVATAR_TOKEN,
4225                             token);
4226 
4227     mcd_storage_commit (priv->storage, account_name);
4228 }
4229 
4230 gchar *
_mcd_account_get_avatar_token(McdAccount * account)4231 _mcd_account_get_avatar_token (McdAccount *account)
4232 {
4233     McdAccountPrivate *priv = account->priv;
4234     const gchar *account_name = mcd_account_get_unique_name (account);
4235 
4236     return mcd_storage_dup_string (priv->storage,
4237                                    account_name,
4238                                    MC_ACCOUNTS_KEY_AVATAR_TOKEN);
4239 }
4240 
4241 gboolean
_mcd_account_set_avatar(McdAccount * account,const GArray * avatar,const gchar * mime_type,const gchar * token,GError ** error)4242 _mcd_account_set_avatar (McdAccount *account, const GArray *avatar,
4243 			const gchar *mime_type, const gchar *token,
4244 			GError **error)
4245 {
4246     McdAccountPrivate *priv = MCD_ACCOUNT_PRIV (account);
4247     const gchar *account_name = mcd_account_get_unique_name (account);
4248 
4249     DEBUG ("called");
4250 
4251     if (G_LIKELY(avatar) && avatar->len > 0)
4252     {
4253         if (!save_avatar (account, avatar->data, avatar->len, error))
4254 	{
4255 	    g_warning ("%s: writing avatar failed", G_STRLOC);
4256 	    return FALSE;
4257 	}
4258     }
4259     else
4260     {
4261         /* We implement "deleting" an avatar by writing out a zero-length
4262          * file, so that it will override lower-priority directories. */
4263         if (!save_avatar (account, "", 0, error))
4264         {
4265             g_warning ("%s: writing empty avatar failed", G_STRLOC);
4266             return FALSE;
4267         }
4268     }
4269 
4270     if (mime_type != NULL)
4271         mcd_storage_set_string (priv->storage,
4272                                 account_name,
4273                                 MC_ACCOUNTS_KEY_AVATAR_MIME,
4274                                 mime_type);
4275 
4276     if (token)
4277     {
4278         gchar *prev_token;
4279 
4280         prev_token = _mcd_account_get_avatar_token (account);
4281 
4282         mcd_storage_set_string (priv->storage,
4283                                 account_name,
4284                                 MC_ACCOUNTS_KEY_AVATAR_TOKEN,
4285                                 token);
4286 
4287         if (!prev_token || strcmp (prev_token, token) != 0)
4288             tp_svc_account_interface_avatar_emit_avatar_changed (account);
4289 
4290         g_free (prev_token);
4291     }
4292     else
4293     {
4294         mcd_storage_set_attribute (priv->storage, account_name,
4295                                    MC_ACCOUNTS_KEY_AVATAR_TOKEN, NULL);
4296 
4297         mcd_account_send_avatar_to_connection (account, avatar, mime_type);
4298     }
4299 
4300     mcd_storage_commit (priv->storage, account_name);
4301 
4302     return TRUE;
4303 }
4304 
4305 static GArray *
load_avatar_or_warn(const gchar * filename)4306 load_avatar_or_warn (const gchar *filename)
4307 {
4308     GError *error = NULL;
4309     gchar *data = NULL;
4310     gsize length;
4311 
4312     if (g_file_get_contents (filename, &data, &length, &error))
4313     {
4314         if (length > 0 && length < G_MAXUINT)
4315         {
4316             GArray *ret;
4317 
4318             ret = g_array_new (FALSE, FALSE, 1);
4319             g_array_append_vals (ret, data, (guint) length);
4320             return ret;
4321         }
4322         else
4323         {
4324             DEBUG ("avatar %s was empty or ridiculously large (%"
4325                    G_GSIZE_FORMAT " bytes)", filename, length);
4326             return NULL;
4327         }
4328     }
4329     else
4330     {
4331         DEBUG ("error reading %s: %s", filename, error->message);
4332         g_error_free (error);
4333         return NULL;
4334     }
4335 }
4336 
4337 void
_mcd_account_get_avatar(McdAccount * account,GArray ** avatar,gchar ** mime_type)4338 _mcd_account_get_avatar (McdAccount *account, GArray **avatar,
4339                          gchar **mime_type)
4340 {
4341     McdAccountPrivate *priv = MCD_ACCOUNT_PRIV (account);
4342     const gchar *account_name = mcd_account_get_unique_name (account);
4343     gchar *basename;
4344     gchar *filename;
4345 
4346     if (mime_type != NULL)
4347         *mime_type =  mcd_storage_dup_string (priv->storage, account_name,
4348                                               MC_ACCOUNTS_KEY_AVATAR_MIME);
4349 
4350     if (avatar == NULL)
4351         return;
4352 
4353     *avatar = NULL;
4354 
4355     get_avatar_paths (account, NULL, &basename, &filename);
4356 
4357     if (g_file_test (filename, G_FILE_TEST_EXISTS))
4358     {
4359         *avatar = load_avatar_or_warn (filename);
4360     }
4361     else
4362     {
4363         const gchar * const *iter;
4364 
4365         for (iter = g_get_system_data_dirs ();
4366              iter != NULL && *iter != NULL;
4367              iter++)
4368         {
4369             gchar *candidate = g_build_filename (*iter, "telepathy",
4370                                                  "mission-control",
4371                                                  basename, NULL);
4372 
4373             if (g_file_test (candidate, G_FILE_TEST_EXISTS))
4374             {
4375                 *avatar = load_avatar_or_warn (candidate);
4376                 g_free (candidate);
4377                 break;
4378             }
4379 
4380             g_free (candidate);
4381         }
4382     }
4383 
4384     g_free (filename);
4385     g_free (basename);
4386 }
4387 
4388 GPtrArray *
_mcd_account_get_supersedes(McdAccount * self)4389 _mcd_account_get_supersedes (McdAccount *self)
4390 {
4391   return self->priv->supersedes;
4392 }
4393 
4394 static void
mcd_account_self_contact_notify_alias_cb(McdAccount * self,GParamSpec * unused_param_spec G_GNUC_UNUSED,TpContact * self_contact)4395 mcd_account_self_contact_notify_alias_cb (McdAccount *self,
4396     GParamSpec *unused_param_spec G_GNUC_UNUSED,
4397     TpContact *self_contact)
4398 {
4399     GValue value = G_VALUE_INIT;
4400 
4401     if (self_contact != self->priv->self_contact)
4402         return;
4403 
4404     g_value_init (&value, G_TYPE_STRING);
4405     g_object_get_property (G_OBJECT (self_contact), "alias", &value);
4406     mcd_account_set_string_val (self, MC_ACCOUNTS_KEY_NICKNAME, &value,
4407                                 MCD_DBUS_PROP_SET_FLAG_NONE, NULL);
4408     g_value_unset (&value);
4409 }
4410 
4411 static gchar *
mcd_account_get_alias(McdAccount * account)4412 mcd_account_get_alias (McdAccount *account)
4413 {
4414     McdAccountPrivate *priv = MCD_ACCOUNT_PRIV (account);
4415     const gchar *account_name = mcd_account_get_unique_name (account);
4416 
4417     return mcd_storage_dup_string (priv->storage, account_name,
4418                                    MC_ACCOUNTS_KEY_NICKNAME);
4419 }
4420 
4421 static void
_mcd_account_online_request_completed(McdAccount * account,GError * error)4422 _mcd_account_online_request_completed (McdAccount *account, GError *error)
4423 {
4424     McdAccountPrivate *priv = MCD_ACCOUNT_PRIV (account);
4425     GList *list;
4426 
4427     list = priv->online_requests;
4428 
4429     while (list)
4430     {
4431         McdOnlineRequestData *data = list->data;
4432 
4433         data->callback (account, data->user_data, error);
4434         g_slice_free (McdOnlineRequestData, data);
4435         list = g_list_delete_link (list, list);
4436     }
4437     if (error)
4438         g_error_free (error);
4439     priv->online_requests = NULL;
4440 }
4441 
4442 static inline void
process_online_requests(McdAccount * account,TpConnectionStatus status,TpConnectionStatusReason reason)4443 process_online_requests (McdAccount *account,
4444 			 TpConnectionStatus status,
4445 			 TpConnectionStatusReason reason)
4446 {
4447     McdAccountPrivate *priv = MCD_ACCOUNT_PRIV (account);
4448     GError *error;
4449 
4450     switch (status)
4451     {
4452     case TP_CONNECTION_STATUS_CONNECTED:
4453         error = NULL;
4454 	break;
4455     case TP_CONNECTION_STATUS_DISCONNECTED:
4456         error = g_error_new (TP_ERROR, TP_ERROR_DISCONNECTED,
4457                              "Account %s disconnected with reason %d",
4458                              priv->unique_name, reason);
4459 	break;
4460     default:
4461 	return;
4462     }
4463     _mcd_account_online_request_completed (account, error);
4464 }
4465 
4466 static void
on_conn_status_changed(McdConnection * connection,TpConnectionStatus status,TpConnectionStatusReason reason,TpConnection * tp_conn,const gchar * dbus_error,GHashTable * details,McdAccount * account)4467 on_conn_status_changed (McdConnection *connection,
4468                         TpConnectionStatus status,
4469                         TpConnectionStatusReason reason,
4470                         TpConnection *tp_conn,
4471                         const gchar *dbus_error,
4472                         GHashTable *details,
4473                         McdAccount *account)
4474 {
4475     _mcd_account_set_connection_status (account, status, reason, tp_conn,
4476                                         dbus_error, details);
4477 }
4478 
4479 /* clear the "register" flag, if necessary */
4480 static void
clear_register(McdAccount * self)4481 clear_register (McdAccount *self)
4482 {
4483     GHashTable *params = _mcd_account_dup_parameters (self);
4484 
4485     if (params == NULL)
4486     {
4487         DEBUG ("no params returned");
4488         return;
4489     }
4490 
4491     if (tp_asv_get_boolean (params, "register", NULL))
4492     {
4493         GValue value = G_VALUE_INIT;
4494         const gchar *account_name = mcd_account_get_unique_name (self);
4495 
4496         _mcd_account_set_parameter (self, "register", NULL);
4497 
4498         g_hash_table_remove (params, "register");
4499 
4500         g_value_init (&value, TP_HASH_TYPE_STRING_VARIANT_MAP);
4501         g_value_take_boxed (&value, params);
4502         mcd_account_changed_property (self, "Parameters", &value);
4503         g_value_unset (&value);
4504 
4505         mcd_storage_commit (self->priv->storage, account_name);
4506     }
4507     else
4508     {
4509       g_hash_table_unref (params);
4510     }
4511 }
4512 
4513 void
_mcd_account_set_connection_status(McdAccount * account,TpConnectionStatus status,TpConnectionStatusReason reason,TpConnection * tp_conn,const gchar * dbus_error,const GHashTable * details)4514 _mcd_account_set_connection_status (McdAccount *account,
4515                                     TpConnectionStatus status,
4516                                     TpConnectionStatusReason reason,
4517                                     TpConnection *tp_conn,
4518                                     const gchar *dbus_error,
4519                                     const GHashTable *details)
4520 {
4521     McdAccountPrivate *priv = MCD_ACCOUNT_PRIV (account);
4522     gboolean changed = FALSE;
4523 
4524     DEBUG ("%s: %u because %u", priv->unique_name, status, reason);
4525 
4526     mcd_account_freeze_properties (account);
4527 
4528     if (status == TP_CONNECTION_STATUS_CONNECTED)
4529     {
4530         _mcd_account_set_has_been_online (account);
4531         clear_register (account);
4532 
4533         DEBUG ("clearing connection error details");
4534         g_free (priv->conn_dbus_error);
4535         priv->conn_dbus_error = g_strdup ("");
4536         g_hash_table_remove_all (priv->conn_error_details);
4537 
4538     }
4539     else if (status == TP_CONNECTION_STATUS_DISCONNECTED)
4540     {
4541         /* we'll get this from the TpContact soon, but it makes sense
4542          * to bundle everything together into one signal */
4543         mcd_account_update_self_presence (account,
4544             TP_CONNECTION_PRESENCE_TYPE_OFFLINE,
4545             "offline",
4546             "",
4547             priv->self_contact);
4548 
4549         if (dbus_error == NULL)
4550             dbus_error = "";
4551 
4552         if (tp_strdiff (dbus_error, priv->conn_dbus_error))
4553         {
4554             DEBUG ("changing detailed D-Bus error from '%s' to '%s'",
4555                    priv->conn_dbus_error, dbus_error);
4556             g_free (priv->conn_dbus_error);
4557             priv->conn_dbus_error = g_strdup (dbus_error);
4558             changed = TRUE;
4559         }
4560 
4561         /* to avoid having to do deep comparisons, we assume that any change to
4562          * or from a non-empty hash table is interesting. */
4563         if ((details != NULL && tp_asv_size (details) > 0) ||
4564             tp_asv_size (priv->conn_error_details) > 0)
4565         {
4566             DEBUG ("changing error details");
4567             g_hash_table_remove_all (priv->conn_error_details);
4568 
4569             if (details != NULL)
4570                 tp_g_hash_table_update (priv->conn_error_details,
4571                                         (GHashTable *) details,
4572                                         (GBoxedCopyFunc) g_strdup,
4573                                         (GBoxedCopyFunc) tp_g_value_slice_dup);
4574 
4575             changed = TRUE;
4576         }
4577     }
4578 
4579     if (priv->tp_connection != tp_conn
4580         || (tp_conn != NULL && status == TP_CONNECTION_STATUS_DISCONNECTED))
4581     {
4582         tp_clear_object (&priv->tp_connection);
4583         tp_clear_object (&priv->self_contact);
4584 
4585         if (tp_conn != NULL && status != TP_CONNECTION_STATUS_DISCONNECTED)
4586             priv->tp_connection = g_object_ref (tp_conn);
4587         else
4588             priv->tp_connection = NULL;
4589 
4590         changed = TRUE;
4591     }
4592 
4593     if (status != priv->conn_status)
4594     {
4595         DEBUG ("changing connection status from %u to %u", priv->conn_status,
4596                status);
4597 	priv->conn_status = status;
4598 	changed = TRUE;
4599     }
4600 
4601     if (reason != priv->conn_reason)
4602     {
4603         DEBUG ("changing connection status reason from %u to %u",
4604                priv->conn_reason, reason);
4605 	priv->conn_reason = reason;
4606 	changed = TRUE;
4607     }
4608 
4609     if (changed)
4610     {
4611         GValue value = G_VALUE_INIT;
4612 
4613         _mcd_account_tp_connection_changed (account, priv->tp_connection);
4614 
4615         g_value_init (&value, G_TYPE_UINT);
4616         g_value_set_uint (&value, priv->conn_status);
4617         mcd_account_changed_property (account, "ConnectionStatus", &value);
4618         g_value_set_uint (&value, priv->conn_reason);
4619         mcd_account_changed_property (account, "ConnectionStatusReason",
4620                                       &value);
4621         g_value_unset (&value);
4622 
4623         g_value_init (&value, G_TYPE_STRING);
4624         g_value_set_string (&value, priv->conn_dbus_error);
4625         mcd_account_changed_property (account, "ConnectionError", &value);
4626         g_value_unset (&value);
4627 
4628         g_value_init (&value, TP_HASH_TYPE_STRING_VARIANT_MAP);
4629         g_value_set_boxed (&value, priv->conn_error_details);
4630         mcd_account_changed_property (account, "ConnectionErrorDetails",
4631                                       &value);
4632         g_value_unset (&value);
4633     }
4634 
4635     mcd_account_thaw_properties (account);
4636 
4637     process_online_requests (account, status, reason);
4638 }
4639 
4640 TpConnectionStatus
mcd_account_get_connection_status(McdAccount * account)4641 mcd_account_get_connection_status (McdAccount *account)
4642 {
4643     McdAccountPrivate *priv = MCD_ACCOUNT_PRIV (account);
4644     return priv->conn_status;
4645 }
4646 
4647 void
_mcd_account_tp_connection_changed(McdAccount * account,TpConnection * tp_conn)4648 _mcd_account_tp_connection_changed (McdAccount *account,
4649                                     TpConnection *tp_conn)
4650 {
4651     GValue value = G_VALUE_INIT;
4652 
4653     g_value_init (&value, DBUS_TYPE_G_OBJECT_PATH);
4654 
4655     if (tp_conn == NULL)
4656     {
4657         g_value_set_static_boxed (&value, "/");
4658     }
4659     else
4660     {
4661         g_value_set_boxed (&value, tp_proxy_get_object_path (tp_conn));
4662     }
4663 
4664     mcd_account_changed_property (account, "Connection", &value);
4665 
4666     g_signal_emit (account, _mcd_account_signals[CONNECTION_PATH_CHANGED], 0,
4667         g_value_get_boxed (&value));
4668     g_value_unset (&value);
4669 }
4670 
4671 McdConnection *
mcd_account_get_connection(McdAccount * account)4672 mcd_account_get_connection (McdAccount *account)
4673 {
4674     McdAccountPrivate *priv = MCD_ACCOUNT_PRIV (account);
4675     return priv->connection;
4676 }
4677 
4678 typedef struct
4679 {
4680     McdAccountCheckValidityCb callback;
4681     gpointer user_data;
4682 } CheckValidityData;
4683 
4684 static void
check_validity_check_parameters_cb(McdAccount * account,const GError * invalid_reason,gpointer user_data)4685 check_validity_check_parameters_cb (McdAccount *account,
4686                                     const GError *invalid_reason,
4687                                     gpointer user_data)
4688 {
4689     CheckValidityData *data = (CheckValidityData *) user_data;
4690     McdAccountPrivate *priv = account->priv;
4691     gboolean now_valid = (invalid_reason == NULL);
4692     gboolean was_valid = (priv->invalid_reason == NULL);
4693 
4694     g_clear_error (&priv->invalid_reason);
4695     if (invalid_reason != NULL)
4696     {
4697         priv->invalid_reason = g_error_copy (invalid_reason);
4698     }
4699 
4700     if (was_valid != now_valid)
4701     {
4702         GValue value = G_VALUE_INIT;
4703         DEBUG ("Account validity changed (old: %d, new: %d)",
4704                was_valid, now_valid);
4705         g_signal_emit (account, _mcd_account_signals[VALIDITY_CHANGED], 0,
4706                        now_valid);
4707         g_value_init (&value, G_TYPE_BOOLEAN);
4708         g_value_set_boolean (&value, now_valid);
4709         mcd_account_changed_property (account, "Valid", &value);
4710 
4711         if (now_valid)
4712         {
4713             /* Newly valid - try setting requested presence again.
4714              * This counts as user-initiated, because the user caused the
4715              * account to become valid somehow. */
4716             mcd_account_rerequest_presence (account, TRUE);
4717         }
4718     }
4719 
4720     if (data->callback != NULL)
4721         data->callback (account, invalid_reason, data->user_data);
4722 
4723     g_slice_free (CheckValidityData, data);
4724 }
4725 
4726 void
mcd_account_check_validity(McdAccount * account,McdAccountCheckValidityCb callback,gpointer user_data)4727 mcd_account_check_validity (McdAccount *account,
4728                             McdAccountCheckValidityCb callback,
4729                             gpointer user_data)
4730 {
4731     CheckValidityData *data;
4732 
4733     g_return_if_fail (MCD_IS_ACCOUNT (account));
4734 
4735     data = g_slice_new0 (CheckValidityData);
4736     data->callback = callback;
4737     data->user_data = user_data;
4738 
4739     mcd_account_check_parameters (account, check_validity_check_parameters_cb,
4740                                   data);
4741 }
4742 
4743 /*
4744  * _mcd_account_connect_with_auto_presence:
4745  * @account: the #McdAccount.
4746  * @user_initiated: %TRUE if the connection attempt is in response to a user
4747  *                  request (like a request for a channel)
4748  *
4749  * Request the account to go back online with the current RequestedPresence, if
4750  * it is not Offline, or with the configured AutomaticPresence otherwise.
4751  *
4752  * This is appropriate in these situations:
4753  * - going online automatically because we've gained connectivity
4754  * - going online automatically in order to request a channel
4755  */
4756 void
_mcd_account_connect_with_auto_presence(McdAccount * account,gboolean user_initiated)4757 _mcd_account_connect_with_auto_presence (McdAccount *account,
4758                                          gboolean user_initiated)
4759 {
4760     McdAccountPrivate *priv = account->priv;
4761 
4762     if (_presence_type_is_online (priv->req_presence_type))
4763         mcd_account_rerequest_presence (account, user_initiated);
4764     else
4765         mcd_account_request_presence_int (account,
4766                                           priv->auto_presence_type,
4767                                           priv->auto_presence_status,
4768                                           priv->auto_presence_message,
4769                                           user_initiated);
4770 }
4771 
4772 /*
4773  * _mcd_account_online_request:
4774  * @account: the #McdAccount.
4775  * @callback: a #McdOnlineRequestCb.
4776  * @userdata: user data to be passed to @callback.
4777  *
4778  * If the account is online, call @callback immediately; else, try to put the
4779  * account online (set its presence to the automatic presence) and eventually
4780  * invoke @callback.
4781  *
4782  * @callback is always invoked exactly once.
4783  */
4784 void
_mcd_account_online_request(McdAccount * account,McdOnlineRequestCb callback,gpointer userdata)4785 _mcd_account_online_request (McdAccount *account,
4786                              McdOnlineRequestCb callback,
4787                              gpointer userdata)
4788 {
4789     McdAccountPrivate *priv = account->priv;
4790     McdOnlineRequestData *data;
4791 
4792     DEBUG ("connection status for %s is %d",
4793            priv->unique_name, priv->conn_status);
4794     if (priv->conn_status == TP_CONNECTION_STATUS_CONNECTED)
4795     {
4796         /* invoke the callback now */
4797         DEBUG ("%s is already connected", priv->unique_name);
4798         callback (account, userdata, NULL);
4799         return;
4800     }
4801 
4802     if (priv->loaded && !mcd_account_is_valid (account))
4803     {
4804         /* FIXME: pick a better error and put it in telepathy-spec? */
4805         GError e = { TP_ERROR, TP_ERROR_NOT_AVAILABLE,
4806             "account isn't Valid (not enough information to put it online)" };
4807 
4808         DEBUG ("%s: %s", priv->unique_name, e.message);
4809         callback (account, userdata, &e);
4810         return;
4811     }
4812 
4813     if (priv->loaded && !priv->enabled)
4814     {
4815         /* FIXME: pick a better error and put it in telepathy-spec? */
4816         GError e = { TP_ERROR, TP_ERROR_NOT_AVAILABLE,
4817             "account isn't Enabled" };
4818 
4819         DEBUG ("%s: %s", priv->unique_name, e.message);
4820         callback (account, userdata, &e);
4821         return;
4822     }
4823 
4824     /* listen to the StatusChanged signal */
4825     if (priv->loaded && priv->conn_status == TP_CONNECTION_STATUS_DISCONNECTED)
4826         _mcd_account_connect_with_auto_presence (account, TRUE);
4827 
4828     /* now the connection should be in connecting state; insert the
4829      * callback in the online_requests hash table, which will be processed
4830      * in the connection-status-changed callback */
4831     data = g_slice_new (McdOnlineRequestData);
4832     data->callback = callback;
4833     data->user_data = userdata;
4834     priv->online_requests = g_list_append (priv->online_requests, data);
4835 }
4836 
4837 GKeyFile *
_mcd_account_get_keyfile(McdAccount * account)4838 _mcd_account_get_keyfile (McdAccount *account)
4839 {
4840     McdAccountPrivate *priv = MCD_ACCOUNT_PRIV (account);
4841     return priv->keyfile;
4842 }
4843 
4844 static gchar *
_mcd_account_get_old_avatar_filename(McdAccount * account,gchar ** old_dir)4845 _mcd_account_get_old_avatar_filename (McdAccount *account,
4846                                       gchar **old_dir)
4847 {
4848     McdAccountPrivate *priv = account->priv;
4849     gchar *filename;
4850 
4851     *old_dir = get_old_account_data_path (priv);
4852     filename = g_build_filename (*old_dir, MC_OLD_AVATAR_FILENAME, NULL);
4853     return filename;
4854 }
4855 
4856 static void
mcd_account_process_initial_avatar_token(McdAccount * self,const gchar * token)4857 mcd_account_process_initial_avatar_token (McdAccount *self,
4858     const gchar *token)
4859 {
4860   GArray *avatar = NULL;
4861   gchar *mime_type = NULL;
4862   gchar *prev_token;
4863 
4864   g_assert (self->priv->self_contact != NULL);
4865 
4866   prev_token = _mcd_account_get_avatar_token (self);
4867 
4868   DEBUG ("%s", self->priv->unique_name);
4869 
4870   if (prev_token == NULL)
4871     DEBUG ("no previous local avatar token");
4872   else
4873     DEBUG ("previous local avatar token: '%s'", prev_token);
4874 
4875   if (avatar == NULL)
4876     DEBUG ("no previous local avatar");
4877   else
4878     DEBUG ("previous local avatar: %u bytes, MIME type '%s'", avatar->len,
4879         (mime_type != NULL ? mime_type : "(null)"));
4880 
4881   if (token == NULL)
4882     DEBUG ("no remote avatar token");
4883   else
4884     DEBUG ("remote avatar token: '%s'", token);
4885 
4886   _mcd_account_get_avatar (self, &avatar, &mime_type);
4887 
4888   /* If we have a stored avatar but no avatar token, we must have
4889    * changed it locally; set it.
4890    *
4891    * Meanwhile, if the self-contact's avatar token is missing, this is
4892    * a protocol like link-local XMPP where avatars don't persist.
4893    * We can distinguish between this case (token is missing, so token = NULL)
4894    * and the case where there is no avatar on an XMPP server (token is
4895    * present and empty), although it's ridiculously subtle.
4896    *
4897    * Either way, upload our avatar, if any. */
4898   if (tp_str_empty (prev_token) || token == NULL)
4899     {
4900 
4901       if (avatar != NULL)
4902         {
4903           if (tp_str_empty (prev_token))
4904             DEBUG ("We have an avatar that has never been uploaded");
4905           if (tp_str_empty (token))
4906             DEBUG ("We have an avatar and the server doesn't");
4907 
4908           mcd_account_send_avatar_to_connection (self, avatar, mime_type);
4909           goto out;
4910         }
4911     }
4912 
4913   /* Otherwise, if the self-contact's avatar token
4914    * differs from ours, one of our "other selves" must have changed
4915    * it remotely. Behave the same as if it changes remotely
4916    * mid-session - i.e. download it and use it as our new avatar.
4917    *
4918    * In particular, this includes the case where we had
4919    * a non-empty avatar last time we signed in, but another client
4920    * has deleted it from the server since then (prev_token nonempty,
4921    * token = ""). */
4922   if (tp_strdiff (token, prev_token))
4923     {
4924       GFile *file = tp_contact_get_avatar_file (self->priv->self_contact);
4925 
4926       DEBUG ("The server's avatar does not match ours");
4927 
4928       if (file != NULL)
4929         {
4930           /* We have already downloaded it: copy it. */
4931           mcd_account_self_contact_notify_avatar_file_cb (self, NULL,
4932               self->priv->self_contact);
4933         }
4934       /* ... else we haven't downloaded it yet, but when we do,
4935        * notify::avatar-file will go off. */
4936     }
4937 
4938 out:
4939   g_free (prev_token);
4940   tp_clear_pointer (&avatar, g_array_unref);
4941   g_free (mime_type);
4942 }
4943 
4944 static void
account_conn_get_known_avatar_tokens_cb(TpConnection * conn,GHashTable * tokens,const GError * error,gpointer user_data,GObject * weak_object)4945 account_conn_get_known_avatar_tokens_cb (TpConnection *conn,
4946     GHashTable *tokens,
4947     const GError *error,
4948     gpointer user_data,
4949     GObject *weak_object)
4950 {
4951   McdAccount *self = g_object_ref (weak_object);
4952 
4953   self->priv->waiting_for_initial_avatar = FALSE;
4954 
4955   if (error != NULL)
4956     {
4957       DEBUG ("%s: GetKnownAvatarTokens raised %s #%d: %s",
4958           self->priv->unique_name, g_quark_to_string (error->domain),
4959           error->code, error->message);
4960     }
4961   else if (self->priv->self_contact == user_data)
4962     {
4963       TpHandle handle = tp_contact_get_handle (self->priv->self_contact);
4964 
4965       mcd_account_process_initial_avatar_token (self,
4966           g_hash_table_lookup (tokens, GUINT_TO_POINTER (handle)));
4967     }
4968   else
4969     {
4970       DEBUG ("%s: GetKnownAvatarTokens for outdated self-contact '%s', "
4971           "ignoring",
4972           self->priv->unique_name, tp_contact_get_identifier (user_data));
4973     }
4974 
4975   g_object_unref (self);
4976 }
4977 
4978 static void
mcd_account_self_contact_upgraded_cb(GObject * source_object,GAsyncResult * res,gpointer user_data)4979 mcd_account_self_contact_upgraded_cb (GObject *source_object,
4980     GAsyncResult *res,
4981     gpointer user_data)
4982 {
4983   TpConnection *conn = TP_CONNECTION (source_object);
4984   McdAccount *self = tp_weak_ref_dup_object (user_data);
4985   GPtrArray *contacts = NULL;
4986   GError *error = NULL;
4987 
4988   if (self == NULL)
4989     return;
4990 
4991   g_return_if_fail (MCD_IS_ACCOUNT (self));
4992 
4993   if (tp_connection_upgrade_contacts_finish (conn, res, &contacts, &error))
4994     {
4995       TpContact *self_contact;
4996 
4997       g_assert (contacts->len == 1);
4998       self_contact = g_ptr_array_index (contacts, 0);
4999 
5000       if (self_contact == self->priv->self_contact)
5001         {
5002           DEBUG ("%s", tp_contact_get_identifier (self_contact));
5003 
5004           tp_g_signal_connect_object (self_contact, "notify::alias",
5005               G_CALLBACK (mcd_account_self_contact_notify_alias_cb),
5006               self, G_CONNECT_SWAPPED);
5007           mcd_account_self_contact_notify_alias_cb (self, NULL, self_contact);
5008 
5009           tp_g_signal_connect_object (self_contact, "notify::avatar-file",
5010               G_CALLBACK (mcd_account_self_contact_notify_avatar_file_cb),
5011               self, G_CONNECT_SWAPPED);
5012 
5013           tp_g_signal_connect_object (self_contact, "presence-changed",
5014               G_CALLBACK (mcd_account_update_self_presence),
5015               self, G_CONNECT_SWAPPED);
5016 
5017           /* If the connection doesn't support SimplePresence then the
5018            * presence will be (UNSET, '', '') which is what we want anyway. */
5019           mcd_account_update_self_presence (self,
5020               tp_contact_get_presence_type (self_contact),
5021               tp_contact_get_presence_status (self_contact),
5022               tp_contact_get_presence_message (self_contact),
5023               self_contact);
5024 
5025           /* We have to use GetKnownAvatarTokens() because of its special
5026            * case for CMs that don't always download an up-to-date
5027            * avatar token before signalling CONNECTED. */
5028           if (tp_proxy_has_interface_by_id (conn,
5029               TP_IFACE_QUARK_CONNECTION_INTERFACE_AVATARS))
5030             {
5031               guint self_handle = tp_contact_get_handle (self_contact);
5032               GArray *arr = g_array_new (FALSE, FALSE, sizeof (guint));
5033 
5034               g_array_append_val (arr, self_handle);
5035               tp_cli_connection_interface_avatars_call_get_known_avatar_tokens (
5036                   conn, -1, arr, account_conn_get_known_avatar_tokens_cb,
5037                   g_object_ref (self_contact), g_object_unref,
5038                   (GObject *) self);
5039             }
5040         }
5041       else if (self->priv->self_contact == NULL)
5042         {
5043           DEBUG ("self-contact '%s' has disappeared since we asked to "
5044               "upgrade it", tp_contact_get_identifier (self_contact));
5045         }
5046       else
5047         {
5048           DEBUG ("self-contact '%s' has changed to '%s' since we asked to "
5049               "upgrade it", tp_contact_get_identifier (self_contact),
5050               tp_contact_get_identifier (self->priv->self_contact));
5051         }
5052 
5053       g_ptr_array_unref (contacts);
5054     }
5055   else
5056     {
5057       DEBUG ("failed to prepare self-contact: %s", error->message);
5058       g_clear_error (&error);
5059     }
5060 
5061   g_object_unref (self);
5062   tp_weak_ref_destroy (user_data);
5063 }
5064 
5065 static void
mcd_account_self_contact_changed_cb(McdAccount * self,GParamSpec * unused_param_spec G_GNUC_UNUSED,TpConnection * tp_connection)5066 mcd_account_self_contact_changed_cb (McdAccount *self,
5067     GParamSpec *unused_param_spec G_GNUC_UNUSED,
5068     TpConnection *tp_connection)
5069 {
5070   static const TpContactFeature contact_features[] = {
5071       TP_CONTACT_FEATURE_AVATAR_TOKEN,
5072       TP_CONTACT_FEATURE_AVATAR_DATA,
5073       TP_CONTACT_FEATURE_ALIAS,
5074       TP_CONTACT_FEATURE_PRESENCE
5075   };
5076   TpContact *self_contact;
5077 
5078   if (tp_connection != self->priv->tp_connection)
5079     return;
5080 
5081   self_contact = tp_connection_get_self_contact (tp_connection);
5082   g_assert (self_contact != NULL);
5083 
5084   DEBUG ("%s", tp_contact_get_identifier (self_contact));
5085 
5086   if (self_contact == self->priv->self_contact)
5087     return;
5088 
5089   g_clear_object (&self->priv->self_contact);
5090   self->priv->self_contact = g_object_ref (self_contact);
5091 
5092   _mcd_account_set_normalized_name (self,
5093       tp_contact_get_identifier (self_contact));
5094 
5095   tp_connection_upgrade_contacts_async (tp_connection,
5096       1, &self_contact,
5097       G_N_ELEMENTS (contact_features), contact_features,
5098       mcd_account_self_contact_upgraded_cb,
5099       tp_weak_ref_new (self, NULL, NULL));
5100 }
5101 
5102 static void
mcd_account_connection_ready_cb(McdAccount * account,McdConnection * connection)5103 mcd_account_connection_ready_cb (McdAccount *account,
5104                                  McdConnection *connection)
5105 {
5106     McdAccountPrivate *priv = account->priv;
5107     gchar *nickname;
5108     TpConnection *tp_connection;
5109     TpConnectionStatus status;
5110     TpConnectionStatusReason reason;
5111     const gchar *dbus_error = NULL;
5112     const GHashTable *details = NULL;
5113 
5114     g_return_if_fail (MCD_IS_ACCOUNT (account));
5115     g_return_if_fail (connection == priv->connection);
5116 
5117     tp_connection = mcd_connection_get_tp_connection (connection);
5118     g_return_if_fail (tp_connection != NULL);
5119     g_return_if_fail (priv->tp_connection == NULL ||
5120                       tp_connection == priv->tp_connection);
5121     g_assert (tp_proxy_is_prepared (tp_connection,
5122                                     TP_CONNECTION_FEATURE_CONNECTED));
5123 
5124     status = tp_connection_get_status (tp_connection, &reason);
5125     dbus_error = tp_connection_get_detailed_error (tp_connection, &details);
5126     _mcd_account_set_connection_status (account, status, reason,
5127                                         tp_connection, dbus_error, details);
5128 
5129     tp_g_signal_connect_object (tp_connection, "notify::self-contact",
5130         G_CALLBACK (mcd_account_self_contact_changed_cb), account,
5131         G_CONNECT_SWAPPED);
5132     mcd_account_self_contact_changed_cb (account, NULL, tp_connection);
5133     g_assert (priv->self_contact != NULL);
5134 
5135     /* FIXME: ideally, on protocols with server-stored nicknames, this should
5136      * only be done if the local Nickname has been changed since last time we
5137      * were online; Aliasing doesn't currently offer a way to tell whether
5138      * this is such a protocol, though.
5139      *
5140      * As a first step towards doing the right thing, we assume that if our
5141      * locally-stored nickname is just the protocol identifer, the
5142      * server-stored nickname (if any) takes precedence.
5143      */
5144     nickname = mcd_account_get_alias (account);
5145 
5146     if (tp_str_empty (nickname))
5147     {
5148         DEBUG ("no nickname yet");
5149     }
5150     else if (!tp_strdiff (nickname,
5151             tp_contact_get_identifier (priv->self_contact)))
5152     {
5153         DEBUG ("not setting nickname to '%s' since it matches the "
5154             "NormalizedName", nickname);
5155     }
5156     else
5157     {
5158         mcd_account_send_nickname_to_connection (account, nickname);
5159     }
5160 
5161     g_free (nickname);
5162 }
5163 
5164 void
_mcd_account_set_connection(McdAccount * account,McdConnection * connection)5165 _mcd_account_set_connection (McdAccount *account, McdConnection *connection)
5166 {
5167     McdAccountPrivate *priv;
5168 
5169     g_return_if_fail (MCD_IS_ACCOUNT (account));
5170     priv = account->priv;
5171     if (connection == priv->connection) return;
5172 
5173     if (priv->connection)
5174     {
5175         g_signal_handlers_disconnect_by_func (priv->connection,
5176                                               on_connection_abort, account);
5177         g_signal_handlers_disconnect_by_func (priv->connection,
5178                                               on_conn_status_changed,
5179                                               account);
5180         g_signal_handlers_disconnect_by_func (priv->connection,
5181                                               mcd_account_connection_ready_cb,
5182                                               account);
5183         g_object_unref (priv->connection);
5184     }
5185 
5186     tp_clear_object (&priv->tp_connection);
5187 
5188     priv->connection = connection;
5189     priv->waiting_for_initial_avatar = TRUE;
5190 
5191     if (connection)
5192     {
5193         g_return_if_fail (MCD_IS_CONNECTION (connection));
5194         g_object_ref (connection);
5195 
5196         if (_mcd_connection_is_ready (connection))
5197         {
5198             mcd_account_connection_ready_cb (account, connection);
5199         }
5200         else
5201         {
5202             g_signal_connect_swapped (connection, "ready",
5203                 G_CALLBACK (mcd_account_connection_ready_cb), account);
5204         }
5205 
5206         g_signal_connect (connection, "connection-status-changed",
5207                           G_CALLBACK (on_conn_status_changed), account);
5208         g_signal_connect (connection, "abort",
5209                           G_CALLBACK (on_connection_abort), account);
5210     }
5211     else
5212     {
5213         priv->conn_status = TP_CONNECTION_STATUS_DISCONNECTED;
5214     }
5215 }
5216 
5217 void
_mcd_account_set_has_been_online(McdAccount * account)5218 _mcd_account_set_has_been_online (McdAccount *account)
5219 {
5220     if (!account->priv->has_been_online)
5221     {
5222         GValue value = G_VALUE_INIT;
5223         const gchar *account_name = mcd_account_get_unique_name (account);
5224 
5225         g_value_init (&value, G_TYPE_BOOLEAN);
5226         g_value_set_boolean (&value, TRUE);
5227 
5228         mcd_storage_set_attribute (account->priv->storage, account_name,
5229                                    MC_ACCOUNTS_KEY_HAS_BEEN_ONLINE, &value);
5230         account->priv->has_been_online = TRUE;
5231         mcd_storage_commit (account->priv->storage, account_name);
5232         mcd_account_changed_property (account, MC_ACCOUNTS_KEY_HAS_BEEN_ONLINE,
5233                                       &value);
5234         g_value_unset (&value);
5235     }
5236 }
5237 
5238 McdAccountConnectionContext *
_mcd_account_get_connection_context(McdAccount * self)5239 _mcd_account_get_connection_context (McdAccount *self)
5240 {
5241     g_return_val_if_fail (MCD_IS_ACCOUNT (self), NULL);
5242 
5243     return self->priv->connection_context;
5244 }
5245 
5246 void
_mcd_account_set_connection_context(McdAccount * self,McdAccountConnectionContext * c)5247 _mcd_account_set_connection_context (McdAccount *self,
5248                                      McdAccountConnectionContext *c)
5249 {
5250     g_return_if_fail (MCD_IS_ACCOUNT (self));
5251 
5252     if (self->priv->connection_context != NULL)
5253     {
5254         _mcd_account_connection_context_free (self->priv->connection_context);
5255     }
5256 
5257     self->priv->connection_context = c;
5258 }
5259 
5260 gboolean
_mcd_account_get_always_on(McdAccount * self)5261 _mcd_account_get_always_on (McdAccount *self)
5262 {
5263     g_return_val_if_fail (MCD_IS_ACCOUNT (self), FALSE);
5264 
5265     return self->priv->always_on;
5266 }
5267 
5268 gboolean
_mcd_account_needs_dispatch(McdAccount * self)5269 _mcd_account_needs_dispatch (McdAccount *self)
5270 {
5271     g_return_val_if_fail (MCD_IS_ACCOUNT (self), FALSE);
5272 
5273     return self->priv->always_dispatch;
5274 }
5275 
5276 gboolean
mcd_account_parameter_is_secret(McdAccount * self,const gchar * name)5277 mcd_account_parameter_is_secret (McdAccount *self, const gchar *name)
5278 {
5279     McdAccountPrivate *priv = self->priv;
5280     const TpConnectionManagerParam *param;
5281 
5282     param = mcd_manager_get_protocol_param (priv->manager,
5283                                             priv->protocol_name, name);
5284 
5285     return (param != NULL &&
5286         tp_connection_manager_param_is_secret (param));
5287 }
5288 
5289 void
_mcd_account_set_changing_presence(McdAccount * self,gboolean value)5290 _mcd_account_set_changing_presence (McdAccount *self, gboolean value)
5291 {
5292     McdAccountPrivate *priv = self->priv;
5293     GValue changing_presence = G_VALUE_INIT;
5294 
5295     priv->changing_presence = value;
5296 
5297     g_value_init (&changing_presence, G_TYPE_BOOLEAN);
5298     g_value_set_boolean (&changing_presence, value);
5299 
5300     mcd_account_changed_property (self, "ChangingPresence",
5301                                   &changing_presence);
5302 
5303     g_value_unset (&changing_presence);
5304 }
5305 
5306 gchar *
mcd_account_dup_display_name(McdAccount * self)5307 mcd_account_dup_display_name (McdAccount *self)
5308 {
5309     const gchar *name = mcd_account_get_unique_name (self);
5310 
5311     return mcd_storage_dup_string (self->priv->storage, name, "DisplayName");
5312 }
5313 
5314 gchar *
mcd_account_dup_icon(McdAccount * self)5315 mcd_account_dup_icon (McdAccount *self)
5316 {
5317     const gchar *name = mcd_account_get_unique_name (self);
5318 
5319     return mcd_storage_dup_string (self->priv->storage, name, "Icon");
5320 }
5321 
5322 gchar *
mcd_account_dup_nickname(McdAccount * self)5323 mcd_account_dup_nickname (McdAccount *self)
5324 {
5325     const gchar *name = mcd_account_get_unique_name (self);
5326 
5327     return mcd_storage_dup_string (self->priv->storage, name, "Nickname");
5328 }
5329 
5330 McdConnectivityMonitor *
mcd_account_get_connectivity_monitor(McdAccount * self)5331 mcd_account_get_connectivity_monitor (McdAccount *self)
5332 {
5333     return self->priv->connectivity;
5334 }
5335 
5336 gboolean
mcd_account_get_waiting_for_connectivity(McdAccount * self)5337 mcd_account_get_waiting_for_connectivity (McdAccount *self)
5338 {
5339   return self->priv->waiting_for_connectivity;
5340 }
5341 
5342 void
mcd_account_set_waiting_for_connectivity(McdAccount * self,gboolean waiting)5343 mcd_account_set_waiting_for_connectivity (McdAccount *self,
5344     gboolean waiting)
5345 {
5346   self->priv->waiting_for_connectivity = waiting;
5347 }
5348