~elementary-os/ubuntu-package-imports/geoclue-2.0-bionic

« back to all changes in this revision

Viewing changes to src/gclue-web-source.c

  • Committer: RabbitBot
  • Date: 2018-03-13 02:28:15 UTC
  • Revision ID: rabbitbot@elementary.io-20180313022815-09zi5rzcm3grfnxe
Initial import, version 2.4.7-1ubuntu1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* vim: set et ts=8 sw=8: */
 
2
/*
 
3
 * Copyright (C) 2014 Red Hat, Inc.
 
4
 *
 
5
 * Geoclue is free software; you can redistribute it and/or modify it under
 
6
 * the terms of the GNU General Public License as published by the Free
 
7
 * Software Foundation; either version 2 of the License, or (at your option)
 
8
 * any later version.
 
9
 *
 
10
 * Geoclue is distributed in the hope that it will be useful, but WITHOUT ANY
 
11
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 
12
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 
13
 * details.
 
14
 *
 
15
 * You should have received a copy of the GNU General Public License along
 
16
 * with Geoclue; if not, write to the Free Software Foundation, Inc.,
 
17
 * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 
18
 *
 
19
 * Authors: Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
 
20
 */
 
21
 
 
22
#include <stdlib.h>
 
23
#include <glib.h>
 
24
#include <libsoup/soup.h>
 
25
#include <json-glib/json-glib.h>
 
26
#include <string.h>
 
27
#include "gclue-web-source.h"
 
28
#include "gclue-error.h"
 
29
#include "gclue-location.h"
 
30
 
 
31
/**
 
32
 * SECTION:gclue-web-source
 
33
 * @short_description: Web-based geolocation
 
34
 * @include: gclue-glib/gclue-web-source.h
 
35
 *
 
36
 * Baseclass for all sources that solely use a web resource for geolocation.
 
37
 **/
 
38
 
 
39
static gboolean
 
40
gclue_web_source_start (GClueLocationSource *source);
 
41
 
 
42
struct _GClueWebSourcePrivate {
 
43
        SoupSession *soup_session;
 
44
 
 
45
        SoupMessage *query;
 
46
 
 
47
        gulong network_changed_id;
 
48
 
 
49
        guint64 last_submitted;
 
50
 
 
51
        gboolean internet_available;
 
52
};
 
53
 
 
54
G_DEFINE_ABSTRACT_TYPE (GClueWebSource, gclue_web_source, GCLUE_TYPE_LOCATION_SOURCE)
 
55
 
 
56
static void
 
57
query_callback (SoupSession *session,
 
58
                SoupMessage *query,
 
59
                gpointer     user_data)
 
60
{
 
61
        GClueWebSource *web;
 
62
        GError *error = NULL;
 
63
        char *contents;
 
64
        char *str;
 
65
        GClueLocation *location;
 
66
        SoupURI *uri;
 
67
 
 
68
        if (query->status_code == SOUP_STATUS_CANCELLED)
 
69
                return;
 
70
 
 
71
        web = GCLUE_WEB_SOURCE (user_data);
 
72
        web->priv->query = NULL;
 
73
 
 
74
        if (query->status_code != SOUP_STATUS_OK) {
 
75
                g_warning ("Failed to query location: %s", query->reason_phrase);
 
76
                return;
 
77
        }
 
78
 
 
79
        contents = g_strndup (query->response_body->data, query->response_body->length);
 
80
        uri = soup_message_get_uri (query);
 
81
        str = soup_uri_to_string (uri, FALSE);
 
82
        g_debug ("Got following response from '%s':\n%s",
 
83
                 str,
 
84
                 contents);
 
85
        g_free (str);
 
86
        location = GCLUE_WEB_SOURCE_GET_CLASS (web)->parse_response (web,
 
87
                                                                     contents,
 
88
                                                                     &error);
 
89
        g_free (contents);
 
90
        if (error != NULL) {
 
91
                g_warning ("Failed to parse following response: %s\n%s",
 
92
                           error->message,
 
93
                           contents);
 
94
                return;
 
95
        }
 
96
 
 
97
        gclue_location_source_set_location (GCLUE_LOCATION_SOURCE (web),
 
98
                                            location);
 
99
        g_object_unref (location);
 
100
}
 
