goaprovider.c 46.8 KB
Newer Older
1 2
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
3
 * Copyright (C) 2011, 2012, 2013, 2014 Red Hat, Inc.
4 5 6 7 8 9 10 11 12 13 14 15
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General
Daniel Mustieles's avatar
Daniel Mustieles committed
16
 * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
17 18 19
 */

#include "config.h"
20

21 22
#include <glib/gi18n-lib.h>

23
#include "goaprovider.h"
24
#include "goaprovider-priv.h"
25
#include "goaproviderfactory.h"
26
#include "goaexchangeprovider.h"
27 28
#include "goagoogleprovider.h"
#include "goafacebookprovider.h"
Debarshi Ray's avatar
Debarshi Ray committed
29
#include "goaimapsmtpprovider.h"
Debarshi Ray's avatar
Debarshi Ray committed
30
#include "goaowncloudprovider.h"
31
#include "goayahooprovider.h"
Willem van Engen's avatar
Willem van Engen committed
32
#include "goaflickrprovider.h"
33
#include "goawindowsliveprovider.h"
34
#include "goatelepathyfactory.h"
Bastien Nocera's avatar
Bastien Nocera committed
35
#include "goapocketprovider.h"
36

37 38 39 40
#ifdef GOA_KERBEROS_ENABLED
#include "goakerberosprovider.h"
#endif

41
#include "goautils.h"
42

43
/**
44 45
 * SECTION:goaprovider
 * @title: GoaProvider
46 47
 * @short_description: Abstract base class for providers
 *
48
 * #GoaProvider is the base type for all providers.
49 50
 */

51 52 53 54 55 56 57 58 59 60 61 62 63
struct _GoaProviderPrivate
{
  GVariant *preseed_data;
};

enum {
  PROP_0,
  PROP_PRESEED_DATA,
  NUM_PROPERTIES
};

static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };

64 65 66 67 68
static gboolean goa_provider_ensure_credentials_sync_real (GoaProvider   *provider,
                                                           GoaObject     *object,
                                                           gint          *out_expires_in,
                                                           GCancellable  *cancellable,
                                                           GError       **error);
69

David Zeuthen's avatar
David Zeuthen committed
70 71 72 73
static gboolean goa_provider_build_object_real (GoaProvider         *provider,
                                                GoaObjectSkeleton   *object,
                                                GKeyFile            *key_file,
                                                const gchar         *group,
74
                                                GDBusConnection     *connection,
75
                                                gboolean             just_added,
David Zeuthen's avatar
David Zeuthen committed
76 77
                                                GError             **error);

78 79
static guint goa_provider_get_credentials_generation_real (GoaProvider *provider);

David Zeuthen's avatar
David Zeuthen committed
80 81 82
static GIcon *goa_provider_get_provider_icon_default (GoaProvider *provider,
                                                      GoaObject   *object);

83 84
#define GOA_PROVIDER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOA_TYPE_PROVIDER, GoaProviderPrivate))

85
G_DEFINE_ABSTRACT_TYPE (GoaProvider, goa_provider, G_TYPE_OBJECT);
86 87

static void
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
goa_provider_get_property (GObject *object,
                           guint property_id,
                           GValue *value,
                           GParamSpec *pspec)
{
    GoaProvider *self = GOA_PROVIDER (object);

    switch (property_id) {
    case PROP_PRESEED_DATA:
        g_value_set_variant (value, self->priv->preseed_data);
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
        break;
    }
}

static void
goa_provider_set_property (GObject *object,
                           guint property_id,
                           const GValue *value,
                           GParamSpec *pspec)
{
    GoaProvider *self = GOA_PROVIDER (object);

    switch (property_id) {
    case PROP_PRESEED_DATA:
        goa_provider_set_preseed_data (self, g_value_get_variant (value));
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
        break;
    }
}

static void
goa_provider_dispose (GObject *object)
125
{
126 127 128 129 130 131 132 133 134 135 136
  GoaProvider *provider = GOA_PROVIDER (object);

  g_clear_pointer (&provider->priv->preseed_data, g_variant_unref);

  G_OBJECT_CLASS (goa_provider_parent_class)->dispose (object);
}

static void
goa_provider_init (GoaProvider *provider)
{
  provider->priv = GOA_PROVIDER_GET_PRIVATE (provider);
137 138 139
}

