summaryrefslogtreecommitdiff
path: root/src/mcd-account-manager-default.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/mcd-account-manager-default.c')
-rw-r--r--src/mcd-account-manager-default.c975
1 files changed, 819 insertions, 156 deletions
diff --git a/src/mcd-account-manager-default.c b/src/mcd-account-manager-default.c
index 1403f843..e6fc14fc 100644
--- a/src/mcd-account-manager-default.c
+++ b/src/mcd-account-manager-default.c
@@ -1,8 +1,8 @@
/*
- * The default account manager keyfile storage pseudo-plugin
+ * The default account manager storage pseudo-plugin
*
* Copyright © 2010 Nokia Corporation
- * Copyright © 2010 Collabora Ltd.
+ * Copyright © 2010-2012 Collabora Ltd.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -30,12 +30,75 @@
#include "mcd-account-manager-default.h"
#include "mcd-debug.h"
+#include "mcd-storage.h"
#include "mcd-misc.h"
-#define PLUGIN_NAME "default-gkeyfile"
+#define PLUGIN_NAME "default"
#define PLUGIN_PRIORITY MCP_ACCOUNT_STORAGE_PLUGIN_PRIO_DEFAULT
-#define PLUGIN_DESCRIPTION "GKeyFile (default) account storage backend"
-#define INITIAL_CONFIG "# Telepathy accounts\n"
+#define PLUGIN_DESCRIPTION "Default account storage backend"
+
+typedef struct {
+ /* owned string, attribute => owned GVariant, value
+ * attributes to be stored in the variant-file */
+ GHashTable *attributes;
+ /* owned string, parameter (without "param-") => owned GVariant, value
+ * parameters of known type to be stored in the variant-file */
+ GHashTable *parameters;
+ /* owned string, parameter (without "param-") => owned string, value
+ * parameters of unknwn type to be stored in the variant-file */
+ GHashTable *untyped_parameters;
+ /* TRUE if the account doesn't really exist, but is here to stop us
+ * loading it from a lower-priority file */
+ gboolean absent;
+ /* TRUE if this account needs saving */
+ gboolean dirty;
+} McdDefaultStoredAccount;
+
+static GVariant *
+variant_ref0 (GVariant *v)
+{
+ return (v == NULL ? NULL : g_variant_ref (v));
+}
+
+static McdDefaultStoredAccount *
+lookup_stored_account (McdAccountManagerDefault *self,
+ const gchar *account)
+{
+ return g_hash_table_lookup (self->accounts, account);
+}
+
+static McdDefaultStoredAccount *
+ensure_stored_account (McdAccountManagerDefault *self,
+ const gchar *account)
+{
+ McdDefaultStoredAccount *sa = lookup_stored_account (self, account);
+
+ if (sa == NULL)
+ {
+ sa = g_slice_new0 (McdDefaultStoredAccount);
+ sa->attributes = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, (GDestroyNotify) g_variant_unref);
+ sa->parameters = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, (GDestroyNotify) g_variant_unref);
+ sa->untyped_parameters = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, g_free);
+ g_hash_table_insert (self->accounts, g_strdup (account), sa);
+ }
+
+ sa->absent = FALSE;
+ return sa;
+}
+
+static void
+stored_account_free (gpointer p)
+{
+ McdDefaultStoredAccount *sa = p;
+
+ g_hash_table_unref (sa->attributes);
+ g_hash_table_unref (sa->parameters);
+ g_hash_table_unref (sa->untyped_parameters);
+ g_slice_free (McdDefaultStoredAccount, sa);
+}
static void account_storage_iface_init (McpAccountStorageIface *,
gpointer);
@@ -65,22 +128,39 @@ get_old_filename (void)
}
static gchar *
-account_filename_in (const gchar *dir)
+accounts_cfg_in (const gchar *dir)
{
return g_build_filename (dir, "telepathy", "mission-control", "accounts.cfg",
NULL);
}
+static gchar *
+account_directory_in (const gchar *dir)
+{
+ return g_build_filename (dir, "telepathy", "mission-control", NULL);
+}
+
+static gchar *
+account_file_in (const gchar *dir,
+ const gchar *account)
+{
+ gchar *basename = g_strdup_printf ("%s.account", account);
+ gchar *ret;
+
+ g_strdelimit (basename, "/", '-');
+ ret = g_build_filename (dir, "telepathy", "mission-control",
+ basename, NULL);
+ g_free (basename);
+ return ret;
+}
+
static void
mcd_account_manager_default_init (McdAccountManagerDefault *self)
{
DEBUG ("mcd_account_manager_default_init");
- self->filename = account_filename_in (g_get_user_data_dir ());
- self->keyfile = g_key_file_new ();
- self->removed = g_key_file_new ();
- self->removed_accounts =
- g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
- self->save = FALSE;
+ self->directory = account_directory_in (g_get_user_data_dir ());
+ self->accounts = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
+ stored_account_free);
self->loaded = FALSE;
}
@@ -90,184 +170,433 @@ mcd_account_manager_default_class_init (McdAccountManagerDefaultClass *cls)
DEBUG ("mcd_account_manager_default_class_init");
}
-/* We happen to know that the string MC gave us is "sufficiently escaped" to
- * put it in the keyfile as-is. */
-static gboolean
-_set (const McpAccountStorage *self,
- const McpAccountManager *am,
+static McpAccountStorageSetResult
+set_parameter (McpAccountStorage *self,
+ McpAccountManager *am,
const gchar *account,
- const gchar *key,
- const gchar *val)
+ const gchar *parameter,
+ GVariant *val,
+ McpParameterFlags flags)
{
McdAccountManagerDefault *amd = MCD_ACCOUNT_MANAGER_DEFAULT (self);
+ McdDefaultStoredAccount *sa;
- amd->save = TRUE;
+ sa = lookup_stored_account (amd, account);
+ g_return_val_if_fail (sa != NULL, MCP_ACCOUNT_STORAGE_SET_RESULT_FAILED);
+ g_return_val_if_fail (!sa->absent, MCP_ACCOUNT_STORAGE_SET_RESULT_FAILED);
- if (val != NULL)
- g_key_file_set_value (amd->keyfile, account, key, val);
+ if (val == NULL)
+ {
+ gboolean changed = FALSE;
+
+ changed = g_hash_table_remove (sa->parameters, parameter);
+ /* deliberately not ||= - if we removed it from parameters, we
+ * still want to remove it from untyped_parameters if it was there */
+ changed |= g_hash_table_remove (sa->untyped_parameters, parameter);
+
+ if (!changed)
+ return MCP_ACCOUNT_STORAGE_SET_RESULT_UNCHANGED;
+ }
else
- g_key_file_remove_key (amd->keyfile, account, key, NULL);
+ {
+ GVariant *old;
+
+ old = g_hash_table_lookup (sa->parameters, parameter);
+
+ if (old != NULL && g_variant_equal (old, val))
+ {
+ return MCP_ACCOUNT_STORAGE_SET_RESULT_UNCHANGED;
+ }
+
+ /* We haven't checked whether it's in untyped_parameters with the
+ * same value - but if it is, we want to migrate it to parameters
+ * anyway (in order to record its type), so treat it as having
+ * actually changed. */
- return TRUE;
+ g_hash_table_remove (sa->untyped_parameters, parameter);
+ g_hash_table_insert (sa->parameters, g_strdup (parameter),
+ g_variant_ref (val));
+ }
+
+ sa->dirty = TRUE;
+ return MCP_ACCOUNT_STORAGE_SET_RESULT_CHANGED;
}
-static gboolean
-_get (const McpAccountStorage *self,
- const McpAccountManager *am,
+static McpAccountStorageSetResult
+set_attribute (McpAccountStorage *self,
+ McpAccountManager *am,
const gchar *account,
- const gchar *key)
+ const gchar *attribute,
+ GVariant *val,
+ McpAttributeFlags flags)
{
McdAccountManagerDefault *amd = MCD_ACCOUNT_MANAGER_DEFAULT (self);
+ McdDefaultStoredAccount *sa;
- if (key != NULL)
+ sa = lookup_stored_account (amd, account);
+ g_return_val_if_fail (sa != NULL, MCP_ACCOUNT_STORAGE_SET_RESULT_FAILED);
+ g_return_val_if_fail (!sa->absent, MCP_ACCOUNT_STORAGE_SET_RESULT_FAILED);
+
+ if (val == NULL)
+ {
+ if (!g_hash_table_remove (sa->attributes, attribute))
+ return MCP_ACCOUNT_STORAGE_SET_RESULT_UNCHANGED;
+ }
+ else
{
- gchar *v = NULL;
+ GVariant *old;
- v = g_key_file_get_value (amd->keyfile, account, key, NULL);
+ old = g_hash_table_lookup (sa->attributes, attribute);
- if (v == NULL)
- return FALSE;
+ if (old != NULL && g_variant_equal (old, val))
+ return MCP_ACCOUNT_STORAGE_SET_RESULT_UNCHANGED;
- mcp_account_manager_set_value (am, account, key, v);
- g_free (v);
+ g_hash_table_insert (sa->attributes, g_strdup (attribute),
+ g_variant_ref (val));
}
- else
- {
- gsize i;
- gsize n;
- GStrv keys = g_key_file_get_keys (amd->keyfile, account, &n, NULL);
- if (keys == NULL)
- n = 0;
+ sa->dirty = TRUE;
+ return MCP_ACCOUNT_STORAGE_SET_RESULT_CHANGED;
+}
- for (i = 0; i < n; i++)
- {
- gchar *v = g_key_file_get_value (amd->keyfile, account, keys[i], NULL);
+static GVariant *
+get_attribute (McpAccountStorage *self,
+ McpAccountManager *am,
+ const gchar *account,
+ const gchar *attribute,
+ const GVariantType *type,
+ McpAttributeFlags *flags)
+{
+ McdAccountManagerDefault *amd = MCD_ACCOUNT_MANAGER_DEFAULT (self);
+ McdDefaultStoredAccount *sa = lookup_stored_account (amd, account);
- if (v != NULL)
- mcp_account_manager_set_value (am, account, keys[i], v);
+ if (flags != NULL)
+ *flags = 0;
- g_free (v);
- }
+ g_return_val_if_fail (sa != NULL, NULL);
+ g_return_val_if_fail (!sa->absent, NULL);
- g_strfreev (keys);
- }
+ /* ignore @type, we store every attribute with its type anyway; MC will
+ * coerce values to an appropriate type if needed */
+ return variant_ref0 (g_hash_table_lookup (sa->attributes, attribute));
+}
- return TRUE;
+static GVariant *
+get_parameter (McpAccountStorage *self,
+ McpAccountManager *am,
+ const gchar *account,
+ const gchar *parameter,
+ const GVariantType *type,
+ McpParameterFlags *flags)
+{
+ McdAccountManagerDefault *amd = MCD_ACCOUNT_MANAGER_DEFAULT (self);
+ McdDefaultStoredAccount *sa = lookup_stored_account (amd, account);
+ GVariant *variant;
+ gchar *str;
+
+ if (flags != NULL)
+ *flags = 0;
+
+ g_return_val_if_fail (sa != NULL, NULL);
+ g_return_val_if_fail (!sa->absent, NULL);
+
+ variant = g_hash_table_lookup (sa->parameters, parameter);
+
+ if (variant != NULL)
+ return g_variant_ref (variant);
+
+ if (type == NULL)
+ return NULL;
+
+ str = g_hash_table_lookup (sa->untyped_parameters, parameter);
+
+ if (str == NULL)
+ return NULL;
+
+ return mcp_account_manager_unescape_variant_from_keyfile (am,
+ str, type, NULL);
+}
+
+static gchar **
+list_typed_parameters (McpAccountStorage *self,
+ McpAccountManager *am,
+ const gchar *account)
+{
+ McdAccountManagerDefault *amd = MCD_ACCOUNT_MANAGER_DEFAULT (self);
+ McdDefaultStoredAccount *sa = lookup_stored_account (amd, account);
+ GPtrArray *arr;
+ GHashTableIter iter;
+ gpointer k;
+
+ g_return_val_if_fail (sa != NULL, NULL);
+ g_return_val_if_fail (!sa->absent, NULL);
+
+ arr = g_ptr_array_sized_new (g_hash_table_size (sa->parameters) + 1);
+
+ g_hash_table_iter_init (&iter, sa->parameters);
+
+ while (g_hash_table_iter_next (&iter, &k, NULL))
+ g_ptr_array_add (arr, g_strdup (k));
+
+ g_ptr_array_add (arr, NULL);
+
+ return (gchar **) g_ptr_array_free (arr, FALSE);
+}
+
+static gchar **
+list_untyped_parameters (McpAccountStorage *self,
+ McpAccountManager *am,
+ const gchar *account)
+{
+ McdAccountManagerDefault *amd = MCD_ACCOUNT_MANAGER_DEFAULT (self);
+ McdDefaultStoredAccount *sa = lookup_stored_account (amd, account);
+ GPtrArray *arr;
+ GHashTableIter iter;
+ gpointer k;
+
+ g_return_val_if_fail (sa != NULL, NULL);
+ g_return_val_if_fail (!sa->absent, NULL);
+
+ arr = g_ptr_array_sized_new (g_hash_table_size (sa->untyped_parameters) + 1);
+
+ g_hash_table_iter_init (&iter, sa->untyped_parameters);
+
+ while (g_hash_table_iter_next (&iter, &k, NULL))
+ g_ptr_array_add (arr, g_strdup (k));
+
+ g_ptr_array_add (arr, NULL);
+
+ return (gchar **) g_ptr_array_free (arr, FALSE);
}
static gchar *
-_create (const McpAccountStorage *self,
- const McpAccountManager *am,
+_create (McpAccountStorage *self,
+ McpAccountManager *am,
const gchar *manager,
const gchar *protocol,
- GHashTable *params,
+ const gchar *identification,
GError **error)
{
+ McdAccountManagerDefault *amd = MCD_ACCOUNT_MANAGER_DEFAULT (self);
gchar *unique_name;
- /* See comment in plugin-account.c::_storage_create_account() before changing
- * this implementation, it's more subtle than it looks */
unique_name = mcp_account_manager_get_unique_name (MCP_ACCOUNT_MANAGER (am),
- manager, protocol, params);
+ manager, protocol,
+ identification);
g_return_val_if_fail (unique_name != NULL, NULL);
+ ensure_stored_account (amd, unique_name);
return unique_name;
}
-static gboolean
-_delete (const McpAccountStorage *self,
- const McpAccountManager *am,
- const gchar *account,
- const gchar *key)
+static void
+delete_async (McpAccountStorage *self,
+ McpAccountManager *am,
+ const gchar *account,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
{
McdAccountManagerDefault *amd = MCD_ACCOUNT_MANAGER_DEFAULT (self);
+ McdDefaultStoredAccount *sa = lookup_stored_account (amd, account);
+ GTask *task;
+ gchar *filename = NULL;
+ const gchar * const *iter;
+
+ task = g_task_new (amd, cancellable, callback, user_data);
+
+ g_return_if_fail (sa != NULL);
+ g_return_if_fail (!sa->absent);
- if (key == NULL)
+ filename = account_file_in (g_get_user_data_dir (), account);
+
+ DEBUG ("Deleting account %s from %s", account, filename);
+
+ if (g_unlink (filename) != 0)
{
- if (g_key_file_remove_group (amd->keyfile, account, NULL))
- amd->save = TRUE;
+ int e = errno;
+
+ /* ENOENT is OK, anything else is more upsetting */
+ if (e != ENOENT)
+ {
+ WARNING ("Unable to delete %s: %s", filename,
+ g_strerror (e));
+ g_task_return_new_error (task, G_IO_ERROR, g_io_error_from_errno (e),
+ "Unable to delete %s: %s", filename, g_strerror (e));
+ goto finally;
+ }
}
- else
+
+ for (iter = g_get_system_data_dirs ();
+ iter != NULL && *iter != NULL;
+ iter++)
{
- gsize n;
- GStrv keys;
- gboolean save = FALSE;
+ gchar *other = account_file_in (*iter, account);
+ gboolean other_exists = g_file_test (other, G_FILE_TEST_EXISTS);
- save = g_key_file_remove_key (amd->keyfile, account, key, NULL);
+ g_free (other);
- if (save)
- amd->save = TRUE;
+ if (other_exists)
+ {
+ GError *error = NULL;
- keys = g_key_file_get_keys (amd->keyfile, account, &n, NULL);
+ /* There is a lower-priority file that would provide this
+ * account. We can't delete a file from XDG_DATA_DIRS which
+ * are conceptually read-only, but we can mask it with an
+ * empty file (prior art: systemd) */
+ if (!g_file_set_contents (filename, "", 0, &error))
+ {
+ g_prefix_error (&error,
+ "Unable to save empty account file to %s: ", filename);
+ WARNING ("%s", error->message);
+ g_task_return_error (task, error);
+ g_free (filename);
+ goto finally;
+ }
- /* if that was the last parameter, the account is gone too */
- if (keys == NULL || n == 0)
- {
- g_key_file_remove_group (amd->keyfile, account, NULL);
+ break;
}
-
- g_strfreev (keys);
}
- return TRUE;
+ /* clean up the mess */
+ g_hash_table_remove (amd->accounts, account);
+ mcp_account_storage_emit_deleted (self, account);
+
+ g_task_return_boolean (task, TRUE);
+
+finally:
+ g_free (filename);
+ g_object_unref (task);
}
+static gboolean
+delete_finish (McpAccountStorage *storage,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
static gboolean
-_commit (const McpAccountStorage *self,
- const McpAccountManager *am,
- const gchar *account)
+am_default_commit_one (McdAccountManagerDefault *self,
+ const gchar *account_name,
+ McdDefaultStoredAccount *sa)
{
- gsize n;
- gchar *data;
- McdAccountManagerDefault *amd = MCD_ACCOUNT_MANAGER_DEFAULT (self);
- gboolean rval = FALSE;
- gchar *dir;
+ gchar *filename;
+ GHashTableIter inner;
+ gpointer k, v;
+ GVariantBuilder params_builder;
+ GVariantBuilder attrs_builder;
+ GVariant *content;
+ gchar *content_text;
+ gboolean ret;
GError *error = NULL;
- if (!amd->save)
+ g_return_val_if_fail (sa != NULL, FALSE);
+ g_return_val_if_fail (!sa->absent, FALSE);
+
+ if (!sa->dirty)
return TRUE;
- dir = g_path_get_dirname (amd->filename);
+ if (!mcd_ensure_directory (self->directory, &error))
+ {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ return FALSE;
+ }
+
+ filename = account_file_in (g_get_user_data_dir (), account_name);
+
+ DEBUG ("Saving account %s to %s", account_name, filename);
+
+ g_variant_builder_init (&attrs_builder, G_VARIANT_TYPE_VARDICT);
- DEBUG ("Saving accounts to %s", amd->filename);
+ g_hash_table_iter_init (&inner, sa->attributes);
- if (!mcd_ensure_directory (dir, &error))
+ while (g_hash_table_iter_next (&inner, &k, &v))
{
- g_warning ("%s", error->message);
- g_clear_error (&error);
- /* fall through anyway: writing to the file will fail, but it does
- * give us a chance to commit to the keyring too */
+ g_variant_builder_add (&attrs_builder, "{sv}", k, v);
+ }
+
+ g_variant_builder_init (&params_builder, G_VARIANT_TYPE ("a{sv}"));
+ g_hash_table_iter_init (&inner, sa->parameters);
+
+ while (g_hash_table_iter_next (&inner, &k, &v))
+ {
+ g_variant_builder_add (&params_builder, "{sv}", k, v);
+ }
+
+ g_variant_builder_add (&attrs_builder, "{sv}",
+ "Parameters", g_variant_builder_end (&params_builder));
+
+ g_variant_builder_init (&params_builder, G_VARIANT_TYPE ("a{ss}"));
+ g_hash_table_iter_init (&inner, sa->untyped_parameters);
+
+ while (g_hash_table_iter_next (&inner, &k, &v))
+ {
+ g_variant_builder_add (&params_builder, "{ss}", k, v);
}
- g_free (dir);
+ g_variant_builder_add (&attrs_builder, "{sv}",
+ "KeyFileParameters", g_variant_builder_end (&params_builder));
- data = g_key_file_to_data (amd->keyfile, &n, NULL);
- rval = g_file_set_contents (amd->filename, data, n, &error);
+ content = g_variant_ref_sink (g_variant_builder_end (&attrs_builder));
+ content_text = g_variant_print (content, TRUE);
+ DEBUG ("%s", content_text);
+ g_variant_unref (content);
- if (rval)
+ if (g_file_set_contents (filename, content_text, -1, &error))
{
- amd->save = FALSE;
+ sa->dirty = FALSE;
+ ret = TRUE;
}
else
{
- g_warning ("%s", error->message);
- g_error_free (error);
+ WARNING ("Unable to save account to %s: %s", filename,
+ error->message);
+ g_clear_error (&error);
+ ret = FALSE;
}
- g_free (data);
+ g_free (filename);
+ g_free (content_text);
+ return ret;
+}
- return rval;
+static gboolean
+_commit (McpAccountStorage *self,
+ McpAccountManager *am,
+ const gchar *account)
+{
+ McdAccountManagerDefault *amd = MCD_ACCOUNT_MANAGER_DEFAULT (self);
+ McdDefaultStoredAccount *sa = lookup_stored_account (amd, account);
+
+ g_return_val_if_fail (sa != NULL, FALSE);
+ g_return_val_if_fail (!sa->absent, FALSE);
+
+ DEBUG ("Saving account %s to %s", account, amd->directory);
+
+ return am_default_commit_one (amd, account, sa);
}
-static void
+static gboolean
am_default_load_keyfile (McdAccountManagerDefault *self,
const gchar *filename)
{
GError *error = NULL;
+ GKeyFile *keyfile = g_key_file_new ();
+ gsize i;
+ gsize n = 0;
+ GStrv account_tails;
+ gboolean all_ok = TRUE;
- if (g_key_file_load_from_file (self->keyfile, filename,
+ /* We shouldn't call this function without modification if we think we've
+ * migrated to a newer storage format, because it doesn't check whether
+ * each account has already been loaded. */
+ g_assert (!self->loaded);
+ g_assert (g_hash_table_size (self->accounts) == 0);
+
+ if (g_key_file_load_from_file (keyfile, filename,
G_KEY_FILE_KEEP_COMMENTS, &error))
{
DEBUG ("Loaded accounts from %s", filename);
@@ -277,33 +606,311 @@ am_default_load_keyfile (McdAccountManagerDefault *self,
DEBUG ("Failed to load accounts from %s: %s", filename, error->message);
g_error_free (error);
- /* Start with a blank configuration, but do not save straight away;
- * we don't want to overwrite a corrupt-but-maybe-recoverable
- * configuration file with an empty one until given a reason to
- * do so. */
- g_key_file_load_from_data (self->keyfile, INITIAL_CONFIG, -1,
- G_KEY_FILE_KEEP_COMMENTS, NULL);
+ /* Start with a blank configuration. */
+ g_key_file_load_from_data (keyfile, "", -1, 0, NULL);
+ /* Don't delete the old file, which might be recoverable. */
+ all_ok = FALSE;
+ }
+
+ account_tails = g_key_file_get_groups (keyfile, &n);
+
+ for (i = 0; i < n; i++)
+ {
+ const gchar *account = account_tails[i];
+ McdDefaultStoredAccount *sa = ensure_stored_account (self, account);
+ gsize j;
+ gsize m = 0;
+ GStrv keys = g_key_file_get_keys (keyfile, account, &m, NULL);
+
+ /* We're going to need to migrate this account. */
+ sa->dirty = TRUE;
+
+ for (j = 0; j < m; j++)
+ {
+ gchar *key = keys[j];
+
+ if (g_str_has_prefix (key, "param-"))
+ {
+ gchar *raw = g_key_file_get_value (keyfile, account, key, NULL);
+
+ /* steals ownership of raw */
+ g_hash_table_insert (sa->untyped_parameters, g_strdup (key + 6),
+ raw);
+ }
+ else
+ {
+ const GVariantType *type = mcd_storage_get_attribute_type (key);
+ GVariant *variant = NULL;
+
+ if (type == NULL)
+ {
+ /* go to the error code path */
+ g_set_error (&error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
+ "unknown attribute");
+ }
+ else
+ {
+ variant = mcd_keyfile_get_variant (keyfile,
+ account, key, type, &error);
+ }
+
+ if (variant == NULL)
+ {
+ WARNING ("Unable to migrate %s.%s from keyfile: %s",
+ account, key, error->message);
+ g_clear_error (&error);
+ /* Don't delete the old file, which might be recoverable. */
+ all_ok = FALSE;
+ }
+ else
+ {
+ g_hash_table_insert (sa->attributes, g_strdup (key),
+ g_variant_ref_sink (variant));
+ }
+ }
+ }
+
+ g_strfreev (keys);
+ }
+
+ g_strfreev (account_tails);
+ g_key_file_unref (keyfile);
+ return all_ok;
+}
+
+static void
+am_default_load_variant_file (McdAccountManagerDefault *self,
+ const gchar *account_tail,
+ const gchar *full_name)
+{
+ McdDefaultStoredAccount *sa;
+ gchar *text = NULL;
+ gsize len;
+ GVariant *contents = NULL;
+ GVariantIter iter;
+ const gchar *k;
+ GVariant *v;
+ GError *error = NULL;
+
+ DEBUG ("%s from %s", account_tail, full_name);
+
+ sa = lookup_stored_account (self, account_tail);
+
+ if (sa != NULL)
+ {
+ DEBUG ("Ignoring %s: account %s already %s",
+ full_name, account_tail, sa->absent ? "masked" : "loaded");
+ goto finally;
+ }
+
+ if (!g_file_get_contents (full_name, &text, &len, &error))
+ {
+ WARNING ("Unable to read account %s from %s: %s",
+ account_tail, full_name, error->message);
+ g_error_free (error);
+ goto finally;
+ }
+
+ if (len == 0)
+ {
+ DEBUG ("Empty file %s masks account %s", full_name, account_tail);
+ ensure_stored_account (self, account_tail)->absent = TRUE;
+ goto finally;
+ }
+
+ contents = g_variant_parse (G_VARIANT_TYPE_VARDICT,
+ text, text + len, NULL, &error);
+
+ if (contents == NULL)
+ {
+ WARNING ("Unable to parse account %s from %s: %s",
+ account_tail, full_name, error->message);
+ g_error_free (error);
+ goto finally;
+ }
+
+ sa = ensure_stored_account (self, account_tail);
+
+ g_variant_iter_init (&iter, contents);
+
+ while (g_variant_iter_loop (&iter, "{sv}", &k, &v))
+ {
+ if (!tp_strdiff (k, "KeyFileParameters"))
+ {
+ GVariantIter param_iter;
+ gchar *parameter;
+ gchar *param_value;
+
+ if (!g_variant_is_of_type (v, G_VARIANT_TYPE ("a{ss}")))
+ {
+ gchar *repr = g_variant_print (v, TRUE);
+
+ WARNING ("invalid KeyFileParameters found in %s, "
+ "ignoring: %s", full_name, repr);
+ g_free (repr);
+ continue;
+ }
+
+ g_variant_iter_init (&param_iter, v);
+
+ while (g_variant_iter_next (&param_iter, "{ss}", &parameter,
+ &param_value))
+ {
+ /* steals parameter, param_value */
+ g_hash_table_insert (sa->untyped_parameters, parameter,
+ param_value);
+ }
+ }
+ else if (!tp_strdiff (k, "Parameters"))
+ {
+ GVariantIter param_iter;
+ gchar *parameter;
+ GVariant *param_value;
+
+ if (!g_variant_is_of_type (v, G_VARIANT_TYPE ("a{sv}")))
+ {
+ gchar *repr = g_variant_print (v, TRUE);
+
+ WARNING ("invalid Parameters found in %s, "
+ "ignoring: %s", full_name, repr);
+ g_free (repr);
+ continue;
+ }
+
+ g_variant_iter_init (&param_iter, v);
+
+ while (g_variant_iter_next (&param_iter, "{sv}", &parameter,
+ &param_value))
+ {
+ /* steals parameter, param_value */
+ g_hash_table_insert (sa->parameters, parameter, param_value);
+ }
+ }
+ else
+ {
+ /* an ordinary attribute */
+ g_hash_table_insert (sa->attributes,
+ g_strdup (k), g_variant_ref (v));
+ }
+ }
+
+finally:
+ tp_clear_pointer (&contents, g_variant_unref);
+ g_free (text);
+}
+
+static void
+am_default_load_directory (McdAccountManagerDefault *self,
+ const gchar *directory)
+{
+ GDir *dir_handle;
+ const gchar *basename;
+ GRegex *regex;
+ GError *error = NULL;
+
+ dir_handle = g_dir_open (directory, 0, &error);
+
+ if (dir_handle == NULL)
+ {
+ /* We expect ENOENT. Anything else is a cause for (minor) concern. */
+ if (g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
+ DEBUG ("%s", error->message);
+ else
+ WARNING ("%s", error->message);
+
+ g_error_free (error);
+ return;
+ }
+
+ DEBUG ("Looking for accounts in %s", directory);
+
+ regex = g_regex_new ("^[A-Za-z][A-Za-z0-9_]*-" /* CM name */
+ "[A-Za-z][A-Za-z0-9_]*-" /* protocol with s/-/_/ */
+ "[A-Za-z_][A-Za-z0-9_]*\\.account$", /* account-specific part */
+ G_REGEX_DOLLAR_ENDONLY, 0, &error);
+ g_assert_no_error (error);
+
+ while ((basename = g_dir_read_name (dir_handle)) != NULL)
+ {
+ gchar *full_name;
+ gchar *account_tail;
+
+ /* skip it silently if it's obviously not an account */
+ if (!g_str_has_suffix (basename, ".account"))
+ continue;
+
+ /* We consider ourselves to have migrated to the new storage format
+ * as soon as we find something that looks as though it ought to be an
+ * account. */
+ self->loaded = TRUE;
+
+ if (!g_regex_match (regex, basename, 0, NULL))
+ {
+ WARNING ("Ignoring %s/%s: not a syntactically valid account",
+ directory, basename);
+ }
+
+ full_name = g_build_filename (directory, basename, NULL);
+ account_tail = g_strdup (basename);
+ g_strdelimit (account_tail, "-", '/');
+ g_strdelimit (account_tail, ".", '\0');
+
+ am_default_load_variant_file (self, account_tail, full_name);
+
+ g_free (account_tail);
+ g_free (full_name);
}
}
static GList *
-_list (const McpAccountStorage *self,
- const McpAccountManager *am)
+_list (McpAccountStorage *self,
+ McpAccountManager *am)
{
- gsize i;
- gsize n;
- GStrv accounts;
GList *rval = NULL;
McdAccountManagerDefault *amd = MCD_ACCOUNT_MANAGER_DEFAULT (self);
+ GHashTableIter hash_iter;
+ gchar *migrate_from = NULL;
+ gpointer k, v;
+ gboolean save = FALSE;
- if (!amd->loaded && g_file_test (amd->filename, G_FILE_TEST_EXISTS))
+ if (!amd->loaded)
{
- /* If the file exists, but loading it fails, we deliberately
- * do not fall through to the "initial configuration" case,
- * because we don't want to overwrite a corrupted file
- * with an empty one until an actual write takes place. */
- am_default_load_keyfile (amd, amd->filename);
- amd->loaded = TRUE;
+ const gchar * const *iter;
+
+ am_default_load_directory (amd, amd->directory);
+
+ /* We do this even if am_default_load_directory() succeeded, and
+ * do not stop when amd->loaded becomes true. If XDG_DATA_HOME
+ * contains gabble-jabber-example_2eexample_40com.account, that doesn't
+ * mean a directory in XDG_DATA_DIRS doesn't also contain
+ * haze-msn-example_2ehotmail_40com.account or something, which
+ * should also be loaded. */
+ for (iter = g_get_system_data_dirs ();
+ iter != NULL && *iter != NULL;
+ iter++)
+ {
+ gchar *dir = account_directory_in (*iter);
+
+ am_default_load_directory (amd, dir);
+ g_free (dir);
+ }
+ }
+
+ if (!amd->loaded)
+ {
+ migrate_from = accounts_cfg_in (g_get_user_data_dir ());
+
+ if (g_file_test (migrate_from, G_FILE_TEST_EXISTS))
+ {
+ if (!am_default_load_keyfile (amd, migrate_from))
+ tp_clear_pointer (&migrate_from, g_free);
+
+ amd->loaded = TRUE;
+ }
+ else
+ {
+ tp_clear_pointer (&migrate_from, g_free);
+ }
}
if (!amd->loaded)
@@ -314,14 +921,14 @@ _list (const McpAccountStorage *self,
iter != NULL && *iter != NULL;
iter++)
{
- gchar *filename = account_filename_in (*iter);
+ /* not setting migrate_from here - XDG_DATA_DIRS are conceptually
+ * read-only, so we don't want to delete these files */
+ gchar *filename = accounts_cfg_in (*iter);
if (g_file_test (filename, G_FILE_TEST_EXISTS))
{
am_default_load_keyfile (amd, filename);
amd->loaded = TRUE;
- /* Do not set amd->save: we don't need to write it to a
- * higher-priority directory until it actually changes. */
}
g_free (filename);
@@ -333,49 +940,99 @@ _list (const McpAccountStorage *self,
if (!amd->loaded)
{
- gchar *old_filename = get_old_filename ();
+ migrate_from = get_old_filename ();
- if (g_file_test (old_filename, G_FILE_TEST_EXISTS))
+ if (g_file_test (migrate_from, G_FILE_TEST_EXISTS))
{
- am_default_load_keyfile (amd, old_filename);
+ if (!am_default_load_keyfile (amd, migrate_from))
+ tp_clear_pointer (&migrate_from, g_free);
amd->loaded = TRUE;
- amd->save = TRUE;
-
- if (_commit (self, am, NULL))
- {
- DEBUG ("Migrated %s to new location: deleting old copy",
- old_filename);
- if (g_unlink (old_filename) != 0)
- g_warning ("Unable to delete %s: %s", old_filename,
- g_strerror (errno));
- }
+ save = TRUE;
+ }
+ else
+ {
+ tp_clear_pointer (&migrate_from, g_free);
}
-
- g_free (old_filename);
}
if (!amd->loaded)
{
DEBUG ("Creating initial account data");
- g_key_file_load_from_data (amd->keyfile, INITIAL_CONFIG, -1,
- G_KEY_FILE_KEEP_COMMENTS, NULL);
amd->loaded = TRUE;
- amd->save = TRUE;
- _commit (self, am, NULL);
+ save = TRUE;
}
- accounts = g_key_file_get_groups (amd->keyfile, &n);
+ if (!save)
+ {
+ g_hash_table_iter_init (&hash_iter, amd->accounts);
- for (i = 0; i < n; i++)
+ while (g_hash_table_iter_next (&hash_iter, NULL, &v))
+ {
+ McdDefaultStoredAccount *sa = v;
+
+ if (sa->dirty)
+ {
+ save = TRUE;
+ break;
+ }
+ }
+ }
+
+ if (save)
{
- rval = g_list_prepend (rval, g_strdup (accounts[i]));
+ gboolean all_succeeded = TRUE;
+
+ DEBUG ("Saving initial or migrated account data");
+
+ g_hash_table_iter_init (&hash_iter, amd->accounts);
+
+ while (g_hash_table_iter_next (&hash_iter, &k, &v))
+ {
+ McdDefaultStoredAccount *sa = v;
+
+ if (sa->absent)
+ continue;
+
+ if (!am_default_commit_one (amd, k, v))
+ all_succeeded = FALSE;
+ }
+
+ if (all_succeeded)
+ {
+ if (migrate_from != NULL)
+ {
+ DEBUG ("Migrated %s to new location: deleting old copy",
+ migrate_from);
+
+ if (g_unlink (migrate_from) != 0)
+ WARNING ("Unable to delete %s: %s", migrate_from,
+ g_strerror (errno));
+ }
+ }
}
- g_strfreev (accounts);
+ tp_clear_pointer (&migrate_from, g_free);
+
+ g_hash_table_iter_init (&hash_iter, amd->accounts);
+
+ while (g_hash_table_iter_next (&hash_iter, &k, &v))
+ {
+ McdDefaultStoredAccount *sa = v;
+
+ if (!sa->absent)
+ rval = g_list_prepend (rval, g_strdup (k));
+ }
return rval;
}
+static McpAccountStorageFlags
+get_flags (McpAccountStorage *storage,
+ const gchar *account)
+{
+ return MCP_ACCOUNT_STORAGE_FLAG_STORES_TYPES;
+}
+
static void
account_storage_iface_init (McpAccountStorageIface *iface,
gpointer unused G_GNUC_UNUSED)
@@ -384,11 +1041,17 @@ account_storage_iface_init (McpAccountStorageIface *iface,
iface->desc = PLUGIN_DESCRIPTION;
iface->priority = PLUGIN_PRIORITY;
- iface->get = _get;
- iface->set = _set;
+ iface->get_flags = get_flags;
+ iface->get_attribute = get_attribute;
+ iface->get_parameter = get_parameter;
+ iface->list_typed_parameters = list_typed_parameters;
+ iface->list_untyped_parameters = list_untyped_parameters;
+ iface->set_attribute = set_attribute;
+ iface->set_parameter = set_parameter;
iface->create = _create;
- iface->delete = _delete;
- iface->commit_one = _commit;
+ iface->delete_async = delete_async;
+ iface->delete_finish = delete_finish;
+ iface->commit = _commit;
iface->list = _list;
}