101
 
 
102
static gboolean
 
103
get_internet_available (void)
 
104
{
 
105
        GNetworkMonitor *monitor = g_network_monitor_get_default ();
 
106
        gboolean available;
 
107
 
 
108
#if GLIB_CHECK_VERSION(2, 44, 0)
 
109
        available = (g_network_monitor_get_connectivity (monitor) ==
 
110
                     G_NETWORK_CONNECTIVITY_FULL);
 
111
#else
 
112
        GSocketConnectable *connectable;
 
113
 
 
114
        connectable = g_network_address_new ("location.services.mozilla.com",
 
115
                                             80);
 
116
        available = g_network_monitor_can_reach (monitor,
 
117
                                                 connectable,
 
118
                                                 NULL,
 
119
                                                 NULL);
 
120
        g_object_unref (connectable);
 
121
#endif
 
122
 
 
123
        return available;
 
124
}
 
125
 
 
126
static void
 
127
refresh_accuracy_level (GClueWebSource *web)
 
128
{
 
129
        GClueAccuracyLevel new, existing;
 
130
        gboolean available;
 
131
 
 
132
        available = get_internet_available ();
 
133
        existing = gclue_location_source_get_available_accuracy_level
 
134
                        (GCLUE_LOCATION_SOURCE (web));
 
135
        new = GCLUE_WEB_SOURCE_GET_CLASS (web)->get_available_accuracy_level
 
136
                        (web, available);
 
137
        if (new != existing) {
 
138
                g_debug ("Available accuracy level from %s: %u",
 
139
                         G_OBJECT_TYPE_NAME (web), new);
 
140
                g_object_set (G_OBJECT (web),
 
141
                              "available-accuracy-level", new,
 
142
                              NULL);
 
143
        }
 
144
}
 
145
 
 
146
static void
 
147
on_network_changed (GNetworkMonitor *monitor G_GNUC_UNUSED,
 
148
                    gboolean         available G_GNUC_UNUSED,
 
149
                    gpointer         user_data)
 
150
{
 
151
        GClueWebSource *web = GCLUE_WEB_SOURCE (user_data);
 
152
        GError *error = NULL;
 
153
        gboolean last_available = web->priv->internet_available;
 
154
 
 
155
        refresh_accuracy_level (web);
 
156
 
 
157
        if (!gclue_location_source_get_active (GCLUE_LOCATION_SOURCE (user_data)))
 
158
                return;
 
159
 
 
160
        web->priv->internet_available = get_internet_available ();
 
161
        if (last_available == web->priv->internet_available)
 
162
                return; /* We already reacted to network change */
 
163
        if (!web->priv->internet_available) {
 
164
                g_debug ("Network unavailable");
 
165
                return;
 
166
        }
 
167
        g_debug ("Network available");
 
168
 
 
169
        if (web->priv->query != NULL)
 
170
                return;
 
171
 
 
172
        web->priv->query = GCLUE_WEB_SOURCE_GET_CLASS (web)->create_query
 
173
                                        (web,
 
174
                                         &error);
 
175
 
 
176
        if (web->priv->query == NULL) {
 
177
                g_warning ("Failed to create query: %s", error->message);
 
178
                g_error_free (error);
 
179
                return;
 
180
        }
 
181
 
 
182
        soup_session_queue_message (web->priv->soup_session,
 
183
                                    web->priv->query,
 
184
                                    query_callback,
 
185
                                    web);
 
186
}
 
187
 
 
188
static void
 
189
gclue_web_source_finalize (GObject *gsource)
 