static void
140
goa_provider_class_init (GoaProviderClass *klass)
141
{
142 143 144 145 146 147 148 149
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  g_type_class_add_private (klass, sizeof (GoaProviderPrivate));

  object_class->set_property = goa_provider_set_property;
  object_class->get_property = goa_provider_get_property;
  object_class->dispose = goa_provider_dispose;

David Zeuthen's avatar
David Zeuthen committed
150
  klass->build_object = goa_provider_build_object_real;
151
  klass->ensure_credentials_sync = goa_provider_ensure_credentials_sync_real;
152
  klass->get_credentials_generation = goa_provider_get_credentials_generation_real;
David Zeuthen's avatar
David Zeuthen committed
153
  klass->get_provider_icon = goa_provider_get_provider_icon_default;
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195

/**
 * GoaProvider:preseed-data
 *
 * An #GVariant of type a{sv} storing any information already collected that
 * can be useful when creating a new account. For instance, this can be useful
 * to reuse the HTTP cookies from an existing browser session to skip the
 * prompt for username and password in the OAuth2-based providers by passing
 * a #GVariant with the following contents:
 *
 * <informalexample>
 * <programlisting>
 * {
 *   "cookies": [
 *     {
 *       "domain": "example.com",
 *       "name": "LSID",
 *       "value": "asdfasdfasdf"
 *     },
 *     {
 *       "domain": "accounts.example.com",
 *       "name": "SSID",
 *       "value": "asdfasdfasdf"
 *     }
 *   ]
 * }
 * </programlisting>
 * </informalexample>
 *
 * Unknown or unsupported keys will be ignored by providers.
 */
  properties[PROP_PRESEED_DATA] =
    g_param_spec_variant ("preseed-data",
        "Collected data to pre-seed account creation",
        "A a{sv} #GVariant containing a provider-type specific set of data that"
        "can be useful during account creation (eg. http cookies from an existing"
        "browser session or the entrypoint url for self-hosted services).",
        G_VARIANT_TYPE_VARDICT,
        NULL,
        G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  g_object_class_install_properties (object_class, NUM_PROPERTIES, properties);
196 197 198
}

/**
199 200
 * goa_provider_get_provider_type:
 * @provider: A #GoaProvider.
201 202 203
 *
 * Gets the type of @provider.
 *
David Zeuthen's avatar
David Zeuthen committed
204 205 206
 * This is a pure virtual method - a subclass must provide an
 * implementation.
 *
207 208 209
 * Returns: (transfer none): A string owned by @provider, do not free.
 */
const gchar *
210
goa_provider_get_provider_type (GoaProvider *provider)
211
{
212 213
  g_return_val_if_fail (GOA_IS_PROVIDER (provider), NULL);
  return GOA_PROVIDER_GET_CLASS (provider)->get_provider_type (provider);
214 215
}

216
/**
David Zeuthen's avatar
David Zeuthen committed
217
 * goa_provider_get_provider_name:
218
 * @provider: A #GoaProvider.
David Zeuthen's avatar
David Zeuthen committed
219
 * @object: (allow-none): A #GoaObject for an account.
220
 *
David Zeuthen's avatar
David Zeuthen committed
221 222 223 224
 * Gets a name for @provider and @object that is suitable for display
 * in an user interface. The returned value may depend on @object (if
 * it's not %NULL) - for example, hosted accounts might return a
 * different name.
225
 *
David Zeuthen's avatar
David Zeuthen committed
226 227 228
 * This is a pure virtual method - a subclass must provide an
 * implementation.
 *
David Zeuthen's avatar
David Zeuthen committed
229
 * Returns: (transfer full): A string that should be freed with g_free().
230
 */
David Zeuthen's avatar
David Zeuthen committed
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257
gchar *
goa_provider_get_provider_name (GoaProvider *provider,
                                GoaObject   *object)
{
  g_return_val_if_fail (GOA_IS_PROVIDER (provider), NULL);
  return GOA_PROVIDER_GET_CLASS (provider)->get_provider_name (provider, object);
}

/**
 * goa_provider_get_provider_icon:
 * @provider: A #GoaProvider.
 * @object: A #GoaObject for an account.
 *
 * Gets an icon for @provider and @object that is suitable for display
 * in an user interface. The returned value may depend on @object -
 * for example, hosted accounts might return a different icon.
 *
 * This is a virtual method with a default implementation that returns
 * a #GThemedIcon with fallbacks constructed from the name
 * <literal>goa-account-TYPE</literal> where <literal>TYPE</literal>
 * is the return value of goa_provider_get_provider_type().
 *
 * Returns: (transfer full): An icon that should be freed with g_object_unref().
 */
GIcon *
goa_provider_get_provider_icon (GoaProvider *provider,
                                GoaObject   *object)
258
{
259
  g_return_val_if_fail (GOA_IS_PROVIDER (provider), NULL);
David Zeuthen's avatar
David Zeuthen committed
260 261 262 263 264 265 266 267 268 269 270 271 272
  return GOA_PROVIDER_GET_CLASS (provider)->get_provider_icon (provider, object);
}

static GIcon *
goa_provider_get_provider_icon_default (GoaProvider *provider,
                                        GoaObject   *object)
{
  GIcon *ret;
  gchar *s;
  s = g_strdup_printf ("goa-account-%s", goa_provider_get_provider_type (provider));
  ret = g_themed_icon_new_with_default_fallbacks (s);
  g_free (s);
  return ret;
273 274
}

275 276 277 278 279 280 281 282 283 284 285 286 287 288
/**
 * goa_provider_get_provider_group:
 * @provider: A #GoaProvider.
 *
 * Gets the group to which @provider belongs that is suitable for
 * organizing the providers while displaying them in an user
 * interface.
 *
 * This is a pure virtual method - a subclass must provide an
 * implementation.
 *
 * Returns: A #GoaProviderGroup.
 *
 * Since: 3.8
289 290
 *
 * Deprecated: 3.10: Use goa_provider_get_provider_features() instead.
291 292 293 294 295 296 297 298
 */
GoaProviderGroup
goa_provider_get_provider_group (GoaProvider *provider)
{
  g_return_val_if_fail (GOA_IS_PROVIDER (provider), GOA_PROVIDER_GROUP_INVALID);
  return GOA_PROVIDER_GET_CLASS (provider)->get_provider_group (provider);
}

299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317
/**
 * goa_provider_get_provider_features:
 * @provider: A #GoaProvider.
 *
 * Get the features bitmask (eg. %GOA_PROVIDER_FEATURE_CHAT|%GOA_PROVIDER_FEATURE_CONTACTS)
 * supported by the provider.
 *
 * Returns: The #GoaProviderFeatures bitmask with the provided features.
 *
 * Since: 3.10
 */
GoaProviderFeatures
goa_provider_get_provider_features (GoaProvider *provider)
{
  g_return_val_if_fail (GOA_IS_PROVIDER (provider), GOA_PROVIDER_FEATURE_INVALID);
  g_return_val_if_fail (GOA_PROVIDER_GET_CLASS (provider)->get_provider_features != NULL, GOA_PROVIDER_FEATURE_INVALID);
  return GOA_PROVIDER_GET_CLASS (provider)->get_provider_features (provider);
}

318 319
/* ---------------------------------------------------------------------------------------------------- */

320
/**
321 322
 * goa_provider_add_account:
 * @provider: A #GoaProvider.
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340
 * @client: A #GoaClient.
 * @dialog: A #GtkDialog.
 * @vbox: A vertically oriented #GtkBox to put content in.
 * @error: Return location for error or %NULL.
 *
 * This method brings up the user interface necessary to create a new
 * account on @client of the type for @provider, interacts with the
 * user to get all information needed and creates the account.
 *
 * The passed in @dialog widget is guaranteed to be visible with @vbox
 * being empty and the only visible widget in @dialog's content
 * area. The dialog has exactly one action widget, a cancel button
 * with response id GTK_RESPONSE_CANCEL. Implementations are free to
 * add additional action widgets, as needed.
 *
 * If an account was successfully created, a #GoaObject for the
 * created account is returned. If @dialog is dismissed, %NULL is
 * returned and @error is set to %GOA_ERROR_DIALOG_DISMISSED. If an
341 342 343
 * account couldn't be created then @error is set. In some cases,
 * for example, when the credentials could not be stored in the
 * keyring, a #GoaObject can be returned even if @error is set.
344 345 346 347 348 349 350 351 352
 *
 * The caller will always show an error dialog if @error is set unless
 * the error is %GOA_ERROR_DIALOG_DISMISSED.
 *
 * Implementations should run the <link
 * linkend="g_main_context_default">default main loop</link> while
 * interacting with the user and may do so using e.g. gtk_dialog_run()
 * on @dialog.
 *
David Zeuthen's avatar
David Zeuthen committed
353 354 355
 * This is a pure virtual method - a subclass must provide an
 * implementation.
 *
356 357 358 359
 * Returns: The #GoaObject for the created account (must be relased
 *   with g_object_unref()) or %NULL if @error is set.
 */
GoaObject *
360 361 362 363 364
goa_provider_add_account (GoaProvider  *provider,
                          GoaClient    *client,
                          GtkDialog    *dialog,
                          GtkBox       *vbox,
                          GError      **error)
365 366 367
{
  GoaObject *ret;

368
  g_return_val_if_fail (GOA_IS_PROVIDER (provider), NULL);
369 370 371 372
  g_return_val_if_fail (GOA_IS_CLIENT (client), NULL);
  g_return_val_if_fail (GTK_IS_DIALOG (dialog), NULL);
  g_return_val_if_fail (error == NULL || *error == NULL, NULL);

373
  ret = GOA_PROVIDER_GET_CLASS (provider)->add_account (provider, client, dialog, vbox, error);
374 375 376 377 378 379

  g_warn_if_fail ((ret == NULL && (error == NULL || *error != NULL)) || GOA_IS_OBJECT (ret));

  return ret;
}

380 381
/* ---------------------------------------------------------------------------------------------------- */

382
/**
383 384
 * goa_provider_refresh_account:
 * @provider: A #GoaProvider.
385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400
 * @client: A #GoaClient.
 * @object: A #GoaObject with a #GoaAccount interface.
 * @parent: (allow-none): Transient parent of dialogs or %NULL.
 * @error: Return location for error or %NULL.
 *
 * This method brings up the user interface necessary for refreshing
 * the credentials for the account specified by @object. This
 * typically involves having the user log in to the account again.
 *
 * Implementations should use @parent (unless %NULL) as the transient
 * parent of any created windows/dialogs.
 *
 * Implementations should run the <link
 * linkend="g_main_context_default">default main loop</link> while
 * interacting with the user.
 *
David Zeuthen's avatar
David Zeuthen committed
401 402 403
 * This is a pure virtual method - a subclass must provide an
 * implementation.
 *
404 405 406 407
 * Returns: %TRUE if the account has been refreshed, %FALSE if @error
 * is set.
 */
gboolean
408 409 410 411 412
goa_provider_refresh_account (GoaProvider  *provider,
                              GoaClient    *client,
                              GoaObject    *object,
                              GtkWindow    *parent,
                              GError      **error)
413
{
414
  g_return_val_if_fail (GOA_IS_PROVIDER (provider), FALSE);
415 416 417 418 419
  g_return_val_if_fail (GOA_IS_CLIENT (client), FALSE);
  g_return_val_if_fail (GOA_IS_OBJECT (object) && goa_object_peek_account (object) != NULL, FALSE);
  g_return_val_if_fail (parent == NULL || GTK_IS_WINDOW (parent), FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

420
  return GOA_PROVIDER_GET_CLASS (provider)->refresh_account (provider, client, object, parent, error);
421 422 423 424
}

/* ---------------------------------------------------------------------------------------------------- */

425 426 427 428 429 430
/**
 * goa_provider_show_account:
 * @provider: A #GoaProvider.
 * @client: A #GoaClient.
 * @object: A #GoaObject with a #GoaAccount interface.
 * @vbox: A vertically oriented #GtkBox to put content in.
431 432
 * @grid: A #GtkGrid to put content in.
 * @dummy: Unused.
433 434 435 436
 *
 * Method used to add widgets in the control panel for the account
 * represented by @object.
 *
437 438
 * This is a pure virtual method - a subclass must provide an
 * implementation.
439 440 441 442 443 444
 */
void
goa_provider_show_account (GoaProvider         *provider,
                           GoaClient           *client,
                           GoaObject           *object,
                           GtkBox              *vbox,
445 446
                           GtkGrid             *grid,
                           GtkGrid             *dummy)
447 448 449 450 451
{
  g_return_if_fail (GOA_IS_PROVIDER (provider));
  g_return_if_fail (GOA_IS_CLIENT (client));
  g_return_if_fail (GOA_IS_OBJECT (object) && goa_object_peek_account (object) != NULL);
  g_return_if_fail (GTK_IS_BOX (vbox));
452
  g_return_if_fail (GTK_IS_GRID (grid));
453

454
  GOA_PROVIDER_GET_CLASS (provider)->show_account (provider, client, object, vbox, grid, dummy);
455 456 457 458
}

/* ---------------------------------------------------------------------------------------------------- */

459
/**
460 461
 * goa_provider_build_object:
 * @provider: A #GoaProvider.
462 463 464
 * @object: The #GoaObjectSkeleton that is being built.
 * @key_file: The #GKeyFile with configuation data.
 * @group: The group in @key_file to get data from.
465
 * @connection: The #GDBusConnection used by the daemon to connect to the message bus.
466
 * @just_added: Whether the account was newly created or being updated.
467 468 469 470 471 472 473 474 475 476 477 478 479
 * @error: Return location for error or %NULL.
 *
 * This method is called when construction account #GoaObject<!-- -->
 * from configuration data - it basically provides a way to add
 * provider-specific information.
 *
 * The passed in @object will have a #GoaAccount interface
 * set. Implementations should validate and use data from @key_file to
 * add more interfaces to @object.
 *
 * Note that this may be called on already exported objects - for
 * example on configuration files reload.
 *
David Zeuthen's avatar
David Zeuthen committed
480 481 482
 * This is a pure virtual method - a subclass must provide an
 * implementation.
 *
483 484 485
 * Returns: %TRUE if data was valid, %FALSE if @error is set.
 */
gboolean
486 487 488 489
goa_provider_build_object (GoaProvider         *provider,
                           GoaObjectSkeleton   *object,
                           GKeyFile            *key_file,
                           const gchar         *group,
490
                           GDBusConnection     *connection,
491
                           gboolean             just_added,
492
                           GError             **error)
493
{
494
  g_return_val_if_fail (GOA_IS_PROVIDER (provider), FALSE);
495 496 497
  g_return_val_if_fail (GOA_IS_OBJECT_SKELETON (object) && goa_object_peek_account (GOA_OBJECT (object)) != NULL, FALSE);
  g_return_val_if_fail (key_file != NULL, FALSE);
  g_return_val_if_fail (group != NULL, FALSE);
498
  g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE);
499
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
500 501 502 503 504 505 506
  return GOA_PROVIDER_GET_CLASS (provider)->build_object (provider,
                                                          object,
                                                          key_file,
                                                          group,
                                                          connection,
                                                          just_added,
                                                          error);
507 508
}

509 510
/* ---------------------------------------------------------------------------------------------------- */

511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543
typedef struct
{
  GoaObject *object;
  gint expires_in;
} EnsureCredentialsData;

static EnsureCredentialsData *
ensure_credentials_data_new (GoaObject *object)
{
  EnsureCredentialsData *data;
  data = g_new0 (EnsureCredentialsData, 1);
  data->object = g_object_ref (object);
  return data;
}

static void
ensure_credentials_data_free (EnsureCredentialsData *data)
{
  g_object_unref (data->object);
  g_free (data);
}

static void
ensure_credentials_in_thread_func (GSimpleAsyncResult *simple,
                                   GObject            *object,
                                   GCancellable       *cancellable)
{
  EnsureCredentialsData *data;
  GError *error;

  data = g_simple_async_result_get_op_res_gpointer (simple);

  error = NULL;
544 545 546 547 548
  if (!goa_provider_ensure_credentials_sync (GOA_PROVIDER (object),
                                             data->object,
                                             &data->expires_in,
                                             cancellable,
                                             &error))
549 550 551 552
    g_simple_async_result_take_error (simple, error);
}


553
/**
554 555
 * goa_provider_ensure_credentials:
 * @provider: A #GoaProvider.
556 557 558 559 560 561 562 563 564 565
 * @object: A #GoaObject with a #GoaAccount interface.
 * @cancellable: (allow-none): A #GCancellable or %NULL.
 * @callback: The function to call when the request is satisfied.
 * @user_data: Pointer to pass to @callback.
 *
 * Ensures that credentials for @object are still valid.
 *
 * When the result is ready, @callback will be called in the the <link
 * linkend="g-main-context-push-thread-default">thread-default main
 * loop</link> this function was called from. You can then call
566
 * goa_provider_ensure_credentials_finish() to get the result
567 568
 * of the operation.
 *
569
 * This is a virtual method where the default implementation simply
Debarshi Ray's avatar
Debarshi Ray committed
570
 * throws the %GOA_ERROR_NOT_SUPPORTED error. A subclass may provide
571 572 573
 * another implementation.
 */
void
574 575 576 577 578
goa_provider_ensure_credentials (GoaProvider          *provider,
                                 GoaObject            *object,
                                 GCancellable         *cancellable,
                                 GAsyncReadyCallback   callback,
                                 gpointer              user_data)
579
{
580 581
  GSimpleAsyncResult *simple;

582
  g_return_if_fail (GOA_IS_PROVIDER (provider));
583
  g_return_if_fail (GOA_IS_OBJECT (object));
584
  g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
585 586 587 588

  simple = g_simple_async_result_new (G_OBJECT (provider),
                                      callback,
                                      user_data,
589
                                      goa_provider_ensure_credentials);
590 591 592 593 594 595 596 597
  g_simple_async_result_set_op_res_gpointer (simple,
                                             ensure_credentials_data_new (object),
                                             (GDestroyNotify) ensure_credentials_data_free);
  g_simple_async_result_run_in_thread (simple,
                                       ensure_credentials_in_thread_func,
                                       G_PRIORITY_DEFAULT,
                                       cancellable);
  g_object_unref (simple);
598 599 600
}

/**
601 602
 * goa_provider_ensure_credentials_finish:
 * @provider: A #GoaProvider.
603
 * @out_expires_in: (out): Return location for how long the expired credentials are good for (0 if unknown) or %NULL.
604
 * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback passed to goa_provider_ensure_credentials().
605 606
 * @error: Return location for error or %NULL.
 *
607
 * Finishes an operation started with goa_provider_ensure_credentials().
608 609 610 611
 *
 * Returns: %TRUE if the credentials for the passed #GoaObject are valid, %FALSE if @error is set.
 */
gboolean
612
goa_provider_ensure_credentials_finish (GoaProvider  *provider,
613 614 615 616
                                                gint                *out_expires_in,
                                                GAsyncResult        *res,
                                                GError             **error)
{
617 618 619 620 621 622
  GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
  gboolean ret;
  EnsureCredentialsData *data;

  ret = FALSE;

623
  g_return_val_if_fail (GOA_IS_PROVIDER (provider), FALSE);
624 625 626
  g_return_val_if_fail (G_IS_ASYNC_RESULT (res), FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

627
  g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == goa_provider_ensure_credentials);
628

629 630
  if (g_simple_async_result_propagate_error (simple, error))
    goto out;
631

632 633 634 635 636 637 638
  ret = TRUE;
  data = g_simple_async_result_get_op_res_gpointer (simple);
  if (out_expires_in != NULL)
    *out_expires_in = data->expires_in;

 out:
  return ret;
639 640
}

641 642
/* ---------------------------------------------------------------------------------------------------- */

643
/**
644 645
 * goa_provider_ensure_credentials_sync:
 * @provider: A #GoaProvider.
646 647 648 649 650
 * @object: A #GoaObject with a #GoaAccount interface.
 * @out_expires_in: (out): Return location for how long the expired credentials are good for (0 if unknown) or %NULL.
 * @cancellable: (allow-none): A #GCancellable or %NULL.
 * @error: Return location for error or %NULL.
 *
651
 * Like goa_provider_ensure_credentials() but blocks the
652 653 654 655 656
 * calling thread until an answer is received.
 *
 * Returns: %TRUE if the credentials for the passed #GoaObject are valid, %FALSE if @error is set.
 */
gboolean
657 658 659 660 661
goa_provider_ensure_credentials_sync (GoaProvider     *provider,
                                      GoaObject       *object,
                                      gint            *out_expires_in,
                                      GCancellable    *cancellable,
                                      GError         **error)
662
{
663
  g_return_val_if_fail (GOA_IS_PROVIDER (provider), FALSE);
664 665
  g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
666
  return GOA_PROVIDER_GET_CLASS (provider)->ensure_credentials_sync (provider, object, out_expires_in, cancellable, error);
667 668
}

669 670
/* ---------------------------------------------------------------------------------------------------- */

671
static gboolean
672 673 674 675 676
goa_provider_ensure_credentials_sync_real (GoaProvider   *provider,
                                           GoaObject     *object,
                                           gint          *out_expires_in,
                                           GCancellable  *cancellable,
                                           GError       **error)
677
{
678 679 680
  g_set_error (error,
               GOA_ERROR,
               GOA_ERROR_NOT_SUPPORTED,
681
               _("ensure_credentials_sync is not implemented on type %s"),
682 683
               g_type_name (G_TYPE_FROM_INSTANCE (provider)));
  return FALSE;
684 685
}

David Zeuthen's avatar
David Zeuthen committed
686 687 688 689 690
static gboolean
goa_provider_build_object_real (GoaProvider         *provider,
                                GoaObjectSkeleton   *object,
                                GKeyFile            *key_file,
                                const gchar         *group,
691
                                GDBusConnection     *connection,
692
                                gboolean             just_added,
David Zeuthen's avatar
David Zeuthen committed
693 694 695 696 697 698
                                GError             **error)
{
  /* does nothing */
  return TRUE;
}

699 700
/* ---------------------------------------------------------------------------------------------------- */

701 702 703 704 705 706 707 708 709 710 711 712 713 714
/**
 * goa_provider_get_credentials_generation:
 * @provider: A #GoaProvider.
 *
 * Gets the generation of credentials being used for the provider.
 *
 * Implementations should bump this number when changes are introduced
 * that may render existing credentials unusable.
 *
 * For example, if an additional scope is requested (e.g. access to
 * contacts data) while obtaining credentials, then this number needs
 * to be bumped since existing credentials are not good for the added
 * scope.
 *
715 716
 * This is a virtual method where the default implementation returns
 * 0.
717 718 719 720 721 722
 *
 * Returns: The current generation of credentials.
 */
guint
goa_provider_get_credentials_generation (GoaProvider *provider)
{
723
  g_return_val_if_fail (GOA_IS_PROVIDER (provider), 0);
724 725 726 727 728 729 730 731 732 733 734
  return GOA_PROVIDER_GET_CLASS (provider)->get_credentials_generation (provider);
}

static guint
goa_provider_get_credentials_generation_real (GoaProvider *provider)
{
  return 0;
}

/* ---------------------------------------------------------------------------------------------------- */

735 736
void
goa_provider_ensure_extension_points_registered (void)
737 738 739 740 741 742 743
{
  static gsize once_init_value = 0;

  if (g_once_init_enter (&once_init_value))
    {
      GIOExtensionPoint *extension_point;

744 745
      extension_point = g_io_extension_point_register (GOA_PROVIDER_EXTENSION_POINT_NAME);
      g_io_extension_point_set_required_type (extension_point, GOA_TYPE_PROVIDER);
746

747 748 749
      extension_point = g_io_extension_point_register (GOA_PROVIDER_FACTORY_EXTENSION_POINT_NAME);
      g_io_extension_point_set_required_type (extension_point, GOA_TYPE_PROVIDER_FACTORY);

750 751 752 753
      g_once_init_leave (&once_init_value, 1);
    }
}

754
/* ---------------------------------------------------------------------------------------------------- */
755

756 757 758 759 760 761 762 763 764
static struct
{
  const gchar *name;
  GType (*get_type) (void);
} ordered_builtins_map[] = {
  /* The order is in which the providers' types are created is
   * important because it affects the order in which they are
   * returned by goa_provider_get_all.
   */
765
#ifdef GOA_GOOGLE_ENABLED
766
  { GOA_GOOGLE_NAME, goa_google_provider_get_type },
767
#endif
Debarshi Ray's avatar
Debarshi Ray committed
768
#ifdef GOA_OWNCLOUD_ENABLED
769
  { GOA_OWNCLOUD_NAME, goa_owncloud_provider_get_type },
Debarshi Ray's avatar
Debarshi Ray committed
770
#endif
771
#ifdef GOA_FACEBOOK_ENABLED
772
  { GOA_FACEBOOK_NAME, goa_facebook_provider_get_type },
773
#endif
Willem van Engen's avatar
Willem van Engen committed
774
#ifdef GOA_FLICKR_ENABLED
775
  { GOA_FLICKR_NAME, goa_flickr_provider_get_type },
Willem van Engen's avatar
Willem van Engen committed
776
#endif
777
#ifdef GOA_WINDOWS_LIVE_ENABLED
778
  { GOA_WINDOWS_LIVE_NAME, goa_windows_live_provider_get_type },
779
#endif
Bastien Nocera's avatar
Bastien Nocera committed
780
#ifdef GOA_POCKET_ENABLED
781
  { GOA_POCKET_NAME, goa_pocket_provider_get_type },
Bastien Nocera's avatar
Bastien Nocera committed
782
#endif
783
#ifdef GOA_EXCHANGE_ENABLED
784
  { GOA_EXCHANGE_NAME, goa_exchange_provider_get_type },
785
#endif
Debarshi Ray's avatar
Debarshi Ray committed
786
#ifdef GOA_IMAP_SMTP_ENABLED
787
  { GOA_IMAP_SMTP_NAME, goa_imap_smtp_provider_get_type },
Debarshi Ray's avatar
Debarshi Ray committed
788
#endif
789
#ifdef GOA_KERBEROS_ENABLED
790
  { GOA_KERBEROS_NAME, goa_kerberos_provider_get_type },
791
#endif
792
#ifdef GOA_YAHOO_ENABLED
793
  { GOA_YAHOO_NAME, goa_yahoo_provider_get_type },
794
#endif
795
#ifdef GOA_TELEPATHY_ENABLED
796
  { GOA_TELEPATHY_NAME, goa_telepathy_factory_get_type },
797
#endif
798 799 800 801 802 803 804 805 806
  { NULL, NULL }
};

static void
ensure_builtins_loaded (void)
{
  static gsize once_init_value = 0;

  goa_provider_ensure_extension_points_registered ();
807

808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856
  if (g_once_init_enter (&once_init_value))
    {
      GSettings *settings;
      gchar **whitelisted_providers;
      guint i;
      guint j;
      gboolean all = FALSE;

      settings = g_settings_new (GOA_SETTINGS_SCHEMA);
      whitelisted_providers = g_settings_get_strv (settings, GOA_SETTINGS_WHITELISTED_PROVIDERS);

      /* Enable everything if there is 'all'. */
      for (i = 0; whitelisted_providers[i] != NULL; i++)
        {
          if (g_strcmp0 (whitelisted_providers[i], "all") == 0)
            {
              g_debug ("Loading all providers: ");
              for (j = 0; ordered_builtins_map[j].name != NULL; j++)
                {
                  g_debug (" - %s", ordered_builtins_map[j].name);
                  g_type_ensure ((*ordered_builtins_map[j].get_type) ());
                }

              all = TRUE;
              break;
            }
        }

      if (all)
        goto cleanup;

      /* Otherwise try them one by one. */
      g_debug ("Loading whitelisted providers: ");
      for (i = 0; ordered_builtins_map[i].name != NULL; i++)
        {
          for (j = 0; whitelisted_providers[j] != NULL; j++)
            {
              if (g_strcmp0 (whitelisted_providers[j], ordered_builtins_map[i].name) == 0)
                {
                  g_debug (" - %s", ordered_builtins_map[j].name);
                  g_type_ensure ((*ordered_builtins_map[i].get_type) ());
                  break;
                }
            }
        }

    cleanup:
      g_strfreev (whitelisted_providers);
      g_object_unref (settings);
857 858 859 860
      g_once_init_leave (&once_init_value, 1);
    }
}

861 862 863
/* ---------------------------------------------------------------------------------------------------- */

/**
864
 * goa_provider_get_for_provider_type:
865 866
 * @provider_type: A provider type.
 *
867 868 869 870 871 872 873 874 875 876
 * Returns a #GoaProvider for @provider_type (if available).
 *
 * If @provider_type doesn't contain any "/", a
 * %GOA_PROVIDER_EXTENSION_POINT_NAME extension for @provider_type is looked up
 * and the newly created #GoaProvider, if any, is returned.
 *
 * If @provider_type contains a "/", a
 * %GOA_PROVIDER_FACTORY_EXTENSION_POINT_NAME extension for the first part of
 * @provider_type is looked up. If found, the #GoaProviderFactory is used
 * to create a dynamic #GoaProvider matching the second part of @provider_type.
877
 *
878
 * Returns: (transfer full): A #GoaProvider (that must be freed
879 880
 * with g_object_unref()) or %NULL if not found.
 */
881 882
GoaProvider *
goa_provider_get_for_provider_type (const gchar *provider_type)
883 884 885
{
  GIOExtension *extension;
  GIOExtensionPoint *extension_point;
886
  gchar **split_provider_type;
887
  GoaProvider *ret;
888

889 890
  g_return_val_if_fail (provider_type != NULL, NULL);

891
  ensure_builtins_loaded ();
892

893 894
  ret = NULL;

895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919
  split_provider_type = g_strsplit (provider_type, "/", 2);

  if (g_strv_length (split_provider_type) == 1)
    {
      /* Normal provider */
      extension_point = g_io_extension_point_lookup (GOA_PROVIDER_EXTENSION_POINT_NAME);
      extension = g_io_extension_point_get_extension_by_name (extension_point, provider_type);
      if (extension != NULL)
        ret = GOA_PROVIDER (g_object_new (g_io_extension_get_type (extension), NULL));
    }
  else
    {
      /* Dynamic provider created through a factory */
      extension_point = g_io_extension_point_lookup (GOA_PROVIDER_FACTORY_EXTENSION_POINT_NAME);
      extension = g_io_extension_point_get_extension_by_name (extension_point, split_provider_type[0]);
      if (extension != NULL)
        {
          GoaProviderFactory *factory = g_object_new (g_io_extension_get_type (extension), NULL);
          ret = goa_provider_factory_get_provider (factory, split_provider_type[1]);
          g_object_unref (factory);
        }
    }

  g_strfreev (split_provider_type);

920 921 922
  return ret;
}

923 924
/* ---------------------------------------------------------------------------------------------------- */

925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996
typedef struct
{
  GQueue ret;
  gint pending_calls;
  GSimpleAsyncResult *result;
} GetAllData;

static void
free_list_and_unref (gpointer data)
{
  g_list_free_full (data, g_object_unref);
}

static gint
compare_providers (GoaProvider *a,
                   GoaProvider *b)
{
  gboolean a_branded;
  gboolean b_branded;

  if (goa_provider_get_provider_features (a) & GOA_PROVIDER_FEATURE_BRANDED)
    a_branded = TRUE;
  else
    a_branded = FALSE;

  if (goa_provider_get_provider_features (b) & GOA_PROVIDER_FEATURE_BRANDED)
    b_branded = TRUE;
  else
    b_branded = FALSE;

  /* g_queue_sort() uses a stable sort, so, if we return 0, the order
   * is not changed. */
  if (a_branded == b_branded)
    return 0;
  else if (a_branded)
    return -1;
  else
    return 1;
}

static void
get_all_check_done (GetAllData *data)
{
  if (data->pending_calls > 0)
    return;

  /* Make sure that branded providers come first, but don't change the
   * order otherwise. */
  g_queue_sort (&data->ret, (GCompareDataFunc) compare_providers, NULL);

  /* Steal the list out of the GQueue. */
  g_simple_async_result_set_op_res_gpointer (data->result, data->ret.head,
      free_list_and_unref);
  g_simple_async_result_complete_in_idle (data->result);

  g_object_unref (data->result);
  g_slice_free (GetAllData, data);
}

static void
get_providers_cb (GObject      *source,
                  GAsyncResult *res,
                  gpointer      user_data)
{
  GoaProviderFactory *factory = GOA_PROVIDER_FACTORY (source);
  GetAllData *data = user_data;
  GList *providers = NULL;
  GList *l;
  GError *error = NULL;

  if (!goa_provider_factory_get_providers_finish (factory, &providers, res, &error))
    {
997
      g_critical ("Error getting providers from a factory: %s (%s, %d)",
998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017
          error->message,
          g_quark_to_string (error->domain),
          error->code);
      g_clear_error (&error);
      goto out;
    }

  for (l = providers; l != NULL; l = l->next)
    {
      /* Steal the value */
      g_queue_push_tail (&data->ret, l->data);
    }

  g_list_free (providers);

out:
  data->pending_calls--;
  get_all_check_done (data);
}

1018
/**
1019
 * goa_provider_get_all:
1020 1021
 * @callback: The function to call when the request is satisfied.
 * @user_data: Pointer to pass to @callback.
1022
 *
1023 1024 1025 1026 1027 1028 1029 1030 1031
 * Creates a list of all the available #GoaProvider instances.
 *
 * When the result is ready, @callback will be called in the the <link
 * linkend="g-main-context-push-thread-default">thread-default main
 * loop</link> this function was called from. You can then call
 * goa_provider_get_all_finish() to get the result of the operation.
 *
 * See goa_provider_get_for_provider_type() for details on how the providers
 * are found.
1032
 *
1033
 * Returns: (transfer full) (element-type GoaProvider): A list
1034 1035 1036
 *   of element providers that should be freed with g_list_free()
 *   after each element has been freed with g_object_unref().
 */
1037 1038 1039
void
goa_provider_get_all (GAsyncReadyCallback callback,
                      gpointer            user_data)
1040 1041 1042 1043
{
  GList *extensions;
  GList *l;
  GIOExtensionPoint *extension_point;
1044 1045
  GetAllData *data;
  gint i;
1046

1047
  ensure_builtins_loaded ();
1048

1049 1050 1051 1052 1053 1054
  data = g_slice_new0 (GetAllData);
  data->result = g_simple_async_result_new (NULL, callback, user_data,
      goa_provider_get_all);
  g_queue_init (&data->ret);

  /* Load the normal providers. */
1055
  extension_point = g_io_extension_point_lookup (GOA_PROVIDER_EXTENSION_POINT_NAME);
1056 1057
  extensions = g_io_extension_point_get_extensions (extension_point);
  /* TODO: what if there are two extensions with the same name? */
1058
  for (l = extensions, i = 0; l != NULL; l = l->next, i++)
1059 1060
    {
      GIOExtension *extension = l->data;
1061 1062 1063 1064
      /* The extensions are loaded in the reverse order we used in
       * ensure_builtins_loaded, so we need to push extension if front of
       * the already loaded ones. */
      g_queue_push_head (&data->ret, g_object_new (g_io_extension_get_type (extension), NULL));
1065
    }
1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111

  /* Load the provider factories and get the dynamic providers out of them. */
  extension_point = g_io_extension_point_lookup (GOA_PROVIDER_FACTORY_EXTENSION_POINT_NAME);
  extensions = g_io_extension_point_get_extensions (extension_point);
  for (l = extensions, i = 0; l != NULL; l = l->next, i++)
    {
      GIOExtension *extension = l->data;
      goa_provider_factory_get_providers (g_object_new (g_io_extension_get_type (extension), NULL),
          get_providers_cb, data);
      data->pending_calls++;
    }

  get_all_check_done (data);
}

/**
 * goa_provider_get_all_finish:
 * @out_providers: (out): Return location for a list of #GoaProvider instances.
 * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback passed to goa_provider_get_all().
 * @error: Return location for error or %NULL.
 *
 * Finishes an operation started with goa_provider_get_all().
 *
 * Returns: %TRUE if the list was successfully retrieved, %FALSE if @error is set.
 */
gboolean
goa_provider_get_all_finish (GList        **out_providers,
                             GAsyncResult  *result,
                             GError       **error)
{
  GSimpleAsyncResult *simple = (GSimpleAsyncResult *) result;
  GList *providers;

  g_return_val_if_fail (g_simple_async_result_is_valid (result, NULL,
        goa_provider_get_all), FALSE);

  if (g_simple_async_result_propagate_error (simple, error))
    return FALSE;

  if (out_providers != NULL)
    {
      providers = g_simple_async_result_get_op_res_gpointer (simple);
      *out_providers = g_list_copy_deep (providers, (GCopyFunc) g_object_ref, NULL);
    }

  return TRUE;
1112 1113
}

1114 1115
/* ---------------------------------------------------------------------------------------------------- */

1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131
/**
 * goa_provider_set_preseed_data:
 * @provider: The #GoaProvider
 * @preseed_data: A #GVariant of type a{sv}
 *
 * Sets the #GoaProvider:preseed-data property to feed any information already
 * collected that can be useful when creating a new account.
 *
 * If the @preseed_data #GVariant is floating, it is consumed to allow
 * 'inline' use of the g_variant_new() family of functions.
 */
void
goa_provider_set_preseed_data (GoaProvider *provider,
                               GVariant    *preseed_data)
{
  g_clear_pointer (&provider->priv->preseed_data, g_variant_unref);
1132 1133
  if (preseed_data != NULL)
    provider->priv->preseed_data = g_variant_ref_sink (preseed_data);
1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153
  g_object_notify (G_OBJECT (provider), "preseed-data");
}

/**
 * goa_provider_get_preseed_data:
 * @provider: The #GoaProvider
 *
 * Gets the #GVariant set through the #GoaProvider:preseed-data property.
 *
 * Returns: (transfer none): A #GVariant that is known to be valid until
 *   the property is overridden or the provider freed.
 */
GVariant *
goa_provider_get_preseed_data (GoaProvider *provider)
{
  return provider->priv->preseed_data;
}

/* ---------------------------------------------------------------------------------------------------- */

1154 1155 1156 1157 1158 1159 1160 1161
/**
 * SECTION:goautil
 * @title: Utilities
 * @short_description: Various utility routines
 *
 * Various utility routines.
 */

1162 1163
/* ---------------------------------------------------------------------------------------------------- */

1164 1165
/**
 * goa_util_add_row_widget:
1166 1167
 * @left: A #GtkGrid for the left side.
 * @right: A #GtkGrid for the right side.
1168 1169 1170 1171 1172 1173 1174 1175
 * @label_text: (allow-none): The text to insert on the left side or %NULL for no label.
 * @widget: A widget to insert on the right side.
 *
 * Utility function to add @label_text and @widget to @table.
 *
 * Returns: (transfer none): The #GtkWidget that was inserted (e.g. @widget itself).
 */
GtkWidget *
1176 1177
goa_util_add_row_widget (GtkGrid      *grid,
                         gint          row,
1178 1179 1180 1181 1182
                         const gchar  *label_text,
                         GtkWidget    *widget)
{
  GtkWidget *label;

1183
  g_return_val_if_fail (GTK_IS_GRID (grid), NULL);
1184 1185 1186 1187
  g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);

  if (label_text != NULL)
    {
1188 1189 1190 1191
      GtkStyleContext *context;

      label = gtk_label_new (label_text);
      context = gtk_widget_get_style_context (label);
1192
      gtk_style_context_add_class (context, GTK_STYLE_CLASS_DIM_LABEL);
1193 1194 1195
      gtk_widget_set_halign (label, GTK_ALIGN_END);
      gtk_widget_set_hexpand (label, TRUE);
      gtk_grid_attach (grid, label, 0, row, 1, 1);
1196 1197
    }

1198
  gtk_grid_attach (grid, widget, 1, row, 3, 1);
1199
  return widget;
1200 1201 1202 1203
}

