/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ /* * Copyright © 2011 – 2017 Red Hat, Inc. * * 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 * Public License along with this library; if not, see . */ #include "config.h" #include #include "goaclient.h" #include "goaerror.h" G_LOCK_DEFINE_STATIC (init_lock); /** * SECTION:goaclient * @title: GoaClient * @short_description: Object for accessing account information * * #GoaClient is used for accessing the GNOME Online Accounts service * from a client program. */ /** * GoaClient: * * The #GoaClient structure contains only private data and should * only be accessed using the provided API. */ struct _GoaClient { GObject parent_instance; gboolean is_initialized; GError *initialization_error; GDBusObjectManager *object_manager; }; enum { PROP_0, PROP_OBJECT_MANAGER }; enum { ACCOUNT_ADDED_SIGNAL, ACCOUNT_REMOVED_SIGNAL, ACCOUNT_CHANGED_SIGNAL, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = { 0 }; static void initable_iface_init (GInitableIface *initable_iface); static void async_initable_iface_init (GAsyncInitableIface *async_initable_iface); static void on_object_added (GDBusObjectManager *manager, GDBusObject *object, gpointer user_data); static void on_object_removed (GDBusObjectManager *manager, GDBusObject *object, gpointer user_data); static void on_interface_proxy_properties_changed (GDBusObjectManagerClient *manager, GDBusObjectProxy *object_proxy, GDBusProxy *interface_proxy, GVariant *changed_properties, const gchar* const *invalidated_properties, gpointer user_data); static void on_interface_added (GDBusObjectManager *manager, GDBusObject *object, GDBusInterface *interface, gpointer user_data); static void on_interface_removed (GDBusObjectManager *manager, GDBusObject *object, GDBusInterface *interface, gpointer user_data); G_DEFINE_TYPE_WITH_CODE (GoaClient, goa_client, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init) G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, async_initable_iface_init) ); static void goa_client_finalize (GObject *object) { GoaClient *self = GOA_CLIENT (object); if (self->initialization_error != NULL) g_error_free (self->initialization_error); if (self->object_manager != NULL) { g_signal_handlers_disconnect_by_func (self->object_manager, G_CALLBACK (on_object_added), self); g_signal_handlers_disconnect_by_func (self->object_manager, G_CALLBACK (on_object_removed), self); g_signal_handlers_disconnect_by_func (self->object_manager, G_CALLBACK (on_interface_proxy_properties_changed), self); g_signal_handlers_disconnect_by_func (self->object_manager, G_CALLBACK (on_interface_added), self); g_signal_handlers_disconnect_by_func (self->object_manager, G_CALLBACK (on_interface_removed), self); g_object_unref (self->object_manager); } G_OBJECT_CLASS (goa_client_parent_class)->finalize (object); } static void goa_client_init (GoaClient *self) { static volatile GQuark goa_error_domain = 0; /* this will force associating errors in the GOA_ERROR error domain * with org.freedesktop.Goa.Error.* errors via g_dbus_error_register_error_domain(). */ goa_error_domain = GOA_ERROR; goa_error_domain; /* shut up -Wunused-but-set-variable */ } static void goa_client_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GoaClient *self = GOA_CLIENT (object); switch (prop_id) { case PROP_OBJECT_MANAGER: g_value_set_object (value, goa_client_get_object_manager (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void goa_client_class_init (GoaClientClass *klass) { GObjectClass *gobject_class; gobject_class = G_OBJECT_CLASS (klass); gobject_class->finalize = goa_client_finalize; gobject_class->get_property = goa_client_get_property; /** * GoaClient:object-manager: * * The #GDBusObjectManager used by the #GoaClient instance. */ g_object_class_install_property (gobject_class, PROP_OBJECT_MANAGER, g_param_spec_object ("object-manager", "object manager", "The GDBusObjectManager used by the GoaClient", G_TYPE_DBUS_OBJECT_MANAGER, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); /** * GoaClient::account-added: * @client: The #GoaClient object emitting the signal. * @object: The #GoaObject for the added account. * * Emitted when @object has been added. See * goa_client_get_accounts() for information about how to use this * object. */ signals[ACCOUNT_ADDED_SIGNAL] = g_signal_new ("account-added", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GOA_TYPE_OBJECT); /** * GoaClient::account-removed: * @client: The #GoaClient object emitting the signal. * @object: The #GoaObject for the removed account. * * Emitted when @object has been removed. */ signals[ACCOUNT_REMOVED_SIGNAL] = g_signal_new ("account-removed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GOA_TYPE_OBJECT); /** * GoaClient::account-changed: * @client: The #GoaClient object emitting the signal. * @object: The #GoaObject for the account with changes. * * Emitted when something on @object changes. */ signals[ACCOUNT_CHANGED_SIGNAL] = g_signal_new ("account-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GOA_TYPE_OBJECT); } /** * goa_client_new: * @cancellable: A #GCancellable or %NULL. * @callback: Function that will be called when the result is ready. * @user_data: Data to pass to @callback. * * Asynchronously gets a #GoaClient. When the operation is * finished, @callback will be invoked in the thread-default main * loop of the thread you are calling this method from. */ void goa_client_new (GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { g_async_initable_new_async (GOA_TYPE_CLIENT, G_PRIORITY_DEFAULT, cancellable, callback, user_data, NULL); } /** * goa_client_new_finish: * @res: A #GAsyncResult. * @error: Return location for error or %NULL. * * Finishes an operation started with goa_client_new(). * * Returns: A #GoaClient or %NULL if @error is set. Free with * g_object_unref() when done with it. */ GoaClient * goa_client_new_finish (GAsyncResult *res, GError **error) { GObject *ret; GObject *source_object; source_object = g_async_result_get_source_object (res); ret = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object), res, error); g_object_unref (source_object); if (ret != NULL) return GOA_CLIENT (ret); else return NULL; } /** * goa_client_new_sync: * @cancellable: (allow-none): A #GCancellable or %NULL. * @error: (allow-none): Return location for error or %NULL. * * Synchronously gets a #GoaClient for the local system. * * Returns: A #GoaClient or %NULL if @error is set. Free with * g_object_unref() when done with it. */ GoaClient * goa_client_new_sync (GCancellable *cancellable, GError **error) { GInitable *ret; ret = g_initable_new (GOA_TYPE_CLIENT, cancellable, error, NULL); if (ret != NULL) return GOA_CLIENT (ret); else return NULL; } /* ---------------------------------------------------------------------------------------------------- */ static gboolean initable_init (GInitable *initable, GCancellable *cancellable, GError **error) { GoaClient *self = GOA_CLIENT (initable); gboolean ret = FALSE; /* This method needs to be idempotent to work with the singleton * pattern. See the docs for g_initable_init(). We implement this by * locking. */ G_LOCK (init_lock); if (self->is_initialized) { if (self->object_manager != NULL) ret = TRUE; else g_assert (self->initialization_error != NULL); goto out; } g_assert (self->initialization_error == NULL); self->object_manager = goa_object_manager_client_new_for_bus_sync (G_BUS_TYPE_SESSION, G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE, "org.gnome.OnlineAccounts", "/org/gnome/OnlineAccounts", cancellable, &self->initialization_error); if (self->object_manager == NULL) goto out; g_signal_connect (self->object_manager, "object-added", G_CALLBACK (on_object_added), self); g_signal_connect (self->object_manager, "object-removed", G_CALLBACK (on_object_removed), self); g_signal_connect (self->object_manager, "interface-proxy-properties-changed", G_CALLBACK (on_interface_proxy_properties_changed), self); g_signal_connect (self->object_manager, "interface-added", G_CALLBACK (on_interface_added), self); g_signal_connect (self->object_manager, "interface-removed", G_CALLBACK (on_interface_removed), self); ret = TRUE; out: self->is_initialized = TRUE; if (!ret) { g_assert (self->initialization_error != NULL); g_propagate_error (error, g_error_copy (self->initialization_error)); } G_UNLOCK (init_lock); return ret; } static void initable_iface_init (GInitableIface *initable_iface) { initable_iface->init = initable_init; } static void async_initable_iface_init (GAsyncInitableIface *async_initable_iface) { /* Use default implementation (e.g. run GInitable code in a thread) */ } /** * goa_client_get_object_manager: * @self: A #GoaClient. * * Gets the #GDBusObjectManager used by @self. * * Returns: (transfer none): A #GDBusObjectManager. Do not free, the * instance is owned by @self. */ GDBusObjectManager * goa_client_get_object_manager (GoaClient *self) { g_return_val_if_fail (GOA_IS_CLIENT (self), NULL); return self->object_manager; } /** * goa_client_get_manager: * @self: A #GoaClient. * * Gets the #GoaManager for @self. * * Returns: (transfer none): A #GoaManager. Do not free, the returned * object belongs to @self. */ GoaManager * goa_client_get_manager (GoaClient *self) { GDBusObject *object; GoaManager *manager = NULL; object = g_dbus_object_manager_get_object (self->object_manager, "/org/gnome/OnlineAccounts/Manager"); if (object == NULL) goto out; manager = goa_object_peek_manager (GOA_OBJECT (object)); out: g_clear_object (&object); return manager; } /** * goa_client_get_accounts: * @self: A #GoaClient. * * Gets all accounts that @self knows about. The result is a list of * #GoaObject instances where each object at least has an #GoaAccount * interface (that can be obtained via the goa_object_get_account() * method) but may also implement other interfaces such as * #GoaMail or #GoaFiles. * * Returns: (transfer full) (element-type GoaObject): A list of * #GoaObject instances that must be freed with g_list_free() after * each element has been freed with g_object_unref(). */ GList * goa_client_get_accounts (GoaClient *self) { GList *ret = NULL; GList *objects; GList *l; g_return_val_if_fail (GOA_IS_CLIENT (self), NULL); objects = g_dbus_object_manager_get_objects (self->object_manager); for (l = objects; l != NULL; l = l->next) { GoaObject *object = GOA_OBJECT (l->data); if (goa_object_peek_account (object) != NULL) ret = g_list_prepend (ret, g_object_ref (object)); } g_list_free_full (objects, g_object_unref); return ret; } /** * goa_client_lookup_by_id: * @self: A #GoaClient. * @id: The ID to look for. * * Finds and returns the #GoaObject instance whose * "Id" * D-Bus property matches @id. * * Returns: (transfer full): A #GoaObject. Free the returned * object with g_object_unref(). * * Since: 3.6 */ GoaObject * goa_client_lookup_by_id (GoaClient *self, const gchar *id) { GList *accounts; GList *l; GoaObject *ret = NULL; accounts = goa_client_get_accounts (self); for (l = accounts; l != NULL; l = g_list_next (l)) { GoaAccount *account; GoaObject *object = GOA_OBJECT (l->data); account = goa_object_peek_account (object); if (account == NULL) continue; if (g_strcmp0 (goa_account_get_id (account), id) == 0) { ret = g_object_ref (object); break; } } g_list_free_full (accounts, g_object_unref); return ret; } /* ---------------------------------------------------------------------------------------------------- */ static void on_object_added (GDBusObjectManager *manager, GDBusObject *object, gpointer user_data) { GoaClient *self = GOA_CLIENT (user_data); if (goa_object_peek_account (GOA_OBJECT (object)) != NULL) g_signal_emit (self, signals[ACCOUNT_ADDED_SIGNAL], 0, object); } static void on_object_removed (GDBusObjectManager *manager, GDBusObject *object, gpointer user_data) { GoaClient *self = GOA_CLIENT (user_data); if (goa_object_peek_account (GOA_OBJECT (object)) != NULL) g_signal_emit (self, signals[ACCOUNT_REMOVED_SIGNAL], 0, object); } static void on_interface_proxy_properties_changed (GDBusObjectManagerClient *manager, GDBusObjectProxy *object_proxy, GDBusProxy *interface_proxy, GVariant *changed_properties, const gchar* const *invalidated_properties, gpointer user_data) { GoaClient *self = GOA_CLIENT (user_data); if (goa_object_peek_account (GOA_OBJECT (object_proxy)) != NULL) g_signal_emit (self, signals[ACCOUNT_CHANGED_SIGNAL], 0, object_proxy); } static void on_interface_added (GDBusObjectManager *manager, GDBusObject *object, GDBusInterface *interface, gpointer user_data) { GoaClient *self = GOA_CLIENT (user_data); if (goa_object_peek_account (GOA_OBJECT (object)) != NULL) g_signal_emit (self, signals[ACCOUNT_CHANGED_SIGNAL], 0, object); } static void on_interface_removed (GDBusObjectManager *manager, GDBusObject *object, GDBusInterface *interface, gpointer user_data) { GoaClient *self = GOA_CLIENT (user_data); if (goa_object_peek_account (GOA_OBJECT (object)) != NULL) g_signal_emit (self, signals[ACCOUNT_CHANGED_SIGNAL], 0, object); }