190
{
 
191
        GClueWebSourcePrivate *priv = GCLUE_WEB_SOURCE (gsource)->priv;
 
192
 
 
193
        if (priv->network_changed_id) {
 
194
                g_signal_handler_disconnect (g_network_monitor_get_default (),
 
195
                                             priv->network_changed_id);
 
196
                priv->network_changed_id = 0;
 
197
        }
 
198
 
 
199
        if (priv->query != NULL) {
 
200
                g_debug ("Cancelling query");
 
201
                soup_session_cancel_message (priv->soup_session,
 
202
                                             priv->query,
 
203
                                             SOUP_STATUS_CANCELLED);
 
204
                priv->query = NULL;
 
205
        }
 
206
 
 
207
        g_clear_object (&priv->soup_session);
 
208
 
 
209
        G_OBJECT_CLASS (gclue_web_source_parent_class)->finalize (gsource);
 
210
}
 
211
 
 
212
static void
 
213
gclue_web_source_constructed (GObject *object)
 
214
{
 
215
        GNetworkMonitor *monitor;
 
216
        GClueWebSourcePrivate *priv = GCLUE_WEB_SOURCE (object)->priv;
 
217
 
 
218
        G_OBJECT_CLASS (gclue_web_source_parent_class)->constructed (object);
 
219
 
 
220
        priv->soup_session = soup_session_new_with_options
 
221
                        (SOUP_SESSION_REMOVE_FEATURE_BY_TYPE,
 
222
                         SOUP_TYPE_PROXY_RESOLVER_DEFAULT,
 
223
                         NULL);
 
224
 
 
225
        monitor = g_network_monitor_get_default ();
 
226
        priv->network_changed_id =
 
227
                g_signal_connect (monitor,
 
228
                                  "network-changed",
 
229
                                  G_CALLBACK (on_network_changed),
 
230
                                  object);
 
231
        on_network_changed (NULL,
 
232
                            TRUE,
 
233
                            object);
 
234
}
 
235
 
 
236
static void
 
237
gclue_web_source_class_init (GClueWebSourceClass *klass)
 
238
{
 
239
        GClueLocationSourceClass *source_class = GCLUE_LOCATION_SOURCE_CLASS (klass);
 
240
        GObjectClass *gsource_class = G_OBJECT_CLASS (klass);
 
241
 
 
242
        source_class->start = gclue_web_source_start;
 
243
 
 
244
        gsource_class->finalize = gclue_web_source_finalize;
 
245
        gsource_class->constructed = gclue_web_source_constructed;
 
246
 
 
247
        g_type_class_add_private (klass, sizeof (GClueWebSourcePrivate));
 
248
}
 
249
 
 
250
static void
 
251
gclue_web_source_init (GClueWebSource *web)
 
252
{
 
253
        web->priv = G_TYPE_INSTANCE_GET_PRIVATE ((web), GCLUE_TYPE_WEB_SOURCE, GClueWebSourcePrivate);
 
254
}
 
255
 
 
256
/**
 
257
 * gclue_web_source_refresh:
 
258
 * @source: a #GClueWebSource
 
259
 *
 
260
 * Causes @source to refresh location and available accuracy level. Its meant
 
261
 * to be used by subclasses if they have reason to suspect location and/or
 
262
 * available accuracy level might have changed.
 
263
 **/
 
264
void
 
265
gclue_web_source_refresh (GClueWebSource *source)
 
266
{
 
267
        g_return_if_fail (GCLUE_IS_WEB_SOURCE (source));
 
268
 
 
269
        if (get_internet_available ()) {
 
270
                source->priv->internet_available = FALSE;
 
271
                on_network_changed (NULL, TRUE, source);
 
272
        }
 
273
}
 
274
 
 
275
static gboolean
 
276
gclue_web_source_start (GClueLocationSource *source)
 
277
{
 
278
        GClueLocationSourceClass *base_class;
 
279
 
 
280
        base_class = GCLUE_LOCATION_SOURCE_CLASS (gclue_web_source_parent_class);
 
281
        if (!base_class->start (source))
 
282
                return FALSE;
 
283
 
 
284
        return TRUE;
 
285
}
 
286
 
 
287
static void
 