/* ---------------------------------------------------------------------------------------------------- */

1204 1205 1206 1207 1208 1209 1210
gchar *
goa_util_lookup_keyfile_string (GoaObject    *object,
                                const gchar  *key)
{
  GoaAccount *account;
  GError *error;
  GKeyFile *key_file;
1211 1212
  gchar *path;
  gchar *group;
1213 1214 1215 1216 1217
  gchar *ret;

  ret = NULL;

  account = goa_object_peek_account (object);
1218 1219
  path = g_strdup_printf ("%s/goa-1.0/accounts.conf", g_get_user_config_dir ());
  group = g_strdup_printf ("Account %s", goa_account_get_id (account));
1220 1221 1222 1223

  key_file = g_key_file_new ();
  error = NULL;
  if (!g_key_file_load_from_file (key_file,
1224
                                  path,
1225 1226 1227
                                  G_KEY_FILE_NONE,
                                  &error))
    {
1228 1229 1230
      g_warning ("Error loading keyfile %s: %s (%s, %d)",
                 path,
                 error->message, g_quark_to_string (error->domain), error->code);
1231 1232 1233 1234
      g_error_free (error);
      goto out;
    }
  ret = g_key_file_get_string (key_file,
1235
                               group,
1236 1237 1238 1239 1240
                               key,
                               &error);
  if (ret == NULL)
    {
      /* this is not fatal (think upgrade-path) */
1241 1242 1243 1244 1245
      g_debug ("Error getting value for key %s in group `%s' from keyfile %s: %s (%s, %d)",
               key,
               group,
               path,
               error->message, g_quark_to_string (error->domain), error->code);
1246 1247 1248 1249 1250
      g_error_free (error);
      goto out;
    }

 out:
1251 1252
  g_free (group);
  g_free (path);
1253 1254 1255 1256 1257 1258 1259 1260 1261 1262
  return ret;
}

