diff options
-rw-r--r-- | configure.ac | 3 | ||||
-rw-r--r-- | ubuntu-online-accounts/mc-plugin/mcp-account-manager-uoa.c | 611 |
2 files changed, 597 insertions, 17 deletions
diff --git a/configure.ac b/configure.ac index 1e359f260..290280d1c 100644 --- a/configure.ac +++ b/configure.ac @@ -76,6 +76,7 @@ NETWORK_MANAGER_REQUIRED=0.7.0 CHAMPLAIN_REQUIRED=0.12.1 CHEESE_GTK_REQUIRED=3.4.0 LIBACCOUNTS_REQUIRED=1.1 +MC_PLUGINS_REQUIRED=5.13.0 # Use --enable-maintainer-mode to disable deprecated symbols, # disable single include and enable GSEAL. If this is not a released empathy, @@ -511,7 +512,7 @@ if test "x$enable_ubuntu_online_accounts" != "xno"; then PKG_CHECK_MODULES(UOA, [ account-plugin - mission-control-plugins + mission-control-plugins >= $MC_PLUGINS_REQUIRED libaccounts-glib >= $LIBACCOUNTS_REQUIRED ], have_uoa="yes", have_uoa="no") diff --git a/ubuntu-online-accounts/mc-plugin/mcp-account-manager-uoa.c b/ubuntu-online-accounts/mc-plugin/mcp-account-manager-uoa.c index e38067718..87d966f7d 100644 --- a/ubuntu-online-accounts/mc-plugin/mcp-account-manager-uoa.c +++ b/ubuntu-online-accounts/mc-plugin/mcp-account-manager-uoa.c @@ -21,6 +21,11 @@ #include <telepathy-glib/telepathy-glib.h> +#include <libaccounts-glib/ag-account.h> +#include <libaccounts-glib/ag-account-service.h> +#include <libaccounts-glib/ag-manager.h> +#include <libaccounts-glib/ag-service.h> + #include <string.h> #include <ctype.h> @@ -31,6 +36,10 @@ #define DEBUG g_debug +#define SERVICE_TYPE "IM" +#define KEY_PREFIX "telepathy/" +#define KEY_ACCOUNT_NAME "mc-account-name" + static void account_storage_iface_init (McpAccountStorageIface *iface); G_DEFINE_TYPE_WITH_CODE (McpAccountManagerUoa, mcp_account_manager_uoa, @@ -40,11 +49,290 @@ G_DEFINE_TYPE_WITH_CODE (McpAccountManagerUoa, mcp_account_manager_uoa, struct _McpAccountManagerUoaPrivate { + McpAccountManager *am; + + AgManager *manager; + + /* alloc'ed string -> ref'ed AgAccountService + * The key is the account_name, an MC unique identifier. + * Note: There could be multiple services in this table having the same + * AgAccount, even if unlikely. */ + GHashTable *accounts; + + /* Queue of owned DelayedSignalData */ + GQueue *pending_signals; + + gboolean loaded; + gboolean ready; }; +typedef enum { + DELAYED_CREATE, + DELAYED_DELETE, +} DelayedSignal; + +typedef struct { + DelayedSignal signal; + AgAccountId account_id; +} DelayedSignalData; + + +static gchar * +_service_dup_tp_value (AgAccountService *service, + const gchar *key) +{ + gchar *real_key = g_strdup_printf (KEY_PREFIX "%s", key); + GValue value = { 0, }; + gchar *ret; + + g_value_init (&value, G_TYPE_STRING); + ag_account_service_get_value (service, real_key, &value); + ret = g_value_dup_string (&value); + g_value_unset (&value); + + return ret; +} + +static void +_service_set_tp_value (AgAccountService *service, + const gchar *key, + const gchar *value) +{ + gchar *real_key = g_strdup_printf (KEY_PREFIX "%s", key); + + if (value != NULL) + { + GValue gvalue = { 0, }; + + g_value_init (&gvalue, G_TYPE_STRING); + g_value_set_string (&gvalue, value); + ag_account_service_set_value (service, real_key, &gvalue); + g_value_unset (&gvalue); + g_free (real_key); + } + else + { + ag_account_service_set_value (service, real_key, NULL); + } +} + +/* Returns NULL if the account never has been imported into MC before */ +static gchar * +_service_dup_tp_account_name (AgAccountService *service) +{ + return _service_dup_tp_value (service, KEY_ACCOUNT_NAME); +} + +static void +_service_set_tp_account_name (AgAccountService *service, + const gchar *account_name) +{ + _service_set_tp_value (service, KEY_ACCOUNT_NAME, account_name); +} + +static void +_service_enabled_cb (AgAccountService *service, + gboolean enabled, + McpAccountManagerUoa *self) +{ + gchar *account_name = _service_dup_tp_account_name (service); + + if (!self->priv->ready || account_name == NULL) + return; + + DEBUG ("UOA account %s toggled: %s", account_name, + enabled ? "enabled" : "disabled"); + + g_signal_emit_by_name (self, "toggled", account_name, enabled); + + g_free (account_name); +} + +static void +_service_changed_cb (AgAccountService *service, + McpAccountManagerUoa *self) +{ + gchar *account_name = _service_dup_tp_account_name (service); + + if (!self->priv->ready || account_name == NULL) + return; + + DEBUG ("UOA account %s changed", account_name); + + /* FIXME: Could use ag_account_service_get_changed_fields() + * and emit "altered-one" */ + g_signal_emit_by_name (self, "altered", account_name); + + g_free (account_name); +} + +static void +_account_stored_cb (AgAccount *account, + const GError *error, + gpointer user_data) +{ + if (error != NULL) + { + DEBUG ("Error storing UOA account '%s': %s", + ag_account_get_display_name (account), + error->message); + } +} + +static gboolean +_add_service (McpAccountManagerUoa *self, + AgAccountService *service, + const gchar *account_name) +{ + DEBUG ("UOA account %s added", account_name); + + if (g_hash_table_contains (self->priv->accounts, account_name)) + { + DEBUG ("Already exists, ignoring"); + return FALSE; + } + + g_hash_table_insert (self->priv->accounts, + g_strdup (account_name), + g_object_ref (service)); + + g_signal_connect (service, "enabled", + G_CALLBACK (_service_enabled_cb), self); + g_signal_connect (service, "changed", + G_CALLBACK (_service_changed_cb), self); + + return TRUE; +} + +static void +_account_created_cb (AgManager *manager, + AgAccountId id, + McpAccountManagerUoa *self) +{ + AgAccount *account; + GList *l; + + if (!self->priv->ready) + { + DelayedSignalData *data = g_slice_new0 (DelayedSignalData); + + data->signal = DELAYED_CREATE; + data->account_id = id; + + g_queue_push_tail (self->priv->pending_signals, data); + return; + } + + account = ag_manager_get_account (self->priv->manager, id); + + l = ag_account_list_services_by_type (account, SERVICE_TYPE); + while (l != NULL) + { + AgAccountService *service = ag_account_service_new (account, l->data); + gchar *account_name = _service_dup_tp_account_name (service); + + /* If this is the first time we see this service, we have to generate an + * account_name for it. */ + if (account_name == NULL) + { + gchar *cm_name = NULL; + gchar *protocol_name = NULL; + gchar *account_param = NULL; + + cm_name = _service_dup_tp_value (service, "manager"); + protocol_name = _service_dup_tp_value (service, "protocol"); + account_param = _service_dup_tp_value (service, "param-account"); + + if (!tp_str_empty (cm_name) && + !tp_str_empty (protocol_name) && + !tp_str_empty (account_param)) + { + GHashTable *params; + + params = tp_asv_new ( + "account", G_TYPE_STRING, account_param, + NULL); + + account_name = mcp_account_manager_get_unique_name (self->priv->am, + cm_name, protocol_name, params); + _service_set_tp_account_name (service, account_name); + + ag_account_store (account, _account_stored_cb, self); + + g_hash_table_unref (params); + } + + g_free (cm_name); + g_free (protocol_name); + g_free (account_param); + } + + if (account_name != NULL) + { + if (_add_service (self, service, account_name)) + g_signal_emit_by_name (self, "created", account_name); + } + + g_free (account_name); + g_object_unref (service); + ag_service_unref (l->data); + l = g_list_delete_link (l, l); + } + + g_object_unref (account); +} + +static void +_account_deleted_cb (AgManager *manager, + AgAccountId id, + McpAccountManagerUoa *self) +{ + GHashTableIter iter; + gpointer value; + + if (!self->priv->ready) + { + DelayedSignalData *data = g_slice_new0 (DelayedSignalData); + + data->signal = DELAYED_DELETE; + data->account_id = id; + + g_queue_push_tail (self->priv->pending_signals, data); + return; + } + + g_hash_table_iter_init (&iter, self->priv->accounts); + while (g_hash_table_iter_next (&iter, NULL, &value)) + { + AgAccountService *service = value; + AgAccount *account = ag_account_service_get_account (service); + gchar *account_name; + + if (account->id != id) + continue; + + account_name = _service_dup_tp_account_name (service); + if (account_name == NULL) + continue; + + DEBUG ("UOA account %s deleted", account_name); + + g_hash_table_iter_remove (&iter); + g_signal_emit_by_name (self, "deleted", account_name); + + g_free (account_name); + } +} + static void mcp_account_manager_uoa_dispose (GObject *object) { + McpAccountManagerUoa *self = (McpAccountManagerUoa *) object; + + tp_clear_object (&self->priv->am); + tp_clear_object (&self->priv->manager); + tp_clear_pointer (&self->priv->accounts, g_hash_table_unref); + G_OBJECT_CLASS (mcp_account_manager_uoa_parent_class)->dispose (object); } @@ -55,6 +343,17 @@ mcp_account_manager_uoa_init (McpAccountManagerUoa *self) self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MCP_TYPE_ACCOUNT_MANAGER_UOA, McpAccountManagerUoaPrivate); + + self->priv->accounts = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, g_object_unref); + self->priv->pending_signals = g_queue_new (); + + self->priv->manager = ag_manager_new_for_service_type (SERVICE_TYPE); + + g_signal_connect (self->priv->manager, "account-created", + G_CALLBACK (_account_created_cb), self); + g_signal_connect (self->priv->manager, "account-deleted", + G_CALLBACK (_account_deleted_cb), self); } static void @@ -68,68 +367,347 @@ mcp_account_manager_uoa_class_init (McpAccountManagerUoaClass *klass) sizeof (McpAccountManagerUoaPrivate)); } +static void +_ensure_loaded (McpAccountManagerUoa *self) +{ + GList *services; + + if (self->priv->loaded) + return; + + self->priv->loaded = TRUE; + + g_assert (!self->priv->ready); + + services = ag_manager_get_account_services (self->priv->manager); + while (services != NULL) + { + AgAccountService *service = services->data; + AgAccount *account = ag_account_service_get_account (service); + gchar *account_name = _service_dup_tp_account_name (service); + + if (account_name != NULL) + { + /* This service was already known, we can add it now */ + _add_service (self, service, account_name); + g_free (account_name); + } + else + { + DelayedSignalData *data = g_slice_new0 (DelayedSignalData); + + /* This service was created while MC was not running, delay its + * creation until MC is ready */ + data->signal = DELAYED_CREATE; + data->account_id = account->id; + + g_queue_push_tail (self->priv->pending_signals, data); + } + + g_object_unref (services->data); + services = g_list_delete_link (services, services); + } +} + static GList * account_manager_uoa_list (const McpAccountStorage *storage, const McpAccountManager *am) { - return NULL; + McpAccountManagerUoa *self = (McpAccountManagerUoa *) storage; + GList *accounts = NULL; + GHashTableIter iter; + gpointer key; + + DEBUG (G_STRFUNC); + + _ensure_loaded (self); + + g_hash_table_iter_init (&iter, self->priv->accounts); + while (g_hash_table_iter_next (&iter, &key, NULL)) + accounts = g_list_prepend (accounts, g_strdup (key)); + + return accounts; } static gboolean account_manager_uoa_get (const McpAccountStorage *storage, const McpAccountManager *am, - const gchar *acc, + const gchar *account_name, const gchar *key) { - return FALSE; + McpAccountManagerUoa *self = (McpAccountManagerUoa *) storage; + AgAccountService *service; + AgAccount *account; + AgService *s; + gboolean handled = FALSE; + + service = g_hash_table_lookup (self->priv->accounts, account_name); + if (service == NULL) + return FALSE; + + DEBUG ("%s: %s, %s", G_STRFUNC, account_name, key); + + account = ag_account_service_get_account (service); + s = ag_account_service_get_service (service); + + /* NULL key means we want all settings */ + if (key == NULL) + { + AgAccountSettingIter iter; + const gchar *k; + const GValue *v; + + ag_account_service_settings_iter_init (service, &iter, KEY_PREFIX); + while (ag_account_service_settings_iter_next (&iter, &k, &v)) + { + if (!G_VALUE_HOLDS_STRING (v)) + continue; + + mcp_account_manager_set_value (am, account_name, + k, g_value_get_string (v)); + } + } + + /* Some special keys that are not stored in setting */ + if (key == NULL || !tp_strdiff (key, "Enabled")) + { + mcp_account_manager_set_value (am, account_name, "Enabled", + ag_account_service_get_enabled (service) ? "true" : "false"); + handled = TRUE; + } + + if (key == NULL || !tp_strdiff (key, "DisplayName")) + { + mcp_account_manager_set_value (am, account_name, "DisplayName", + ag_account_get_display_name (account)); + handled = TRUE; + } + + if (key == NULL || !tp_strdiff (key, "Service")) + { + mcp_account_manager_set_value (am, account_name, "Service", + ag_account_get_provider_name (account)); + handled = TRUE; + } + + if (key == NULL || !tp_strdiff (key, "Icon")) + { + mcp_account_manager_set_value (am, account_name, "Icon", + ag_service_get_icon_name (s)); + handled = TRUE; + } + + /* If it was none of the above, then just lookup in service' settings */ + if (!handled) + { + gchar *value = _service_dup_tp_value (service, key); + + mcp_account_manager_set_value (am, account_name, key, value); + g_free (value); + } + + return TRUE; } static gboolean account_manager_uoa_set (const McpAccountStorage *storage, const McpAccountManager *am, - const gchar *acc, + const gchar *account_name, const gchar *key, const gchar *val) { - return FALSE; + McpAccountManagerUoa *self = (McpAccountManagerUoa *) storage; + AgAccountService *service; + AgAccount *account; + + service = g_hash_table_lookup (self->priv->accounts, account_name); + if (service == NULL) + return FALSE; + + account = ag_account_service_get_account (service); + + DEBUG ("%s: %s, %s, %s", G_STRFUNC, account_name, key, val); + + if (!tp_strdiff (key, "Enabled")) + { + ag_account_set_enabled (account, !tp_strdiff (val, "true")); + } + else if (!tp_strdiff (key, "DisplayName")) + { + ag_account_set_display_name (account, val); + } + else + { + _service_set_tp_value (service, key, val); + } + + return TRUE; +} + +static gchar * +account_manager_uoa_create (const McpAccountStorage *storage, + const McpAccountManager *am, + const gchar *cm_name, + const gchar *protocol_name, + GHashTable *params, + GError **error) +{ + McpAccountManagerUoa *self = (McpAccountManagerUoa *) storage; + gchar *account_name; + AgAccount *account; + AgAccountService *service; + GList *l; + + if (!self->priv->ready) + { + g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT, + "Cannot create account before being ready"); + return NULL; + } + + DEBUG (G_STRFUNC); + + /* Create a new AgAccountService and keep it internally. This won't save it + * into persistent storage until account_manager_uoa_commit() is called. + * We assume there is only one IM service */ + account = ag_manager_create_account (self->priv->manager, protocol_name); + l = ag_account_list_services_by_type (account, SERVICE_TYPE); + if (l == NULL) + { + g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT, + "Cannot create a %s service for %s provider", + SERVICE_TYPE, protocol_name); + g_object_unref (account); + return NULL; + } + service = ag_account_service_new (account, l->data); + ag_service_list_free (l); + g_object_unref (account); + + account_name = mcp_account_manager_get_unique_name (self->priv->am, + cm_name, protocol_name, params); + _service_set_tp_account_name (service, account_name); + g_assert (_add_service (self, service, account_name)); + + /* MC will set all params on the account and commit */ + + return account_name; } static gboolean account_manager_uoa_delete (const McpAccountStorage *storage, const McpAccountManager *am, - const gchar *acc, + const gchar *account_name, const gchar *key) { - return FALSE; + McpAccountManagerUoa *self = (McpAccountManagerUoa *) storage; + AgAccountService *service; + AgAccount *account; + + service = g_hash_table_lookup (self->priv->accounts, account_name); + if (service == NULL) + return FALSE; + + account = ag_account_service_get_account (service); + + DEBUG ("%s: %s, %s", G_STRFUNC, account_name, key); + + if (key == NULL) + { + ag_account_delete (account); + g_hash_table_remove (self->priv->accounts, account_name); + } + else + { + _service_set_tp_value (service, key, NULL); + } + + return TRUE; } static gboolean account_manager_uoa_commit (const McpAccountStorage *storage, const McpAccountManager *am) { - return FALSE; + McpAccountManagerUoa *self = (McpAccountManagerUoa *) storage; + GHashTableIter iter; + gpointer value; + + DEBUG (G_STRFUNC); + + g_hash_table_iter_init (&iter, self->priv->accounts); + while (g_hash_table_iter_next (&iter, NULL, &value)) + { + AgAccountService *service = value; + AgAccount *account = ag_account_service_get_account (service); + + ag_account_store (account, _account_stored_cb, self); + } + + return TRUE; } static void account_manager_uoa_ready (const McpAccountStorage *storage, const McpAccountManager *am) { + McpAccountManagerUoa *self = (McpAccountManagerUoa *) storage; + DelayedSignalData *data; + + if (self->priv->ready) + return; + + DEBUG (G_STRFUNC); + + self->priv->ready = TRUE; + self->priv->am = g_object_ref (G_OBJECT (am)); + + while ((data = g_queue_pop_head (self->priv->pending_signals)) != NULL) + { + switch (data->signal) + { + case DELAYED_CREATE: + _account_created_cb (self->priv->manager, data->account_id, self); + break; + case DELAYED_DELETE: + _account_deleted_cb (self->priv->manager, data->account_id, self); + break; + default: + g_assert_not_reached (); + } + + g_slice_free (DelayedSignalData, data); + } + + g_queue_free (self->priv->pending_signals); + self->priv->pending_signals = NULL; } static void account_manager_uoa_get_identifier (const McpAccountStorage *storage, - const gchar *acc, + const gchar *account_name, GValue *identifier) { + McpAccountManagerUoa *self = (McpAccountManagerUoa *) storage; + AgAccountService *service; + AgAccount *account; + + service = g_hash_table_lookup (self->priv->accounts, account_name); + if (service == NULL) + return; + + account = ag_account_service_get_account (service); + + g_value_init (identifier, G_TYPE_UINT); + g_value_set_uint (identifier, account->id); } -static gchar * -account_manager_uoa_create_account (const McpAccountStorage *storage, - const gchar *cm_name, - const gchar *protocol_name, - GHashTable *params) +static guint +account_manager_uoa_get_restrictions (const McpAccountStorage *self, + const gchar *account_name) { - return NULL; + /* FIXME: We can't set Icon either, but there is no flag for that */ + return TP_STORAGE_RESTRICTION_FLAG_CANNOT_SET_SERVICE; } static void @@ -145,11 +723,12 @@ account_storage_iface_init (McpAccountStorageIface *iface) IMPLEMENT (get); IMPLEMENT (list); IMPLEMENT (set); + IMPLEMENT (create); IMPLEMENT (delete); IMPLEMENT (commit); IMPLEMENT (ready); IMPLEMENT (get_identifier); - IMPLEMENT (create_account); + IMPLEMENT (get_restrictions); #undef IMPLEMENT } |