1
/* vim: set et ts=8 sw=8: */
3
* Copyright (C) 2014 Red Hat, Inc.
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)
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
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
19
* Authors: Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
24
#include <libsoup/soup.h>
25
#include <json-glib/json-glib.h>
27
#include "gclue-web-source.h"
28
#include "gclue-error.h"
29
#include "gclue-location.h"
32
* SECTION:gclue-web-source
33
* @short_description: Web-based geolocation
34
* @include: gclue-glib/gclue-web-source.h
36
* Baseclass for all sources that solely use a web resource for geolocation.
40
gclue_web_source_start (GClueLocationSource *source);
42
struct _GClueWebSourcePrivate {
43
SoupSession *soup_session;
47
gulong network_changed_id;
49
guint64 last_submitted;
51
gboolean internet_available;
54
G_DEFINE_ABSTRACT_TYPE (GClueWebSource, gclue_web_source, GCLUE_TYPE_LOCATION_SOURCE)
57
query_callback (SoupSession *session,
65
GClueLocation *location;
68
if (query->status_code == SOUP_STATUS_CANCELLED)
71
web = GCLUE_WEB_SOURCE (user_data);
72
web->priv->query = NULL;
74
if (query->status_code != SOUP_STATUS_OK) {
75
g_warning ("Failed to query location: %s", query->reason_phrase);
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",
86
location = GCLUE_WEB_SOURCE_GET_CLASS (web)->parse_response (web,
91
g_warning ("Failed to parse following response: %s\n%s",
97
gclue_location_source_set_location (GCLUE_LOCATION_SOURCE (web),
99
g_object_unref (location);
103
get_internet_available (void)
105
GNetworkMonitor *monitor = g_network_monitor_get_default ();
108
#if GLIB_CHECK_VERSION(2, 44, 0)
109
available = (g_network_monitor_get_connectivity (monitor) ==
110
G_NETWORK_CONNECTIVITY_FULL);
112
GSocketConnectable *connectable;
114
connectable = g_network_address_new ("location.services.mozilla.com",
116
available = g_network_monitor_can_reach (monitor,
120
g_object_unref (connectable);
127
refresh_accuracy_level (GClueWebSource *web)
129
GClueAccuracyLevel new, existing;
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
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,
147
on_network_changed (GNetworkMonitor *monitor G_GNUC_UNUSED,
148
gboolean available G_GNUC_UNUSED,
151
GClueWebSource *web = GCLUE_WEB_SOURCE (user_data);
152
GError *error = NULL;
153
gboolean last_available = web->priv->internet_available;
155
refresh_accuracy_level (web);
157
if (!gclue_location_source_get_active (GCLUE_LOCATION_SOURCE (user_data)))
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");
167
g_debug ("Network available");
169
if (web->priv->query != NULL)
172
web->priv->query = GCLUE_WEB_SOURCE_GET_CLASS (web)->create_query
176
if (web->priv->query == NULL) {
177
g_warning ("Failed to create query: %s", error->message);
178
g_error_free (error);
182
soup_session_queue_message (web->priv->soup_session,
189
gclue_web_source_finalize (GObject *gsource)
191
GClueWebSourcePrivate *priv = GCLUE_WEB_SOURCE (gsource)->priv;
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;
199
if (priv->query != NULL) {
200
g_debug ("Cancelling query");
201
soup_session_cancel_message (priv->soup_session,
203
SOUP_STATUS_CANCELLED);
207
g_clear_object (&priv->soup_session);
209
G_OBJECT_CLASS (gclue_web_source_parent_class)->finalize (gsource);
213
gclue_web_source_constructed (GObject *object)
215
GNetworkMonitor *monitor;
216
GClueWebSourcePrivate *priv = GCLUE_WEB_SOURCE (object)->priv;
218
G_OBJECT_CLASS (gclue_web_source_parent_class)->constructed (object);
220
priv->soup_session = soup_session_new_with_options
221
(SOUP_SESSION_REMOVE_FEATURE_BY_TYPE,
222
SOUP_TYPE_PROXY_RESOLVER_DEFAULT,
225
monitor = g_network_monitor_get_default ();
226
priv->network_changed_id =
227
g_signal_connect (monitor,
229
G_CALLBACK (on_network_changed),
231
on_network_changed (NULL,
237
gclue_web_source_class_init (GClueWebSourceClass *klass)
239
GClueLocationSourceClass *source_class = GCLUE_LOCATION_SOURCE_CLASS (klass);
240
GObjectClass *gsource_class = G_OBJECT_CLASS (klass);
242
source_class->start = gclue_web_source_start;
244
gsource_class->finalize = gclue_web_source_finalize;
245
gsource_class->constructed = gclue_web_source_constructed;
247
g_type_class_add_private (klass, sizeof (GClueWebSourcePrivate));
251
gclue_web_source_init (GClueWebSource *web)
253
web->priv = G_TYPE_INSTANCE_GET_PRIVATE ((web), GCLUE_TYPE_WEB_SOURCE, GClueWebSourcePrivate);
257
* gclue_web_source_refresh:
258
* @source: a #GClueWebSource
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.
265
gclue_web_source_refresh (GClueWebSource *source)
267
g_return_if_fail (GCLUE_IS_WEB_SOURCE (source));
269
if (get_internet_available ()) {
270
source->priv->internet_available = FALSE;
271
on_network_changed (NULL, TRUE, source);
276
gclue_web_source_start (GClueLocationSource *source)
278
GClueLocationSourceClass *base_class;
280
base_class = GCLUE_LOCATION_SOURCE_CLASS (gclue_web_source_parent_class);
281
if (!base_class->start (source))
288
submit_query_callback (SoupSession *session,
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);
303
g_debug ("Successfully submitted location data to '%s'",
304
soup_uri_to_string (uri, FALSE));
307
#define SUBMISSION_ACCURACY_THRESHOLD 100
308
#define SUBMISSION_TIME_THRESHOLD 60 /* seconds */
311
on_submit_source_location_notify (GObject *source_object,
315
GClueLocationSource *source = GCLUE_LOCATION_SOURCE (source_object);
316
GClueWebSource *web = GCLUE_WEB_SOURCE (user_data);
317
GClueLocation *location;
319
GError *error = NULL;
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)
329
web->priv->last_submitted = geocode_location_get_timestamp
330
(GEOCODE_LOCATION (location));
332
if (!get_internet_available ())
335
query = GCLUE_WEB_SOURCE_GET_CLASS (web)->create_submit_query
341
g_warning ("Failed to create submission query: %s",
343
g_error_free (error);
349
soup_session_queue_message (web->priv->soup_session,
351
submit_query_callback,
356
* gclue_web_source_set_submit_source:
357
* @source: a #GClueWebSource
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.
365
gclue_web_source_set_submit_source (GClueWebSource *web,
366
GClueLocationSource *submit_source)
368
/* Not implemented by subclass */
369
if (GCLUE_WEB_SOURCE_GET_CLASS (web)->create_submit_query == NULL)
372
g_signal_connect_object (G_OBJECT (submit_source),
374
G_CALLBACK (on_submit_source_location_notify),
378
on_submit_source_location_notify (G_OBJECT (submit_source), NULL, web);