gboolean
goa_util_lookup_keyfile_boolean (GoaObject    *object,
                                 const gchar  *key)
{
  GoaAccount *account;
  GError *error;
  GKeyFile *key_file;
1263 1264
  gchar *path;
  gchar *group;
1265 1266
  gboolean ret;

David Zeuthen's avatar
David Zeuthen committed
1267
  ret = FALSE;
1268 1269

  account = goa_object_peek_account (object);
1270 1271
  path = g_strdup_printf ("%s/goa-1.0/accounts.conf", g_get_user_config_dir ());
  group = g_strdup_printf ("Account %s", goa_account_get_id (account));
1272 1273 1274 1275

  key_file = g_key_file_new ();
  error = NULL;
  if (!g_key_file_load_from_file (key_file,
1276
                                  path,
1277 1278 1279
                                  G_KEY_FILE_NONE,
                                  &error))
    {
1280 1281 1282
      g_warning ("Error loading keyfile %s: %s (%s, %d)",
                 path,
                 error->message, g_quark_to_string (error->domain), error->code);
1283 1284 1285 1286
      g_error_free (error);
      goto out;
    }
  ret = g_key_file_get_boolean (key_file,
1287
                                group,
1288 1289 1290 1291 1292
                                key,
                                &error);
  if (error != NULL)
    {
      /* this is not fatal (think upgrade-path) */
1293 1294 1295 1296 1297
      g_debug ("Error getting boolean value for key %s in group `%s' from keyfile %s: %s (%s, %d)",
               key,
               group,
               path,
               error->message, g_quark_to_string (error->domain), error->code);
1298 1299 1300 1301 1302
      g_error_free (error);
      goto out;
    }

 out:
1303 1304
  g_free (group);
  g_free (path);
1305 1306 1307 1308 1309
  return ret;
}

