1 /* vim: set et ts=8 sw=8: */
2 /*
3  * Geoclue convenience library.
4  *
5  * Copyright 2015 Red Hat, Inc.
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20  *
21  * Author: Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
22  */
23 
24 /**
25  * SECTION: gclue-simple
26  * @title: GClueSimple
27  * @short_description: Simplified convenience API
28  *
29  * #GClueSimple make it very simple to get latest location and monitoring
30  * location updates. It takes care of the boring tasks of creating a
31  * #GClueClientProxy instance, starting it, waiting till we have a location fix
32  * and then creating a #GClueLocationProxy instance for it.
33  *
34  * Use #gclue_simple_new() or #gclue_simple_new_sync() to create a new
35  * #GClueSimple instance. Once you have a #GClueSimple instance, you can get the
36  * latest location using #gclue_simple_get_location() or reading the
37  * #GClueSimple:location property. To monitor location updates, connect to
38  * notify signal for this property.
39  *
40  * While most applications will find this API very useful, it is most
41  * useful for applications that simply want to get the current location as
42  * quickly as possible and do not care about accuracy (much).
43  */
44 
45 #include <glib/gi18n.h>
46 
47 #include "gclue-simple.h"
48 #include "gclue-helpers.h"
49 
50 #define BUS_NAME "org.freedesktop.GeoClue2"
51 
52 static void
53 gclue_simple_async_initable_init (GAsyncInitableIface *iface);
54 
55 struct _GClueSimplePrivate
56 {
57         char *desktop_id;
58         GClueAccuracyLevel accuracy_level;
59 
60         GClueClient *client;
61         GClueLocation *location;
62 
63         gulong update_id;
64 
65         GTask *task;
66         GCancellable *cancellable;
67 };
68 
69 G_DEFINE_TYPE_WITH_CODE (GClueSimple,
70                          gclue_simple,
71                          G_TYPE_OBJECT,
72                          G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE,
73                                                 gclue_simple_async_initable_init)
74                          G_ADD_PRIVATE (GClueSimple));
75 
76 enum
77 {
78         PROP_0,
79         PROP_DESKTOP_ID,
80         PROP_ACCURACY_LEVEL,
81         PROP_CLIENT,
82         PROP_LOCATION,
83         LAST_PROP
84 };
85 
86 static GParamSpec *gParamSpecs[LAST_PROP];
87 
88 static void
gclue_simple_finalize(GObject * object)89 gclue_simple_finalize (GObject *object)
90 {
91         GClueSimplePrivate *priv = GCLUE_SIMPLE (object)->priv;
92 
93         g_clear_pointer (&priv->desktop_id, g_free);
94         if (priv->update_id != 0) {
95                 g_signal_handler_disconnect (priv->client, priv->update_id);
96                 priv->update_id = 0;
97         }
98         if (priv->cancellable != NULL)
99                 g_cancellable_cancel (priv->cancellable);
100         g_clear_object (&priv->cancellable);
101         g_clear_object (&priv->client);
102         g_clear_object (&priv->location);
103         g_clear_object (&priv->task);
104 
105         /* Chain up to the parent class */
106         G_OBJECT_CLASS (gclue_simple_parent_class)->finalize (object);
107 }
108 
109 static void
gclue_simple_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)110 gclue_simple_get_property (GObject    *object,
111                            guint       prop_id,
112                            GValue     *value,
113                            GParamSpec *pspec)
114 {
115         GClueSimple *simple = GCLUE_SIMPLE (object);
116 
117         switch (prop_id) {
118         case PROP_CLIENT:
119                 g_value_set_object (value, simple->priv->client);
120                 break;
121 
122         case PROP_LOCATION:
123                 g_value_set_object (value, simple->priv->location);
124                 break;
125 
126         default:
127                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
128         }
129 }
130 
131 static void
gclue_simple_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)132 gclue_simple_set_property (GObject      *object,
133                            guint         prop_id,
134                            const GValue *value,
135                            GParamSpec   *pspec)
136 {
137         GClueSimple *simple = GCLUE_SIMPLE (object);
138 
139         switch (prop_id) {
140         case PROP_DESKTOP_ID:
141                 simple->priv->desktop_id = g_value_dup_string (value);
142                 break;
143 
144         case PROP_ACCURACY_LEVEL:
145                 simple->priv->accuracy_level = g_value_get_enum (value);
146                 break;
147 
148         default:
149                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
150         }
151 }
152 
153 static void
gclue_simple_class_init(GClueSimpleClass * klass)154 gclue_simple_class_init (GClueSimpleClass *klass)
155 {
156         GObjectClass *object_class;
157 
158         object_class = G_OBJECT_CLASS (klass);
159         object_class->finalize = gclue_simple_finalize;
160         object_class->get_property = gclue_simple_get_property;
161         object_class->set_property = gclue_simple_set_property;
162 
163         /**
164          * GClueSimple:desktop-id:
165          *
166          * The Desktop ID of the application.
167          */
168         gParamSpecs[PROP_DESKTOP_ID] = g_param_spec_string ("desktop-id",
169                                                             "DesktopID",
170                                                             "Desktop ID",
171                                                             NULL,
172                                                             G_PARAM_WRITABLE |
173                                                             G_PARAM_CONSTRUCT_ONLY);
174         g_object_class_install_property (object_class,
175                                          PROP_DESKTOP_ID,
176                                          gParamSpecs[PROP_DESKTOP_ID]);
177 
178         /**
179          * GClueSimple:accuracy-level:
180          *
181          * The requested maximum accuracy level.
182          */
183         gParamSpecs[PROP_ACCURACY_LEVEL] = g_param_spec_enum ("accuracy-level",
184                                                               "AccuracyLevel",
185                                                               "Requested accuracy level",
186                                                               GCLUE_TYPE_ACCURACY_LEVEL,
187                                                               GCLUE_ACCURACY_LEVEL_NONE,
188                                                               G_PARAM_WRITABLE |
189                                                               G_PARAM_CONSTRUCT_ONLY);
190         g_object_class_install_property (object_class,
191                                          PROP_ACCURACY_LEVEL,
192                                          gParamSpecs[PROP_ACCURACY_LEVEL]);
193 
194         /**
195          * GClueSimple:client:
196          *
197          * The client proxy.
198          */
199         gParamSpecs[PROP_CLIENT] = g_param_spec_object ("client",
200                                                         "Client",
201                                                         "Client proxy",
202                                                          GCLUE_TYPE_CLIENT_PROXY,
203                                                          G_PARAM_READABLE);
204         g_object_class_install_property (object_class,
205                                          PROP_CLIENT,
206                                          gParamSpecs[PROP_CLIENT]);
207 
208         /**
209          * GClueSimple:location:
210          *
211          * The current location.
212          */
213         gParamSpecs[PROP_LOCATION] = g_param_spec_object ("location",
214                                                           "Location",
215                                                           "Location proxy",
216                                                           GCLUE_TYPE_LOCATION_PROXY,
217                                                           G_PARAM_READABLE);
218         g_object_class_install_property (object_class,
219                                          PROP_LOCATION,
220                                          gParamSpecs[PROP_LOCATION]);
221 }
222 
223 static void
on_location_proxy_ready(GObject * source_object,GAsyncResult * res,gpointer user_data)224 on_location_proxy_ready (GObject      *source_object,
225                          GAsyncResult *res,
226                          gpointer      user_data)
227 {
228         GClueSimplePrivate *priv = GCLUE_SIMPLE (user_data)->priv;
229         GClueLocation *location;
230         GError *error = NULL;
231 
232         location = gclue_location_proxy_new_for_bus_finish (res, &error);
233         if (error != NULL) {
234                 if (priv->task != NULL) {
235                         g_task_return_error (priv->task, error);
236                         g_clear_object (&priv->task);
237                 } else {
238                         g_warning ("Failed to create location proxy: %s",
239                                    error->message);
240                 }
241 
242                 return;
243         }
244         g_clear_object (&priv->location);
245         priv->location = location;
246 
247         if (priv->task != NULL) {
248                 g_task_return_boolean (priv->task, TRUE);
249                 g_clear_object (&priv->task);
250         } else {
251                 g_object_notify (G_OBJECT (user_data), "location");
252         }
253 }
254 
255 static void
on_location_updated(GClueClient * client,const char * old_location,const char * new_location,gpointer user_data)256 on_location_updated (GClueClient *client,
257                      const char  *old_location,
258                      const char  *new_location,
259                      gpointer     user_data)
260 {
261         GClueSimplePrivate *priv = GCLUE_SIMPLE (user_data)->priv;
262 
263         if (new_location == NULL || g_strcmp0 (new_location, "/") == 0)
264                 return;
265 
266         gclue_location_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
267                                           G_DBUS_PROXY_FLAGS_NONE,
268                                           BUS_NAME,
269                                           new_location,
270                                           priv->cancellable,
271                                           on_location_proxy_ready,
272                                           user_data);
273 }
274 
275 static void
on_client_started(GObject * source_object,GAsyncResult * res,gpointer user_data)276 on_client_started (GObject      *source_object,
277                    GAsyncResult *res,
278                    gpointer      user_data)
279 {
280         GTask *task = G_TASK (user_data);
281         GClueClient *client = GCLUE_CLIENT (source_object);
282         GClueSimple *simple;
283         const char *location;
284         GError *error = NULL;
285 
286         simple = g_task_get_source_object (task);
287 
288         gclue_client_call_start_finish (client, res, &error);
289         if (error != NULL) {
290                 g_task_return_error (task, error);
291                 g_clear_object (&simple->priv->task);
292 
293                 return;
294         }
295 
296         location = gclue_client_get_location (client);
297 
298         on_location_updated (client, NULL, location, simple);
299 }
300 
301 static void
on_client_created(GObject * source_object,GAsyncResult * res,gpointer user_data)302 on_client_created (GObject      *source_object,
303                    GAsyncResult *res,
304                    gpointer      user_data)
305 {
306         GTask *task = G_TASK (user_data);
307         GClueSimple *simple = g_task_get_source_object (task);
308         GClueSimplePrivate *priv = simple->priv;
309         GError *error = NULL;
310 
311         priv->client = gclue_client_proxy_create_full_finish (res, &error);
312         if (error != NULL) {
313                 g_task_return_error (task, error);
314                 g_clear_object (&priv->task);
315 
316                 return;
317         }
318 
319         priv->task = task;
320         priv->update_id =
321                 g_signal_connect (priv->client,
322                                   "location-updated",
323                                   G_CALLBACK (on_location_updated),
324                                   simple);
325 
326         gclue_client_call_start (priv->client,
327                                  g_task_get_cancellable (task),
328                                  on_client_started,
329                                  task);
330 }
331 
332 static void
gclue_simple_init_async(GAsyncInitable * initable,int io_priority,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)333 gclue_simple_init_async (GAsyncInitable     *initable,
334                          int                 io_priority,
335                          GCancellable       *cancellable,
336                          GAsyncReadyCallback callback,
337                          gpointer            user_data)
338 {
339         GTask *task;
340         GClueSimple *simple = GCLUE_SIMPLE (initable);
341 
342         task = g_task_new (initable, cancellable, callback, user_data);
343 
344         gclue_client_proxy_create_full (simple->priv->desktop_id,
345                                         simple->priv->accuracy_level,
346                                         GCLUE_CLIENT_PROXY_CREATE_AUTO_DELETE,
347                                         cancellable,
348                                         on_client_created,
349                                         task);
350 }
351 
352 static gboolean
gclue_simple_init_finish(GAsyncInitable * initable,GAsyncResult * result,GError ** error)353 gclue_simple_init_finish (GAsyncInitable *initable,
354                           GAsyncResult   *result,
355                           GError        **error)
356 {
357         return g_task_propagate_boolean (G_TASK (result), error);
358 }
359 
360 static void
gclue_simple_async_initable_init(GAsyncInitableIface * iface)361 gclue_simple_async_initable_init (GAsyncInitableIface *iface)
362 {
363         iface->init_async = gclue_simple_init_async;
364         iface->init_finish = gclue_simple_init_finish;
365 }
366 
367 static void
gclue_simple_init(GClueSimple * simple)368 gclue_simple_init (GClueSimple *simple)
369 {
370         simple->priv = G_TYPE_INSTANCE_GET_PRIVATE (simple,
371                                                   GCLUE_TYPE_SIMPLE,
372                                                   GClueSimplePrivate);
373         simple->priv->cancellable = g_cancellable_new ();
374 }
375 
376 /**
377  * gclue_simple_new:
378  * @desktop_id: The desktop file id (the basename of the desktop file).
379  * @accuracy_level: The requested accuracy level as #GClueAccuracyLevel.
380  * @cancellable: (allow-none): A #GCancellable or %NULL.
381  * @callback: A #GAsyncReadyCallback to call when the results are ready.
382  * @user_data: User data to pass to @callback.
383  *
384  * Asynchronously creates a #GClueSimple instance. Use
385  * #gclue_simple_new_finish() to get the created #GClueSimple instance.
386  *
387  * See #gclue_simple_new_sync() for the synchronous, blocking version
388  * of this function.
389  */
390 void
gclue_simple_new(const char * desktop_id,GClueAccuracyLevel accuracy_level,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)391 gclue_simple_new (const char         *desktop_id,
392                   GClueAccuracyLevel  accuracy_level,
393                   GCancellable       *cancellable,
394                   GAsyncReadyCallback callback,
395                   gpointer            user_data)
396 {
397         g_async_initable_new_async (GCLUE_TYPE_SIMPLE,
398                                     G_PRIORITY_DEFAULT,
399                                     cancellable,
400                                     callback,
401                                     user_data,
402                                     "desktop-id", desktop_id,
403                                     "accuracy-level", accuracy_level,
404                                     NULL);
405 }
406 
407 /**
408  * gclue_simple_new_finish:
409  * @result: The #GAsyncResult obtained from the #GAsyncReadyCallback passed to
410  *          #gclue_simple_new().
411  * @error: Return location for error or %NULL.
412  *
413  * Finishes an operation started with #gclue_simple_new().
414  *
415  * Returns: (transfer full) (type GClueSimple): The constructed proxy
416  * object or %NULL if @error is set.
417  */
418 GClueSimple *
gclue_simple_new_finish(GAsyncResult * result,GError ** error)419 gclue_simple_new_finish (GAsyncResult *result,
420                          GError      **error)
421 {
422         GObject *object;
423         GObject *source_object;
424 
425         source_object = g_async_result_get_source_object (result);
426         object = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object),
427                                               result,
428                                               error);
429         g_object_unref (source_object);
430         if (object != NULL)
431                 return GCLUE_SIMPLE (object);
432         else
433                 return NULL;
434 }
435 
436 static void
on_simple_ready(GObject * source_object,GAsyncResult * res,gpointer user_data)437 on_simple_ready (GObject      *source_object,
438                  GAsyncResult *res,
439                  gpointer      user_data)
440 {
441         GClueSimple *simple;
442         GTask *task = G_TASK (user_data);
443         GMainLoop *main_loop;
444         GError *error = NULL;
445 
446         simple = gclue_simple_new_finish (res, &error);
447         if (error != NULL) {
448                 g_task_return_error (task, error);
449 
450                 goto out;
451         }
452 
453         g_task_return_pointer (task, simple, g_object_unref);
454 
455 out:
456         main_loop = g_task_get_task_data (task);
457         g_main_loop_quit (main_loop);
458 }
459 
460 /**
461  * gclue_simple_new_sync:
462  * @desktop_id: The desktop file id (the basename of the desktop file).
463  * @accuracy_level: The requested accuracy level as #GClueAccuracyLevel.
464  * @cancellable: (allow-none): A #GCancellable or %NULL.
465  * @error: Return location for error or %NULL.
466  *
467  * The synchronous and blocking version of #gclue_simple_new().
468  *
469  * Returns: (transfer full) (type GClueSimple): The new #GClueSimple object or
470  * %NULL if @error is set.
471  */
472 GClueSimple *
gclue_simple_new_sync(const char * desktop_id,GClueAccuracyLevel accuracy_level,GCancellable * cancellable,GError ** error)473 gclue_simple_new_sync (const char        *desktop_id,
474                        GClueAccuracyLevel accuracy_level,
475                        GCancellable      *cancellable,
476                        GError           **error)
477 {
478         GClueSimple *simple;
479         GMainLoop *main_loop;
480         GTask *task;
481 
482         task = g_task_new (NULL, cancellable, NULL, NULL);
483         main_loop = g_main_loop_new (NULL, FALSE);
484         g_task_set_task_data (task,
485                               main_loop,
486                               (GDestroyNotify) g_main_loop_unref);
487 
488         gclue_simple_new (desktop_id,
489                           accuracy_level,
490                           cancellable,
491                           on_simple_ready,
492                           task);
493 
494         g_main_loop_run (main_loop);
495 
496         simple = g_task_propagate_pointer (task, error);
497 
498         g_object_unref (task);
499 
500         return simple;
501 }
502 /**
503  * gclue_simple_get_client:
504  * @simple: A #GClueSimple object.
505  *
506  * Gets the client proxy.
507  *
508  * Returns: (transfer none) (type GClueClientProxy): The client object.
509  */
510 GClueClient *
gclue_simple_get_client(GClueSimple * simple)511 gclue_simple_get_client (GClueSimple *simple)
512 {
513         g_return_val_if_fail (GCLUE_IS_SIMPLE(simple), NULL);
514 
515         return simple->priv->client;
516 }
517 
518 /**
519  * gclue_simple_get_location:
520  * @simple: A #GClueSimple object.
521  *
522  * Gets the current location.
523  *
524  * Returns: (transfer none) (type GClueLocationProxy): The last known location
525  * as #GClueLocation.
526  */
527 GClueLocation *
gclue_simple_get_location(GClueSimple * simple)528 gclue_simple_get_location (GClueSimple *simple)
529 {
530         g_return_val_if_fail (GCLUE_IS_SIMPLE(simple), NULL);
531 
532         return simple->priv->location;
533 }
534