288
submit_query_callback (SoupSession *session,
 
289
                       SoupMessage *query,
 
290
                       gpointer     user_data)
 
291
{
 
292
        SoupURI *uri;
 
293
 
 
294
        uri = soup_message_get_uri (query);
 
295
        if (query->status_code != SOUP_STATUS_OK &&
 
296
            query->status_code != SOUP_STATUS_NO_CONTENT) {
 
297
                g_warning ("Failed to submit location data to '%s': %s",
 
298
                           soup_uri_to_string (uri, FALSE),
 
299
                           query->reason_phrase);
 
300
                return;
 
301
        }
 
302
 
 
303
        g_debug ("Successfully submitted location data to '%s'",
 
304
                 soup_uri_to_string (uri, FALSE));
 
305
}
 
306
 
 
307
#define SUBMISSION_ACCURACY_THRESHOLD 100
 
308
#define SUBMISSION_TIME_THRESHOLD     60  /* seconds */
 
309
 
 
310
static void
 
311
on_submit_source_location_notify (GObject    *source_object,
 
312
                                  GParamSpec *pspec,
 
313
                                  gpointer    user_data)
 
314
{
 
315
        GClueLocationSource *source = GCLUE_LOCATION_SOURCE (source_object);
 
316
        GClueWebSource *web = GCLUE_WEB_SOURCE (user_data);
 
317
        GClueLocation *location;
 
318
        SoupMessage *query;
 
319
        GError *error = NULL;
 
320
 
 
321
        location = gclue_location_source_get_location (source);
 
322
        if (location == NULL ||
 
323
            geocode_location_get_accuracy (GEOCODE_LOCATION (location)) >
 
324
            SUBMISSION_ACCURACY_THRESHOLD ||
 
325
            geocode_location_get_timestamp (GEOCODE_LOCATION (location)) <
 
326
            web->priv->last_submitted + SUBMISSION_TIME_THRESHOLD)
 
327
                return;
 
328
 
 
329
        web->priv->last_submitted = geocode_location_get_timestamp
 
330
                (GEOCODE_LOCATION (location));
 
331
 
 
332
        if (!get_internet_available ())
 
333
                return;
 
334
 
 
335
        query = GCLUE_WEB_SOURCE_GET_CLASS (web)->create_submit_query
 
336
                                        (web,
 
337
                                         location,
 
338
                                         &error);
 
339
        if (query == NULL) {
 
340
                if (error != NULL) {
 
341
                        g_warning ("Failed to create submission query: %s",
 
342
                                   error->message);
 
343
                        g_error_free (error);
 
344
                }
 
345
 
 
346
                return;
 
347
        }
 
348
 
 
349
        soup_session_queue_message (web->priv->soup_session,
 
350
                                    query,
 
351
                                    submit_query_callback,
 
352
                                    web);
 
353
}
 
354
 
 
355
/**
 
356
 * gclue_web_source_set_submit_source:
 
357
 * @source: a #GClueWebSource
 
358
 *
 
359
 * Use this function to provide a location source to @source that is used
 
360
 * for submitting location data to resource being used by @source. This will be
 
361
 * a #GClueModemGPS but we don't assume that here, in case we later add a
 
362
 * non-modem GPS source and would like to pass that instead.
 
363
 **/
 
364
void
 
365
gclue_web_source_set_submit_source (GClueWebSource      *web,
 
366
                                    GClueLocationSource *submit_source)
 
367
{
 
368
        /* Not implemented by subclass */
 
369
        if (GCLUE_WEB_SOURCE_GET_CLASS (web)->create_submit_query == NULL)
 
370
                return;
 
371
 
 
372
        g_signal_connect_object (G_OBJECT (submit_source),
 
373
                                 "notify::location",
 
374
                                 G_CALLBACK (on_submit_source_location_notify),
 
375
                                 G_OBJECT (web),
 
376
                                 0);
 
377
 
 
378
        on_submit_source_location_notify (G_OBJECT (submit_source), NULL, web);
 
379
}