/* ---------------------------------------------------------------------------------------------------- */

1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324
void
goa_util_account_notify_property_cb (GObject *object, GParamSpec *pspec, gpointer user_data)
{
  GoaAccount *account;
  gboolean value;
  const gchar *key;
  const gchar *name;

  g_return_if_fail (GOA_IS_ACCOUNT (object));

  account = GOA_ACCOUNT (object);
  key = user_data;
  name = g_param_spec_get_name (pspec);

  g_object_get (account, name, &value, NULL);
1325
  goa_utils_keyfile_set_boolean (account, key, !value);
1326 1327 1328 1329
}

/* ---------------------------------------------------------------------------------------------------- */

1330
void
1331
goa_util_add_account_info (GtkGrid *grid, gint row, GoaObject *object)
1332 1333 1334 1335 1336 1337
{
  GIcon *icon;
  GoaAccount *account;
  GtkWidget *image;
  GtkWidget *label;
  const gchar *icon_str;
Debarshi Ray's avatar
Debarshi Ray committed
1338
  const gchar *identity;
1339 1340 1341 1342 1343 1344 1345 1346 1347
  const gchar *name;
  gchar *markup;

  account = goa_object_peek_account (object);

  icon_str = goa_account_get_provider_icon (account);
  icon = g_icon_new_for_string (icon_str, NULL);
  image = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_DIALOG);
  g_object_unref (icon);
