1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2 /*
3  * Copyright © 2011 – 2017 Red Hat, Inc.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General
16  * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "config.h"
20 #include <glib/gi18n-lib.h>
21 
22 #include "goaclient.h"
23 #include "goaerror.h"
24 
25 G_LOCK_DEFINE_STATIC (init_lock);
26 
27 /**
28  * SECTION:goaclient
29  * @title: GoaClient
30  * @short_description: Object for accessing account information
31  *
32  * #GoaClient is used for accessing the GNOME Online Accounts service
33  * from a client program.
34  */
35 
36 /**
37  * GoaClient:
38  *
39  * The #GoaClient structure contains only private data and should
40  * only be accessed using the provided API.
41  */
42 struct _GoaClient
43 {
44   GObject parent_instance;
45 
46   gboolean is_initialized;
47   GError *initialization_error;
48 
49   GDBusObjectManager *object_manager;
50 };
51 
52 enum
53 {
54   PROP_0,
55   PROP_OBJECT_MANAGER
56 };
57 
58 enum
59 {
60   ACCOUNT_ADDED_SIGNAL,
61   ACCOUNT_REMOVED_SIGNAL,
62   ACCOUNT_CHANGED_SIGNAL,
63   LAST_SIGNAL
64 };
65 
66 static guint signals[LAST_SIGNAL] = { 0 };
67 
68 static void initable_iface_init       (GInitableIface      *initable_iface);
69 static void async_initable_iface_init (GAsyncInitableIface *async_initable_iface);
70 
71 static void on_object_added (GDBusObjectManager   *manager,
72                              GDBusObject          *object,
73                              gpointer              user_data);
74 static void on_object_removed (GDBusObjectManager   *manager,
75                                GDBusObject          *object,
76                                gpointer              user_data);
77 static void on_interface_proxy_properties_changed (GDBusObjectManagerClient   *manager,
78                                                    GDBusObjectProxy           *object_proxy,
79                                                    GDBusProxy                 *interface_proxy,
80                                                    GVariant                   *changed_properties,
81                                                    const gchar* const         *invalidated_properties,
82                                                    gpointer                    user_data);
83 static void on_interface_added (GDBusObjectManager   *manager,
84                                 GDBusObject          *object,
85                                 GDBusInterface       *interface,
86                                 gpointer              user_data);
87 static void on_interface_removed (GDBusObjectManager   *manager,
88                                   GDBusObject          *object,
89                                   GDBusInterface       *interface,
90                                   gpointer              user_data);
91 
92 G_DEFINE_TYPE_WITH_CODE (GoaClient, goa_client, G_TYPE_OBJECT,
93                          G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init)
94                          G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, async_initable_iface_init)
95                          );
96 
97 static void
goa_client_finalize(GObject * object)98 goa_client_finalize (GObject *object)
99 {
100   GoaClient *self = GOA_CLIENT (object);
101 
102   if (self->initialization_error != NULL)
103     g_error_free (self->initialization_error);
104 
105   if (self->object_manager != NULL)
106     {
107       g_signal_handlers_disconnect_by_func (self->object_manager, G_CALLBACK (on_object_added), self);
108       g_signal_handlers_disconnect_by_func (self->object_manager, G_CALLBACK (on_object_removed), self);
109       g_signal_handlers_disconnect_by_func (self->object_manager, G_CALLBACK (on_interface_proxy_properties_changed), self);
110       g_signal_handlers_disconnect_by_func (self->object_manager, G_CALLBACK (on_interface_added), self);
111       g_signal_handlers_disconnect_by_func (self->object_manager, G_CALLBACK (on_interface_removed), self);
112       g_object_unref (self->object_manager);
113     }
114 
115   G_OBJECT_CLASS (goa_client_parent_class)->finalize (object);
116 }
117 
118 static void
goa_client_init(GoaClient * self)119 goa_client_init (GoaClient *self)
120 {
121   static volatile GQuark goa_error_domain = 0;
122   /* this will force associating errors in the GOA_ERROR error domain
123    * with org.freedesktop.Goa.Error.* errors via g_dbus_error_register_error_domain().
124    */
125   goa_error_domain = GOA_ERROR;
126   goa_error_domain; /* shut up -Wunused-but-set-variable */
127 }
128 
129 static void
goa_client_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)130 goa_client_get_property (GObject    *object,
131                             guint       prop_id,
132                             GValue     *value,
133                             GParamSpec *pspec)
134 {
135   GoaClient *self = GOA_CLIENT (object);
136 
137   switch (prop_id)
138     {
139     case PROP_OBJECT_MANAGER:
140       g_value_set_object (value, goa_client_get_object_manager (self));
141       break;
142 
143     default:
144       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
145       break;
146     }
147 }
148 
149 static void
goa_client_class_init(GoaClientClass * klass)150 goa_client_class_init (GoaClientClass *klass)
151 {
152   GObjectClass *gobject_class;
153 
154   gobject_class = G_OBJECT_CLASS (klass);
155   gobject_class->finalize     = goa_client_finalize;
156   gobject_class->get_property = goa_client_get_property;
157 
158   /**
159    * GoaClient:object-manager:
160    *
161    * The #GDBusObjectManager used by the #GoaClient instance.
162    */
163   g_object_class_install_property (gobject_class,
164                                    PROP_OBJECT_MANAGER,
165                                    g_param_spec_object ("object-manager",
166                                                         "object manager",
167                                                         "The GDBusObjectManager used by the GoaClient",
168                                                         G_TYPE_DBUS_OBJECT_MANAGER,
169                                                         G_PARAM_READABLE |
170                                                         G_PARAM_STATIC_STRINGS));
171 
172   /**
173    * GoaClient::account-added:
174    * @client: The #GoaClient object emitting the signal.
175    * @object: The #GoaObject for the added account.
176    *
177    * Emitted when @object has been added. See
178    * goa_client_get_accounts() for information about how to use this
179    * object.
180    */
181   signals[ACCOUNT_ADDED_SIGNAL] =
182     g_signal_new ("account-added",
183                   G_TYPE_FROM_CLASS (klass),
184                   G_SIGNAL_RUN_LAST,
185                   0,
186                   NULL,
187                   NULL,
188                   g_cclosure_marshal_VOID__OBJECT,
189                   G_TYPE_NONE,
190                   1,
191                   GOA_TYPE_OBJECT);
192 
193   /**
194    * GoaClient::account-removed:
195    * @client: The #GoaClient object emitting the signal.
196    * @object: The #GoaObject for the removed account.
197    *
198    * Emitted when @object has been removed.
199    */
200   signals[ACCOUNT_REMOVED_SIGNAL] =
201     g_signal_new ("account-removed",
202                   G_TYPE_FROM_CLASS (klass),
203                   G_SIGNAL_RUN_LAST,
204                   0,
205                   NULL,
206                   NULL,
207                   g_cclosure_marshal_VOID__OBJECT,
208                   G_TYPE_NONE,
209                   1,
210                   GOA_TYPE_OBJECT);
211 
212   /**
213    * GoaClient::account-changed:
214    * @client: The #GoaClient object emitting the signal.
215    * @object: The #GoaObject for the account with changes.
216    *
217    * Emitted when something on @object changes.
218    */
219   signals[ACCOUNT_CHANGED_SIGNAL] =
220     g_signal_new ("account-changed",
221                   G_TYPE_FROM_CLASS (klass),
222                   G_SIGNAL_RUN_LAST,
223                   0,
224                   NULL,
225                   NULL,
226                   g_cclosure_marshal_VOID__OBJECT,
227                   G_TYPE_NONE,
228                   1,
229                   GOA_TYPE_OBJECT);
230 
231 }
232 
233 /**
234  * goa_client_new:
235  * @cancellable: A #GCancellable or %NULL.
236  * @callback: Function that will be called when the result is ready.
237  * @user_data: Data to pass to @callback.
238  *
239  * Asynchronously gets a #GoaClient. When the operation is
240  * finished, @callback will be invoked in the <link
241  * linkend="g-main-context-push-thread-default">thread-default main
242  * loop</link> of the thread you are calling this method from.
243  */
244 void
goa_client_new(GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)245 goa_client_new (GCancellable        *cancellable,
246                    GAsyncReadyCallback  callback,
247                    gpointer             user_data)
248 {
249   g_async_initable_new_async (GOA_TYPE_CLIENT,
250                               G_PRIORITY_DEFAULT,
251                               cancellable,
252                               callback,
253                               user_data,
254                               NULL);
255 }
256 
257 /**
258  * goa_client_new_finish:
259  * @res: A #GAsyncResult.
260  * @error: Return location for error or %NULL.
261  *
262  * Finishes an operation started with goa_client_new().
263  *
264  * Returns: A #GoaClient or %NULL if @error is set. Free with
265  * g_object_unref() when done with it.
266  */
267 GoaClient *
goa_client_new_finish(GAsyncResult * res,GError ** error)268 goa_client_new_finish (GAsyncResult        *res,
269                        GError             **error)
270 {
271   GObject *ret;
272   GObject *source_object;
273   source_object = g_async_result_get_source_object (res);
274   ret = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object), res, error);
275   g_object_unref (source_object);
276   if (ret != NULL)
277     return GOA_CLIENT (ret);
278   else
279     return NULL;
280 }
281 
282 /**
283  * goa_client_new_sync:
284  * @cancellable: (allow-none): A #GCancellable or %NULL.
285  * @error: (allow-none): Return location for error or %NULL.
286  *
287  * Synchronously gets a #GoaClient for the local system.
288  *
289  * Returns: A #GoaClient or %NULL if @error is set. Free with
290  * g_object_unref() when done with it.
291  */
292 GoaClient *
goa_client_new_sync(GCancellable * cancellable,GError ** error)293 goa_client_new_sync (GCancellable  *cancellable,
294                         GError       **error)
295 {
296   GInitable *ret;
297   ret = g_initable_new (GOA_TYPE_CLIENT,
298                         cancellable,
299                         error,
300                         NULL);
301   if (ret != NULL)
302     return GOA_CLIENT (ret);
303   else
304     return NULL;
305 }
306 
307 /* ---------------------------------------------------------------------------------------------------- */
308 
309 static gboolean
initable_init(GInitable * initable,GCancellable * cancellable,GError ** error)310 initable_init (GInitable     *initable,
311                GCancellable  *cancellable,
312                GError       **error)
313 {
314   GoaClient *self = GOA_CLIENT (initable);
315   gboolean ret = FALSE;
316 
317   /* This method needs to be idempotent to work with the singleton
318    * pattern. See the docs for g_initable_init(). We implement this by
319    * locking.
320    */
321   G_LOCK (init_lock);
322   if (self->is_initialized)
323     {
324       if (self->object_manager != NULL)
325         ret = TRUE;
326       else
327         g_assert (self->initialization_error != NULL);
328       goto out;
329     }
330   g_assert (self->initialization_error == NULL);
331 
332   self->object_manager = goa_object_manager_client_new_for_bus_sync (G_BUS_TYPE_SESSION,
333                                                                      G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
334                                                                      "org.gnome.OnlineAccounts",
335                                                                      "/org/gnome/OnlineAccounts",
336                                                                      cancellable,
337                                                                      &self->initialization_error);
338   if (self->object_manager == NULL)
339     goto out;
340   g_signal_connect (self->object_manager,
341                     "object-added",
342                     G_CALLBACK (on_object_added),
343                     self);
344   g_signal_connect (self->object_manager,
345                     "object-removed",
346                     G_CALLBACK (on_object_removed),
347                     self);
348   g_signal_connect (self->object_manager,
349                     "interface-proxy-properties-changed",
350                     G_CALLBACK (on_interface_proxy_properties_changed),
351                     self);
352   g_signal_connect (self->object_manager,
353                     "interface-added",
354                     G_CALLBACK (on_interface_added),
355                     self);
356   g_signal_connect (self->object_manager,
357                     "interface-removed",
358                     G_CALLBACK (on_interface_removed),
359                     self);
360 
361   ret = TRUE;
362 
363 out:
364   self->is_initialized = TRUE;
365   if (!ret)
366     {
367       g_assert (self->initialization_error != NULL);
368       g_propagate_error (error, g_error_copy (self->initialization_error));
369     }
370   G_UNLOCK (init_lock);
371   return ret;
372 }
373 
374 static void
initable_iface_init(GInitableIface * initable_iface)375 initable_iface_init (GInitableIface      *initable_iface)
376 {
377   initable_iface->init = initable_init;
378 }
379 
380 static void
async_initable_iface_init(GAsyncInitableIface * async_initable_iface)381 async_initable_iface_init (GAsyncInitableIface *async_initable_iface)
382 {
383   /* Use default implementation (e.g. run GInitable code in a thread) */
384 }
385 
386 /**
387  * goa_client_get_object_manager:
388  * @self: A #GoaClient.
389  *
390  * Gets the #GDBusObjectManager used by @self.
391  *
392  * Returns: (transfer none): A #GDBusObjectManager. Do not free, the
393  * instance is owned by @self.
394  */
395 GDBusObjectManager *
goa_client_get_object_manager(GoaClient * self)396 goa_client_get_object_manager (GoaClient        *self)
397 {
398   g_return_val_if_fail (GOA_IS_CLIENT (self), NULL);
399   return self->object_manager;
400 }
401 
402 /**
403  * goa_client_get_manager:
404  * @self: A #GoaClient.
405  *
406  * Gets the #GoaManager for @self, if any.
407  *
408  * Returns: (nullable) (transfer none): A #GoaManager or %NULL. Do not
409  * free, the returned object belongs to @self.
410  */
411 GoaManager *
goa_client_get_manager(GoaClient * self)412 goa_client_get_manager (GoaClient *self)
413 {
414   GDBusObject *object;
415   GoaManager *manager = NULL;
416 
417   object = g_dbus_object_manager_get_object (self->object_manager, "/org/gnome/OnlineAccounts/Manager");
418   if (object == NULL)
419     goto out;
420 
421   manager = goa_object_peek_manager (GOA_OBJECT (object));
422 
423  out:
424   g_clear_object (&object);
425   return manager;
426 }
427 
428 /**
429  * goa_client_get_accounts:
430  * @self: A #GoaClient.
431  *
432  * Gets all accounts that @self knows about. The result is a list of
433  * #GoaObject instances where each object at least has an #GoaAccount
434  * interface (that can be obtained via the goa_object_get_account()
435  * method) but may also implement other interfaces such as
436  * #GoaMail or #GoaFiles.
437  *
438  * Returns: (transfer full) (element-type GoaObject): A list of
439  * #GoaObject instances that must be freed with g_list_free() after
440  * each element has been freed with g_object_unref().
441  */
442 GList *
goa_client_get_accounts(GoaClient * self)443 goa_client_get_accounts (GoaClient *self)
444 {
445   GList *ret = NULL;
446   GList *objects;
447   GList *l;
448 
449   g_return_val_if_fail (GOA_IS_CLIENT (self), NULL);
450 
451   objects = g_dbus_object_manager_get_objects (self->object_manager);
452   for (l = objects; l != NULL; l = l->next)
453     {
454       GoaObject *object = GOA_OBJECT (l->data);
455 
456       if (goa_object_peek_account (object) != NULL)
457         ret = g_list_prepend (ret, g_object_ref (object));
458     }
459   g_list_free_full (objects, g_object_unref);
460 
461   return ret;
462 }
463 
464 /**
465  * goa_client_lookup_by_id:
466  * @self: A #GoaClient.
467  * @id: The ID to look for.
468  *
469  * Finds and returns the #GoaObject instance whose
470  * <link
471  * linkend="gdbus-property-org-gnome-OnlineAccounts-Account.Id">"Id"</link>
472  * D-Bus property matches @id.
473  *
474  * Returns: (transfer full): A #GoaObject. Free the returned
475  * object with g_object_unref().
476  *
477  * Since: 3.6
478  */
479 GoaObject *
goa_client_lookup_by_id(GoaClient * self,const gchar * id)480 goa_client_lookup_by_id (GoaClient           *self,
481                          const gchar         *id)
482 {
483   GList *accounts;
484   GList *l;
485   GoaObject *ret = NULL;
486 
487   accounts = goa_client_get_accounts (self);
488   for (l = accounts; l != NULL; l = g_list_next (l))
489     {
490       GoaAccount *account;
491       GoaObject *object = GOA_OBJECT (l->data);
492 
493       account = goa_object_peek_account (object);
494       if (account == NULL)
495         continue;
496 
497       if (g_strcmp0 (goa_account_get_id (account), id) == 0)
498         {
499           ret = g_object_ref (object);
500           break;
501         }
502     }
503 
504   g_list_free_full (accounts, g_object_unref);
505   return ret;
506 }
507 
508 /* ---------------------------------------------------------------------------------------------------- */
509 
510 static void
on_object_added(GDBusObjectManager * manager,GDBusObject * object,gpointer user_data)511 on_object_added (GDBusObjectManager   *manager,
512                  GDBusObject          *object,
513                  gpointer              user_data)
514 {
515   GoaClient *self = GOA_CLIENT (user_data);
516   if (goa_object_peek_account (GOA_OBJECT (object)) != NULL)
517     g_signal_emit (self, signals[ACCOUNT_ADDED_SIGNAL], 0, object);
518 }
519 
520 static void
on_object_removed(GDBusObjectManager * manager,GDBusObject * object,gpointer user_data)521 on_object_removed (GDBusObjectManager   *manager,
522                    GDBusObject          *object,
523                    gpointer              user_data)
524 {
525   GoaClient *self = GOA_CLIENT (user_data);
526   if (goa_object_peek_account (GOA_OBJECT (object)) != NULL)
527     g_signal_emit (self, signals[ACCOUNT_REMOVED_SIGNAL], 0, object);
528 }
529 
530 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)531 on_interface_proxy_properties_changed (GDBusObjectManagerClient   *manager,
532                                        GDBusObjectProxy           *object_proxy,
533                                        GDBusProxy                 *interface_proxy,
534                                        GVariant                   *changed_properties,
535                                        const gchar* const         *invalidated_properties,
536                                        gpointer                    user_data)
537 {
538   GoaClient *self = GOA_CLIENT (user_data);
539   if (goa_object_peek_account (GOA_OBJECT (object_proxy)) != NULL)
540     g_signal_emit (self, signals[ACCOUNT_CHANGED_SIGNAL], 0, object_proxy);
541 }
542 
543 static void
on_interface_added(GDBusObjectManager * manager,GDBusObject * object,GDBusInterface * interface,gpointer user_data)544 on_interface_added (GDBusObjectManager   *manager,
545                     GDBusObject          *object,
546                     GDBusInterface       *interface,
547                     gpointer              user_data)
548 {
549   GoaClient *self = GOA_CLIENT (user_data);
550   if (goa_object_peek_account (GOA_OBJECT (object)) != NULL)
551     g_signal_emit (self, signals[ACCOUNT_CHANGED_SIGNAL], 0, object);
552 }
553 
554 static void
on_interface_removed(GDBusObjectManager * manager,GDBusObject * object,GDBusInterface * interface,gpointer user_data)555 on_interface_removed (GDBusObjectManager   *manager,
556                       GDBusObject          *object,
557                       GDBusInterface       *interface,
558                       gpointer              user_data)
559 {
560   GoaClient *self = GOA_CLIENT (user_data);
561   if (goa_object_peek_account (GOA_OBJECT (object)) != NULL)
562     g_signal_emit (self, signals[ACCOUNT_CHANGED_SIGNAL], 0, object);
563 }
564