account-settings: allow to change the service
[empathy.git] / libempathy / empathy-account-settings.c
1 /*
2  * empathy-account-settings.c - Source for EmpathyAccountSettings
3  * Copyright (C) 2009 Collabora Ltd.
4  * @author Sjoerd Simons <sjoerd.simons@collabora.co.uk>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19  */
20
21
22 #include <stdio.h>
23 #include <stdlib.h>
24
25 #include <telepathy-glib/account-manager.h>
26 #include <telepathy-glib/util.h>
27 #include <telepathy-glib/interfaces.h>
28 #include <telepathy-glib/gtypes.h>
29
30 #include "empathy-account-settings.h"
31 #include "empathy-connection-managers.h"
32 #include "empathy-keyring.h"
33 #include "empathy-utils.h"
34 #include "empathy-presence-manager.h"
35
36 #define DEBUG_FLAG EMPATHY_DEBUG_ACCOUNT
37 #include <libempathy/empathy-debug.h>
38
39 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyAccountSettings)
40
41 G_DEFINE_TYPE(EmpathyAccountSettings, empathy_account_settings, G_TYPE_OBJECT)
42
43 enum {
44   PROP_ACCOUNT = 1,
45   PROP_CM_NAME,
46   PROP_PROTOCOL,
47   PROP_SERVICE,
48   PROP_DISPLAY_NAME,
49   PROP_DISPLAY_NAME_OVERRIDDEN,
50   PROP_READY
51 };
52
53 enum {
54   PASSWORD_RETRIEVED = 1,
55   LAST_SIGNAL
56 };
57
58 static gulong signals[LAST_SIGNAL] = { 0, };
59
60 /* private structure */
61 typedef struct _EmpathyAccountSettingsPriv EmpathyAccountSettingsPriv;
62
63 struct _EmpathyAccountSettingsPriv
64 {
65   gboolean dispose_has_run;
66   EmpathyConnectionManagers *managers;
67   TpAccountManager *account_manager;
68
69   TpConnectionManager *manager;
70   TpProtocol *protocol_obj;
71
72   TpAccount *account;
73   gchar *cm_name;
74   gchar *protocol;
75   gchar *service;
76   gchar *display_name;
77   gchar *icon_name;
78   gboolean display_name_overridden;
79   gboolean ready;
80
81   gboolean supports_sasl;
82   gboolean password_changed;
83
84   gchar *password;
85   gchar *password_original;
86
87   gboolean password_retrieved;
88   gboolean password_requested;
89
90   /* Parameter name (gchar *) -> parameter value (GValue) */
91   GHashTable *parameters;
92   /* Keys are parameter names from the hash above (gchar *).
93    * Values are regular expresions that should match corresponding parameter
94    * values (GRegex *). Possible regexp patterns are defined in
95    * empathy-account-widget.c */
96   GHashTable *param_regexps;
97   GArray *unset_parameters;
98   GList *required_params;
99
100   gulong managers_ready_id;
101   gboolean preparing_protocol;
102
103   /* If TRUE, the account should have 'tel' in its
104    * Account.Interface.Addressing.URISchemes property. */
105   gboolean uri_scheme_tel;
106   /* If TRUE, Service property needs to be updated when applying changes */
107   gboolean update_service;
108
109   GSimpleAsyncResult *apply_result;
110 };
111
112 static void
113 empathy_account_settings_init (EmpathyAccountSettings *obj)
114 {
115   EmpathyAccountSettingsPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE ((obj),
116     EMPATHY_TYPE_ACCOUNT_SETTINGS, EmpathyAccountSettingsPriv);
117
118   obj->priv = priv;
119
120   /* allocate any data required by the object here */
121   priv->managers = empathy_connection_managers_dup_singleton ();
122   priv->account_manager = tp_account_manager_dup ();
123
124   priv->parameters = g_hash_table_new_full (g_str_hash, g_str_equal,
125     g_free, (GDestroyNotify) tp_g_value_slice_free);
126
127   priv->param_regexps = g_hash_table_new_full (g_str_hash, g_str_equal,
128     g_free, (GDestroyNotify) g_regex_unref);
129
130   priv->unset_parameters = g_array_new (TRUE, FALSE, sizeof (gchar *));
131
132   priv->required_params = NULL;
133 }
134
135 static void empathy_account_settings_dispose (GObject *object);
136 static void empathy_account_settings_finalize (GObject *object);
137 static void empathy_account_settings_account_ready_cb (GObject *source_object,
138     GAsyncResult *result, gpointer user_data);
139 static void empathy_account_settings_managers_ready_cb (GObject *obj,
140     GParamSpec *pspec, gpointer user_data);
141 static void empathy_account_settings_check_readyness (
142     EmpathyAccountSettings *self);
143
144 static void
145 empathy_account_settings_set_property (GObject *object,
146     guint prop_id,
147     const GValue *value,
148     GParamSpec *pspec)
149 {
150   EmpathyAccountSettings *settings = EMPATHY_ACCOUNT_SETTINGS (object);
151   EmpathyAccountSettingsPriv *priv = GET_PRIV (settings);
152
153   switch (prop_id)
154     {
155       case PROP_ACCOUNT:
156         priv->account = g_value_dup_object (value);
157         break;
158       case PROP_CM_NAME:
159         priv->cm_name = g_value_dup_string (value);
160         break;
161       case PROP_PROTOCOL:
162         priv->protocol = g_value_dup_string (value);
163         break;
164       case PROP_SERVICE:
165         priv->service = g_value_dup_string (value);
166         break;
167       case PROP_DISPLAY_NAME:
168         priv->display_name = g_value_dup_string (value);
169         break;
170       case PROP_DISPLAY_NAME_OVERRIDDEN:
171         priv->display_name_overridden = g_value_get_boolean (value);
172         break;
173       default:
174         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
175         break;
176     }
177 }
178
179 static void
180 empathy_account_settings_get_property (GObject *object,
181     guint prop_id,
182     GValue *value,
183     GParamSpec *pspec)
184 {
185   EmpathyAccountSettings *settings = EMPATHY_ACCOUNT_SETTINGS (object);
186   EmpathyAccountSettingsPriv *priv = GET_PRIV (settings);
187
188   switch (prop_id)
189     {
190       case PROP_ACCOUNT:
191         g_value_set_object (value, priv->account);
192         break;
193       case PROP_CM_NAME:
194         g_value_set_string (value, priv->cm_name);
195         break;
196       case PROP_PROTOCOL:
197         g_value_set_string (value, priv->protocol);
198         break;
199       case PROP_SERVICE:
200         g_value_set_string (value, priv->service);
201         break;
202       case PROP_DISPLAY_NAME:
203         g_value_set_string (value, priv->display_name);
204         break;
205       case PROP_DISPLAY_NAME_OVERRIDDEN:
206         g_value_set_boolean (value, priv->display_name_overridden);
207         break;
208       case PROP_READY:
209         g_value_set_boolean (value, priv->ready);
210         break;
211       default:
212         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
213         break;
214     }
215 }
216
217 static void
218 empathy_account_settings_constructed (GObject *object)
219 {
220   EmpathyAccountSettings *self = EMPATHY_ACCOUNT_SETTINGS (object);
221   EmpathyAccountSettingsPriv *priv = GET_PRIV (self);
222
223   if (priv->account != NULL)
224     {
225       g_free (priv->cm_name);
226       g_free (priv->protocol);
227       g_free (priv->service);
228
229       priv->cm_name =
230         g_strdup (tp_account_get_connection_manager (priv->account));
231       priv->protocol =
232         g_strdup (tp_account_get_protocol (priv->account));
233       priv->service =
234         g_strdup (tp_account_get_service (priv->account));
235       priv->icon_name = g_strdup
236         (tp_account_get_icon_name (priv->account));
237     }
238   else
239     {
240       priv->icon_name = empathy_protocol_icon_name (priv->protocol);
241     }
242
243   g_assert (priv->cm_name != NULL && priv->protocol != NULL);
244
245   empathy_account_settings_check_readyness (self);
246
247   if (!priv->ready)
248     {
249       GQuark features[] = {
250           TP_ACCOUNT_FEATURE_CORE,
251           TP_ACCOUNT_FEATURE_STORAGE,
252           TP_ACCOUNT_FEATURE_ADDRESSING,
253           0 };
254
255       if (priv->account != NULL)
256         {
257           tp_proxy_prepare_async (priv->account, features,
258               empathy_account_settings_account_ready_cb, self);
259         }
260
261       tp_g_signal_connect_object (priv->managers, "notify::ready",
262         G_CALLBACK (empathy_account_settings_managers_ready_cb), object, 0);
263     }
264
265   if (G_OBJECT_CLASS (
266         empathy_account_settings_parent_class)->constructed != NULL)
267     G_OBJECT_CLASS (
268         empathy_account_settings_parent_class)->constructed (object);
269 }
270
271
272 static void
273 empathy_account_settings_class_init (
274     EmpathyAccountSettingsClass *empathy_account_settings_class)
275 {
276   GObjectClass *object_class = G_OBJECT_CLASS (empathy_account_settings_class);
277
278   g_type_class_add_private (empathy_account_settings_class, sizeof
279       (EmpathyAccountSettingsPriv));
280
281   object_class->dispose = empathy_account_settings_dispose;
282   object_class->finalize = empathy_account_settings_finalize;
283   object_class->set_property = empathy_account_settings_set_property;
284   object_class->get_property = empathy_account_settings_get_property;
285   object_class->constructed = empathy_account_settings_constructed;
286
287   g_object_class_install_property (object_class, PROP_ACCOUNT,
288     g_param_spec_object ("account",
289       "Account",
290       "The TpAccount backing these settings",
291       TP_TYPE_ACCOUNT,
292       G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
293
294   g_object_class_install_property (object_class, PROP_CM_NAME,
295     g_param_spec_string ("connection-manager",
296       "connection-manager",
297       "The name of the connection manager this account uses",
298       NULL,
299       G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
300
301   g_object_class_install_property (object_class, PROP_PROTOCOL,
302     g_param_spec_string ("protocol",
303       "Protocol",
304       "The name of the protocol this account uses",
305       NULL,
306       G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
307
308   g_object_class_install_property (object_class, PROP_SERVICE,
309     g_param_spec_string ("service",
310       "Service",
311       "The service of this account, or NULL",
312       NULL,
313       G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
314
315   g_object_class_install_property (object_class, PROP_DISPLAY_NAME,
316     g_param_spec_string ("display-name",
317       "display-name",
318       "The display name account these settings belong to",
319       NULL,
320       G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
321
322   g_object_class_install_property (object_class, PROP_DISPLAY_NAME_OVERRIDDEN,
323       g_param_spec_boolean ("display-name-overridden",
324         "display-name-overridden",
325         "Whether the display name for this account has been manually "
326         "overridden",
327         FALSE,
328         G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE));
329
330   g_object_class_install_property (object_class, PROP_READY,
331     g_param_spec_boolean ("ready",
332       "Ready",
333       "Whether this account is ready to be used",
334       FALSE,
335       G_PARAM_STATIC_STRINGS | G_PARAM_READABLE));
336
337   signals[PASSWORD_RETRIEVED] =
338       g_signal_new ("password-retrieved",
339           G_TYPE_FROM_CLASS (empathy_account_settings_class),
340           G_SIGNAL_RUN_LAST, 0, NULL, NULL,
341           g_cclosure_marshal_generic,
342           G_TYPE_NONE, 0);
343 }
344
345 static void
346 empathy_account_settings_dispose (GObject *object)
347 {
348   EmpathyAccountSettings *self = EMPATHY_ACCOUNT_SETTINGS (object);
349   EmpathyAccountSettingsPriv *priv = GET_PRIV (self);
350
351   if (priv->dispose_has_run)
352     return;
353
354   priv->dispose_has_run = TRUE;
355
356   if (priv->managers_ready_id != 0)
357     g_signal_handler_disconnect (priv->managers, priv->managers_ready_id);
358   priv->managers_ready_id = 0;
359
360   tp_clear_object (&priv->managers);
361   tp_clear_object (&priv->manager);
362   tp_clear_object (&priv->account_manager);
363   tp_clear_object (&priv->account);
364   tp_clear_object (&priv->protocol_obj);
365
366   /* release any references held by the object here */
367   if (G_OBJECT_CLASS (empathy_account_settings_parent_class)->dispose)
368     G_OBJECT_CLASS (empathy_account_settings_parent_class)->dispose (object);
369 }
370
371 static void
372 empathy_account_settings_free_unset_parameters (
373     EmpathyAccountSettings *settings)
374 {
375   EmpathyAccountSettingsPriv *priv = GET_PRIV (settings);
376   guint i;
377
378   for (i = 0 ; i < priv->unset_parameters->len; i++)
379     g_free (g_array_index (priv->unset_parameters, gchar *, i));
380
381   g_array_set_size (priv->unset_parameters, 0);
382 }
383
384 static void
385 empathy_account_settings_finalize (GObject *object)
386 {
387   EmpathyAccountSettings *self = EMPATHY_ACCOUNT_SETTINGS (object);
388   EmpathyAccountSettingsPriv *priv = GET_PRIV (self);
389   GList *l;
390
391   /* free any data held directly by the object here */
392   g_free (priv->cm_name);
393   g_free (priv->protocol);
394   g_free (priv->service);
395   g_free (priv->display_name);
396   g_free (priv->icon_name);
397   g_free (priv->password);
398   g_free (priv->password_original);
399
400   if (priv->required_params != NULL)
401     {
402       for (l = priv->required_params; l; l = l->next)
403         g_free (l->data);
404       g_list_free (priv->required_params);
405     }
406
407   g_hash_table_unref (priv->parameters);
408   g_hash_table_unref (priv->param_regexps);
409
410   empathy_account_settings_free_unset_parameters (self);
411   g_array_unref (priv->unset_parameters);
412
413   G_OBJECT_CLASS (empathy_account_settings_parent_class)->finalize (object);
414 }
415
416 static void
417 empathy_account_settings_protocol_obj_prepared_cb (GObject *source,
418     GAsyncResult *result,
419     gpointer user_data)
420 {
421   EmpathyAccountSettings *self = user_data;
422   GError *error = NULL;
423
424   if (!tp_proxy_prepare_finish (source, result, &error))
425     {
426       DEBUG ("Failed to prepare protocol object: %s", error->message);
427       g_clear_error (&error);
428       return;
429     }
430
431   empathy_account_settings_check_readyness (self);
432 }
433
434 static void
435 empathy_account_settings_get_password_cb (GObject *source,
436     GAsyncResult *result,
437     gpointer user_data)
438 {
439   EmpathyAccountSettings *self = user_data;
440   EmpathyAccountSettingsPriv *priv = GET_PRIV (self);
441   const gchar *password;
442   GError *error = NULL;
443
444   password = empathy_keyring_get_account_password_finish (TP_ACCOUNT (source),
445       result, &error);
446
447   if (error != NULL)
448     {
449       DEBUG ("Failed to get password: %s", error->message);
450       g_clear_error (&error);
451     }
452
453   /* It doesn't really matter if getting the password failed; that
454    * just means that it's not there, or let's act like that at
455    * least. */
456
457   g_assert (priv->password == NULL);
458
459   priv->password = g_strdup (password);
460   priv->password_original = g_strdup (password);
461
462   g_signal_emit (self, signals[PASSWORD_RETRIEVED], 0);
463 }
464
465 static void
466 empathy_account_settings_migrate_password_cb (GObject *source,
467     GAsyncResult *result,
468     gpointer user_data)
469 {
470   TpAccount *account = TP_ACCOUNT (source);
471   GError *error = NULL;
472   EmpathyAccountSettings *self = user_data;
473   EmpathyAccountSettingsPriv *priv = GET_PRIV (self);
474   GHashTable *empty;
475   const gchar *unset[] = { "password", NULL };
476
477   if (!empathy_keyring_set_account_password_finish (account, result, &error))
478     {
479       DEBUG ("Failed to set password: %s", error->message);
480       g_clear_error (&error);
481       return;
482     }
483
484   /* Now clear the password MC has stored. */
485   empty = tp_asv_new (NULL, NULL);
486   tp_account_update_parameters_async (priv->account,
487       empty, unset, NULL, NULL);
488
489   g_hash_table_remove (priv->parameters, "password");
490
491   g_hash_table_unref (empty);
492 }
493
494 static void
495 empathy_account_settings_try_migrating_password (EmpathyAccountSettings *self)
496 {
497   EmpathyAccountSettingsPriv *priv = GET_PRIV (self);
498   const GValue *v;
499   const gchar *password;
500
501   if (!priv->supports_sasl || empathy_account_settings_get (
502           self, "password") == NULL)
503     return;
504
505   /* mission-control still has our password, although the CM
506    * supports SASL. Let's try migrating it. */
507
508   DEBUG ("Trying to migrate password parameter from MC to the "
509       "keyring ourselves for account %s",
510       tp_account_get_path_suffix (priv->account));
511
512   v = empathy_account_settings_get (self, "password");
513
514   /* I can't imagine why this would fail. */
515   if (!G_VALUE_HOLDS_STRING (v))
516     return;
517
518   password = g_value_get_string (v);
519
520   if (EMP_STR_EMPTY (password))
521     return;
522
523   empathy_keyring_set_account_password_async (priv->account, password,
524       empathy_account_settings_migrate_password_cb, self);
525
526   /* We don't want to request the password again, we
527    * already know it. */
528   priv->password_requested = TRUE;
529
530   priv->password = g_strdup (password);
531   priv->password_original = g_strdup (password);
532 }
533
534 static void
535 empathy_account_settings_check_readyness (EmpathyAccountSettings *self)
536 {
537   EmpathyAccountSettingsPriv *priv = GET_PRIV (self);
538   const TpConnectionManagerProtocol *tp_protocol;
539   GQuark features[] = { TP_PROTOCOL_FEATURE_CORE, 0 };
540
541   if (priv->ready)
542     return;
543
544   if (priv->account != NULL
545       && !tp_account_is_prepared (priv->account, TP_ACCOUNT_FEATURE_CORE))
546       return;
547
548   if (!empathy_connection_managers_is_ready (priv->managers))
549     return;
550
551   if (priv->manager == NULL)
552     {
553       priv->manager = empathy_connection_managers_get_cm (
554           priv->managers, priv->cm_name);
555     }
556
557   if (priv->manager == NULL)
558     return;
559
560   g_object_ref (priv->manager);
561
562   if (priv->account != NULL)
563     {
564       g_free (priv->display_name);
565       priv->display_name =
566         g_strdup (tp_account_get_display_name (priv->account));
567
568       g_free (priv->icon_name);
569       priv->icon_name =
570         g_strdup (tp_account_get_icon_name (priv->account));
571
572       priv->uri_scheme_tel = empathy_account_has_uri_scheme_tel (priv->account);
573     }
574
575   tp_protocol = tp_connection_manager_get_protocol (priv->manager,
576     priv->protocol);
577
578   if (tp_protocol == NULL)
579     {
580       tp_clear_object (&priv->manager);
581       return;
582     }
583
584   if (priv->required_params == NULL)
585     {
586       TpConnectionManagerParam *cur;
587
588       for (cur = tp_protocol->params; cur->name != NULL; cur++)
589         {
590           if (tp_connection_manager_param_is_required (cur))
591             {
592               priv->required_params = g_list_append (priv->required_params,
593                                                      g_strdup (cur->name));
594             }
595         }
596     }
597
598   if (priv->protocol_obj == NULL)
599     {
600       priv->protocol_obj = g_object_ref (
601           tp_connection_manager_get_protocol_object (priv->manager,
602               priv->protocol));
603     }
604
605   if (!tp_proxy_is_prepared (priv->protocol_obj, TP_PROTOCOL_FEATURE_CORE)
606       && !priv->preparing_protocol)
607     {
608       priv->preparing_protocol = TRUE;
609       tp_proxy_prepare_async (priv->protocol_obj, features,
610           empathy_account_settings_protocol_obj_prepared_cb, self);
611       return;
612     }
613   else
614     {
615       if (tp_strv_contains (tp_protocol_get_authentication_types (
616                   priv->protocol_obj),
617               TP_IFACE_CHANNEL_INTERFACE_SASL_AUTHENTICATION))
618         {
619           priv->supports_sasl = TRUE;
620         }
621     }
622
623   /* NOTE: When removing MC migration code, remove this call, and the
624    * function it's calling. That's it. */
625   empathy_account_settings_try_migrating_password (self);
626
627   /* priv->account won't be a proper account if it's the account
628    * assistant showing this widget. */
629   if (priv->supports_sasl && !priv->password_requested
630       && priv->account != NULL)
631     {
632       priv->password_requested = TRUE;
633
634       /* Make this call but don't block on its readiness. We'll signal
635        * if it's updated later with ::password-retrieved. */
636       empathy_keyring_get_account_password_async (priv->account,
637           empathy_account_settings_get_password_cb, self);
638     }
639
640   priv->ready = TRUE;
641   g_object_notify (G_OBJECT (self), "ready");
642 }
643
644 static void
645 empathy_account_settings_account_ready_cb (GObject *source_object,
646     GAsyncResult *result,
647     gpointer user_data)
648 {
649   EmpathyAccountSettings *settings = EMPATHY_ACCOUNT_SETTINGS (user_data);
650   TpAccount *account = TP_ACCOUNT (source_object);
651   GError *error = NULL;
652
653   if (!tp_proxy_prepare_finish (account, result, &error))
654     {
655       DEBUG ("Failed to prepare account: %s", error->message);
656       g_error_free (error);
657       return;
658     }
659
660   empathy_account_settings_check_readyness (settings);
661 }
662
663 static void
664 empathy_account_settings_managers_ready_cb (GObject *object,
665     GParamSpec *pspec,
666     gpointer user_data)
667 {
668   EmpathyAccountSettings *settings = EMPATHY_ACCOUNT_SETTINGS (user_data);
669
670   empathy_account_settings_check_readyness (settings);
671 }
672
673 EmpathyAccountSettings *
674 empathy_account_settings_new (const gchar *connection_manager,
675     const gchar *protocol,
676     const gchar *service,
677     const char *display_name)
678 {
679   return g_object_new (EMPATHY_TYPE_ACCOUNT_SETTINGS,
680       "connection-manager", connection_manager,
681       "protocol", protocol,
682       "service", service,
683       "display-name", display_name,
684       NULL);
685 }
686
687 EmpathyAccountSettings *
688 empathy_account_settings_new_for_account (TpAccount *account)
689 {
690   return g_object_new (EMPATHY_TYPE_ACCOUNT_SETTINGS,
691       "account", account,
692       NULL);
693 }
694
695 TpConnectionManagerParam *
696 empathy_account_settings_get_tp_params (EmpathyAccountSettings *settings)
697 {
698   EmpathyAccountSettingsPriv *priv = GET_PRIV (settings);
699   const TpConnectionManagerProtocol *tp_protocol;
700
701   g_return_val_if_fail (priv->manager != NULL, NULL);
702   g_return_val_if_fail (priv->protocol != NULL, NULL);
703
704   tp_protocol = tp_connection_manager_get_protocol (priv->manager,
705      priv->protocol);
706   if (tp_protocol == NULL)
707     {
708       DEBUG ("Can't retrieve TpConnectionManagerProtocol for protocol '%s'",
709           priv->protocol);
710       return NULL;
711     }
712
713   return tp_protocol->params;
714 }
715
716 gboolean
717 empathy_account_settings_is_ready (EmpathyAccountSettings *settings)
718 {
719   EmpathyAccountSettingsPriv *priv = GET_PRIV (settings);
720
721   return priv->ready;
722 }
723
724 const gchar *
725 empathy_account_settings_get_cm (EmpathyAccountSettings *settings)
726 {
727   EmpathyAccountSettingsPriv *priv = GET_PRIV (settings);
728
729   return priv->cm_name;
730 }
731
732 const gchar *
733 empathy_account_settings_get_protocol (EmpathyAccountSettings *settings)
734 {
735   EmpathyAccountSettingsPriv *priv = GET_PRIV (settings);
736
737   return priv->protocol;
738 }
739
740 const gchar *
741 empathy_account_settings_get_service (EmpathyAccountSettings *settings)
742 {
743   EmpathyAccountSettingsPriv *priv = GET_PRIV (settings);
744
745   return priv->service;
746 }
747
748 void
749 empathy_account_settings_set_service (EmpathyAccountSettings *settings,
750     const gchar *service)
751 {
752   EmpathyAccountSettingsPriv *priv = GET_PRIV (settings);
753
754   if (!tp_strdiff (priv->service, service))
755     return;
756
757   g_free (priv->service);
758   priv->service = g_strdup (service);
759   g_object_notify (G_OBJECT (settings), "service");
760   priv->update_service = TRUE;
761 }
762
763 gchar *
764 empathy_account_settings_get_icon_name (EmpathyAccountSettings *settings)
765 {
766   EmpathyAccountSettingsPriv *priv = GET_PRIV (settings);
767
768   return priv->icon_name;
769 }
770
771 const gchar *
772 empathy_account_settings_get_display_name (EmpathyAccountSettings *settings)
773 {
774   EmpathyAccountSettingsPriv *priv = GET_PRIV (settings);
775
776   return priv->display_name;
777 }
778
779 TpAccount *
780 empathy_account_settings_get_account (EmpathyAccountSettings *settings)
781 {
782   EmpathyAccountSettingsPriv *priv = GET_PRIV (settings);
783
784   return priv->account;
785 }
786
787 static gboolean
788 empathy_account_settings_is_unset (EmpathyAccountSettings *settings,
789     const gchar *param)
790 {
791   EmpathyAccountSettingsPriv *priv = GET_PRIV (settings);
792   GArray *a;
793   guint i;
794
795   a = priv->unset_parameters;
796
797   for (i = 0; i < a->len; i++)
798     {
799       if (!tp_strdiff (g_array_index (a, gchar *, i), param))
800         return TRUE;
801     }
802
803   return FALSE;
804 }
805
806 static TpConnectionManagerParam *
807 empathy_account_settings_get_tp_param (EmpathyAccountSettings *settings,
808     const gchar *param)
809 {
810   TpConnectionManagerParam *tp_params =
811       empathy_account_settings_get_tp_params (settings);
812   TpConnectionManagerParam *p;
813
814   for (p = tp_params; p != NULL && p->name != NULL; p++)
815     {
816       if (tp_strdiff (p->name, param))
817         continue;
818
819       return p;
820     }
821
822   return NULL;
823 }
824
825 gboolean
826 empathy_account_settings_have_tp_param (EmpathyAccountSettings *settings,
827     const gchar *param)
828 {
829   return (empathy_account_settings_get_tp_param (settings, param) != NULL);
830 }
831
832 static void
833 account_settings_remove_from_unset (EmpathyAccountSettings *settings,
834     const gchar *param)
835 {
836   EmpathyAccountSettingsPriv *priv = GET_PRIV (settings);
837   guint idx;
838   gchar *val;
839
840   for (idx = 0; idx < priv->unset_parameters->len; idx++)
841     {
842       val = g_array_index (priv->unset_parameters, gchar *, idx);
843
844       if (!tp_strdiff (val, param))
845         {
846           priv->unset_parameters =
847             g_array_remove_index (priv->unset_parameters, idx);
848           g_free (val);
849
850           break;
851         }
852     }
853 }
854
855 const GValue *
856 empathy_account_settings_get_default (EmpathyAccountSettings *settings,
857     const gchar *param)
858 {
859   TpConnectionManagerParam *p;
860
861   p = empathy_account_settings_get_tp_param (settings, param);
862
863   if (p == NULL || !(p->flags & TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT))
864     return NULL;
865
866   return &(p->default_value);
867 }
868
869 const gchar *
870 empathy_account_settings_get_dbus_signature (EmpathyAccountSettings *settings,
871     const gchar *param)
872 {
873   TpConnectionManagerParam *p;
874
875   p = empathy_account_settings_get_tp_param (settings, param);
876
877   if (p == NULL)
878     return NULL;
879
880   return p->dbus_signature;
881 }
882
883 const GValue *
884 empathy_account_settings_get (EmpathyAccountSettings *settings,
885     const gchar *param)
886 {
887   EmpathyAccountSettingsPriv *priv = GET_PRIV (settings);
888   const GValue *result = NULL;
889
890   /* Lookup the update parameters we set */
891   result = tp_asv_lookup (priv->parameters, param);
892   if (result != NULL)
893     return result;
894
895   /* If the parameters isn't unset use the accounts setting if any */
896   if (priv->account != NULL
897       && !empathy_account_settings_is_unset (settings, param))
898     {
899       const GHashTable *parameters;
900
901       parameters = tp_account_get_parameters (priv->account);
902       result = tp_asv_lookup (parameters, param);
903
904       if (result != NULL)
905         return result;
906     }
907
908   /* fallback to the default */
909   return empathy_account_settings_get_default (settings, param);
910 }
911
912 void
913 empathy_account_settings_unset (EmpathyAccountSettings *settings,
914     const gchar *param)
915 {
916   EmpathyAccountSettingsPriv *priv = GET_PRIV (settings);
917   gchar *v;
918   if (empathy_account_settings_is_unset (settings, param))
919     return;
920
921   if (priv->supports_sasl && !tp_strdiff (param, "password"))
922     {
923       g_free (priv->password);
924       priv->password = NULL;
925       priv->password_changed = TRUE;
926       return;
927     }
928
929   v = g_strdup (param);
930
931   g_array_append_val (priv->unset_parameters, v);
932   g_hash_table_remove (priv->parameters, param);
933 }
934
935 void
936 empathy_account_settings_discard_changes (EmpathyAccountSettings *settings)
937 {
938   EmpathyAccountSettingsPriv *priv = GET_PRIV (settings);
939
940   g_hash_table_remove_all (priv->parameters);
941   empathy_account_settings_free_unset_parameters (settings);
942
943   priv->password_changed = FALSE;
944   g_free (priv->password);
945   priv->password = g_strdup (priv->password_original);
946
947   if (priv->account != NULL)
948     priv->uri_scheme_tel = empathy_account_has_uri_scheme_tel (priv->account);
949   else
950     priv->uri_scheme_tel = FALSE;
951 }
952
953 const gchar *
954 empathy_account_settings_get_string (EmpathyAccountSettings *settings,
955     const gchar *param)
956 {
957   EmpathyAccountSettingsPriv *priv = GET_PRIV (settings);
958   const GValue *v;
959
960   if (!tp_strdiff (param, "password") && priv->supports_sasl)
961     {
962       return priv->password;
963     }
964
965   v = empathy_account_settings_get (settings, param);
966
967   if (v == NULL || !G_VALUE_HOLDS_STRING (v))
968     return NULL;
969
970   return g_value_get_string (v);
971 }
972
973 const gchar * const *
974 empathy_account_settings_get_strv (EmpathyAccountSettings *settings,
975     const gchar *param)
976 {
977   const GValue *v;
978
979   v = empathy_account_settings_get (settings, param);
980
981   if (v == NULL || !G_VALUE_HOLDS (v, G_TYPE_STRV))
982     return NULL;
983
984   return g_value_get_boxed (v);
985 }
986
987 gint32
988 empathy_account_settings_get_int32 (EmpathyAccountSettings *settings,
989     const gchar *param)
990 {
991   const GValue *v;
992   gint32 ret = 0;
993
994   v = empathy_account_settings_get (settings, param);
995
996   if (v == NULL)
997     return 0;
998
999   switch G_VALUE_TYPE (v)
1000     {
1001       case G_TYPE_UCHAR:
1002         ret = g_value_get_uchar (v);
1003         break;
1004       case G_TYPE_INT:
1005         ret = g_value_get_int (v);
1006         break;
1007       case G_TYPE_UINT:
1008         ret = CLAMP (g_value_get_uint (v), (guint) G_MININT32,
1009             G_MAXINT32);
1010         break;
1011       case G_TYPE_INT64:
1012         ret = CLAMP (g_value_get_int64 (v), G_MININT32, G_MAXINT32);
1013         break;
1014       case G_TYPE_UINT64:
1015         ret = CLAMP (g_value_get_uint64 (v), (guint64) G_MININT32,
1016             G_MAXINT32);
1017         break;
1018       default:
1019         ret = 0;
1020         break;
1021     }
1022
1023   return ret;
1024 }
1025
1026 gint64
1027 empathy_account_settings_get_int64 (EmpathyAccountSettings *settings,
1028     const gchar *param)
1029 {
1030   const GValue *v;
1031   gint64 ret = 0;
1032
1033   v = empathy_account_settings_get (settings, param);
1034   if (v == NULL)
1035     return 0;
1036
1037   switch G_VALUE_TYPE (v)
1038     {
1039       case G_TYPE_UCHAR:
1040         ret = g_value_get_uchar (v);
1041         break;
1042       case G_TYPE_INT:
1043         ret = g_value_get_int (v);
1044         break;
1045       case G_TYPE_UINT:
1046         ret = g_value_get_uint (v);
1047         break;
1048       case G_TYPE_INT64:
1049         ret = g_value_get_int64 (v);
1050         break;
1051       case G_TYPE_UINT64:
1052         ret = CLAMP (g_value_get_uint64 (v), (guint64) G_MININT64, G_MAXINT64);
1053         break;
1054       default:
1055         ret = 0;
1056         break;
1057     }
1058
1059   return ret;
1060 }
1061
1062 guint32
1063 empathy_account_settings_get_uint32 (EmpathyAccountSettings *settings,
1064     const gchar *param)
1065 {
1066   const GValue *v;
1067   guint32 ret;
1068
1069   v = empathy_account_settings_get (settings, param);
1070   if (v == NULL)
1071     return 0;
1072
1073   switch G_VALUE_TYPE (v)
1074     {
1075       case G_TYPE_UCHAR:
1076         ret = g_value_get_uchar (v);
1077         break;
1078       case G_TYPE_INT:
1079         ret = MAX (0, g_value_get_int (v));
1080         break;
1081       case G_TYPE_UINT:
1082         ret = g_value_get_uint (v);
1083         break;
1084       case G_TYPE_INT64:
1085         ret = CLAMP (g_value_get_int64 (v), 0, G_MAXUINT32);
1086         break;
1087       case G_TYPE_UINT64:
1088         ret = MIN (g_value_get_uint64 (v), G_MAXUINT32);
1089         break;
1090       default:
1091         ret = 0;
1092         break;
1093     }
1094
1095   return ret;
1096 }
1097
1098 guint64
1099 empathy_account_settings_get_uint64 (EmpathyAccountSettings *settings,
1100     const gchar *param)
1101 {
1102   const GValue *v;
1103   guint64 ret = 0;
1104
1105   v = empathy_account_settings_get (settings, param);
1106
1107   if (v == NULL || !G_VALUE_HOLDS_INT (v))
1108     return 0;
1109
1110   switch G_VALUE_TYPE (v)
1111     {
1112       case G_TYPE_UCHAR:
1113         ret = g_value_get_uchar (v);
1114         break;
1115       case G_TYPE_INT:
1116         ret = MAX (0, g_value_get_int (v));
1117         break;
1118       case G_TYPE_UINT:
1119         ret = g_value_get_uint (v);
1120         break;
1121       case G_TYPE_INT64:
1122         ret = MAX (0, g_value_get_int64 (v));
1123         break;
1124       case G_TYPE_UINT64:
1125         ret = g_value_get_uint64 (v);
1126         break;
1127       default:
1128         ret = 0;
1129         break;
1130     }
1131
1132   return ret;
1133 }
1134
1135 gboolean
1136 empathy_account_settings_get_boolean (EmpathyAccountSettings *settings,
1137     const gchar *param)
1138 {
1139   const GValue *v;
1140
1141   v = empathy_account_settings_get (settings, param);
1142
1143   if (v == NULL || !G_VALUE_HOLDS_BOOLEAN (v))
1144     return FALSE;
1145
1146   return g_value_get_boolean (v);
1147 }
1148
1149 void
1150 empathy_account_settings_set_string (EmpathyAccountSettings *settings,
1151     const gchar *param,
1152     const gchar *value)
1153 {
1154   EmpathyAccountSettingsPriv *priv = GET_PRIV (settings);
1155
1156   g_return_if_fail (param != NULL);
1157   g_return_if_fail (value != NULL);
1158
1159   if (!tp_strdiff (param, "password") && priv->supports_sasl)
1160     {
1161       g_free (priv->password);
1162       priv->password = g_strdup (value);
1163       priv->password_changed = TRUE;
1164     }
1165   else
1166     {
1167       tp_asv_set_string (priv->parameters, g_strdup (param), value);
1168     }
1169
1170   account_settings_remove_from_unset (settings, param);
1171 }
1172
1173 void
1174 empathy_account_settings_set_strv (EmpathyAccountSettings *settings,
1175     const gchar *param,
1176     gchar **value)
1177 {
1178   EmpathyAccountSettingsPriv *priv = GET_PRIV (settings);
1179
1180   g_return_if_fail (param != NULL);
1181   g_return_if_fail (value != NULL);
1182
1183   tp_asv_set_strv (priv->parameters, g_strdup (param), value);
1184
1185   account_settings_remove_from_unset (settings, param);
1186 }
1187
1188 void
1189 empathy_account_settings_set_int32 (EmpathyAccountSettings *settings,
1190     const gchar *param,
1191     gint32 value)
1192 {
1193   EmpathyAccountSettingsPriv *priv = GET_PRIV (settings);
1194
1195   g_return_if_fail (param != NULL);
1196
1197   tp_asv_set_int32 (priv->parameters, g_strdup (param), value);
1198
1199   account_settings_remove_from_unset (settings, param);
1200 }
1201
1202 void
1203 empathy_account_settings_set_int64 (EmpathyAccountSettings *settings,
1204     const gchar *param,
1205     gint64 value)
1206 {
1207   EmpathyAccountSettingsPriv *priv = GET_PRIV (settings);
1208
1209   g_return_if_fail (param != NULL);
1210
1211   tp_asv_set_int64 (priv->parameters, g_strdup (param), value);
1212
1213   account_settings_remove_from_unset (settings, param);
1214 }
1215
1216 void
1217 empathy_account_settings_set_uint32 (EmpathyAccountSettings *settings,
1218     const gchar *param,
1219     guint32 value)
1220 {
1221   EmpathyAccountSettingsPriv *priv = GET_PRIV (settings);
1222
1223   g_return_if_fail (param != NULL);
1224
1225   tp_asv_set_uint32 (priv->parameters, g_strdup (param), value);
1226
1227   account_settings_remove_from_unset (settings, param);
1228 }
1229
1230 void
1231 empathy_account_settings_set_uint64 (EmpathyAccountSettings *settings,
1232     const gchar *param,
1233     guint64 value)
1234 {
1235   EmpathyAccountSettingsPriv *priv = GET_PRIV (settings);
1236
1237   g_return_if_fail (param != NULL);
1238
1239   tp_asv_set_uint64 (priv->parameters, g_strdup (param), value);
1240
1241   account_settings_remove_from_unset (settings, param);
1242 }
1243
1244 void
1245 empathy_account_settings_set_boolean (EmpathyAccountSettings *settings,
1246     const gchar *param,
1247     gboolean value)
1248 {
1249   EmpathyAccountSettingsPriv *priv = GET_PRIV (settings);
1250
1251   g_return_if_fail (param != NULL);
1252
1253   tp_asv_set_boolean (priv->parameters, g_strdup (param), value);
1254
1255   account_settings_remove_from_unset (settings, param);
1256 }
1257
1258 static void
1259 account_settings_display_name_set_cb (GObject *src,
1260     GAsyncResult *res,
1261     gpointer user_data)
1262 {
1263   GError *error = NULL;
1264   TpAccount *account = TP_ACCOUNT (src);
1265   GSimpleAsyncResult *set_result = user_data;
1266
1267   tp_account_set_display_name_finish (account, res, &error);
1268
1269   if (error != NULL)
1270     {
1271       g_simple_async_result_set_from_error (set_result, error);
1272       g_error_free (error);
1273     }
1274
1275   g_simple_async_result_complete (set_result);
1276   g_object_unref (set_result);
1277 }
1278
1279 void
1280 empathy_account_settings_set_display_name_async (
1281   EmpathyAccountSettings *settings,
1282   const gchar *name,
1283   GAsyncReadyCallback callback,
1284   gpointer user_data)
1285 {
1286   EmpathyAccountSettingsPriv *priv = GET_PRIV (settings);
1287   GSimpleAsyncResult *result;
1288
1289   g_return_if_fail (name != NULL);
1290
1291   result = g_simple_async_result_new (G_OBJECT (settings),
1292       callback, user_data, empathy_account_settings_set_display_name_finish);
1293
1294   if (!tp_strdiff (name, priv->display_name))
1295     {
1296       /* Nothing to do */
1297       g_simple_async_result_complete_in_idle (result);
1298       return;
1299     }
1300
1301   if (priv->account == NULL)
1302     {
1303       if (priv->display_name != NULL)
1304         g_free (priv->display_name);
1305
1306       priv->display_name = g_strdup (name);
1307
1308       g_simple_async_result_complete_in_idle (result);
1309
1310       return;
1311     }
1312
1313   tp_account_set_display_name_async (priv->account, name,
1314       account_settings_display_name_set_cb, result);
1315 }
1316
1317 gboolean
1318 empathy_account_settings_set_display_name_finish (
1319   EmpathyAccountSettings *settings,
1320   GAsyncResult *result,
1321   GError **error)
1322 {
1323   if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result),
1324       error))
1325     return FALSE;
1326
1327   g_return_val_if_fail (g_simple_async_result_is_valid (result,
1328     G_OBJECT (settings), empathy_account_settings_set_display_name_finish),
1329       FALSE);
1330
1331   return TRUE;
1332 }
1333
1334 static void
1335 account_settings_icon_name_set_cb (GObject *src,
1336     GAsyncResult *res,
1337     gpointer user_data)
1338 {
1339   GError *error = NULL;
1340   TpAccount *account = TP_ACCOUNT (src);
1341   GSimpleAsyncResult *set_result = user_data;
1342
1343   tp_account_set_icon_name_finish (account, res, &error);
1344
1345   if (error != NULL)
1346     {
1347       g_simple_async_result_set_from_error (set_result, error);
1348       g_error_free (error);
1349     }
1350
1351   g_simple_async_result_complete (set_result);
1352   g_object_unref (set_result);
1353 }
1354
1355 void
1356 empathy_account_settings_set_icon_name_async (
1357   EmpathyAccountSettings *settings,
1358   const gchar *name,
1359   GAsyncReadyCallback callback,
1360   gpointer user_data)
1361 {
1362   EmpathyAccountSettingsPriv *priv = GET_PRIV (settings);
1363   GSimpleAsyncResult *result;
1364
1365   g_return_if_fail (name != NULL);
1366
1367   result = g_simple_async_result_new (G_OBJECT (settings),
1368       callback, user_data, empathy_account_settings_set_icon_name_finish);
1369
1370   if (priv->account == NULL)
1371     {
1372       if (priv->icon_name != NULL)
1373         g_free (priv->icon_name);
1374
1375       priv->icon_name = g_strdup (name);
1376
1377       g_simple_async_result_complete_in_idle (result);
1378
1379       return;
1380     }
1381
1382   tp_account_set_icon_name_async (priv->account, name,
1383       account_settings_icon_name_set_cb, result);
1384 }
1385
1386 gboolean
1387 empathy_account_settings_set_icon_name_finish (
1388   EmpathyAccountSettings *settings,
1389   GAsyncResult *result,
1390   GError **error)
1391 {
1392   if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result),
1393       error))
1394     return FALSE;
1395
1396   g_return_val_if_fail (g_simple_async_result_is_valid (result,
1397     G_OBJECT (settings), empathy_account_settings_set_icon_name_finish),
1398       FALSE);
1399
1400   return TRUE;
1401 }
1402
1403 static void
1404 empathy_account_settings_processed_password (GObject *source,
1405     GAsyncResult *result,
1406     gpointer user_data,
1407     gpointer finish_func)
1408 {
1409   EmpathyAccountSettings *settings = EMPATHY_ACCOUNT_SETTINGS (user_data);
1410   EmpathyAccountSettingsPriv *priv = GET_PRIV (settings);
1411   GSimpleAsyncResult *r;
1412   GError *error = NULL;
1413   gboolean (*func) (TpAccount *source, GAsyncResult *result, GError **error) =
1414     finish_func;
1415
1416   g_free (priv->password_original);
1417   priv->password_original = g_strdup (priv->password);
1418
1419   if (!func (TP_ACCOUNT (source), result, &error))
1420     {
1421       g_simple_async_result_set_from_error (priv->apply_result, error);
1422       g_error_free (error);
1423     }
1424
1425   empathy_account_settings_discard_changes (settings);
1426
1427   r = priv->apply_result;
1428   priv->apply_result = NULL;
1429
1430   g_simple_async_result_complete (r);
1431   g_object_unref (r);
1432 }
1433
1434 static void
1435 empathy_account_settings_set_password_cb (GObject *source,
1436     GAsyncResult *result,
1437     gpointer user_data)
1438 {
1439   empathy_account_settings_processed_password (source, result, user_data,
1440       empathy_keyring_set_account_password_finish);
1441 }
1442
1443 static void
1444 empathy_account_settings_delete_password_cb (GObject *source,
1445     GAsyncResult *result,
1446     gpointer user_data)
1447 {
1448   empathy_account_settings_processed_password (source, result, user_data,
1449       empathy_keyring_delete_account_password_finish);
1450 }
1451
1452 static void
1453 update_account_uri_schemes (EmpathyAccountSettings *self)
1454 {
1455   EmpathyAccountSettingsPriv *priv = GET_PRIV (self);
1456
1457   if (priv->uri_scheme_tel == empathy_account_has_uri_scheme_tel (
1458         priv->account))
1459     return;
1460
1461   tp_account_set_uri_scheme_association_async (priv->account, "tel",
1462       priv->uri_scheme_tel, NULL, NULL);
1463 }
1464
1465 static void
1466 set_service_cb (GObject *source,
1467     GAsyncResult *result,
1468     gpointer user_data)
1469 {
1470   GError *error = NULL;
1471
1472   if (!tp_account_set_service_finish (TP_ACCOUNT (source), result, &error))
1473     {
1474       DEBUG ("Failed to set Account.Service: %s", error->message);
1475       g_error_free (error);
1476     }
1477 }
1478
1479 static void
1480 update_account_service (EmpathyAccountSettings *self)
1481 {
1482   EmpathyAccountSettingsPriv *priv = GET_PRIV (self);
1483
1484   if (!priv->update_service)
1485     return;
1486
1487   tp_account_set_service_async (priv->account,
1488       priv->service != NULL ? priv->service : "", set_service_cb, self);
1489 }
1490
1491 static void
1492 empathy_account_settings_account_updated (GObject *source,
1493     GAsyncResult *result,
1494     gpointer user_data)
1495 {
1496   EmpathyAccountSettings *settings = EMPATHY_ACCOUNT_SETTINGS (user_data);
1497   EmpathyAccountSettingsPriv *priv = GET_PRIV (settings);
1498   GSimpleAsyncResult *r;
1499   GError *error = NULL;
1500   GStrv reconnect_required = NULL;
1501
1502   if (!tp_account_update_parameters_finish (TP_ACCOUNT (source),
1503           result, &reconnect_required, &error))
1504     {
1505       g_simple_async_result_set_from_error (priv->apply_result, error);
1506       g_error_free (error);
1507       goto out;
1508     }
1509
1510   /* Only set the password in the keyring if the CM supports SASL and
1511    * it's changed. */
1512   if (priv->supports_sasl && priv->password_changed)
1513     {
1514       if (priv->password != NULL)
1515         {
1516           /* FIXME: we shouldn't save the password if we
1517            * can't (MaySaveResponse=False) but we don't have API to check that
1518            * at this point (fdo #35382). */
1519           empathy_keyring_set_account_password_async (priv->account, priv->password,
1520               empathy_account_settings_set_password_cb, settings);
1521         }
1522       else
1523         {
1524           empathy_keyring_delete_account_password_async (priv->account,
1525               empathy_account_settings_delete_password_cb, settings);
1526         }
1527
1528       return;
1529     }
1530
1531   update_account_uri_schemes (settings);
1532   update_account_service (settings);
1533
1534   g_simple_async_result_set_op_res_gboolean (priv->apply_result,
1535       g_strv_length (reconnect_required) > 0);
1536
1537 out:
1538   empathy_account_settings_discard_changes (settings);
1539
1540   r = priv->apply_result;
1541   priv->apply_result = NULL;
1542
1543   g_simple_async_result_complete (r);
1544   g_object_unref (r);
1545   g_strfreev (reconnect_required);
1546 }
1547
1548 static void
1549 empathy_account_settings_created_cb (GObject *source,
1550     GAsyncResult *result,
1551     gpointer user_data)
1552 {
1553   EmpathyAccountSettings *settings = EMPATHY_ACCOUNT_SETTINGS (user_data);
1554   EmpathyAccountSettingsPriv *priv = GET_PRIV (settings);
1555   TpAccount *account;
1556   GError *error = NULL;
1557   GSimpleAsyncResult *r;
1558
1559   account = tp_account_manager_create_account_finish (
1560     TP_ACCOUNT_MANAGER (source), result, &error);
1561
1562   if (account == NULL)
1563     {
1564       g_simple_async_result_set_from_error (priv->apply_result, error);
1565     }
1566   else
1567     {
1568       priv->account = g_object_ref (account);
1569
1570       if (priv->supports_sasl && priv->password != NULL)
1571         {
1572           /* Save the password before connecting */
1573           /* FIXME: we shouldn't save the password if we
1574            * can't (MaySaveResponse=False) but we don't have API to check that
1575            * at this point (fdo #35382). */
1576           empathy_keyring_set_account_password_async (priv->account,
1577               priv->password, empathy_account_settings_set_password_cb,
1578               settings);
1579           return;
1580         }
1581
1582       update_account_uri_schemes (settings);
1583
1584       empathy_account_settings_discard_changes (settings);
1585     }
1586
1587   r = priv->apply_result;
1588   priv->apply_result = NULL;
1589
1590   g_simple_async_result_complete (r);
1591   g_object_unref (r);
1592 }
1593
1594
1595 static void
1596 empathy_account_settings_do_create_account (EmpathyAccountSettings *settings)
1597 {
1598   EmpathyAccountSettingsPriv *priv = GET_PRIV (settings);
1599   GHashTable *properties;
1600   TpConnectionPresenceType type;
1601   gchar *status;
1602   gchar *message;
1603   EmpathyPresenceManager *presence_mgr;
1604
1605   properties = tp_asv_new (NULL, NULL);
1606
1607   presence_mgr = empathy_presence_manager_dup_singleton ();
1608   type = empathy_presence_manager_get_requested_presence (presence_mgr, &status,
1609       &message);
1610   g_object_unref (presence_mgr);
1611
1612   if (type != TP_CONNECTION_PRESENCE_TYPE_UNSET)
1613     {
1614       /* Create the account with the requested presence the same as the current
1615         * global requested presence, but don't enable it */
1616       GValueArray *presence;
1617       GValue vtype = { 0, };
1618       GValue vstatus = { 0, };
1619       GValue vmessage = { 0, };
1620
1621       presence = g_value_array_new (3);
1622
1623       g_value_init (&vtype, G_TYPE_UINT);
1624       g_value_set_uint (&vtype, type);
1625       g_value_array_append (presence, &vtype);
1626
1627       g_value_init (&vstatus, G_TYPE_STRING);
1628       g_value_take_string (&vstatus, status);
1629       g_value_array_append (presence, &vstatus);
1630
1631       g_value_init (&vmessage, G_TYPE_STRING);
1632       g_value_take_string (&vmessage, message);
1633       g_value_array_append (presence, &vmessage);
1634
1635       tp_asv_take_boxed (properties, TP_IFACE_ACCOUNT ".RequestedPresence",
1636         TP_STRUCT_TYPE_SIMPLE_PRESENCE, presence);
1637     }
1638
1639   tp_asv_set_string (properties, TP_IFACE_ACCOUNT ".Icon",
1640       priv->icon_name);
1641
1642   if (priv->service != NULL)
1643     tp_asv_set_string (properties, TP_PROP_ACCOUNT_SERVICE, priv->service);
1644
1645   tp_account_manager_create_account_async (priv->account_manager,
1646     priv->cm_name, priv->protocol, priv->display_name,
1647     priv->parameters, properties,
1648     empathy_account_settings_created_cb,
1649     settings);
1650
1651   g_hash_table_unref (properties);
1652 }
1653
1654 static void
1655 empathy_account_settings_manager_ready_cb (GObject *source_object,
1656     GAsyncResult *result,
1657     gpointer user_data)
1658 {
1659   EmpathyAccountSettings *settings = EMPATHY_ACCOUNT_SETTINGS (user_data);
1660   EmpathyAccountSettingsPriv *priv = GET_PRIV (settings);
1661   TpAccountManager *account_manager = TP_ACCOUNT_MANAGER (source_object);
1662   GError *error = NULL;
1663
1664   if (!tp_proxy_prepare_finish (account_manager, result, &error))
1665     {
1666       DEBUG ("Failed to prepare account manager: %s", error->message);
1667       g_error_free (error);
1668       return;
1669     }
1670
1671   g_assert (priv->apply_result != NULL && priv->account == NULL);
1672   empathy_account_settings_do_create_account (settings);
1673 }
1674
1675 void
1676 empathy_account_settings_apply_async (EmpathyAccountSettings *settings,
1677     GAsyncReadyCallback callback,
1678     gpointer user_data)
1679 {
1680   EmpathyAccountSettingsPriv *priv = GET_PRIV (settings);
1681
1682   if (priv->apply_result != NULL)
1683     {
1684       g_simple_async_report_error_in_idle (G_OBJECT (settings),
1685           callback, user_data,
1686           G_IO_ERROR, G_IO_ERROR_PENDING, "Applying already in progress");
1687       return;
1688     }
1689
1690   priv->apply_result = g_simple_async_result_new (G_OBJECT (settings),
1691       callback, user_data, empathy_account_settings_apply_finish);
1692
1693   /* We'll have to reconnect only if we change none DBus_Property on an
1694    * existing account. */
1695   g_simple_async_result_set_op_res_gboolean (priv->apply_result, FALSE);
1696
1697   if (priv->account == NULL)
1698     {
1699       tp_proxy_prepare_async (priv->account_manager, NULL,
1700           empathy_account_settings_manager_ready_cb, settings);
1701     }
1702   else
1703     {
1704       tp_account_update_parameters_async (priv->account,
1705           priv->parameters, (const gchar **)priv->unset_parameters->data,
1706           empathy_account_settings_account_updated, settings);
1707     }
1708 }
1709
1710 gboolean
1711 empathy_account_settings_apply_finish (EmpathyAccountSettings *settings,
1712     GAsyncResult *result,
1713     gboolean *reconnect_required,
1714     GError **error)
1715 {
1716   if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result),
1717       error))
1718     return FALSE;
1719
1720   g_return_val_if_fail (g_simple_async_result_is_valid (result,
1721     G_OBJECT (settings), empathy_account_settings_apply_finish), FALSE);
1722
1723   if (reconnect_required != NULL)
1724     *reconnect_required = g_simple_async_result_get_op_res_gboolean (
1725         G_SIMPLE_ASYNC_RESULT (result));
1726
1727   return TRUE;
1728 }
1729
1730 gboolean
1731 empathy_account_settings_has_account (EmpathyAccountSettings *settings,
1732     TpAccount *account)
1733 {
1734   EmpathyAccountSettingsPriv *priv;
1735   const gchar *account_path;
1736   const gchar *priv_account_path;
1737
1738   g_return_val_if_fail (EMPATHY_IS_ACCOUNT_SETTINGS (settings), FALSE);
1739   g_return_val_if_fail (TP_IS_ACCOUNT (account), FALSE);
1740
1741   priv = GET_PRIV (settings);
1742
1743   if (priv->account == NULL)
1744     return FALSE;
1745
1746   account_path = tp_proxy_get_object_path (TP_PROXY (account));
1747   priv_account_path = tp_proxy_get_object_path (TP_PROXY (priv->account));
1748
1749   return (!tp_strdiff (account_path, priv_account_path));
1750 }
1751
1752 void
1753 empathy_account_settings_set_regex (EmpathyAccountSettings *settings,
1754     const gchar *param,
1755     const gchar *pattern)
1756 {
1757   EmpathyAccountSettingsPriv *priv = GET_PRIV (settings);
1758   GRegex *regex;
1759   GError *error = NULL;
1760
1761   regex = g_regex_new (pattern, 0, 0, &error);
1762   if (regex == NULL)
1763     {
1764       g_warning ("Failed to create reg exp: %s", error->message);
1765       g_error_free (error);
1766       return;
1767     }
1768
1769   g_hash_table_insert (priv->param_regexps, g_strdup (param), regex);
1770 }
1771
1772 gboolean
1773 empathy_account_settings_parameter_is_valid (
1774     EmpathyAccountSettings *settings,
1775     const gchar *param)
1776 {
1777   EmpathyAccountSettingsPriv *priv;
1778   const GRegex *regex;
1779   const gchar *value;
1780
1781   g_return_val_if_fail (EMPATHY_IS_ACCOUNT_SETTINGS (settings), FALSE);
1782
1783   priv = GET_PRIV (settings);
1784
1785   if (g_list_find_custom (priv->required_params, param, (GCompareFunc) strcmp))
1786     {
1787       /* first, look if it's set in our own parameters */
1788       if (tp_asv_lookup (priv->parameters, param))
1789         goto test_regex;
1790
1791       /* if we did not unset the parameter, look if it's in the account */
1792       if (priv->account != NULL &&
1793           !empathy_account_settings_is_unset (settings, param))
1794         {
1795           const GHashTable *account_params;
1796
1797           account_params = tp_account_get_parameters (priv->account);
1798           if (tp_asv_lookup (account_params, param))
1799             goto test_regex;
1800         }
1801
1802       return FALSE;
1803     }
1804
1805 test_regex:
1806   /* test whether parameter value matches its regex */
1807   regex = g_hash_table_lookup (priv->param_regexps, param);
1808   if (regex)
1809     {
1810       value = empathy_account_settings_get_string (settings, param);
1811       if (value != NULL && !g_regex_match (regex, value, 0, NULL))
1812         return FALSE;
1813     }
1814
1815   return TRUE;
1816 }
1817
1818 gboolean
1819 empathy_account_settings_is_valid (EmpathyAccountSettings *settings)
1820 {
1821   EmpathyAccountSettingsPriv *priv;
1822   const gchar *param;
1823   GHashTableIter iter;
1824   GList *l;
1825
1826   g_return_val_if_fail (EMPATHY_IS_ACCOUNT_SETTINGS (settings), FALSE);
1827
1828   priv = GET_PRIV (settings);
1829
1830   for (l = priv->required_params; l; l = l->next)
1831     {
1832       if (!empathy_account_settings_parameter_is_valid (settings, l->data))
1833         return FALSE;
1834     }
1835
1836   g_hash_table_iter_init (&iter, priv->param_regexps);
1837   while (g_hash_table_iter_next (&iter, (gpointer *) &param, NULL))
1838     {
1839       if (!empathy_account_settings_parameter_is_valid (settings, param))
1840         return FALSE;
1841     }
1842
1843   return TRUE;
1844 }
1845
1846 const TpConnectionManagerProtocol *
1847 empathy_account_settings_get_tp_protocol (EmpathyAccountSettings *self)
1848 {
1849   EmpathyAccountSettingsPriv *priv = GET_PRIV (self);
1850
1851   return tp_connection_manager_get_protocol (priv->manager, priv->protocol);
1852 }
1853
1854 gboolean
1855 empathy_account_settings_supports_sasl (EmpathyAccountSettings *self)
1856 {
1857   EmpathyAccountSettingsPriv *priv = GET_PRIV (self);
1858
1859   return priv->supports_sasl;
1860 }
1861
1862 gboolean
1863 empathy_account_settings_param_is_supported (EmpathyAccountSettings *self,
1864     const gchar *param)
1865 {
1866   EmpathyAccountSettingsPriv *priv = GET_PRIV (self);
1867
1868   return tp_protocol_has_param (priv->protocol_obj, param);
1869 }
1870
1871 void
1872 empathy_account_settings_set_uri_scheme_tel (EmpathyAccountSettings *self,
1873     gboolean associate)
1874 {
1875   EmpathyAccountSettingsPriv *priv = GET_PRIV (self);
1876
1877   priv->uri_scheme_tel = associate;
1878 }
1879
1880 gboolean
1881 empathy_account_settings_has_uri_scheme_tel (
1882     EmpathyAccountSettings *self)
1883 {
1884   EmpathyAccountSettingsPriv *priv = GET_PRIV (self);
1885
1886   return priv->uri_scheme_tel;
1887 }