1348 1349
  gtk_widget_set_halign (image, GTK_ALIGN_END);
  gtk_widget_set_hexpand (image, TRUE);
1350
  gtk_widget_set_margin_bottom (image, 12);
1351
  gtk_grid_attach (grid, image, 0, row, 1, 1);
1352 1353

  name = goa_account_get_provider_name (account);
1354 1355 1356 1357
  identity = goa_account_get_presentation_identity (account);
  markup = g_strdup_printf ("<b>%s</b>\n%s",
                            name,
                            (identity == NULL || identity[0] == '\0') ? "\xe2\x80\x94" : identity);
1358 1359
  label = gtk_label_new (NULL);
  gtk_label_set_markup (GTK_LABEL (label), markup);
1360 1361 1362 1363
  gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
  gtk_label_set_max_width_chars (GTK_LABEL (label), 24);
  gtk_label_set_width_chars (GTK_LABEL (label), 24);
  gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
1364
  gtk_widget_set_margin_bottom (label, 12);
Debarshi Ray's avatar
Debarshi Ray committed
1365
  g_free (markup);
1366
  gtk_grid_attach (grid, label, 1, row, 3, 1);
1367

1368 1369 1370 1371 1372
  return;
}

/* ---------------------------------------------------------------------------------------------------- */

1373
GtkWidget *
1374 1375
goa_util_add_row_switch_from_keyfile_with_blurb (GtkGrid      *grid,
                                                 gint          row,
1376 1377
                                                 GoaObject    *object,
                                                 const gchar  *label_text,
1378
                                                 const gchar  *property,
1379 1380 1381 1382 1383 1384
                                                 const gchar  *blurb)
{
  GoaAccount *account;
  GtkWidget *hbox;
  GtkWidget *switch_;
  gboolean value;
1385

1386
  account = goa_object_peek_account (object);
1387
  g_object_get (account, property, &value, NULL);
1388
  switch_ = gtk_switch_new ();
1389

1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401
  if (goa_account_get_attention_needed (account))
    {
      gtk_widget_set_sensitive (switch_, FALSE);
      gtk_switch_set_active (GTK_SWITCH (switch_), FALSE);
    }
  else
    {
      gtk_switch_set_active (GTK_SWITCH (switch_), !value);
      g_object_bind_property (switch_, "active",
                              account, property,
                              G_BINDING_BIDIRECTIONAL | G_BINDING_INVERT_BOOLEAN);
    }
1402

1403 1404
  hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
  gtk_box_set_homogeneous (GTK_BOX (hbox), FALSE);
1405 1406 1407 1408 1409

  if (blurb != NULL)
    {
      GtkWidget *label;

1410
      label = gtk_label_new_with_mnemonic (blurb);
1411
      gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
1412
      gtk_label_set_mnemonic_widget (GTK_LABEL (label), switch_);
1413 1414 1415 1416
      gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
    }

  gtk_box_pack_end (GTK_BOX (hbox), switch_, FALSE, FALSE, 0);
1417
  goa_util_add_row_widget (grid, row, label_text, hbox);
1418 1419
  return switch_;
}