diff -Nurw geoclue-2.6.0/.gitlab-ci.yml geoclue-2.6.0-soup-3.2/.gitlab-ci.yml --- geoclue-2.6.0/.gitlab-ci.yml 2022-02-10 21:03:30.000000000 +0300 +++ geoclue-2.6.0-soup-3.2/.gitlab-ci.yml 1970-01-01 03:00:00.000000000 +0300 @@ -1,28 +0,0 @@ -before_script: - - sed -i '/^#\sdeb-src /s/^#//' '/etc/apt/sources.list' - - apt-get --allow-unauthenticated update && apt-get build-dep --yes geoclue-2.0 - - apt-get install --yes git gobject-introspection libmm-glib-dev wget valac - - apt-get install --yes libnotify-dev xsltproc gtk-doc-tools python3-pip - - apt-get install --yes ninja-build gettext modemmanager-dev - - pip3 install meson==0.53.2 - -# Ubuntu 14.04 is not supported, see README for details -# - -ubuntu-18.04: - image: ubuntu:bionic - artifacts: - when: always - name: "bionic-${CI_COMMIT_REF_NAME}" - paths: - - "${CI_PROJECT_DIR}/build" - script: meson build && ninja -C build && ninja -C build test && ninja -C build install - -ubuntu-18.04-no-backend: - image: ubuntu:bionic - artifacts: - when: always - name: "bionic-no-backend-${CI_COMMIT_REF_NAME}" - paths: - - "${CI_PROJECT_DIR}/build" - script: meson -Denable-backend=false build && ninja -C build && ninja -C build test && ninja -C build install diff -Nurw geoclue-2.6.0/data/geoclue.conf.in geoclue-2.6.0-soup-3.2/data/geoclue.conf.in --- geoclue-2.6.0/data/geoclue.conf.in 2022-02-10 21:03:30.000000000 +0300 +++ geoclue-2.6.0-soup-3.2/data/geoclue.conf.in 2022-11-10 23:30:31.817463792 +0300 @@ -64,6 +64,12 @@ # If set to true, geoclue will automatically submit network data to Mozilla # each time it gets a GPS lock. # +# Currently, only Modem-GPS or Network NMEA sources are supported as providers +# of a location to submit (one at a time). +# If Modem-GPS source is enabled above it will be the exclusive provider +# (regardless whether the system is actually equipped with such modem), +# otherwise Network NMEA source will be considered. +# submit-data=false # URL to submission API of Mozilla Location Service. If not set, defaults to diff -Nurw geoclue-2.6.0/data/geoclue.service.in geoclue-2.6.0-soup-3.2/data/geoclue.service.in --- geoclue-2.6.0/data/geoclue.service.in 2022-02-10 21:03:30.000000000 +0300 +++ geoclue-2.6.0-soup-3.2/data/geoclue.service.in 2022-11-10 23:30:31.817463792 +0300 @@ -5,6 +5,7 @@ Type=dbus BusName=org.freedesktop.GeoClue2 User=@dbus_srv_user@ +Environment="GSETTINGS_BACKEND=memory" ExecStart=@libexecdir@/geoclue # Filesystem lockdown diff -Nurw geoclue-2.6.0/src/gclue-3g-tower.h geoclue-2.6.0-soup-3.2/src/gclue-3g-tower.h --- geoclue-2.6.0/src/gclue-3g-tower.h 2022-02-10 21:03:30.000000000 +0300 +++ geoclue-2.6.0-soup-3.2/src/gclue-3g-tower.h 2022-11-10 23:30:31.818463784 +0300 @@ -26,9 +26,12 @@ typedef enum { GCLUE_TOWER_TEC_UNKNOWN = 0, - GCLUE_TOWER_TEC_3G = 1, - GCLUE_TOWER_TEC_4G = 2, + GCLUE_TOWER_TEC_2G = 1, + GCLUE_TOWER_TEC_3G = 2, + GCLUE_TOWER_TEC_4G = 3, + GCLUE_TOWER_TEC_NO_FIX = 99, } GClueTowerTec; +# define GCLUE_TOWER_TEC_MAX_VALID GCLUE_TOWER_TEC_4G typedef struct _GClue3GTower GClue3GTower; diff -Nurw geoclue-2.6.0/src/gclue-3g.c geoclue-2.6.0-soup-3.2/src/gclue-3g.c --- geoclue-2.6.0/src/gclue-3g.c 2022-02-10 21:03:30.000000000 +0300 +++ geoclue-2.6.0-soup-3.2/src/gclue-3g.c 2022-11-10 23:30:31.819463777 +0300 @@ -24,9 +24,11 @@ #include #include #include "gclue-3g.h" +#include "gclue-3g-tower.h" #include "gclue-modem-manager.h" #include "gclue-location.h" #include "gclue-mozilla.h" +#include "gclue-wifi.h" /** * SECTION:gclue-3g @@ -35,14 +37,24 @@ * Contains functions to get the geolocation based on 3GPP cell towers. **/ +/* Should be slightly less than MAX_LOCATION_AGE in gclue-locator.c, so we don't + * get replaced by a less accurate WiFi location while still connected to a tower. + * Technically, this can only happen on the NEIGHBORHOOD accuracy level (since at + * this level WiFi does scrambling), but it won't hurt on higher ones, too. + * In seconds. + */ +#define LOCATION_3GPP_TIMEOUT (25 * 60) + +static unsigned int gclue_3g_running; + struct _GClue3GPrivate { + GClueMozilla *mozilla; GClueModem *modem; GCancellable *cancellable; gulong threeg_notify_id; - - GClue3GTower *tower; + guint location_3gpp_timeout_id; }; G_DEFINE_TYPE_WITH_CODE (GClue3G, @@ -56,6 +68,7 @@ gclue_3g_stop (GClueLocationSource *source); static SoupMessage * gclue_3g_create_query (GClueWebSource *web, + const char **query_data_description, GError **error); static SoupMessage * gclue_3g_create_submit_query (GClueWebSource *web, @@ -64,24 +77,21 @@ static GClueAccuracyLevel gclue_3g_get_available_accuracy_level (GClueWebSource *web, gboolean available); -static GClueLocation * -gclue_3g_parse_response (GClueWebSource *web, - const char *xml, - GError **error); static void on_3g_enabled (GObject *source_object, GAsyncResult *result, gpointer user_data) { - GClue3G *source = GCLUE_3G (user_data); - GError *error = NULL; + g_autoptr(GError) error = NULL; - if (!gclue_modem_enable_3g_finish (source->priv->modem, + if (!gclue_modem_enable_3g_finish (GCLUE_MODEM (source_object), result, &error)) { + if (error && !g_error_matches (error, G_IO_ERROR, + G_IO_ERROR_CANCELLED)) { g_warning ("Failed to enable 3GPP: %s", error->message); - g_error_free (error); + } } } @@ -92,23 +102,30 @@ { GClue3G *source = GCLUE_3G (user_data); GClue3GPrivate *priv = source->priv; + gboolean available_3g; + + available_3g = gclue_modem_get_is_3g_available (priv->modem); + g_debug ("3G available notify %d", (int)available_3g); gclue_web_source_refresh (GCLUE_WEB_SOURCE (source)); if (gclue_location_source_get_active (GCLUE_LOCATION_SOURCE (source)) && - gclue_modem_get_is_3g_available (priv->modem)) + available_3g) gclue_modem_enable_3g (priv->modem, priv->cancellable, on_3g_enabled, source); } -static GClueLocation * -gclue_3g_parse_response (GClueWebSource *web, - const char *content, - GError **error) +static void cancel_location_3gpp_timeout (GClue3G *g3g) { - return gclue_mozilla_parse_response (content, error); + GClue3GPrivate *priv = g3g->priv; + + if (!priv->location_3gpp_timeout_id) + return; + + g_source_remove (priv->location_3gpp_timeout_id); + priv->location_3gpp_timeout_id = 0; } static void @@ -125,7 +142,10 @@ priv->threeg_notify_id); priv->threeg_notify_id = 0; + cancel_location_3gpp_timeout (source); + g_clear_object (&priv->modem); + g_clear_object (&priv->mozilla); g_clear_object (&priv->cancellable); } @@ -142,7 +162,6 @@ source_class->stop = gclue_3g_stop; web_class->create_query = gclue_3g_create_query; web_class->create_submit_query = gclue_3g_create_submit_query; - web_class->parse_response = gclue_3g_parse_response; web_class->get_available_accuracy_level = gclue_3g_get_available_accuracy_level; } @@ -151,18 +170,26 @@ gclue_3g_init (GClue3G *source) { GClue3GPrivate *priv; + GClueWebSource *web_source = GCLUE_WEB_SOURCE (source); source->priv = gclue_3g_get_instance_private (source); priv = source->priv; priv->cancellable = g_cancellable_new (); + priv->mozilla = gclue_mozilla_get_singleton (); + gclue_web_source_set_locate_url (web_source, + gclue_mozilla_get_locate_url (priv->mozilla)); + gclue_web_source_set_submit_url (web_source, + gclue_mozilla_get_submit_url (priv->mozilla)); + priv->modem = gclue_modem_manager_get_singleton (); priv->threeg_notify_id = g_signal_connect (priv->modem, "notify::is-3g-available", G_CALLBACK (on_is_3g_available_notify), source); + priv->location_3gpp_timeout_id = 0; } static void @@ -177,36 +204,53 @@ /** * gclue_3g_new: * - * Get the #GClue3G singleton. + * Get the #GClue3G singleton, for the specified max accuracy level @level. * * Returns: (transfer full): a new ref to #GClue3G. Use g_object_unref() * when done. **/ GClue3G * -gclue_3g_get_singleton (void) +gclue_3g_get_singleton (GClueAccuracyLevel level) { - static GClue3G *source = NULL; + static GClue3G *source[] = { NULL, NULL }; + int i; + + g_return_val_if_fail (level >= GCLUE_ACCURACY_LEVEL_CITY, NULL); - if (source == NULL) { - source = g_object_new (GCLUE_TYPE_3G, + i = gclue_wifi_should_skip_bsss (level) ? 0 : 1; + if (source[i] == NULL) { + source[i] = g_object_new (GCLUE_TYPE_3G, + "accuracy-level", level, "compute-movement", FALSE, NULL); - g_object_weak_ref (G_OBJECT (source), + g_object_weak_ref (G_OBJECT (source[i]), on_3g_destroyed, - &source); + &source[i]); } else - g_object_ref (source); + g_object_ref (source[i]); - return source; + return source[i]; +} + +static gboolean +g3g_should_skip_bsss (GClue3G *g3g) +{ + GClueAccuracyLevel level; + + g_object_get (G_OBJECT (g3g), "accuracy-level", &level, NULL); + return gclue_wifi_should_skip_bsss (level); } static SoupMessage * gclue_3g_create_query (GClueWebSource *web, + const char **query_data_description, GError **error) { - GClue3GPrivate *priv = GCLUE_3G (web)->priv; + GClue3G *g3g = GCLUE_3G (web); + GClue3GPrivate *priv = g3g->priv; + gboolean skip_bss; - if (priv->tower == NULL) { + if (!gclue_mozilla_has_tower (priv->mozilla)) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED, @@ -214,7 +258,13 @@ return NULL; /* Not initialized yet */ } - return gclue_mozilla_create_query (NULL, priv->tower, error); + skip_bss = g3g_should_skip_bsss (g3g); + if (skip_bss) { + g_debug ("Will skip BSSs in query due to our accuracy level"); + } + + return gclue_mozilla_create_query (priv->mozilla, FALSE, skip_bss, + query_data_description, error); } static SoupMessage * @@ -224,7 +274,7 @@ { GClue3GPrivate *priv = GCLUE_3G (web)->priv; - if (priv->tower == NULL) { + if (!gclue_mozilla_has_tower (priv->mozilla)) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED, @@ -232,9 +282,8 @@ return NULL; /* Not initialized yet */ } - return gclue_mozilla_create_submit_query (location, - NULL, - priv->tower, + return gclue_mozilla_create_submit_query (priv->mozilla, + location, error); } @@ -249,6 +298,41 @@ return GCLUE_ACCURACY_LEVEL_NONE; } +gboolean gclue_3g_should_skip_tower (GClueAccuracyLevel level) +{ + return level < GCLUE_ACCURACY_LEVEL_NEIGHBORHOOD; +} + +static gboolean +on_location_3gpp_timeout (gpointer user_data) +{ + GClue3G *g3g = GCLUE_3G (user_data); + GClue3GPrivate *priv = g3g->priv; + + if (!gclue_mozilla_has_tower (priv->mozilla)) { + g_debug ("3GPP location timeout, but no tower"); + priv->location_3gpp_timeout_id = 0; + return G_SOURCE_REMOVE; + } + + g_debug ("3GPP location timeout, re-sending existing location"); + gclue_web_source_refresh (GCLUE_WEB_SOURCE (g3g)); + + return G_SOURCE_CONTINUE; +} + +static void set_location_3gpp_timeout (GClue3G *g3g) +{ + GClue3GPrivate *priv = g3g->priv; + + g_debug ("Scheduling new 3GPP location timeout"); + + cancel_location_3gpp_timeout (g3g); + priv->location_3gpp_timeout_id = g_timeout_add_seconds (LOCATION_3GPP_TIMEOUT, + on_location_3gpp_timeout, + g3g); +} + static void on_fix_3g (GClueModem *modem, const gchar *opc, @@ -257,15 +341,26 @@ GClueTowerTec tec, gpointer user_data) { - GClue3GPrivate *priv = GCLUE_3G (user_data)->priv; + GClue3G *g3g = GCLUE_3G (user_data); + GClue3GPrivate *priv = g3g->priv; + + g_debug ("3GPP %s fix available", + tec == GCLUE_TOWER_TEC_NO_FIX ? "no" : "new"); - if (priv->tower == NULL) - priv->tower = g_slice_new0 (GClue3GTower); - g_strlcpy (priv->tower->opc, opc, + if (tec != GCLUE_TOWER_TEC_NO_FIX) { + GClue3GTower tower; + + g_strlcpy (tower.opc, opc, GCLUE_3G_TOWER_OPERATOR_CODE_STR_LEN + 1); - priv->tower->lac = lac; - priv->tower->cell_id = cell_id; - priv->tower->tec = tec; + tower.lac = lac; + tower.cell_id = cell_id; + tower.tec = tec; + set_location_3gpp_timeout (g3g); + gclue_mozilla_set_tower (priv->mozilla, &tower); + } else { + cancel_location_3gpp_timeout (g3g); + gclue_mozilla_set_tower (priv->mozilla, NULL); + } gclue_web_source_refresh (GCLUE_WEB_SOURCE (user_data)); } @@ -286,16 +381,17 @@ if (base_result != GCLUE_LOCATION_SOURCE_START_RESULT_OK) return base_result; - if (priv->tower != NULL) { - g_slice_free (GClue3GTower, priv->tower); - priv->tower = NULL; + if (gclue_3g_running == 0) { + g_debug ("First 3GPP source starting up"); } + gclue_3g_running++; g_signal_connect (priv->modem, "fix-3g", G_CALLBACK (on_fix_3g), source); + /* Emits fix-3g signal even if the location hasn't actually changed to prime us */ if (gclue_modem_get_is_3g_available (priv->modem)) gclue_modem_enable_3g (priv->modem, priv->cancellable, @@ -307,7 +403,8 @@ static GClueLocationSourceStopResult gclue_3g_stop (GClueLocationSource *source) { - GClue3GPrivate *priv = GCLUE_3G (source)->priv; + GClue3G *g3g = GCLUE_3G (source); + GClue3GPrivate *priv = g3g->priv; GClueLocationSourceClass *base_class; GError *error = NULL; GClueLocationSourceStopResult base_result; @@ -316,13 +413,23 @@ base_class = GCLUE_LOCATION_SOURCE_CLASS (gclue_3g_parent_class); base_result = base_class->stop (source); - if (base_result == GCLUE_LOCATION_SOURCE_STOP_RESULT_STILL_USED) + if (base_result != GCLUE_LOCATION_SOURCE_STOP_RESULT_OK) return base_result; g_signal_handlers_disconnect_by_func (G_OBJECT (priv->modem), G_CALLBACK (on_fix_3g), source); + cancel_location_3gpp_timeout (g3g); + + g_assert (gclue_3g_running > 0); + gclue_3g_running--; + if (gclue_3g_running > 0) { + return base_result; + } + + g_debug ("Last 3GPP source stopping, disabling location gathering and invalidating existing tower"); + if (gclue_modem_get_is_3g_available (priv->modem)) if (!gclue_modem_disable_3g (priv->modem, priv->cancellable, @@ -332,5 +439,7 @@ g_error_free (error); } + gclue_mozilla_set_tower (priv->mozilla, NULL); + return base_result; } diff -Nurw geoclue-2.6.0/src/gclue-3g.h geoclue-2.6.0-soup-3.2/src/gclue-3g.h --- geoclue-2.6.0/src/gclue-3g.h 2022-02-10 21:03:30.000000000 +0300 +++ geoclue-2.6.0-soup-3.2/src/gclue-3g.h 2022-11-10 23:30:31.819463777 +0300 @@ -22,6 +22,8 @@ #ifndef GCLUE_3G_H #define GCLUE_3G_H +#include "config.h" + #include #include #include "gclue-web-source.h" @@ -62,7 +64,13 @@ GClueWebSourceClass parent_class; }; -GClue3G * gclue_3g_get_singleton (void); +GClue3G * gclue_3g_get_singleton (GClueAccuracyLevel level); + +#if GCLUE_USE_3G_SOURCE +gboolean gclue_3g_should_skip_tower (GClueAccuracyLevel level); +#else +static inline gboolean gclue_3g_should_skip_tower (GClueAccuracyLevel level) { return TRUE; } +#endif G_END_DECLS diff -Nurw geoclue-2.6.0/src/gclue-cdma.c geoclue-2.6.0-soup-3.2/src/gclue-cdma.c --- geoclue-2.6.0/src/gclue-cdma.c 2022-02-10 21:03:30.000000000 +0300 +++ geoclue-2.6.0-soup-3.2/src/gclue-cdma.c 2022-11-10 23:30:31.819463777 +0300 @@ -79,14 +79,15 @@ GAsyncResult *result, gpointer user_data) { - GClueCDMA *source = GCLUE_CDMA (user_data); - GError *error = NULL; + g_autoptr(GError) error = NULL; - if (!gclue_modem_enable_cdma_finish (source->priv->modem, + if (!gclue_modem_enable_cdma_finish (GCLUE_MODEM (source_object), result, &error)) { + if (error && !g_error_matches (error, G_IO_ERROR, + G_IO_ERROR_CANCELLED)) { g_warning ("Failed to enable CDMA: %s", error->message); - g_error_free (error); + } } } @@ -199,7 +200,8 @@ location = gclue_location_new (latitude, longitude, - 1000); /* Assume 1 km accuracy */ + 1000, /* Assume 1 km accuracy */ + "CDMA"); gclue_location_source_set_location (GCLUE_LOCATION_SOURCE (user_data), location); diff -Nurw geoclue-2.6.0/src/gclue-client-info.c geoclue-2.6.0-soup-3.2/src/gclue-client-info.c --- geoclue-2.6.0/src/gclue-client-info.c 2022-02-10 21:03:30.000000000 +0300 +++ geoclue-2.6.0-soup-3.2/src/gclue-client-info.c 2022-11-10 23:30:31.820463769 +0300 @@ -356,15 +356,18 @@ GTask *task = G_TASK (user_data); gpointer *info = g_task_get_source_object (task); GClueClientInfoPrivate *priv = GCLUE_CLIENT_INFO (info)->priv; + GDBusProxy *dbus_proxy; GError *error = NULL; - priv->dbus_proxy = g_dbus_proxy_new_for_bus_finish (res, &error); - if (priv->dbus_proxy == NULL) { + dbus_proxy = g_dbus_proxy_new_for_bus_finish (res, &error); + if (dbus_proxy == NULL) { g_task_return_error (task, error); g_object_unref (task); return; } + priv->dbus_proxy = dbus_proxy; + g_dbus_proxy_call (priv->dbus_proxy, "GetConnectionUnixUser", g_variant_new ("(s)", priv->bus_name), diff -Nurw geoclue-2.6.0/src/gclue-compass.c geoclue-2.6.0-soup-3.2/src/gclue-compass.c --- geoclue-2.6.0/src/gclue-compass.c 2022-02-10 21:03:30.000000000 +0300 +++ geoclue-2.6.0-soup-3.2/src/gclue-compass.c 2022-11-10 23:30:31.820463769 +0300 @@ -141,15 +141,16 @@ { GClueCompass *compass; Compass *proxy = COMPASS (source_object); - GError *error = NULL; + g_autoptr(GError) error = NULL; if (!compass_call_claim_compass_finish (proxy, res, &error)) { - if (error->code != G_IO_ERROR_CANCELLED) + if (error && !g_error_matches (error, G_IO_ERROR, + G_IO_ERROR_CANCELLED)) { g_debug ("Failed to claim IIO proxy compass: %s", error->message); - g_error_free (error); - g_object_unref (proxy); + } + g_object_unref (proxy); return; } g_debug ("IIO compass claimed"); @@ -173,14 +174,15 @@ { GClueCompass *compass; Compass *proxy; - GError *error = NULL; + g_autoptr(GError) error = NULL; proxy = compass_proxy_new_for_bus_finish (res, &error); if (proxy == NULL) { - if (error->code != G_IO_ERROR_CANCELLED) + if (error && !g_error_matches (error, G_IO_ERROR, + G_IO_ERROR_CANCELLED)) { g_debug ("Failed to connect to IIO compass proxy: %s", error->message); - g_error_free (error); + } return; } diff -Nurw geoclue-2.6.0/src/gclue-config.c geoclue-2.6.0-soup-3.2/src/gclue-config.c --- geoclue-2.6.0/src/gclue-config.c 2022-02-10 21:03:30.000000000 +0300 +++ geoclue-2.6.0-soup-3.2/src/gclue-config.c 2022-11-10 23:30:31.820463769 +0300 @@ -236,7 +236,7 @@ "url", &error); if (error != NULL) { - g_debug ("Failed to get config \"wifi/url\": %s", + g_debug ("Using the default locate URL: %s", error->message); g_clear_error (&error); priv->wifi_url = g_strdup (DEFAULT_WIFI_URL); @@ -259,7 +259,7 @@ "submission-url", &error); if (error != NULL) { - g_debug ("Failed to get config \"wifi/submission-url\": %s", + g_debug ("Using the default submit URL: %s", error->message); g_clear_error (&error); priv->wifi_submit_url = g_strdup (DEFAULT_WIFI_SUBMIT_URL); @@ -270,7 +270,7 @@ "submission-nick", &error); if (error != NULL) { - g_debug ("Failed to get config \"wifi/submission-nick\": %s", + g_debug ("Using the default submission nick: %s", error->message); g_error_free (error); priv->wifi_submit_nick = g_strdup (DEFAULT_WIFI_SUBMIT_NICK); diff -Nurw geoclue-2.6.0/src/gclue-location-source.c geoclue-2.6.0-soup-3.2/src/gclue-location-source.c --- geoclue-2.6.0/src/gclue-location-source.c 2022-02-10 21:03:30.000000000 +0300 +++ geoclue-2.6.0-soup-3.2/src/gclue-location-source.c 2022-11-10 23:30:31.820463769 +0300 @@ -419,27 +419,36 @@ priv->location = gclue_location_duplicate (location); if (priv->scramble_location) { - gdouble latitude, distance, accuracy; + gdouble latitude, distance, accuracy, scramble_range; latitude = gclue_location_get_latitude (priv->location); accuracy = gclue_location_get_accuracy (priv->location); + scramble_range = GCLUE_LOCATION_ACCURACY_NEIGHBORHOOD; + if (accuracy >= scramble_range) { + /* If the location source is already pretty inaccurate + * do just a limited range scrambling to be sure. + */ + scramble_range /= 3; + } + /* Randomization is needed to stop apps from calculationg the * actual location. */ - distance = (gdouble) g_random_int_range (1, 3); + distance = g_random_double_range (0, scramble_range); + distance /= 1000; if (g_random_boolean ()) latitude += distance * LATITUDE_IN_KM; else latitude -= distance * LATITUDE_IN_KM; - accuracy += 3000; + accuracy += scramble_range; g_object_set (G_OBJECT (priv->location), "latitude", latitude, "accuracy", accuracy, NULL); - g_debug ("location scrambled"); + g_debug ("%s location scrambled", G_OBJECT_TYPE_NAME (source)); } speed = gclue_location_get_speed (location); diff -Nurw geoclue-2.6.0/src/gclue-location.c geoclue-2.6.0-soup-3.2/src/gclue-location.c --- geoclue-2.6.0/src/gclue-location.c 2022-02-10 21:03:30.000000000 +0300 +++ geoclue-2.6.0-soup-3.2/src/gclue-location.c 2022-11-10 23:30:31.820463769 +0300 @@ -546,6 +546,7 @@ * @latitude: a valid latitude * @longitude: a valid longitude * @accuracy: accuracy of location in meters + * @description: a description for the location * * Creates a new #GClueLocation object. * @@ -554,12 +555,14 @@ GClueLocation * gclue_location_new (gdouble latitude, gdouble longitude, - gdouble accuracy) + gdouble accuracy, + const char *description) { return g_object_new (GCLUE_TYPE_LOCATION, "latitude", latitude, "longitude", longitude, "accuracy", accuracy, + "description", description, NULL); } @@ -642,6 +645,7 @@ "longitude", longitude, "accuracy", accuracy, "timestamp", timestamp, + "description", "GPS GGA", NULL); if (altitude != GCLUE_LOCATION_ALTITUDE_UNKNOWN) g_object_set (location, "altitude", altitude, NULL); @@ -689,6 +693,7 @@ "timestamp", timestamp, "speed", speed, "heading", heading, + "description", "GPS RMC", NULL); if (prev_location != NULL) { @@ -770,7 +775,7 @@ * gclue_location_duplicate: * @location: the #GClueLocation instance to duplicate. * - * Creates a new copy of @location object. + * Creates a new copy of @location object (with the same timestamp). * * Returns: a new #GClueLocation object. Use g_object_unref() when done. **/ @@ -788,6 +793,32 @@ "timestamp", location->priv->timestamp, "speed", location->priv->speed, "heading", location->priv->heading, + "description", location->priv->description, + NULL); +} + +/** + * gclue_location_duplicate_fresh: + * @location: the #GClueLocation instance to duplicate. + * + * Creates a new copy of @location object with a refreshed timestamp. + * + * Returns: a new #GClueLocation object. Use g_object_unref() when done. + **/ +GClueLocation * +gclue_location_duplicate_fresh (GClueLocation *location) +{ + g_return_val_if_fail (GCLUE_IS_LOCATION (location), NULL); + + return g_object_new + (GCLUE_TYPE_LOCATION, + "latitude", location->priv->latitude, + "longitude", location->priv->longitude, + "accuracy", location->priv->accuracy, + "altitude", location->priv->altitude, + "speed", location->priv->speed, + "heading", location->priv->heading, + "description", location->priv->description, NULL); } diff -Nurw geoclue-2.6.0/src/gclue-location.h geoclue-2.6.0-soup-3.2/src/gclue-location.h --- geoclue-2.6.0/src/gclue-location.h 2022-02-10 21:03:30.000000000 +0300 +++ geoclue-2.6.0-soup-3.2/src/gclue-location.h 2022-11-10 23:30:31.820463769 +0300 @@ -77,6 +77,13 @@ #define GCLUE_LOCATION_ACCURACY_UNKNOWN -1 /** + * GCLUE_LOCATION_ACCURACY_EXACT: + * + * Constant representing exact-level accuracy. + */ +#define GCLUE_LOCATION_ACCURACY_EXACT 50 /* 50 m */ + +/** * GCLUE_LOCATION_ACCURACY_STREET: * * Constant representing street-level accuracy. @@ -84,6 +91,13 @@ #define GCLUE_LOCATION_ACCURACY_STREET 1000 /* 1 km */ /** + * GCLUE_LOCATION_ACCURACY_NEIGHBORHOOD: + * + * Constant representing neighborhood-level accuracy. + */ +#define GCLUE_LOCATION_ACCURACY_NEIGHBORHOOD 3000 /* 3 km */ + +/** * GCLUE_LOCATION_ACCURACY_CITY: * * Constant representing city-level accuracy. @@ -94,6 +108,8 @@ * GCLUE_LOCATION_ACCURACY_REGION: * * Constant representing region-level accuracy. + * + * Currently unused. */ #define GCLUE_LOCATION_ACCURACY_REGION 50000 /* 50 km */ @@ -108,6 +124,8 @@ * GCLUE_LOCATION_ACCURACY_CONTINENT: * * Constant representing continent-level accuracy. + * + * Currently unused. */ #define GCLUE_LOCATION_ACCURACY_CONTINENT 3000000 /* 3000 km */ @@ -127,7 +145,8 @@ GClueLocation *gclue_location_new (gdouble latitude, gdouble longitude, - gdouble accuracy); + gdouble accuracy, + const char *description); GClueLocation *gclue_location_new_full (gdouble latitude, @@ -146,6 +165,8 @@ GClueLocation *gclue_location_duplicate (GClueLocation *location); +GClueLocation *gclue_location_duplicate_fresh + (GClueLocation *location); void gclue_location_set_description (GClueLocation *loc, diff -Nurw geoclue-2.6.0/src/gclue-locator.c geoclue-2.6.0-soup-3.2/src/gclue-locator.c --- geoclue-2.6.0/src/gclue-locator.c 2022-02-10 21:03:30.000000000 +0300 +++ geoclue-2.6.0-soup-3.2/src/gclue-locator.c 2022-11-10 23:30:31.820463769 +0300 @@ -81,15 +81,14 @@ static void set_location (GClueLocator *locator, - GClueLocation *location) + GClueLocation *location, + const char *src_name) { GClueLocation *cur_location; cur_location = gclue_location_source_get_location (GCLUE_LOCATION_SOURCE (locator)); - g_debug ("New location available"); - if (cur_location != NULL) { guint64 cur_timestamp, new_timestamp; double dist, speed; @@ -97,7 +96,8 @@ cur_timestamp = gclue_location_get_timestamp (cur_location); new_timestamp = gclue_location_get_timestamp (location); if (new_timestamp < cur_timestamp) { - g_debug ("New location older than current, ignoring."); + g_debug ("New %s location older than current, ignoring.", + src_name); return; } @@ -126,11 +126,12 @@ * a reasonable speed, OR it is more or as accurate as * the previous one. */ - g_debug ("Ignoring less accurate new location"); + g_debug ("Ignoring less accurate new %s location", src_name); return; } } + g_debug ("New location available from %s", src_name); gclue_location_source_set_location (GCLUE_LOCATION_SOURCE (locator), location); } @@ -183,7 +184,7 @@ GClueLocation *location; location = gclue_location_source_get_location (source); - set_location (locator, location); + set_location (locator, location, G_OBJECT_TYPE_NAME (source)); } static gboolean @@ -206,7 +207,7 @@ location = gclue_location_source_get_location (src); if (gclue_location_source_get_active (src) && location != NULL) - set_location (locator, location); + set_location (locator, location, G_OBJECT_TYPE_NAME (src)); gclue_location_source_start (src); } @@ -364,7 +365,7 @@ #if GCLUE_USE_3G_SOURCE if (gclue_config_get_enable_3g_source (gconfig)) { - GClue3G *source = gclue_3g_get_singleton (); + GClue3G *source = gclue_3g_get_singleton (locator->priv->accuracy_level); locator->priv->sources = g_list_append (locator->priv->sources, source); } @@ -387,14 +388,20 @@ GClueModemGPS *gps = gclue_modem_gps_get_singleton (); locator->priv->sources = g_list_append (locator->priv->sources, gps); + if (!submit_source) { submit_source = GCLUE_LOCATION_SOURCE (gps); } + } #endif #if GCLUE_USE_NMEA_SOURCE if (gclue_config_get_enable_nmea_source (gconfig)) { GClueNMEASource *nmea = gclue_nmea_source_get_singleton (); locator->priv->sources = g_list_append (locator->priv->sources, nmea); + if (!submit_source) { + submit_source = GCLUE_LOCATION_SOURCE (nmea); + } + } #endif diff -Nurw geoclue-2.6.0/src/gclue-modem-gps.c geoclue-2.6.0-soup-3.2/src/gclue-modem-gps.c --- geoclue-2.6.0/src/gclue-modem-gps.c 2022-02-10 21:03:30.000000000 +0300 +++ geoclue-2.6.0-soup-3.2/src/gclue-modem-gps.c 2022-11-10 23:30:31.821463761 +0300 @@ -80,14 +80,15 @@ GAsyncResult *result, gpointer user_data) { - GClueModemGPS *source = GCLUE_MODEM_GPS (user_data); - GError *error = NULL; + g_autoptr(GError) error = NULL; - if (!gclue_modem_enable_gps_finish (source->priv->modem, + if (!gclue_modem_enable_gps_finish (GCLUE_MODEM (source_object), result, &error)) { + if (error && !g_error_matches (error, G_IO_ERROR, + G_IO_ERROR_CANCELLED)) { g_warning ("Failed to enable GPS: %s", error->message); - g_error_free (error); + } } } @@ -168,10 +169,10 @@ source); threshold = gclue_location_source_get_time_threshold (GCLUE_LOCATION_SOURCE (source)); - g_signal_connect (threshold, + g_signal_connect_object (threshold, "notify::value", G_CALLBACK (on_time_threshold_changed), - source); + source, 0); } static void diff -Nurw geoclue-2.6.0/src/gclue-modem-manager.c geoclue-2.6.0-soup-3.2/src/gclue-modem-manager.c --- geoclue-2.6.0/src/gclue-modem-manager.c 2022-02-10 21:03:30.000000000 +0300 +++ geoclue-2.6.0-soup-3.2/src/gclue-modem-manager.c 2022-11-10 23:30:31.821463761 +0300 @@ -45,11 +45,13 @@ MMModem *modem; MMModemLocation *modem_location; MMLocation3gpp *location_3gpp; + gboolean location_3gpp_ignore_previous; MMLocationGpsNmea *location_nmea; GCancellable *cancellable; MMModemLocationSource caps; /* Caps we set or are going to set */ + GClueTowerTec tec; guint time_threshold; }; @@ -286,7 +288,8 @@ is_location_3gpp_same (GClueModemManager *manager, const gchar *new_opc, gulong new_lac, - gulong new_cell_id) + gulong new_cell_id, + GClueTowerTec new_tec) { GClueModemManagerPrivate *priv = manager->priv; const gchar *opc; @@ -295,7 +298,7 @@ gchar opc_buf[GCLUE_3G_TOWER_OPERATOR_CODE_STR_LEN + 1]; #endif - if (priv->location_3gpp == NULL) + if (priv->location_3gpp == NULL || priv->location_3gpp_ignore_previous) return FALSE; #if MM_CHECK_VERSION(1, 18, 0) @@ -306,11 +309,10 @@ #endif lac = mm_location_3gpp_get_location_area_code (priv->location_3gpp); - // Most likely this is an LTE connection and with the mozilla - // services they use the tracking area code in place of the - // location area code in this case. + // Use the tracking area code in place of the + // location area code for LTE. // https://ichnaea.readthedocs.io/en/latest/api/geolocate.html#cell-tower-fields - if (lac == 0x0 || lac == 0xFFFE) { + if (priv->tec == GCLUE_TOWER_TEC_4G) { lac = mm_location_3gpp_get_tracking_area_code(priv->location_3gpp); } @@ -318,7 +320,20 @@ return (g_strcmp0 (opc, new_opc) == 0 && lac == new_lac && - cell_id == new_cell_id); + cell_id == new_cell_id && + priv->tec == new_tec); +} + +static void clear_3gpp_location (GClueModemManager *manager) +{ + GClueModemManagerPrivate *priv = manager->priv; + + if (!priv->location_3gpp && !priv->location_3gpp_ignore_previous) { + return; + } + + g_clear_object (&priv->location_3gpp); + g_signal_emit (manager, signals[FIX_3G], 0, NULL, 0, 0, GCLUE_TOWER_TEC_NO_FIX); } static void @@ -326,30 +341,40 @@ GAsyncResult *res, gpointer user_data) { - GClueModemManager *manager = GCLUE_MODEM_MANAGER (user_data); - GClueModemManagerPrivate *priv = manager->priv; + GClueModemManager *manager; + GClueModemManagerPrivate *priv; MMModemLocation *modem_location = MM_MODEM_LOCATION (source_object); + MMModemAccessTechnology modem_access_tec; g_autoptr(MMLocation3gpp) location_3gpp = NULL; - GError *error = NULL; const gchar *opc; gulong lac, cell_id; - GClueTowerTec tec = GCLUE_TOWER_TEC_3G; + GClueTowerTec tec; #if !MM_CHECK_VERSION(1, 18, 0) + g_autoptr(GError) error = NULL; gchar opc_buf[GCLUE_3G_TOWER_OPERATOR_CODE_STR_LEN + 1]; -#endif location_3gpp = mm_modem_location_get_3gpp_finish (modem_location, res, &error); if (error != NULL) { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { g_warning ("Failed to get location from 3GPP: %s", error->message); - g_error_free (error); + } + return; } +#else + location_3gpp = mm_modem_location_get_signaled_3gpp (modem_location); +#endif + + manager = GCLUE_MODEM_MANAGER (user_data); + priv = manager->priv; if (location_3gpp == NULL) { g_debug ("No 3GPP"); + clear_3gpp_location (manager); + priv->location_3gpp_ignore_previous = FALSE; return; } @@ -364,46 +389,80 @@ lac = mm_location_3gpp_get_location_area_code (location_3gpp); - // Most likely this is an LTE connection and with the mozilla - // services they use the tracking area code in place of the - // location area code in this case. - // https://ichnaea.readthedocs.io/en/latest/api/geolocate.html#cell-tower-fields - if (lac == 0x0 || lac == 0xFFFE) { + cell_id = mm_location_3gpp_get_cell_id (location_3gpp); + + modem_access_tec = mm_modem_get_access_technologies(priv->modem); + + if (modem_access_tec == MM_MODEM_ACCESS_TECHNOLOGY_GSM || + modem_access_tec == MM_MODEM_ACCESS_TECHNOLOGY_GPRS || + modem_access_tec == MM_MODEM_ACCESS_TECHNOLOGY_EDGE) { + tec = GCLUE_TOWER_TEC_2G; + } else if (modem_access_tec == MM_MODEM_ACCESS_TECHNOLOGY_UMTS || + modem_access_tec == MM_MODEM_ACCESS_TECHNOLOGY_HSDPA || + modem_access_tec == MM_MODEM_ACCESS_TECHNOLOGY_HSUPA || + modem_access_tec == MM_MODEM_ACCESS_TECHNOLOGY_HSPA || + modem_access_tec == MM_MODEM_ACCESS_TECHNOLOGY_HSPA_PLUS) { + tec = GCLUE_TOWER_TEC_3G; + } else if (modem_access_tec == MM_MODEM_ACCESS_TECHNOLOGY_LTE) { lac = mm_location_3gpp_get_tracking_area_code(location_3gpp); tec = GCLUE_TOWER_TEC_4G; + } else { + tec = GCLUE_TOWER_TEC_UNKNOWN; } - cell_id = mm_location_3gpp_get_cell_id (location_3gpp); - - if (is_location_3gpp_same (manager, opc, lac, cell_id)) { + if (is_location_3gpp_same (manager, opc, lac, cell_id, tec)) { g_debug ("New 3GPP location is same as last one"); return; } g_clear_object (&priv->location_3gpp); priv->location_3gpp = g_steal_pointer (&location_3gpp); + priv->location_3gpp_ignore_previous = FALSE; + priv->tec = tec; g_signal_emit (manager, signals[FIX_3G], 0, opc, lac, cell_id, tec); } static void +on_location_changed_get_3gpp (GObject *modem_object, + GClueModemManager *manager) +{ +#if MM_CHECK_VERSION(1, 18, 0) + on_get_3gpp_ready(modem_object, NULL, manager); +#else + mm_modem_location_get_3gpp (MM_MODEM_LOCATION (modem_object), + manager->priv->cancellable, + on_get_3gpp_ready, + manager); +#endif +} + +static void on_get_cdma_ready (GObject *source_object, GAsyncResult *res, gpointer user_data) { - GClueModemManager *manager = GCLUE_MODEM_MANAGER (user_data); + GClueModemManager *manager; MMModemLocation *modem_location = MM_MODEM_LOCATION (source_object); g_autoptr(MMLocationCdmaBs) location_cdma = NULL; - GError *error = NULL; +#if !MM_CHECK_VERSION(1, 18, 0) + g_autoptr(GError) error = NULL; location_cdma = mm_modem_location_get_cdma_bs_finish (modem_location, res, &error); if (error != NULL) { - g_warning ("Failed to get location from 3GPP: %s", + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + g_warning ("Failed to get location from CDMA: %s", error->message); - g_error_free (error); + } + return; } +#else + location_cdma = mm_modem_location_get_signaled_cdma_bs (modem_location); +#endif + + manager = GCLUE_MODEM_MANAGER (user_data); if (location_cdma == NULL) { g_debug ("No CDMA"); @@ -417,6 +476,20 @@ mm_location_cdma_bs_get_longitude (location_cdma)); } +static void +on_location_changed_get_cdma (GObject *modem_object, + GClueModemManager *manager) +{ +#if MM_CHECK_VERSION(1, 18, 0) + on_get_cdma_ready(modem_object, NULL, manager); +#else + mm_modem_location_get_cdma_bs (MM_MODEM_LOCATION (modem_object), + manager->priv->cancellable, + on_get_cdma_ready, + manager); +#endif +} + static gboolean is_location_gga_same (GClueModemManager *manager, const char *new_gga) @@ -436,24 +509,33 @@ GAsyncResult *res, gpointer user_data) { - GClueModemManager *manager = GCLUE_MODEM_MANAGER (user_data); - GClueModemManagerPrivate *priv = manager->priv; + GClueModemManager *manager; + GClueModemManagerPrivate *priv; MMModemLocation *modem_location = MM_MODEM_LOCATION (source_object); g_autoptr(MMLocationGpsNmea) location_nmea = NULL; static const gchar *sentences[3]; const gchar *gga, *rmc; gint i = 0; - GError *error = NULL; +#if !MM_CHECK_VERSION(1, 18, 0) + g_autoptr(GError) error = NULL; location_nmea = mm_modem_location_get_gps_nmea_finish (modem_location, res, &error); if (error != NULL) { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { g_warning ("Failed to get location from NMEA information: %s", error->message); - g_error_free (error); + } + return; } +#else + location_nmea = mm_modem_location_get_signaled_gps_nmea (modem_location); +#endif + + manager = GCLUE_MODEM_MANAGER (user_data); + priv = manager->priv; if (location_nmea == NULL) { g_debug ("No NMEA"); @@ -486,28 +568,32 @@ } static void +on_location_changed_get_gps_nmea (GObject *modem_object, + GClueModemManager *manager) +{ +#if MM_CHECK_VERSION(1, 18, 0) + on_get_gps_nmea_ready(modem_object, NULL, manager); +#else + mm_modem_location_get_gps_nmea (MM_MODEM_LOCATION (modem_object), + manager->priv->cancellable, + on_get_gps_nmea_ready, + manager); +#endif +} + +static void on_location_changed (GObject *modem_object, GParamSpec *pspec, gpointer user_data) { - MMModemLocation *modem_location = MM_MODEM_LOCATION (modem_object); GClueModemManager *manager = GCLUE_MODEM_MANAGER (user_data); if ((manager->priv->caps & MM_MODEM_LOCATION_SOURCE_3GPP_LAC_CI) != 0) - mm_modem_location_get_3gpp (modem_location, - manager->priv->cancellable, - on_get_3gpp_ready, - manager); + on_location_changed_get_3gpp (modem_object, manager); if ((manager->priv->caps & MM_MODEM_LOCATION_SOURCE_CDMA_BS) != 0) - mm_modem_location_get_cdma_bs (modem_location, - manager->priv->cancellable, - on_get_cdma_ready, - manager); + on_location_changed_get_cdma (modem_object, manager); if ((manager->priv->caps & MM_MODEM_LOCATION_SOURCE_GPS_NMEA) != 0) - mm_modem_location_get_gps_nmea (modem_location, - manager->priv->cancellable, - on_get_gps_nmea_ready, - manager); + on_location_changed_get_gps_nmea (modem_object, manager); } static void @@ -516,8 +602,9 @@ gpointer user_data) { GTask *task = G_TASK (user_data); - GClueModemManager *manager; - GClueModemManagerPrivate *priv; + GClueModemManager *manager = GCLUE_MODEM_MANAGER + (g_task_get_source_object (task)); + GClueModemManagerPrivate *priv = manager->priv; GError *error = NULL; if (!mm_modem_location_setup_finish (MM_MODEM_LOCATION (modem_object), @@ -527,10 +614,11 @@ goto out; } - manager = GCLUE_MODEM_MANAGER (g_task_get_source_object (task)); - priv = manager->priv; + g_debug ("Modem '%s' setup.", mm_object_get_path (priv->mm_object)); + /* Make sure that we actually emit that signal */ + priv->location_3gpp_ignore_previous = TRUE; on_location_changed (modem_object, NULL, manager); g_task_return_boolean (task, TRUE); @@ -551,8 +639,6 @@ priv->caps |= caps; task = g_task_new (manager, cancellable, callback, user_data); - priv = GCLUE_MODEM_MANAGER (g_task_get_source_object (task))->priv; - caps = mm_modem_location_get_enabled (priv->modem_location) | priv->caps; mm_modem_location_setup (priv->modem_location, caps, @@ -656,15 +742,13 @@ GAsyncResult *res, gpointer user_data) { - gboolean ret; - GError *error = NULL; + g_autoptr(GError) error = NULL; - ret = mm_modem_location_set_gps_refresh_rate_finish + mm_modem_location_set_gps_refresh_rate_finish (MM_MODEM_LOCATION (source_object), res, &error); - if (!ret) { + if (error && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { g_warning ("Failed to set GPS refresh rate: %s", error->message); - g_error_free (error); } } @@ -736,6 +820,8 @@ return; g_debug ("Modem '%s' removed.", mm_object_get_path (priv->mm_object)); + clear_3gpp_location (manager); + g_signal_handlers_disconnect_by_func (G_OBJECT (priv->modem_location), G_CALLBACK (on_location_changed), user_data); @@ -753,19 +839,27 @@ GAsyncResult *res, gpointer user_data) { - GClueModemManagerPrivate *priv = GCLUE_MODEM_MANAGER (user_data)->priv; + MMManager *mmmanager; + GClueModemManager *manager; + GClueModemManagerPrivate *priv; GList *objects, *node; - GError *error = NULL; + g_autoptr(GError) error = NULL; - priv->manager = mm_manager_new_finish (res, &error); - if (priv->manager == NULL) { + mmmanager = mm_manager_new_finish (res, &error); + if (mmmanager == NULL) { + if (error && !g_error_matches (error, G_IO_ERROR, + G_IO_ERROR_CANCELLED)) { g_warning ("Failed to connect to ModemManager: %s", error->message); - g_error_free (error); + } return; } + manager = GCLUE_MODEM_MANAGER (user_data); + priv = manager->priv; + priv->manager = mmmanager; + objects = g_dbus_object_manager_get_objects (G_DBUS_OBJECT_MANAGER (priv->manager)); for (node = objects; node != NULL; node = node->next) { @@ -779,15 +873,15 @@ } g_list_free_full (objects, g_object_unref); - g_signal_connect (G_OBJECT (priv->manager), + g_signal_connect_object (G_OBJECT (priv->manager), "object-added", G_CALLBACK (on_mm_object_added), - user_data); + manager, 0); - g_signal_connect (G_OBJECT (priv->manager), + g_signal_connect_object (G_OBJECT (priv->manager), "object-removed", G_CALLBACK (on_mm_object_removed), - user_data); + manager, 0); } static void @@ -795,19 +889,22 @@ GAsyncResult *res, gpointer user_data) { - GClueModemManagerPrivate *priv = GCLUE_MODEM_MANAGER (user_data)->priv; - GDBusConnection *connection; - GError *error = NULL; + GClueModemManagerPrivate *priv; + g_autoptr(GDBusConnection) connection = NULL; + g_autoptr(GError) error = NULL; connection = g_bus_get_finish (res, &error); if (connection == NULL) { + if (error && !g_error_matches (error, G_IO_ERROR, + G_IO_ERROR_CANCELLED)) { g_warning ("Failed to connect to system D-Bus: %s", error->message); - g_error_free (error); + } return; } + priv = GCLUE_MODEM_MANAGER (user_data)->priv; mm_manager_new (connection, 0, priv->cancellable, @@ -1043,7 +1140,7 @@ g_return_val_if_fail (gclue_modem_manager_get_is_3g_available (modem), FALSE); manager = GCLUE_MODEM_MANAGER (modem); - g_clear_object (&manager->priv->location_3gpp); + clear_3gpp_location (manager); g_debug ("Clearing 3GPP location caps from modem"); return clear_caps (manager, MM_MODEM_LOCATION_SOURCE_3GPP_LAC_CI, @@ -1062,7 +1159,7 @@ g_return_val_if_fail (gclue_modem_manager_get_is_cdma_available (modem), FALSE); manager = GCLUE_MODEM_MANAGER (modem); - g_clear_object (&manager->priv->location_3gpp); + clear_3gpp_location (manager); g_debug ("Clearing CDMA location caps from modem"); return clear_caps (manager, MM_MODEM_LOCATION_SOURCE_CDMA_BS, diff -Nurw geoclue-2.6.0/src/gclue-mozilla.c geoclue-2.6.0-soup-3.2/src/gclue-mozilla.c --- geoclue-2.6.0/src/gclue-mozilla.c 2022-02-10 21:03:30.000000000 +0300 +++ geoclue-2.6.0-soup-3.2/src/gclue-mozilla.c 2022-11-10 23:30:31.821463761 +0300 @@ -25,8 +25,10 @@ #include #include #include "gclue-mozilla.h" +#include "gclue-3g-tower.h" #include "gclue-config.h" #include "gclue-error.h" +#include "gclue-wifi.h" /** * SECTION:gclue-mozilla @@ -40,6 +42,22 @@ * its easy to switch to Google's API. **/ +struct _GClueMozillaPrivate +{ + GClueWifi *wifi; + + GClue3GTower tower; + gboolean tower_valid; + gboolean tower_submitted; + + gboolean bss_submitted; +}; + +G_DEFINE_TYPE_WITH_CODE (GClueMozilla, + gclue_mozilla, + G_TYPE_OBJECT, + G_ADD_PRIVATE (GClueMozilla)) + #define BSSID_LEN 6 #define BSSID_STR_LEN 17 #define MAX_SSID_LEN 32 @@ -102,12 +120,10 @@ return TRUE; } -static const char * -get_url (void) +const char * +gclue_mozilla_get_locate_url (GClueMozilla *mozilla) { - GClueConfig *config; - - config = gclue_config_get_singleton (); + GClueConfig *config = gclue_config_get_singleton (); return gclue_config_get_wifi_url (config); } @@ -136,25 +152,55 @@ return FALSE; } +static gboolean +towertec_to_radiotype (GClueTowerTec tec, + const char **radiotype_p) +{ + switch (tec) { + case GCLUE_TOWER_TEC_2G: + *radiotype_p = "gsm"; + break; + case GCLUE_TOWER_TEC_3G: + *radiotype_p = "wcdma"; + break; + case GCLUE_TOWER_TEC_4G: + *radiotype_p = "lte"; + break; + default: + *radiotype_p = NULL; + return FALSE; + } + + return TRUE; +} + SoupMessage * -gclue_mozilla_create_query (GList *bss_list, /* As in Access Points */ - GClue3GTower *tower, +gclue_mozilla_create_query (GClueMozilla *mozilla, + gboolean skip_tower, + gboolean skip_bss, + const char **query_data_description, GError **error) { + gboolean has_tower = FALSE, has_bss = FALSE; SoupMessage *ret = NULL; JsonBuilder *builder; + g_autoptr(GList) bss_list = NULL; JsonGenerator *generator; JsonNode *root_node; char *data; gsize data_len; - const char *uri; + const char *uri, *radiotype; guint n_non_ignored_bsss; GList *iter; gint64 mcc, mnc; + g_autoptr(GBytes) body = NULL; builder = json_builder_new (); json_builder_begin_object (builder); + if (mozilla->priv->wifi && !skip_bss) { + bss_list = gclue_wifi_get_bss_list (mozilla->priv->wifi); + } /* We send pure geoip query using empty object if both bss_list and * tower are NULL. * @@ -172,11 +218,11 @@ n_non_ignored_bsss++; } - if (tower != NULL && - operator_code_to_mcc_mnc (tower->opc, &mcc, &mnc)) { - + if (mozilla->priv->tower_valid && !skip_tower && + towertec_to_radiotype (mozilla->priv->tower.tec, &radiotype) && + operator_code_to_mcc_mnc (mozilla->priv->tower.opc, &mcc, &mnc)) { json_builder_set_member_name (builder, "radioType"); - json_builder_add_string_value (builder, "gsm"); + json_builder_add_string_value (builder, radiotype); json_builder_set_member_name (builder, "cellTowers"); json_builder_begin_array (builder); @@ -184,21 +230,21 @@ json_builder_begin_object (builder); json_builder_set_member_name (builder, "cellId"); - json_builder_add_int_value (builder, tower->cell_id); + json_builder_add_int_value (builder, mozilla->priv->tower.cell_id); json_builder_set_member_name (builder, "mobileCountryCode"); json_builder_add_int_value (builder, mcc); json_builder_set_member_name (builder, "mobileNetworkCode"); json_builder_add_int_value (builder, mnc); json_builder_set_member_name (builder, "locationAreaCode"); - json_builder_add_int_value (builder, tower->lac); - if (tower->tec == GCLUE_TOWER_TEC_4G) { + json_builder_add_int_value (builder, mozilla->priv->tower.lac); json_builder_set_member_name (builder, "radioType"); - json_builder_add_string_value (builder, "lte"); - } + json_builder_add_string_value (builder, radiotype); json_builder_end_object (builder); json_builder_end_array (builder); + + has_tower = TRUE; } if (n_non_ignored_bsss >= 2) { @@ -222,6 +268,8 @@ strength_dbm = wpa_bss_get_signal (bss); json_builder_add_int_value (builder, strength_dbm); json_builder_end_object (builder); + + has_bss = TRUE; } json_builder_end_array (builder); } @@ -236,15 +284,24 @@ g_object_unref (builder); g_object_unref (generator); - uri = get_url (); + uri = gclue_mozilla_get_locate_url (mozilla); ret = soup_message_new ("POST", uri); - soup_message_set_request (ret, - "application/json", - SOUP_MEMORY_TAKE, - data, - data_len); + body = g_bytes_new_take (data, data_len); + soup_message_set_request_body_from_bytes (ret, "application/json", body); g_debug ("Sending following request to '%s':\n%s", uri, data); + if (query_data_description) { + if (has_tower && has_bss) { + *query_data_description = "3GPP + WiFi"; + } else if (has_tower) { + *query_data_description = "3GPP"; + } else if (has_bss) { + *query_data_description = "WiFi"; + } else { + *query_data_description = "GeoIP"; + } + } + return ret; } @@ -252,28 +309,32 @@ parse_server_error (JsonObject *object, GError **error) { JsonObject *error_obj; - int code; const char *message; if (!json_object_has_member (object, "error")) return FALSE; error_obj = json_object_get_object_member (object, "error"); - code = json_object_get_int_member (error_obj, "code"); + if (json_object_has_member (error_obj, "message")) { message = json_object_get_string_member (error_obj, "message"); + } else { + message = "Unknown error"; + } - g_set_error_literal (error, G_IO_ERROR, code, message); + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, message); return TRUE; } GClueLocation * gclue_mozilla_parse_response (const char *json, + const char *location_description, GError **error) { - JsonParser *parser; + g_autoptr(JsonParser) parser = NULL; JsonNode *node; JsonObject *object, *loc_object; + g_autofree char *desc_new = NULL; GClueLocation *location; gdouble latitude, longitude, accuracy; @@ -288,54 +349,78 @@ if (parse_server_error (object, error)) return NULL; + if (json_object_has_member (object, "fallback")) { + const char *fallback; + + fallback = json_object_get_string_member (object, "fallback"); + if (fallback && strlen (fallback)) { + desc_new = g_strdup_printf ("%s fallback (from %s data)", + fallback, location_description); + location_description = desc_new; + } + } + loc_object = json_object_get_object_member (object, "location"); latitude = json_object_get_double_member (loc_object, "lat"); longitude = json_object_get_double_member (loc_object, "lng"); accuracy = json_object_get_double_member (object, "accuracy"); - location = gclue_location_new (latitude, longitude, accuracy); - - g_object_unref (parser); + location = gclue_location_new (latitude, longitude, accuracy, + location_description); return location; } -static const char * -get_submit_config (const char **nick) +const char * +gclue_mozilla_get_submit_url (GClueMozilla *mozilla) { - GClueConfig *config; - - config = gclue_config_get_singleton (); - if (!gclue_config_get_wifi_submit_data (config)) - return NULL; - - *nick = gclue_config_get_wifi_submit_nick (config); + GClueConfig *config = gclue_config_get_singleton (); + if (gclue_config_get_wifi_submit_data (config)) return gclue_config_get_wifi_submit_url (config); + else + return NULL; } SoupMessage * -gclue_mozilla_create_submit_query (GClueLocation *location, - GList *bss_list, /* As in Access Points */ - GClue3GTower *tower, +gclue_mozilla_create_submit_query (GClueMozilla *mozilla, + GClueLocation *location, GError **error) { SoupMessage *ret = NULL; + SoupMessageHeaders *request_headers; JsonBuilder *builder; JsonGenerator *generator; JsonNode *root_node; char *data, *timestr; + g_autoptr(GList) bss_list = NULL; const char *url, *nick; gsize data_len; GList *iter; gdouble lat, lon, accuracy, altitude; GDateTime *datetime; gint64 mcc, mnc; + GClueConfig *config; + g_autoptr(GBytes) body = NULL; + + if (mozilla->priv->bss_submitted && + (!mozilla->priv->tower_valid || + mozilla->priv->tower_submitted)) + { + g_debug ("Already created submit req for this data (bss submitted %d; tower: valid %d submitted %d)", + (int)mozilla->priv->bss_submitted, + (int)mozilla->priv->tower_valid, + (int)mozilla->priv->tower_submitted); + goto out; + } - url = get_submit_config (&nick); + + url = gclue_mozilla_get_submit_url (mozilla); if (url == NULL) goto out; + config = gclue_config_get_singleton (); + nick = gclue_config_get_wifi_submit_nick (config); builder = json_builder_new (); json_builder_begin_object (builder); @@ -379,6 +464,9 @@ json_builder_set_member_name (builder, "radioType"); json_builder_add_string_value (builder, "gsm"); + if (mozilla->priv->wifi) { + bss_list = gclue_wifi_get_bss_list (mozilla->priv->wifi); + } if (bss_list != NULL) { json_builder_set_member_name (builder, "wifi"); json_builder_begin_array (builder); @@ -410,24 +498,32 @@ json_builder_end_array (builder); /* wifi */ } - if (tower != NULL && - operator_code_to_mcc_mnc (tower->opc, &mcc, &mnc)) { - + if (mozilla->priv->tower_valid && + operator_code_to_mcc_mnc (mozilla->priv->tower.opc, &mcc, &mnc)) { json_builder_set_member_name (builder, "cell"); json_builder_begin_array (builder); json_builder_begin_object (builder); + if (mozilla->priv->tower.tec == GCLUE_TOWER_TEC_4G) { + json_builder_set_member_name (builder, "radio"); + json_builder_add_string_value (builder, "lte"); + } else if (mozilla->priv->tower.tec == GCLUE_TOWER_TEC_3G) { + json_builder_set_member_name (builder, "radio"); + json_builder_add_string_value (builder, "umts"); + } else if (mozilla->priv->tower.tec == GCLUE_TOWER_TEC_2G) { json_builder_set_member_name (builder, "radio"); json_builder_add_string_value (builder, "gsm"); + } + json_builder_set_member_name (builder, "cid"); - json_builder_add_int_value (builder, tower->cell_id); + json_builder_add_int_value (builder, mozilla->priv->tower.cell_id); json_builder_set_member_name (builder, "mcc"); json_builder_add_int_value (builder, mcc); json_builder_set_member_name (builder, "mnc"); json_builder_add_int_value (builder, mnc); json_builder_set_member_name (builder, "lac"); - json_builder_add_int_value (builder, tower->lac); + json_builder_add_int_value (builder, mozilla->priv->tower.lac); json_builder_end_object (builder); @@ -448,17 +544,18 @@ g_object_unref (generator); ret = soup_message_new ("POST", url); + request_headers = soup_message_get_request_headers (ret); if (nick != NULL && nick[0] != '\0') - soup_message_headers_append (ret->request_headers, + soup_message_headers_append (request_headers, "X-Nickname", nick); - soup_message_set_request (ret, - "application/json", - SOUP_MEMORY_TAKE, - data, - data_len); + body = g_bytes_new_take (data, data_len); + soup_message_set_request_body_from_bytes (ret, "application/json", body); g_debug ("Sending following request to '%s':\n%s", url, data); + mozilla->priv->bss_submitted = TRUE; + mozilla->priv->tower_submitted = TRUE; + out: return ret; } @@ -485,3 +582,134 @@ return FALSE; } + +static void +gclue_mozilla_finalize (GObject *object) +{ + GClueMozilla *mozilla = GCLUE_MOZILLA (object); + + g_clear_weak_pointer (&mozilla->priv->wifi); + + G_OBJECT_CLASS (gclue_mozilla_parent_class)->finalize (object); +} + +static void +gclue_mozilla_init (GClueMozilla *mozilla) +{ + mozilla->priv = gclue_mozilla_get_instance_private (mozilla); + mozilla->priv->wifi = NULL; + mozilla->priv->tower_valid = FALSE; + mozilla->priv->bss_submitted = FALSE; +} + +static void +gclue_mozilla_class_init (GClueMozillaClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gclue_mozilla_finalize; +} + +GClueMozilla * +gclue_mozilla_get_singleton (void) +{ + static GClueMozilla *mozilla = NULL; + + if (!mozilla) { + mozilla = g_object_new (GCLUE_TYPE_MOZILLA, NULL); + g_object_add_weak_pointer (G_OBJECT (mozilla), (gpointer) &mozilla); + } else + g_object_ref (mozilla); + + return mozilla; +} + +void +gclue_mozilla_set_wifi (GClueMozilla *mozilla, + GClueWifi *wifi) +{ + g_return_if_fail (GCLUE_IS_MOZILLA (mozilla)); + + if (mozilla->priv->wifi == wifi) + return; + + g_clear_weak_pointer (&mozilla->priv->wifi); + + if (!wifi) { + return; + } + + mozilla->priv->wifi = wifi; + g_object_add_weak_pointer (G_OBJECT (mozilla->priv->wifi), + (gpointer) &mozilla->priv->wifi); +} + +gboolean +gclue_mozilla_test_set_wifi (GClueMozilla *mozilla, + GClueWifi *old, GClueWifi *new) +{ + if (mozilla->priv->wifi != old) + return FALSE; + + gclue_mozilla_set_wifi (mozilla, new); + return TRUE; +} + +void +gclue_mozilla_set_bss_dirty (GClueMozilla *mozilla) +{ + g_return_if_fail (GCLUE_IS_MOZILLA (mozilla)); + + mozilla->priv->bss_submitted = FALSE; +} + +static gboolean gclue_mozilla_tower_identical (const GClue3GTower *t1, + const GClue3GTower *t2) +{ + return g_strcmp0 (t1->opc, t2->opc) == 0 && t1->lac == t2->lac && + t1->cell_id == t2->cell_id && t1->tec == t2->tec; +} + +void +gclue_mozilla_set_tower (GClueMozilla *mozilla, + const GClue3GTower *tower) +{ + g_return_if_fail (GCLUE_IS_MOZILLA (mozilla)); + + if (!tower || + !(tower->tec > GCLUE_TOWER_TEC_UNKNOWN + && tower->tec <= GCLUE_TOWER_TEC_MAX_VALID)) { + mozilla->priv->tower_valid = FALSE; + return; + } + + if (mozilla->priv->tower_valid && + mozilla->priv->tower_submitted) { + mozilla->priv->tower_submitted = + gclue_mozilla_tower_identical (&mozilla->priv->tower, + tower); + } else + mozilla->priv->tower_submitted = FALSE; + + mozilla->priv->tower = *tower; + mozilla->priv->tower_valid = TRUE; +} + +gboolean +gclue_mozilla_has_tower (GClueMozilla *mozilla) +{ + g_return_val_if_fail (GCLUE_IS_MOZILLA (mozilla), FALSE); + + return mozilla->priv->tower_valid; +} + +GClue3GTower * +gclue_mozilla_get_tower (GClueMozilla *mozilla) +{ + g_return_val_if_fail (GCLUE_IS_MOZILLA (mozilla), NULL); + + if (!mozilla->priv->tower_valid) + return NULL; + + return &mozilla->priv->tower; +} diff -Nurw geoclue-2.6.0/src/gclue-mozilla.h geoclue-2.6.0-soup-3.2/src/gclue-mozilla.h --- geoclue-2.6.0/src/gclue-mozilla.h 2022-02-10 21:03:30.000000000 +0300 +++ geoclue-2.6.0-soup-3.2/src/gclue-mozilla.h 2022-11-10 23:30:31.821463761 +0300 @@ -30,21 +30,72 @@ G_BEGIN_DECLS +#define GCLUE_TYPE_MOZILLA (gclue_mozilla_get_type()) +#define GCLUE_MOZILLA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GCLUE_TYPE_MOZILLA, GClueMozilla)) +#define GCLUE_MOZILLA_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GCLUE_TYPE_MOZILLA, GClueMozilla const)) +#define GCLUE_MOZILLA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GCLUE_TYPE_MOZILLA, GClueMozillaClass)) +#define GCLUE_IS_MOZILLA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GCLUE_TYPE_MOZILLA)) +#define GCLUE_IS_MOZILLA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GCLUE_TYPE_MOZILLA)) +#define GCLUE_MOZILLA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GCLUE_TYPE_MOZILLA, GClueMozillaClass)) + +typedef struct _GClueMozilla GClueMozilla; +typedef struct _GClueMozillaClass GClueMozillaClass; +typedef struct _GClueMozillaPrivate GClueMozillaPrivate; + +struct _GClueMozilla +{ + GObject parent; + + /*< private >*/ + GClueMozillaPrivate *priv; +}; + +struct _GClueMozillaClass +{ + GObjectClass parent_class; +}; + +struct GClueWifi; +typedef struct _GClueWifi GClueWifi; + +GType gclue_mozilla_get_type (void) G_GNUC_CONST; + +GClueMozilla *gclue_mozilla_get_singleton (void); + +void gclue_mozilla_set_wifi (GClueMozilla *mozilla, + GClueWifi *wifi); +gboolean +gclue_mozilla_test_set_wifi (GClueMozilla *mozilla, + GClueWifi *old, GClueWifi *new); +void gclue_mozilla_set_bss_dirty (GClueMozilla *mozilla); + +void gclue_mozilla_set_tower (GClueMozilla *mozilla, + const GClue3GTower *tower); +gboolean +gclue_mozilla_has_tower (GClueMozilla *mozilla); +GClue3GTower * +gclue_mozilla_get_tower (GClueMozilla *mozilla); + SoupMessage * -gclue_mozilla_create_query (GList *bss_list, /* As in Access Points */ - GClue3GTower *tower, +gclue_mozilla_create_query (GClueMozilla *mozilla, + gboolean skip_tower, + gboolean skip_bss, + const char **query_data_description, GError **error); GClueLocation * gclue_mozilla_parse_response (const char *json, + const char *location_description, GError **error); SoupMessage * -gclue_mozilla_create_submit_query (GClueLocation *location, - GList *bss_list, /* As in Access Points */ - GClue3GTower *tower, +gclue_mozilla_create_submit_query (GClueMozilla *mozilla, + GClueLocation *location, GError **error); gboolean gclue_mozilla_should_ignore_bss (WPABSS *bss); +const char *gclue_mozilla_get_locate_url (GClueMozilla *mozilla); +const char *gclue_mozilla_get_submit_url (GClueMozilla *mozilla); + G_END_DECLS #endif /* GCLUE_MOZILLA_H */ diff -Nurw geoclue-2.6.0/src/gclue-nmea-source.c geoclue-2.6.0-soup-3.2/src/gclue-nmea-source.c --- geoclue-2.6.0/src/gclue-nmea-source.c 2022-02-10 21:03:30.000000000 +0300 +++ geoclue-2.6.0-soup-3.2/src/gclue-nmea-source.c 2022-11-10 23:30:31.821463761 +0300 @@ -22,11 +22,13 @@ */ #include +#include #include #include "gclue-config.h" +#include "gclue-location.h" #include "gclue-nmea-utils.h" #include "gclue-nmea-source.h" -#include "gclue-location.h" +#include "gclue-utils.h" #include "config.h" #include "gclue-enum-types.h" @@ -37,21 +39,35 @@ #include #include +/* Once we run out of NMEA services to try how long to wait + * until retrying all of them. + * In seconds. + */ +#define SERVICE_UNBREAK_TIME 5 + typedef struct AvahiServiceInfo AvahiServiceInfo; struct _GClueNMEASourcePrivate { GSocketConnection *connection; + GDataInputStream *input_stream; GSocketClient *client; GCancellable *cancellable; + AvahiGLibPoll *glib_poll; + AvahiClient *avahi_client; AvahiServiceInfo *active_service; - /* List of all services but only the most accurate one is used. */ - GList *all_services; + /* List of services to try but only the most accurate one is used. */ + GList *try_services; + + /* List of known-broken services. */ + GList *broken_services; + + guint accuracy_refresh_source, unbreak_timer; }; G_DEFINE_TYPE_WITH_CODE (GClueNMEASource, @@ -65,16 +81,15 @@ gclue_nmea_source_stop (GClueLocationSource *source); static void -connect_to_service (GClueNMEASource *source); -static void -disconnect_from_service (GClueNMEASource *source); +try_connect_to_service (GClueNMEASource *source); struct AvahiServiceInfo { char *identifier; char *host_name; + gboolean is_socket; guint16 port; GClueAccuracyLevel accuracy; - guint64 timestamp; + gint64 timestamp_add; }; static void @@ -99,7 +114,7 @@ service->host_name = g_strdup (host_name); service->port = port; service->accuracy = accuracy; - service->timestamp = g_get_real_time () / G_USEC_PER_SEC; + service->timestamp_add = g_get_monotonic_time (); return service; } @@ -122,16 +137,41 @@ { AvahiServiceInfo *first, *second; gint diff; + gint64 tdiff; first = (AvahiServiceInfo *) a; second = (AvahiServiceInfo *) b; diff = second->accuracy - first->accuracy; + if (diff) + return diff; - if (diff == 0) - return first->timestamp - second->timestamp; + g_assert (first->timestamp_add >= 0); + g_assert (second->timestamp_add >= 0); + tdiff = first->timestamp_add - second->timestamp_add; + if (tdiff < 0) + return -1; + else if (tdiff > 0) + return 1; + else + return 0; +} - return diff; +static void +disconnect_from_service (GClueNMEASource *source) +{ + GClueNMEASourcePrivate *priv = source->priv; + + if (!priv->active_service) + return; + + g_cancellable_cancel (priv->cancellable); + + g_clear_object (&priv->input_stream); + g_clear_object (&priv->connection); + g_clear_object (&priv->client); + g_clear_object (&priv->cancellable); + priv->active_service = NULL; } static gboolean @@ -145,9 +185,9 @@ * 2. a more accurate service than one currently in use, is now * available. */ - return (priv->active_service != NULL && - (priv->all_services == NULL || - priv->active_service != priv->all_services->data)); + return priv->active_service == NULL || + priv->try_services == NULL || + priv->active_service != priv->try_services->data; } static void @@ -157,25 +197,35 @@ return; disconnect_from_service (source); - connect_to_service (source); + try_connect_to_service (source); } -static void -refresh_accuracy_level (GClueNMEASource *source) +static GClueAccuracyLevel get_head_accuracy (GList *list) +{ + AvahiServiceInfo *service; + + if (!list) + return GCLUE_ACCURACY_LEVEL_NONE; + + service = (AvahiServiceInfo *) list->data; + return service->accuracy; +} + +static gboolean +on_refresh_accuracy_level (gpointer user_data) { - GClueAccuracyLevel new, existing; + GClueNMEASource *source = GCLUE_NMEA_SOURCE (user_data); + GClueNMEASourcePrivate *priv = source->priv; + GClueAccuracyLevel new_try, new_broken, new, existing; + + priv->accuracy_refresh_source = 0; existing = gclue_location_source_get_available_accuracy_level (GCLUE_LOCATION_SOURCE (source)); - if (source->priv->all_services != NULL) { - AvahiServiceInfo *service; - - service = (AvahiServiceInfo *) source->priv->all_services->data; - new = service->accuracy; - } else { - new = GCLUE_ACCURACY_LEVEL_NONE; - } + new_try = get_head_accuracy (priv->try_services); + new_broken = get_head_accuracy (priv->broken_services); + new = MAX (new_try, new_broken); if (new != existing) { g_debug ("Available accuracy level from %s: %u", @@ -184,6 +234,109 @@ "available-accuracy-level", new, NULL); } + + return G_SOURCE_REMOVE; +} + +static void +refresh_accuracy_level (GClueNMEASource *source) +{ + GClueNMEASourcePrivate *priv = source->priv; + + if (priv->accuracy_refresh_source) { + return; + } + + g_debug ("Scheduling NMEA accuracy level refresh"); + priv->accuracy_refresh_source = g_idle_add (on_refresh_accuracy_level, + source); +} + +static gboolean +on_service_unbreak_time (gpointer source) +{ + GClueNMEASourcePrivate *priv = GCLUE_NMEA_SOURCE (source)->priv; + + priv->unbreak_timer = 0; + + if (!priv->try_services && priv->broken_services) { + g_debug ("Unbreaking existing NMEA services"); + + priv->try_services = priv->broken_services; + priv->broken_services = NULL; + + reconnect_service (source); + } + + return G_SOURCE_REMOVE; +} + +static void +check_unbreak_timer (GClueNMEASource *source) +{ + GClueNMEASourcePrivate *priv = source->priv; + + if (priv->try_services || !priv->broken_services) { + if (priv->unbreak_timer) { + g_debug ("Removing unnecessary NMEA unbreaking timer"); + + g_source_remove (priv->unbreak_timer); + priv->unbreak_timer = 0; + } + + return; + } + + if (priv->unbreak_timer) { + return; + } + + g_debug ("Scheduling NMEA unbreaking timer"); + priv->unbreak_timer = g_timeout_add_seconds (SERVICE_UNBREAK_TIME, + on_service_unbreak_time, + source); +} + +static void +service_lists_changed (GClueNMEASource *source) +{ + check_unbreak_timer (source); + reconnect_service (source); + refresh_accuracy_level (source); +} + +static gboolean +check_service_exists (GClueNMEASource *source, + const char *name) +{ + GClueNMEASourcePrivate *priv = source->priv; + AvahiServiceInfo *service; + GList *item; + gboolean ret = FALSE; + + /* only `name` is required here */ + service = avahi_service_new (name, + NULL, + 0, + GCLUE_ACCURACY_LEVEL_NONE); + + item = g_list_find_custom (priv->try_services, + service, + compare_avahi_service_by_identifier); + if (item) { + ret = TRUE; + } else { + item = g_list_find_custom (priv->broken_services, + service, + compare_avahi_service_by_identifier); + if (item) { + ret = TRUE; + } + } + + g_clear_pointer (&service, avahi_service_free); + + return ret; } static void @@ -191,6 +344,7 @@ const char *name, const char *host_name, uint16_t port, + gboolean is_socket, AvahiStringList *txt) { GClueAccuracyLevel accuracy = GCLUE_ACCURACY_LEVEL_NONE; @@ -201,7 +355,12 @@ GEnumClass *enum_class; GEnumValue *enum_value; - if (port == 0) { + if (check_service_exists (source, name)) { + g_debug ("NMEA service %s already exists", name); + return; + } + + if (!txt) { accuracy = GCLUE_ACCURACY_LEVEL_EXACT; goto CREATE_SERVICE; @@ -242,43 +401,72 @@ CREATE_SERVICE: service = avahi_service_new (name, host_name, port, accuracy); + service->is_socket = is_socket; - source->priv->all_services = g_list_insert_sorted - (source->priv->all_services, + source->priv->try_services = g_list_insert_sorted + (source->priv->try_services, service, compare_avahi_service_by_accuracy_n_time); - refresh_accuracy_level (source); - reconnect_service (source); + n_services = g_list_length (source->priv->try_services); + g_debug ("No. of _nmea-0183._tcp services %u", n_services); - n_services = g_list_length (source->priv->all_services); + service_lists_changed (source); +} - g_debug ("No. of _nmea-0183._tcp services %u", n_services); +static void +add_new_service_avahi (GClueNMEASource *source, + const char *name, + const char *host_name, + uint16_t port, + AvahiStringList *txt) +{ + add_new_service (source, name, host_name, port, FALSE, txt); } static void -remove_service (GClueNMEASource *source, - AvahiServiceInfo *service) +add_new_service_socket (GClueNMEASource *source, + const char *name, + const char *socket_path) { - guint n_services = 0; + add_new_service (source, name, socket_path, 0, TRUE, NULL); +} - avahi_service_free (service); - source->priv->all_services = g_list_remove - (source->priv->all_services, service); +static void +service_broken (GClueNMEASource *source) +{ + GClueNMEASourcePrivate *priv = source->priv; + AvahiServiceInfo *service = priv->active_service; - n_services = g_list_length (source->priv->all_services); + g_assert (service); - g_debug ("No. of _nmea-0183._tcp services %u", - n_services); + disconnect_from_service (source); - refresh_accuracy_level (source); - reconnect_service (source); + priv->try_services = g_list_remove (priv->try_services, + service); + priv->broken_services = g_list_insert_sorted + (priv->broken_services, + service, + compare_avahi_service_by_accuracy_n_time); + + service_lists_changed (source); +} + +static void +remove_service_from_list (GList **list, + GList *item) +{ + AvahiServiceInfo *service = item->data; + + *list = g_list_delete_link (*list, item); + avahi_service_free (service); } static void remove_service_by_name (GClueNMEASource *source, const char *name) { + GClueNMEASourcePrivate *priv = source->priv; AvahiServiceInfo *service; GList *item; @@ -288,15 +476,31 @@ 0, GCLUE_ACCURACY_LEVEL_NONE); - item = g_list_find_custom (source->priv->all_services, + item = g_list_find_custom (priv->try_services, service, compare_avahi_service_by_identifier); - avahi_service_free (service); + if (item) { + if (item->data == priv->active_service) { + g_debug ("Active NMEA service removed, disconnecting."); + disconnect_from_service (source); + } - if (item == NULL) - return; + remove_service_from_list (&priv->try_services, + item); + } else { + item = g_list_find_custom (priv->broken_services, + service, + compare_avahi_service_by_identifier); + if (item) { + g_assert (item->data != priv->active_service); + remove_service_from_list (&priv->broken_services, + item); + } + } + + g_clear_pointer (&service, avahi_service_free); - remove_service (source, item->data); + service_lists_changed (source); } static void @@ -337,11 +541,14 @@ } case AVAHI_RESOLVER_FOUND: - g_debug ("Service %s:%u resolved", + g_debug ("Service '%s' of type '%s' in domain '%s' resolved to %s:%u", + name, + type, + domain, host_name, - port); + (unsigned int)port); - add_new_service (GCLUE_NMEA_SOURCE (user_data), + add_new_service_avahi (GCLUE_NMEA_SOURCE (user_data), name, host_name, port, @@ -358,12 +565,8 @@ AvahiClientState state, void *user_data) { - GClueNMEASourcePrivate *priv = GCLUE_NMEA_SOURCE (user_data)->priv; - g_return_if_fail (avahi_client != NULL); - priv->avahi_client = avahi_client; - if (state == AVAHI_CLIENT_FAILURE) { const char *errorstr = avahi_strerror (avahi_client_errno (avahi_client)); @@ -448,52 +651,91 @@ } } +#define NMEA_LINE_END "\r\n" +#define NMEA_LINE_END_CTR (sizeof (NMEA_LINE_END) - 1) + +static void nmea_skip_delim (GBufferedInputStream *stream, + GCancellable *cancellable) +{ + const char *buf; + gsize buf_size; + size_t delim_skip; + g_autoptr(GError) error = NULL; + + buf = (const char *) g_buffered_input_stream_peek_buffer (stream, + &buf_size); + + delim_skip = strnspn (buf, NMEA_LINE_END, buf_size); + for (size_t ctr = 0; ctr < delim_skip; ctr++) { + if (g_buffered_input_stream_read_byte (stream, cancellable, &error) < 0) { + if (error && !g_error_matches (error, G_IO_ERROR, + G_IO_ERROR_CANCELLED)) { + g_warning ("Failed to skip %zu / %zu NMEA delimiter: %s", + ctr, delim_skip, error->message); + } + break; + } + } +} + +static gboolean nmea_check_delim (GBufferedInputStream *stream) +{ + const char *buf; + gsize buf_size; + + buf = (const char *) g_buffered_input_stream_peek_buffer (stream, + &buf_size); + + return strnpbrk (buf, NMEA_LINE_END, buf_size) != NULL; +} + #define NMEA_STR_LEN 128 static void on_read_nmea_sentence (GObject *object, GAsyncResult *result, gpointer user_data) { - GClueNMEASource *source = GCLUE_NMEA_SOURCE (user_data); + GClueNMEASource *source = NULL; GDataInputStream *data_input_stream = G_DATA_INPUT_STREAM (object); - GError *error = NULL; + g_autoptr(GError) error = NULL; GClueLocation *prev_location; g_autoptr(GClueLocation) location = NULL; gsize data_size = 0 ; - char *message; + g_autofree char *message = NULL; gint i; - static const gchar *sentences[3] = { 0 }; - static gchar gga[NMEA_STR_LEN] = { 0 }; - static gchar rmc[NMEA_STR_LEN] = { 0 }; + const gchar *sentences[3]; + gchar gga[NMEA_STR_LEN]; + gchar rmc[NMEA_STR_LEN]; - - message = g_data_input_stream_read_line_finish (data_input_stream, + message = g_data_input_stream_read_upto_finish (data_input_stream, result, &data_size, &error); + gga[0] = '\0'; + rmc[0] = '\0'; + do { + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + if (!source) + source = GCLUE_NMEA_SOURCE (user_data); + if (message == NULL) { if (error != NULL) { - if (error->code == G_IO_ERROR_CLOSED) - g_debug ("Socket closed."); - else if (error->code != G_IO_ERROR_CANCELLED) + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CLOSED)) { + g_debug ("NMEA socket closed."); + } else { g_warning ("Error when receiving message: %s", error->message); - g_error_free (error); + } } else { - g_debug ("Nothing to read"); + g_debug ("NMEA nothing to read"); } - g_object_unref (data_input_stream); - if (source->priv->active_service != NULL) - /* In case service did not advertise it exiting - * or we failed to receive it's notification. - */ - remove_service (source, source->priv->active_service); + service_broken (source); - gga[0] = '\0'; - rmc[0] = '\0'; return; } g_debug ("Network source sent: \"%s\"", message); @@ -506,12 +748,15 @@ g_debug ("Ignoring NMEA sentence, as it's neither GGA or RMC: %s", message); } - message = (char *) g_buffered_input_stream_peek_buffer - (G_BUFFERED_INPUT_STREAM (data_input_stream), - &data_size); - if (g_strstr_len (message, data_size, "\n")) { - message = g_data_input_stream_read_line - (data_input_stream, &data_size, NULL, &error); + nmea_skip_delim (G_BUFFERED_INPUT_STREAM (data_input_stream), + source->priv->cancellable); + + if (nmea_check_delim (G_BUFFERED_INPUT_STREAM (data_input_stream))) { + g_clear_pointer (&message, g_free); + message = g_data_input_stream_read_upto + (data_input_stream, + NMEA_LINE_END, NMEA_LINE_END_CTR, + &data_size, NULL, &error); } else { break; } @@ -524,6 +769,7 @@ sentences[i++] = rmc; sentences[i] = NULL; + if (i > 0) { prev_location = gclue_location_source_get_location (GCLUE_LOCATION_SOURCE (source)); location = gclue_location_create_from_nmeas (sentences, @@ -532,17 +778,15 @@ if (error != NULL) { g_warning ("Error: %s", error->message); - g_clear_error (&error); } else { gclue_location_source_set_location (GCLUE_LOCATION_SOURCE (source), location); } + } - gga[0] = '\0'; - rmc[0] = '\0'; - sentences[0] = NULL; - - g_data_input_stream_read_line_async (data_input_stream, + g_data_input_stream_read_upto_async (data_input_stream, + NMEA_LINE_END, + NMEA_LINE_END_CTR, G_PRIORITY_DEFAULT, source->priv->cancellable, on_read_nmea_sentence, @@ -554,30 +798,41 @@ GAsyncResult *result, gpointer user_data) { - GClueNMEASource *source = GCLUE_NMEA_SOURCE (user_data); GSocketClient *client = G_SOCKET_CLIENT (object); - GError *error = NULL; - GDataInputStream *data_input_stream; - GInputStream *input_stream; + GClueNMEASource *source; + g_autoptr(GSocketConnection) connection = NULL; + g_autoptr(GError) error = NULL; - source->priv->connection = g_socket_client_connect_to_host_finish + connection = g_socket_client_connect_to_host_finish (client, result, &error); + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + return; + } + + source = GCLUE_NMEA_SOURCE (user_data); + if (error != NULL) { - if (error->code != G_IO_ERROR_CANCELLED) g_warning ("Failed to connect to NMEA service: %s", error->message); - g_clear_error (&error); - + service_broken (source); return; } - input_stream = g_io_stream_get_input_stream - (G_IO_STREAM (source->priv->connection)); - data_input_stream = g_data_input_stream_new (input_stream); + g_assert (connection); + g_debug ("NMEA service connected."); - g_data_input_stream_read_line_async (data_input_stream, + g_assert (!source->priv->connection); + source->priv->connection = g_steal_pointer (&connection); + + g_assert (!source->priv->input_stream); + source->priv->input_stream = g_data_input_stream_new + (g_io_stream_get_input_stream (G_IO_STREAM (source->priv->connection))); + + g_data_input_stream_read_upto_async (source->priv->input_stream, + NMEA_LINE_END, + NMEA_LINE_END_CTR, G_PRIORITY_DEFAULT, source->priv->cancellable, on_read_nmea_sentence, @@ -585,24 +840,39 @@ } static void -connect_to_service (GClueNMEASource *source) +try_connect_to_service (GClueNMEASource *source) { GClueNMEASourcePrivate *priv = source->priv; - GSocketAddress *addr; - GSocketConnectable *connectable; - if (priv->all_services == NULL) + if (!gclue_location_source_get_active (GCLUE_LOCATION_SOURCE (source))) { + g_warn_if_fail (!priv->active_service); + + return; + } + + if (priv->active_service) + return; + + if (priv->try_services == NULL) return; + g_assert (!priv->cancellable); + priv->cancellable = g_cancellable_new (); + + g_assert (!priv->client); priv->client = g_socket_client_new (); - g_cancellable_reset (priv->cancellable); /* The service with the highest accuracy will be stored in the beginning * of the list. */ - priv->active_service = (AvahiServiceInfo *) priv->all_services->data; + priv->active_service = (AvahiServiceInfo *) priv->try_services->data; - if ( priv->active_service->port != 0 ) + g_debug ("Trying to connect to NMEA %sservice %s:%u.", + priv->active_service->is_socket ? "socket " : "", + priv->active_service->host_name, + (unsigned int) priv->active_service->port); + + if (!priv->active_service->is_socket) { g_socket_client_connect_to_host_async (priv->client, priv->active_service->host_name, @@ -610,52 +880,86 @@ priv->cancellable, on_connection_to_location_server, source); - else { + } else { + g_autoptr(GSocketAddress) addr = NULL; + addr = g_unix_socket_address_new(priv->active_service->host_name); - connectable = G_SOCKET_CONNECTABLE (addr); g_socket_client_connect_async (priv->client, - connectable, + G_SOCKET_CONNECTABLE (addr), priv->cancellable, on_connection_to_location_server, source); } } -static void -disconnect_from_service (GClueNMEASource *source) +static gboolean +remove_avahi_services_from_list (GClueNMEASource *source, GList **list) { GClueNMEASourcePrivate *priv = source->priv; + gboolean removed_active = FALSE; + GList *l = *list; - g_cancellable_cancel (priv->cancellable); + while (l != NULL) { + GList *next = l->next; + AvahiServiceInfo *service = l->data; + + if (!service->is_socket) { + if (service == priv->active_service) { + g_debug ("Active NMEA service was Avahi-provided, disconnecting."); + disconnect_from_service (source); + removed_active = TRUE; + } - if (priv->connection != NULL) { - GError *error = NULL; + remove_service_from_list (list, l); + } - g_io_stream_close (G_IO_STREAM (priv->connection), - NULL, - &error); - if (error != NULL) - g_warning ("Error in closing socket connection: %s", error->message); + l = next; } - g_clear_object (&priv->connection); - g_clear_object (&priv->client); - priv->active_service = NULL; + return removed_active; +} + +static void +disconnect_avahi_client (GClueNMEASource *source) +{ + GClueNMEASourcePrivate *priv = source->priv; + + remove_avahi_services_from_list (source, &priv->try_services); + if (remove_avahi_services_from_list (source, &priv->broken_services)) { + g_warn_if_reached (); + } + + g_clear_pointer (&priv->avahi_client, avahi_client_free); + + service_lists_changed (source); } static void gclue_nmea_source_finalize (GObject *gnmea) { - GClueNMEASourcePrivate *priv = GCLUE_NMEA_SOURCE (gnmea)->priv; + GClueNMEASource *source = GCLUE_NMEA_SOURCE (gnmea); + GClueNMEASourcePrivate *priv = source->priv; G_OBJECT_CLASS (gclue_nmea_source_parent_class)->finalize (gnmea); - g_clear_object (&priv->connection); - g_clear_object (&priv->client); - g_clear_object (&priv->cancellable); - if (priv->avahi_client) - avahi_client_free (priv->avahi_client); - g_list_free_full (priv->all_services, + disconnect_avahi_client (source); + disconnect_from_service (source); + + if (priv->accuracy_refresh_source) { + g_source_remove (priv->accuracy_refresh_source); + priv->accuracy_refresh_source = 0; + } + + if (priv->unbreak_timer) { + g_source_remove (priv->unbreak_timer); + priv->unbreak_timer = 0; + } + + g_clear_pointer (&priv->glib_poll, avahi_glib_poll_free); + + g_list_free_full (g_steal_pointer (&priv->try_services), + avahi_service_free); + g_list_free_full (g_steal_pointer (&priv->broken_services), avahi_service_free); } @@ -672,41 +976,33 @@ } static void -gclue_nmea_source_init (GClueNMEASource *source) +try_connect_avahi_client (GClueNMEASource *source) { - GClueNMEASourcePrivate *priv; AvahiServiceBrowser *service_browser; + GClueNMEASourcePrivate *priv = source->priv; const AvahiPoll *poll_api; - AvahiGLibPoll *glib_poll; - const char *nmea_socket; - GClueConfig *config; int error; - source->priv = gclue_nmea_source_get_instance_private (source); - priv = source->priv; + if (priv->avahi_client) { + AvahiClientState avahi_state; - glib_poll = avahi_glib_poll_new (NULL, G_PRIORITY_DEFAULT); - poll_api = avahi_glib_poll_get (glib_poll); - - priv->cancellable = g_cancellable_new (); - - config = gclue_config_get_singleton (); + avahi_state = avahi_client_get_state (priv->avahi_client); + if (avahi_state != AVAHI_CLIENT_FAILURE) { + return; + } - nmea_socket = gclue_config_get_nmea_socket (config); - if (nmea_socket != NULL) { - add_new_service (source, - "nmea-socket", - nmea_socket, - 0, - NULL); + g_debug ("Avahi client in failure state, trying to reinit."); + disconnect_avahi_client (source); } - avahi_client_new (poll_api, + g_assert (priv->glib_poll); + poll_api = avahi_glib_poll_get (priv->glib_poll); + + priv->avahi_client = avahi_client_new (poll_api, 0, client_callback, source, &error); - if (priv->avahi_client == NULL) { g_warning ("Failed to connect to avahi service: %s", avahi_strerror (error)); @@ -722,15 +1018,43 @@ 0, browse_callback, source); - - if (service_browser == NULL) { const char *errorstr; error = avahi_client_errno (priv->avahi_client); errorstr = avahi_strerror (error); g_warning ("Failed to browse avahi services: %s", errorstr); + goto fail_client; + } + + return; + +fail_client: + disconnect_avahi_client (source); +} + +static void +gclue_nmea_source_init (GClueNMEASource *source) +{ + GClueNMEASourcePrivate *priv; + const char *nmea_socket; + GClueConfig *config; + + source->priv = gclue_nmea_source_get_instance_private (source); + priv = source->priv; + + priv->glib_poll = avahi_glib_poll_new (NULL, G_PRIORITY_DEFAULT); + + config = gclue_config_get_singleton (); + + nmea_socket = gclue_config_get_nmea_socket (config); + if (nmea_socket != NULL) { + add_new_service_socket (source, + "nmea-socket", + nmea_socket); } + + try_connect_avahi_client (source); } /** @@ -750,8 +1074,10 @@ source = g_object_new (GCLUE_TYPE_NMEA_SOURCE, NULL); g_object_add_weak_pointer (G_OBJECT (source), (gpointer) &source); - } else + } else { g_object_ref (source); + try_connect_avahi_client (source); + } return source; } @@ -767,10 +1093,11 @@ base_class = GCLUE_LOCATION_SOURCE_CLASS (gclue_nmea_source_parent_class); base_result = base_class->start (source); - if (base_result != GCLUE_LOCATION_SOURCE_START_RESULT_OK) + if (base_result == GCLUE_LOCATION_SOURCE_START_RESULT_FAILED) return base_result; - connect_to_service (GCLUE_NMEA_SOURCE (source)); + try_connect_avahi_client (GCLUE_NMEA_SOURCE (source)); + reconnect_service (GCLUE_NMEA_SOURCE (source)); return base_result; } diff -Nurw geoclue-2.6.0/src/gclue-service-client.c geoclue-2.6.0-soup-3.2/src/gclue-service-client.c --- geoclue-2.6.0/src/gclue-service-client.c 2022-02-10 21:03:30.000000000 +0300 +++ geoclue-2.6.0-soup-3.2/src/gclue-service-client.c 2022-11-10 23:30:31.821463761 +0300 @@ -262,10 +262,10 @@ gclue_dbus_client_set_active (GCLUE_DBUS_CLIENT (client), TRUE); priv->locator = gclue_locator_new (accuracy_level); gclue_locator_set_time_threshold (priv->locator, priv->time_threshold); - g_signal_connect (priv->locator, + g_signal_connect_object (priv->locator, "notify::location", G_CALLBACK (on_locator_location_changed), - client); + client, 0); gclue_location_source_start (GCLUE_LOCATION_SOURCE (priv->locator)); } diff -Nurw geoclue-2.6.0/src/gclue-service-manager.c geoclue-2.6.0-soup-3.2/src/gclue-service-manager.c --- geoclue-2.6.0/src/gclue-service-manager.c 2022-02-10 21:03:30.000000000 +0300 +++ geoclue-2.6.0-soup-3.2/src/gclue-service-manager.c 2022-11-10 23:30:31.821463761 +0300 @@ -228,15 +228,15 @@ } g_debug ("Number of connected clients: %u", priv->num_clients); - g_signal_connect (client, + g_signal_connect_object (client, "notify::active", G_CALLBACK (on_client_notify_active), - data->manager); + data->manager, 0); - g_signal_connect (info, + g_signal_connect_object (info, "peer-vanished", G_CALLBACK (on_peer_vanished), - data->manager); + data->manager, 0); client_created: if (data->reuse_client) @@ -434,6 +434,7 @@ add_agent_data_free (AddAgentData *data) { g_clear_pointer (&data->desktop_id, g_free); + g_clear_object (&data->manager); g_slice_free (AddAgentData, data); } @@ -470,10 +471,10 @@ g_debug ("New agent for user ID '%u'", user_id); g_hash_table_replace (priv->agents, GINT_TO_POINTER (user_id), agent); - g_signal_connect (data->info, + g_signal_connect_object (data->info, "peer-vanished", G_CALLBACK (on_agent_vanished), - data->manager); + data->manager, 0); gclue_dbus_manager_complete_add_agent (data->manager, data->invocation); @@ -564,7 +565,7 @@ peer = g_dbus_method_invocation_get_sender (invocation); data = g_slice_new0 (AddAgentData); - data->manager = manager; + data->manager = g_object_ref (manager); data->invocation = invocation; data->desktop_id = g_strdup (id); gclue_client_info_new_async (peer, @@ -659,10 +660,10 @@ G_OBJECT_CLASS (gclue_service_manager_parent_class)->constructed (object); priv->locator = gclue_locator_new (GCLUE_ACCURACY_LEVEL_EXACT); - g_signal_connect (G_OBJECT (priv->locator), + g_signal_connect_object (G_OBJECT (priv->locator), "notify::available-accuracy-level", G_CALLBACK (on_avail_accuracy_level_changed), - object); + object, 0); on_avail_accuracy_level_changed (G_OBJECT (priv->locator), NULL, object); diff -Nurw geoclue-2.6.0/src/gclue-utils.h geoclue-2.6.0-soup-3.2/src/gclue-utils.h --- geoclue-2.6.0/src/gclue-utils.h 1970-01-01 03:00:00.000000000 +0300 +++ geoclue-2.6.0-soup-3.2/src/gclue-utils.h 2022-11-10 23:30:31.821463761 +0300 @@ -0,0 +1,61 @@ +/* vim: set et ts=8 sw=8: */ +/* + * Geoclue is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * Geoclue 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 General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along + * with Geoclue; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef GCLUE_UTILS_H +#define GCLUE_UTILS_H + +#include +#include + +G_BEGIN_DECLS + +#ifndef strnpbrk +inline static const char * +strnpbrk (const char *s, const char *accept, size_t n) +{ + const char *end; + + for (end = s + n; s < end && *s != '\0'; s++) { + if (strchr (accept, *s)) { + return s; + } + } + + return NULL; +} +#endif + +#ifndef strnspn +inline static size_t +strnspn (const char *s, const char *accept, size_t n) +{ + const char *cur, *end; + + for (cur = s, end = s + n; cur < end && *cur != '\0'; cur++) { + if (!strchr (accept, *cur)) { + break; + } + } + + return cur - s; +} +#endif + +G_END_DECLS + +#endif /* GCLUE_UTILS_H */ diff -Nurw geoclue-2.6.0/src/gclue-web-source.c geoclue-2.6.0-soup-3.2/src/gclue-web-source.c --- geoclue-2.6.0/src/gclue-web-source.c 2022-02-10 21:03:30.000000000 +0300 +++ geoclue-2.6.0-soup-3.2/src/gclue-web-source.c 2022-11-10 23:30:31.821463761 +0300 @@ -27,6 +27,7 @@ #include "gclue-web-source.h" #include "gclue-error.h" #include "gclue-location.h" +#include "gclue-mozilla.h" /** * SECTION:gclue-web-source @@ -36,23 +37,37 @@ * Baseclass for all sources that solely use a web resource for geolocation. **/ -static gboolean -get_internet_available (void); static void refresh_accuracy_level (GClueWebSource *web); struct _GClueWebSourcePrivate { + GCancellable *cancellable; + + GClueAccuracyLevel accuracy_level; + SoupSession *soup_session; SoupMessage *query; + const char *query_data_description; gulong network_changed_id; gulong connectivity_changed_id; guint64 last_submitted; - gboolean internet_available; + const char *locate_url; + const char *submit_url; + gboolean locate_url_reachable; + gboolean submit_url_reachable; +}; + +enum +{ + PROP_0, + PROP_ACCURACY_LEVEL, + LAST_PROP }; +static GParamSpec *gParamSpecs[LAST_PROP]; G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GClueWebSource, gclue_web_source, @@ -60,7 +75,7 @@ G_ADD_PRIVATE (GClueWebSource)) static void refresh_callback (SoupSession *session, - SoupMessage *query, + GAsyncResult *result, gpointer user_data); static void @@ -83,12 +98,11 @@ return; } - if (!get_internet_available ()) { + if (!source->priv->locate_url_reachable) { g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NETWORK_UNREACHABLE, - "Network unavailable"); + "Cannot reach locate URL"); return; } - g_debug ("Network available"); if (source->priv->query != NULL) { g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_PENDING, @@ -96,53 +110,59 @@ return; } - source->priv->query = GCLUE_WEB_SOURCE_GET_CLASS (source)->create_query (source, &local_error); - + source->priv->query = GCLUE_WEB_SOURCE_GET_CLASS (source)->create_query + (source, &source->priv->query_data_description, &local_error); if (source->priv->query == NULL) { g_task_return_error (task, g_steal_pointer (&local_error)); return; } - /* TODO handle cancellation */ - soup_session_queue_message (source->priv->soup_session, + soup_session_send_and_read_async (source->priv->soup_session, source->priv->query, - refresh_callback, + G_PRIORITY_DEFAULT, + cancellable, + (GAsyncReadyCallback)refresh_callback, g_steal_pointer (&task)); } static void refresh_callback (SoupSession *session, - SoupMessage *query, + GAsyncResult *result, gpointer user_data) { g_autoptr(GTask) task = g_steal_pointer (&user_data); GClueWebSource *web; + g_autoptr(SoupMessage) query = NULL; + g_autoptr(GBytes) body = NULL; g_autoptr(GError) local_error = NULL; g_autofree char *contents = NULL; g_autofree char *str = NULL; g_autoptr(GClueLocation) location = NULL; - SoupURI *uri; + GUri *uri; + + web = GCLUE_WEB_SOURCE (g_task_get_source_object (task)); + query = g_steal_pointer (&web->priv->query); - if (query->status_code == SOUP_STATUS_CANCELLED) { - g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_CANCELLED, - "Operation cancelled"); + body = soup_session_send_and_read_finish (session, result, &local_error); + if (!body) { + g_task_return_error (task, g_steal_pointer (&local_error)); return; } - web = GCLUE_WEB_SOURCE (g_task_get_source_object (task)); - web->priv->query = NULL; - - if (query->status_code != SOUP_STATUS_OK) { + if (soup_message_get_status (query) != SOUP_STATUS_OK) { g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED, - "Failed to query location: %s", query->reason_phrase); + "Query location SOUP error: %s", + soup_message_get_reason_phrase (query)); return; } - contents = g_strndup (query->response_body->data, query->response_body->length); + contents = g_strndup (g_bytes_get_data (body, NULL), g_bytes_get_size (body)); uri = soup_message_get_uri (query); - str = soup_uri_to_string (uri, FALSE); + str = g_uri_to_string (uri); g_debug ("Got following response from '%s':\n%s", str, contents); - location = GCLUE_WEB_SOURCE_GET_CLASS (web)->parse_response (web, contents, &local_error); + location = gclue_mozilla_parse_response (contents, + web->priv->query_data_description, + &local_error); if (local_error != NULL) { g_task_return_error (task, g_steal_pointer (&local_error)); return; @@ -177,22 +197,16 @@ location = GCLUE_WEB_SOURCE_GET_CLASS (web)->refresh_finish (web, result, &local_error); if (local_error != NULL && - !g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED)) { - g_warning ("Failed to query location: %s", local_error->message); - return; + !g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + if (!g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED) && + !g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_PENDING)) { + g_warning ("Failed to query location: %s", + local_error->message); + } else { + g_debug ("Failed to query location: %s", + local_error->message); } } - -static gboolean -get_internet_available (void) -{ - GNetworkMonitor *monitor = g_network_monitor_get_default (); - gboolean available; - - available = (g_network_monitor_get_connectivity (monitor) == - G_NETWORK_CONNECTIVITY_FULL); - - return available; } static void @@ -203,7 +217,7 @@ existing = gclue_location_source_get_available_accuracy_level (GCLUE_LOCATION_SOURCE (web)); new = GCLUE_WEB_SOURCE_GET_CLASS (web)->get_available_accuracy_level - (web, web->priv->internet_available); + (web, web->priv->locate_url_reachable); if (new != existing) { g_debug ("Available accuracy level from %s: %u", G_OBJECT_TYPE_NAME (web), new); @@ -214,18 +228,108 @@ } static void -on_network_changed (GNetworkMonitor *monitor G_GNUC_UNUSED, - gboolean available G_GNUC_UNUSED, +locate_url_checked_cb (GObject *source_object, + GAsyncResult *result, gpointer user_data) { - GClueWebSource *web = GCLUE_WEB_SOURCE (user_data); - gboolean last_available = web->priv->internet_available; + GNetworkMonitor *mon = G_NETWORK_MONITOR (source_object); + GClueWebSource *web; + gboolean reachable, last_reachable; + g_autoptr(GError) error = NULL; - web->priv->internet_available = get_internet_available (); - if (last_available == web->priv->internet_available) + reachable = g_network_monitor_can_reach_finish (mon, result, &error); + if (error && g_error_matches (error, G_IO_ERROR, + G_IO_ERROR_CANCELLED)) { + return; /* WebSource instance is finalized */ + } + + web = GCLUE_WEB_SOURCE (user_data); + last_reachable = web->priv->locate_url_reachable; + web->priv->locate_url_reachable = reachable; + if (last_reachable == reachable) return; /* We already reacted to network change */ - GCLUE_WEB_SOURCE_GET_CLASS (web)->refresh_async (web, NULL, query_callback, NULL); + g_debug ("Network changed: %s", + reachable ? "Enabling locate URL queries" : + "Disabling locate URL queries"); + if (reachable) { + GCLUE_WEB_SOURCE_GET_CLASS (web)->refresh_async + (web, NULL, query_callback, NULL); + } +} + +static void +submit_url_checked_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + GNetworkMonitor *mon = G_NETWORK_MONITOR (source_object); + GClueWebSource *web; + gboolean reachable, last_reachable; + g_autoptr(GError) error = NULL; + + reachable = g_network_monitor_can_reach_finish (mon, result, &error); + if (error && g_error_matches (error, G_IO_ERROR, + G_IO_ERROR_CANCELLED)) { + return; /* WebSource instance is finalized */ + } + + web = GCLUE_WEB_SOURCE (user_data); + last_reachable = web->priv->submit_url_reachable; + web->priv->submit_url_reachable = reachable; + if (last_reachable == reachable) { + return; + } + g_debug ("Network changed: %s", + reachable ? "Enabling submit URL queries" : + "Disabling submit URL queries"); +} + +static void +on_network_changed (GNetworkMonitor *unused_monitor G_GNUC_UNUSED, + gboolean available G_GNUC_UNUSED, + gpointer user_data) +{ + GNetworkMonitor *monitor = g_network_monitor_get_default (); + GClueWebSource *web = GCLUE_WEB_SOURCE (user_data); + g_autoptr(GSocketConnectable) submit_addr = NULL; + g_autoptr(GSocketConnectable) locate_addr = NULL; + + if (web->priv->submit_url) { + submit_addr = g_network_address_parse_uri (web->priv->submit_url, + 80, NULL); + if (submit_addr) { + g_network_monitor_can_reach_async (monitor, + submit_addr, + web->priv->cancellable, + submit_url_checked_cb, + web); + } else { + g_warning ("Could not parse submit URL '%s'", + web->priv->submit_url); + web->priv->submit_url_reachable = FALSE; + } + } else { + web->priv->submit_url_reachable = FALSE; + } + + if (web->priv->locate_url) { + locate_addr = g_network_address_parse_uri (web->priv->locate_url, + 80, NULL); + if (locate_addr) { + g_network_monitor_can_reach_async (monitor, + locate_addr, + web->priv->cancellable, + locate_url_checked_cb, + web); + } else { + g_warning ("Could not parse locate URL '%s'", + web->priv->locate_url); + web->priv->locate_url_reachable = FALSE; + } + } else { + web->priv->locate_url_reachable = FALSE; + } } static void @@ -241,6 +345,8 @@ { GClueWebSourcePrivate *priv = GCLUE_WEB_SOURCE (gsource)->priv; + g_cancellable_cancel (priv->cancellable); + if (priv->network_changed_id) { g_signal_handler_disconnect (g_network_monitor_get_default (), priv->network_changed_id); @@ -253,15 +359,13 @@ priv->connectivity_changed_id = 0; } - if (priv->query != NULL) { - g_debug ("Cancelling query"); - soup_session_cancel_message (priv->soup_session, - priv->query, - SOUP_STATUS_CANCELLED); - priv->query = NULL; + if (priv->soup_session) { + soup_session_abort (priv->soup_session); + g_clear_object (&priv->soup_session); } - g_clear_object (&priv->soup_session); + g_clear_object (&priv->query); + g_clear_object (&priv->cancellable); G_OBJECT_CLASS (gclue_web_source_parent_class)->finalize (gsource); } @@ -274,10 +378,8 @@ G_OBJECT_CLASS (gclue_web_source_parent_class)->constructed (object); - priv->soup_session = soup_session_new_with_options - (SOUP_SESSION_REMOVE_FEATURE_BY_TYPE, - SOUP_TYPE_PROXY_RESOLVER_DEFAULT, - NULL); + priv->soup_session = soup_session_new (); + soup_session_remove_feature_by_type (priv->soup_session, G_TYPE_PROXY_RESOLVER); monitor = g_network_monitor_get_default (); priv->network_changed_id = @@ -296,6 +398,42 @@ } static void +gclue_web_source_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GClueWebSource *web = GCLUE_WEB_SOURCE (object); + + switch (prop_id) { + case PROP_ACCURACY_LEVEL: + g_value_set_enum (value, web->priv->accuracy_level); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gclue_web_source_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GClueWebSource *web = GCLUE_WEB_SOURCE (object); + + switch (prop_id) { + case PROP_ACCURACY_LEVEL: + web->priv->accuracy_level = g_value_get_enum (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void gclue_web_source_class_init (GClueWebSourceClass *klass) { GObjectClass *gsource_class = G_OBJECT_CLASS (klass); @@ -303,14 +441,28 @@ klass->refresh_async = gclue_web_source_real_refresh_async; klass->refresh_finish = gclue_web_source_real_refresh_finish; + gsource_class->get_property = gclue_web_source_get_property; + gsource_class->set_property = gclue_web_source_set_property; gsource_class->finalize = gclue_web_source_finalize; gsource_class->constructed = gclue_web_source_constructed; + + gParamSpecs[PROP_ACCURACY_LEVEL] = g_param_spec_enum ("accuracy-level", + "AccuracyLevel", + "Max accuracy level", + GCLUE_TYPE_ACCURACY_LEVEL, + GCLUE_ACCURACY_LEVEL_CITY, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY); + g_object_class_install_property (gsource_class, + PROP_ACCURACY_LEVEL, + gParamSpecs[PROP_ACCURACY_LEVEL]); } static void gclue_web_source_init (GClueWebSource *web) { web->priv = gclue_web_source_get_instance_private (web); + web->priv->cancellable = g_cancellable_new (); } /** @@ -331,24 +483,33 @@ static void submit_query_callback (SoupSession *session, - SoupMessage *query, + GAsyncResult *result, gpointer user_data) { - SoupURI *uri; - g_autofree char *str = NULL; + g_autoptr(GBytes) body = NULL; + g_autoptr(GError) local_error = NULL; + SoupMessage *query; + g_autofree char *uri_str = NULL; + gint status_code; - uri = soup_message_get_uri (query); - str = soup_uri_to_string (uri, FALSE); - if (query->status_code != SOUP_STATUS_OK && - query->status_code != SOUP_STATUS_NO_CONTENT) { + query = soup_session_get_async_result_message (session, result); + uri_str = g_uri_to_string (soup_message_get_uri (query)); + + body = soup_session_send_and_read_finish (session, result, &local_error); + if (!body) { + g_warning ("Failed to submit location data to '%s': %s", + uri_str, local_error->message); + return; + } + + status_code = soup_message_get_status (query); + if (status_code != SOUP_STATUS_OK && status_code != SOUP_STATUS_NO_CONTENT) { g_warning ("Failed to submit location data to '%s': %s", - str, - query->reason_phrase); + uri_str, soup_message_get_reason_phrase (query)); return; } - g_debug ("Successfully submitted location data to '%s'", - str); + g_debug ("Successfully submitted location data to '%s'", uri_str); } #define SUBMISSION_ACCURACY_THRESHOLD 100 @@ -362,8 +523,8 @@ GClueLocationSource *source = GCLUE_LOCATION_SOURCE (source_object); GClueWebSource *web = GCLUE_WEB_SOURCE (user_data); GClueLocation *location; - SoupMessage *query; - GError *error = NULL; + g_autoptr(SoupMessage) query = NULL; + g_autoptr(GError) error = NULL; location = gclue_location_source_get_location (source); if (location == NULL || @@ -375,7 +536,7 @@ web->priv->last_submitted = gclue_location_get_timestamp (location); - if (!get_internet_available ()) + if (!web->priv->submit_url_reachable) return; query = GCLUE_WEB_SOURCE_GET_CLASS (web)->create_submit_query @@ -386,15 +547,16 @@ if (error != NULL) { g_warning ("Failed to create submission query: %s", error->message); - g_error_free (error); } return; } - soup_session_queue_message (web->priv->soup_session, + soup_session_send_and_read_async (web->priv->soup_session, query, - submit_query_callback, + G_PRIORITY_DEFAULT, + NULL, + (GAsyncReadyCallback)submit_query_callback, web); } @@ -423,3 +585,17 @@ on_submit_source_location_notify (G_OBJECT (submit_source), NULL, web); } + +void +gclue_web_source_set_locate_url (GClueWebSource *source, + const char *url) +{ + source->priv->locate_url = url; +} + +void +gclue_web_source_set_submit_url (GClueWebSource *source, + const char *url) +{ + source->priv->submit_url = url; +} diff -Nurw geoclue-2.6.0/src/gclue-web-source.h geoclue-2.6.0-soup-3.2/src/gclue-web-source.h --- geoclue-2.6.0/src/gclue-web-source.h 2022-02-10 21:03:30.000000000 +0300 +++ geoclue-2.6.0-soup-3.2/src/gclue-web-source.h 2022-11-10 23:30:31.822463754 +0300 @@ -71,13 +71,11 @@ GError **error); SoupMessage * (*create_query) (GClueWebSource *source, + const char **query_data_description, GError **error); SoupMessage * (*create_submit_query) (GClueWebSource *source, GClueLocation *location, GError **error); - GClueLocation * (*parse_response) (GClueWebSource *source, - const char *response, - GError **error); GClueAccuracyLevel (*get_available_accuracy_level) (GClueWebSource *source, gboolean network_available); @@ -86,6 +84,10 @@ void gclue_web_source_refresh (GClueWebSource *source); void gclue_web_source_set_submit_source (GClueWebSource *source, GClueLocationSource *submit_source); +void gclue_web_source_set_locate_url (GClueWebSource *source, + const char *url); +void gclue_web_source_set_submit_url (GClueWebSource *source, + const char *url); G_END_DECLS diff -Nurw geoclue-2.6.0/src/gclue-wifi.c geoclue-2.6.0-soup-3.2/src/gclue-wifi.c --- geoclue-2.6.0/src/gclue-wifi.c 2022-02-10 21:03:30.000000000 +0300 +++ geoclue-2.6.0-soup-3.2/src/gclue-wifi.c 2022-11-10 23:30:31.822463754 +0300 @@ -24,6 +24,7 @@ #include #include #include "gclue-wifi.h" +#include "gclue-3g.h" #include "gclue-config.h" #include "gclue-error.h" #include "gclue-mozilla.h" @@ -49,6 +50,12 @@ * full cache (excluding overheads). */ #define CACHE_ENTRY_MAX_AGE_SECONDS (48 * 60 * 60) +/* The signal strength can typically vary by ±5 for a stationary laptop, so + * match cache entries with that tolerance. + * In dBm units. + */ +#define CACHE_ENTRY_MATCH_SIGNAL_WINDOW 10 + /** * SECTION:gclue-wifi * @short_description: WiFi-based geolocation @@ -78,7 +85,58 @@ static void disconnect_cache_prune_timeout (GClueWifi *wifi); +typedef struct { + GArray *signals; + GClueLocation *location; +} LocationCacheElement; + +static LocationCacheElement * +location_cache_element_new (GArray *signals, + GClueLocation *location) +{ + LocationCacheElement *element; + + element = g_slice_new (LocationCacheElement); + element->signals = signals; + element->location = g_object_ref (location); + return element; +} + +static void location_cache_element_free (gpointer data) +{ + LocationCacheElement *element = data; + + if (element->signals) + g_array_free (element->signals, TRUE); + g_clear_object (&element->location); + g_slice_free (LocationCacheElement, element); +} + +typedef struct { + GList *elements; +} LocationCacheValue; + +static LocationCacheValue * +location_cache_value_new (void) +{ + LocationCacheValue *value; + + value = g_slice_new (LocationCacheValue); + value->elements = NULL; + return value; +} + +static void location_cache_value_free (gpointer data) +{ + LocationCacheValue *value = data; + + g_list_free_full (value->elements, location_cache_element_free); + g_slice_free (LocationCacheValue, value); +} + struct _GClueWifiPrivate { + GCancellable *intf_cancellable, *bss_cancellable; + GClueMozilla *mozilla; WPASupplicant *supplicant; WPAInterface *interface; GHashTable *bss_proxies; @@ -92,9 +150,7 @@ guint scan_timeout; - GClueAccuracyLevel accuracy_level; - - GHashTable *location_cache; /* (element-type GVariant GClueLocation) (owned) */ + GHashTable *location_cache; /* (element-type GVariant LocationCacheValue) (owned) */ guint cache_prune_timeout_id; guint cache_hits, cache_misses; @@ -104,25 +160,14 @@ #endif }; -enum -{ - PROP_0, - PROP_ACCURACY_LEVEL, - LAST_PROP -}; -static GParamSpec *gParamSpecs[LAST_PROP]; - static SoupMessage * gclue_wifi_create_query (GClueWebSource *source, + const char **query_data_description, GError **error); static SoupMessage * gclue_wifi_create_submit_query (GClueWebSource *source, GClueLocation *location, GError **error); -static GClueLocation * -gclue_wifi_parse_response (GClueWebSource *source, - const char *json, - GError **error); static GClueAccuracyLevel gclue_wifi_get_available_accuracy_level (GClueWebSource *source, gboolean net_available); @@ -150,6 +195,8 @@ G_OBJECT_CLASS (gclue_wifi_parent_class)->finalize (gwifi); + g_cancellable_cancel (wifi->priv->intf_cancellable); + disconnect_bss_signals (wifi); disconnect_cache_prune_timeout (wifi); @@ -158,48 +205,14 @@ g_clear_pointer (&wifi->priv->bss_proxies, g_hash_table_unref); g_clear_pointer (&wifi->priv->ignored_bss_proxies, g_hash_table_unref); g_clear_pointer (&wifi->priv->location_cache, g_hash_table_unref); + g_clear_object (&wifi->priv->mozilla); + g_clear_object (&wifi->priv->intf_cancellable); } static void gclue_wifi_constructed (GObject *object); static void -gclue_wifi_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - GClueWifi *wifi = GCLUE_WIFI (object); - - switch (prop_id) { - case PROP_ACCURACY_LEVEL: - g_value_set_enum (value, wifi->priv->accuracy_level); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -gclue_wifi_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - GClueWifi *wifi = GCLUE_WIFI (object); - - switch (prop_id) { - case PROP_ACCURACY_LEVEL: - wifi->priv->accuracy_level = g_value_get_enum (value); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void gclue_wifi_class_init (GClueWifiClass *klass) { GClueWebSourceClass *web_class = GCLUE_WEB_SOURCE_CLASS (klass); @@ -212,24 +225,10 @@ web_class->refresh_finish = gclue_wifi_refresh_finish; web_class->create_submit_query = gclue_wifi_create_submit_query; web_class->create_query = gclue_wifi_create_query; - web_class->parse_response = gclue_wifi_parse_response; web_class->get_available_accuracy_level = gclue_wifi_get_available_accuracy_level; - gwifi_class->get_property = gclue_wifi_get_property; - gwifi_class->set_property = gclue_wifi_set_property; gwifi_class->finalize = gclue_wifi_finalize; gwifi_class->constructed = gclue_wifi_constructed; - - gParamSpecs[PROP_ACCURACY_LEVEL] = g_param_spec_enum ("accuracy-level", - "AccuracyLevel", - "Max accuracy level", - GCLUE_TYPE_ACCURACY_LEVEL, - GCLUE_ACCURACY_LEVEL_CITY, - G_PARAM_READWRITE | - G_PARAM_CONSTRUCT_ONLY); - g_object_class_install_property (gwifi_class, - PROP_ACCURACY_LEVEL, - gParamSpecs[PROP_ACCURACY_LEVEL]); } static void @@ -345,19 +344,24 @@ GAsyncResult *res, gpointer user_data) { - GClueWifi *wifi = GCLUE_WIFI (user_data); + GClueWifi *wifi; WPABSS *bss; - GError *error = NULL; + g_autoptr(GError) error = NULL; char ssid[MAX_SSID_LEN + 1] = { 0 }; bss = wpa_bss_proxy_new_for_bus_finish (res, &error); if (bss == NULL) { - g_debug ("%s", error->message); - g_error_free (error); + if (error && !g_error_matches (error, G_IO_ERROR, + G_IO_ERROR_CANCELLED)) { + g_warning ("BSS proxy setup failed: %s", + error->message); + } return; } + wifi = GCLUE_WIFI (user_data); + if (gclue_mozilla_should_ignore_bss (bss)) { g_object_unref (bss); @@ -365,7 +369,7 @@ } get_ssid_from_bss (bss, ssid); - g_debug ("WiFi AP '%s' added.", ssid); + g_debug ("Got WiFi AP '%s'", ssid); if (wpa_bss_get_signal (bss) <= WIFI_SCAN_BSS_NOISE_LEVEL) { const char *path; @@ -396,13 +400,15 @@ GVariant *properties, gpointer user_data) { + GClueWifi *wifi = GCLUE_WIFI (user_data); + wpa_bss_proxy_new_for_bus (G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, "fi.w1.wpa_supplicant1", path, - NULL, + wifi->priv->bss_cancellable, on_bss_proxy_ready, - user_data); + wifi); } static gboolean @@ -459,7 +465,7 @@ wpa_interface_call_scan (WPA_INTERFACE (priv->interface), args, - NULL, + priv->bss_cancellable, on_scan_call_done, wifi); } @@ -492,7 +498,6 @@ GClueWifi *wifi = GCLUE_WIFI (user_data); GClueWifiPrivate *priv = wifi->priv; - g_debug ("WiFi scan timeout."); priv->scan_timeout = 0; if (priv->interface == NULL) @@ -503,6 +508,11 @@ return G_SOURCE_REMOVE; } +gboolean gclue_wifi_should_skip_bsss (GClueAccuracyLevel level) +{ + return level < GCLUE_ACCURACY_LEVEL_STREET; +} + static gboolean on_scan_wait_done (gpointer wifi) { @@ -511,9 +521,13 @@ g_return_val_if_fail (GCLUE_IS_WIFI (wifi), G_SOURCE_REMOVE); priv = GCLUE_WIFI(wifi)->priv; + /* We have the latest scan result */ + gclue_mozilla_set_wifi (priv->mozilla, wifi); + if (priv->bss_list_changed) { priv->bss_list_changed = FALSE; - g_debug ("Refreshing location…"); + g_debug ("WiFi BSS list changed, refreshing location…"); + gclue_mozilla_set_bss_dirty (priv->mozilla); gclue_web_source_refresh (GCLUE_WEB_SOURCE (wifi)); } priv->scan_wait_id = 0; @@ -521,6 +535,15 @@ return G_SOURCE_REMOVE; } +static GClueAccuracyLevel +get_accuracy_level (GClueWifi *wifi) +{ + GClueAccuracyLevel level; + + g_object_get (G_OBJECT (wifi), "accuracy-level", &level, NULL); + return level; +} + static void on_scan_done (WPAInterface *object, gboolean success, @@ -535,7 +558,6 @@ return; } - g_debug ("WiFi scan completed"); if (priv->interface == NULL) return; @@ -558,14 +580,14 @@ * user's location can change quickly. With low accuracy, we don't since * we wouldn't want to drain power unnecessarily. */ - if (priv->accuracy_level >= GCLUE_ACCURACY_LEVEL_STREET) + if (get_accuracy_level (wifi) >= GCLUE_ACCURACY_LEVEL_STREET) timeout = WIFI_SCAN_TIMEOUT_HIGH_ACCURACY; else timeout = WIFI_SCAN_TIMEOUT_LOW_ACCURACY; priv->scan_timeout = g_timeout_add_seconds (timeout, on_scan_timeout, wifi); - g_debug ("Next scan scheduled in %u seconds", timeout); + g_debug ("WiFi scan done, next scheduled in %u seconds", timeout); } static void @@ -573,20 +595,24 @@ GAsyncResult *res, gpointer user_data) { - GClueWifi *wifi = GCLUE_WIFI (user_data); - GError *error = NULL; + g_autoptr(GError) error = NULL; - if (!wpa_interface_call_scan_finish - (WPA_INTERFACE (source_object), - res, - &error)) { + if (!wpa_interface_call_scan_finish (WPA_INTERFACE (source_object), + res, &error)) { + GClueWifi *wifi; + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + return; + } + + wifi = GCLUE_WIFI (user_data); + + if (error) { g_warning ("Scanning of WiFi networks failed: %s", error->message); - g_error_free (error); + } cancel_wifi_scan (wifi); - - return; } } @@ -605,6 +631,9 @@ return; } + g_assert (!priv->bss_cancellable); + priv->bss_cancellable = g_cancellable_new (); + start_wifi_scan (wifi); priv->bss_list_changed = TRUE; @@ -633,6 +662,12 @@ { GClueWifiPrivate *priv = wifi->priv; + if (priv->bss_cancellable) { + g_debug ("Cancelling WiFi requests"); + g_cancellable_cancel (priv->bss_cancellable); + g_clear_object (&priv->bss_cancellable); + } + cancel_wifi_scan (wifi); if (priv->bss_added_id != 0) { @@ -657,22 +692,46 @@ GHashTableIter iter; gpointer value; guint64 cutoff_seconds; - guint old_cache_size; + guint old_cache_size, removed_elements = 0; old_cache_size = g_hash_table_size (priv->location_cache); cutoff_seconds = g_get_real_time () / G_USEC_PER_SEC - CACHE_ENTRY_MAX_AGE_SECONDS; g_hash_table_iter_init (&iter, priv->location_cache); while (g_hash_table_iter_next (&iter, NULL, &value)) { - GClueLocation *location = GCLUE_LOCATION (value); - guint64 timestamp_seconds = gclue_location_get_timestamp (location); + LocationCacheValue *lcvalue = (LocationCacheValue *)value; + GList *l = lcvalue->elements; + + g_assert (l); + while (l) { + LocationCacheElement *element = (LocationCacheElement *)l->data; + GList *lnext = l->next; + + /* Keep this location? */ + if (gclue_location_get_timestamp (element->location) > + cutoff_seconds) + goto next_el; + + location_cache_element_free (element); + lcvalue->elements = g_list_delete_link (lcvalue->elements, l); + removed_elements++; - if (timestamp_seconds <= cutoff_seconds) + /* Deleted the last entry (element) in this hash bucket? + * Remove this hash table entry then. + */ + if (!lcvalue->elements) { + g_assert (!lnext); g_hash_table_iter_remove (&iter); } - g_debug ("Pruned cache (old size: %u, new size: %u)", - old_cache_size, g_hash_table_size (priv->location_cache)); + next_el: + l = lnext; + } + } + + g_debug ("Pruned cache (old size: %u, new size: %u, removed elements: %u)", + old_cache_size, g_hash_table_size (priv->location_cache), + removed_elements); } #if GLIB_CHECK_VERSION(2, 64, 0) @@ -782,6 +841,8 @@ static GClueLocationSourceStopResult gclue_wifi_stop (GClueLocationSource *source) { + GClueWifi *wifi = GCLUE_WIFI (source); + GClueWifiPrivate *priv = wifi->priv; GClueLocationSourceClass *base_class; GClueLocationSourceStopResult base_result; @@ -795,6 +856,10 @@ disconnect_bss_signals (GCLUE_WIFI (source)); disconnect_cache_prune_timeout (GCLUE_WIFI (source)); + if (gclue_mozilla_test_set_wifi (priv->mozilla, wifi, NULL)) { + g_debug ("Removed us as the WiFi source on stop"); + } + return base_result; } @@ -802,15 +867,15 @@ gclue_wifi_get_available_accuracy_level (GClueWebSource *source, gboolean net_available) { - GClueWifiPrivate *priv = GCLUE_WIFI (source)->priv; + GClueWifi *wifi = GCLUE_WIFI (source); + GClueWifiPrivate *priv = wifi->priv; if (!net_available) return GCLUE_ACCURACY_LEVEL_NONE; - else if (priv->interface != NULL && - priv->accuracy_level != GCLUE_ACCURACY_LEVEL_CITY) - return GCLUE_ACCURACY_LEVEL_STREET; - else + else if (!priv->interface) return GCLUE_ACCURACY_LEVEL_CITY; + else + return MIN (get_accuracy_level (wifi), GCLUE_ACCURACY_LEVEL_STREET); } static void @@ -818,18 +883,25 @@ GAsyncResult *res, gpointer user_data) { - GClueWifi *wifi = GCLUE_WIFI (user_data); + GClueWifi *wifi; WPAInterface *interface; - GError *error = NULL; + g_autoptr(GError) error = NULL; interface = wpa_interface_proxy_new_for_bus_finish (res, &error); if (interface == NULL) { - g_debug ("%s", error->message); - g_error_free (error); + if (error) { + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + return; + } + + g_warning ("Interface proxy add failed: %s", + error->message); + } return; } + wifi = GCLUE_WIFI (user_data); if (wifi->priv->interface != NULL) { g_object_unref (interface); return; @@ -860,7 +932,7 @@ G_DBUS_PROXY_FLAGS_NONE, "fi.w1.wpa_supplicant1", path, - NULL, + wifi->priv->intf_cancellable, on_interface_proxy_ready, wifi); } @@ -888,14 +960,27 @@ disconnect_bss_signals (wifi); g_clear_object (&wifi->priv->interface); + if (gclue_mozilla_test_set_wifi (priv->mozilla, wifi, NULL)) { + g_debug ("Removed interface was the WiFi source"); + } + gclue_web_source_refresh (GCLUE_WEB_SOURCE (wifi)); } static void gclue_wifi_init (GClueWifi *wifi) { + GClueWebSource *web_source = GCLUE_WEB_SOURCE (wifi); + wifi->priv = gclue_wifi_get_instance_private (wifi); + wifi->priv->intf_cancellable = g_cancellable_new (); + wifi->priv->mozilla = gclue_mozilla_get_singleton (); + gclue_web_source_set_locate_url (web_source, + gclue_mozilla_get_locate_url (wifi->priv->mozilla)); + gclue_web_source_set_submit_url (web_source, + gclue_mozilla_get_submit_url (wifi->priv->mozilla)); + wifi->priv->bss_proxies = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, @@ -907,7 +992,7 @@ wifi->priv->location_cache = g_hash_table_new_full (variant_hash, g_variant_equal, (GDestroyNotify) g_variant_unref, - g_object_unref); + location_cache_value_free); } static void @@ -916,11 +1001,11 @@ GClueWifi *wifi = GCLUE_WIFI (object); GClueWifiPrivate *priv = wifi->priv; const gchar *const *interfaces; - GError *error = NULL; + g_autoptr(GError) error = NULL; G_OBJECT_CLASS (gclue_wifi_parent_class)->constructed (object); - if (wifi->priv->accuracy_level == GCLUE_ACCURACY_LEVEL_CITY) { + if (get_accuracy_level (wifi) == GCLUE_ACCURACY_LEVEL_CITY) { GClueConfig *config = gclue_config_get_singleton (); if (!gclue_config_get_enable_wifi_source (config)) @@ -936,20 +1021,20 @@ NULL, &error); if (priv->supplicant == NULL) { + if (error) g_warning ("Failed to connect to wpa_supplicant service: %s", error->message); - g_error_free (error); goto refresh_n_exit; } - g_signal_connect (priv->supplicant, + g_signal_connect_object (priv->supplicant, "interface-added", G_CALLBACK (on_interface_added), - wifi); - g_signal_connect (priv->supplicant, + wifi, 0); + g_signal_connect_object (priv->supplicant, "interface-removed", G_CALLBACK (on_interface_removed), - wifi); + wifi, 0); interfaces = wpa_supplicant_get_interfaces (priv->supplicant); if (interfaces != NULL && interfaces[0] != NULL) @@ -982,23 +1067,29 @@ GClueWifi * gclue_wifi_get_singleton (GClueAccuracyLevel level) { - static GClueWifi *wifi[] = { NULL, NULL }; + static GClueWifi *wifi[] = { NULL, NULL, NULL }; guint i; + GClueConfig *config = gclue_config_get_singleton (); + gboolean wifi_enabled; gboolean scramble_location = FALSE; gboolean compute_movement = FALSE; g_return_val_if_fail (level >= GCLUE_ACCURACY_LEVEL_CITY, NULL); - if (level == GCLUE_ACCURACY_LEVEL_NEIGHBORHOOD) - level = GCLUE_ACCURACY_LEVEL_CITY; + wifi_enabled = gclue_config_get_enable_wifi_source (config); if (level == GCLUE_ACCURACY_LEVEL_CITY) { - GClueConfig *config = gclue_config_get_singleton (); - i = 0; - if (gclue_config_get_enable_wifi_source (config)) + if (wifi_enabled) scramble_location = TRUE; - } else { + } else if (level == GCLUE_ACCURACY_LEVEL_NEIGHBORHOOD) { + g_return_val_if_fail (wifi_enabled, NULL); + i = 1; + scramble_location = TRUE; + } else { + g_return_val_if_fail (wifi_enabled, NULL); + + i = 2; compute_movement = TRUE; } @@ -1017,38 +1108,33 @@ return wifi[i]; } -GClueAccuracyLevel -gclue_wifi_get_accuracy_level (GClueWifi *wifi) +static gboolean +wifi_should_skip_tower (GClueWifi *wifi) { - g_return_val_if_fail (GCLUE_IS_WIFI (wifi), - GCLUE_ACCURACY_LEVEL_NONE); - - return wifi->priv->accuracy_level; + return gclue_3g_should_skip_tower (get_accuracy_level (wifi)); } /* Can return NULL, signifying an empty BSS list. */ -static GList * -get_bss_list (GClueWifi *wifi) +GList * +gclue_wifi_get_bss_list (GClueWifi *wifi) { return g_hash_table_get_values (wifi->priv->bss_proxies); } static SoupMessage * gclue_wifi_create_query (GClueWebSource *source, + const char **query_data_description, GError **error) { GClueWifi *wifi = GCLUE_WIFI (source); - GList *bss_list = NULL; /* As in Access Points */ - SoupMessage *msg; + gboolean skip_tower; if (wifi->priv->interface == NULL) { goto create_query; } - bss_list = get_bss_list (wifi); - /* Empty list? */ - if (bss_list == NULL) { + if (!g_hash_table_size (wifi->priv->bss_proxies)) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, @@ -1057,17 +1143,13 @@ } create_query: - msg = gclue_mozilla_create_query (bss_list, NULL, error); - g_list_free (bss_list); - return msg; + skip_tower = wifi_should_skip_tower (wifi); + if (skip_tower) { + g_debug ("Will skip 3GPP tower in query due to our accuracy level"); } -static GClueLocation * -gclue_wifi_parse_response (GClueWebSource *source, - const char *json, - GError **error) -{ - return gclue_mozilla_parse_response (json, error); + return gclue_mozilla_create_query (wifi->priv->mozilla, skip_tower, FALSE, + query_data_description, error); } static SoupMessage * @@ -1076,7 +1158,6 @@ GError **error) { GClueWifi *wifi = GCLUE_WIFI (source); - GList *bss_list; /* As in Access Points */ SoupMessage * msg; if (wifi->priv->interface == NULL) { @@ -1087,10 +1168,8 @@ return NULL; } - bss_list = get_bss_list (wifi); - /* Empty list? */ - if (bss_list == NULL) { + if (!g_hash_table_size (wifi->priv->bss_proxies)) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, @@ -1098,11 +1177,9 @@ return NULL; } - msg = gclue_mozilla_create_submit_query (location, - bss_list, - NULL, + msg = gclue_mozilla_create_submit_query (wifi->priv->mozilla, + location, error); - g_list_free (bss_list); return msg; } @@ -1145,20 +1222,46 @@ return g_bytes_hash (bytes); } -static GVariant * -get_location_cache_key (GClueWifi *wifi) +static void location_cache_key_fill_tower (GClueWifi *wifi, GClue3GTower *tower) +{ + GClueWifiPrivate *priv = wifi->priv; + GClue3GTower *moztower; + + memset (tower, 0, sizeof (*tower)); + tower->tec = GCLUE_TOWER_TEC_NO_FIX; + + moztower = gclue_mozilla_get_tower (priv->mozilla); + if (!moztower || wifi_should_skip_tower (wifi)) { + return; + } + + g_assert (moztower->tec != GCLUE_TOWER_TEC_NO_FIX); + *tower = *moztower; +} + +static void location_cache_key_add_tower (GClueWifi *wifi, GVariantBuilder *builder) +{ + GClue3GTower tower; + + location_cache_key_fill_tower (wifi, &tower); + g_variant_builder_add (builder, "u", (guint32)tower.tec); + g_variant_builder_add (builder, "s", tower.opc); + g_variant_builder_add (builder, "t", (guint64)tower.lac); + g_variant_builder_add (builder, "t", (guint64)tower.cell_id); +} + +static GPtrArray * +get_location_cache_bss_array (GClueWifi *wifi) { GHashTableIter iter; gpointer value; g_autoptr(GPtrArray) bss_array = g_ptr_array_new_with_free_func (NULL); /* (element-type WPABSS) */ - guint i; - GVariantBuilder builder; /* The Mozilla service puts BSSID and signal strength for each BSS into - * its query. The signal strength can typically vary by ±5 for a - * stationary laptop, so quantise by that. Pack the whole lot into a - * #GVariant for simplicity, sorted by MAC address. The sorting has to - * happen in an array beforehand, as variants are immutable. */ + * its query. Pack the whole lot into a #GVariant for simplicity, sorted + * by MAC address. The sorting has to happen in an array beforehand, + * as variants are immutable. + */ g_hash_table_iter_init (&iter, wifi->priv->bss_proxies); while (g_hash_table_iter_next (&iter, NULL, &value)) { @@ -1169,39 +1272,142 @@ g_ptr_array_sort (bss_array, bss_compare); + return g_steal_pointer (&bss_array); +} + +static GVariant * +get_location_cache_hashtable_key (GClueWifi *wifi, GPtrArray *bss_array) +{ + guint i; + GVariantBuilder builder; + /* Serialise to a variant. */ - g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ayn)")); + g_variant_builder_init (&builder, G_VARIANT_TYPE ("(usttaay)")); + location_cache_key_add_tower (wifi, &builder); + + g_variant_builder_open (&builder, G_VARIANT_TYPE ("aay")); for (i = 0; i < bss_array->len; i++) { WPABSS *bss = WPA_BSS (bss_array->pdata[i]); GVariant *bssid; - g_variant_builder_open (&builder, G_VARIANT_TYPE ("(ayn)")); - bssid = wpa_bss_get_bssid (bss); if (bssid == NULL) continue; g_variant_builder_add_value (&builder, bssid); - g_variant_builder_add (&builder, "n", wpa_bss_get_signal (bss) / 10); - - g_variant_builder_close (&builder); } + g_variant_builder_close (&builder); return g_variant_builder_end (&builder); } +static GArray * +get_location_cache_signal_array (GClueWifi *wifi, GPtrArray *bss_array) +{ + g_autoptr(GArray) signal_array = NULL; + guint i; + + signal_array = g_array_sized_new (FALSE, FALSE, sizeof (gint16), bss_array->len); + for (i = 0; i < bss_array->len; i++) { + WPABSS *bss = WPA_BSS (bss_array->pdata[i]); + gint16 signal = wpa_bss_get_signal (bss); + + g_array_append_val (signal_array, signal); + } + + return g_steal_pointer (&signal_array); +} + +static gboolean cached_signals_match (GArray *signals1, GArray *signals2) +{ + guint i; + + if (signals1->len != signals2->len) { + g_warning ("Different signal count in one hash table entry: %u vs %u", + signals1->len, signals2->len); + return FALSE; + } + + for (i = 0; i < signals1->len; i++) { + gint s1 = g_array_index (signals1, gint16, i); + gint s2 = g_array_index (signals2, gint16, i); + + if (ABS (s1 - s2) > CACHE_ENTRY_MATCH_SIGNAL_WINDOW / 2) + return FALSE; + } + + return TRUE; +} + static GClueLocation * -duplicate_location_new_timestamp (GClueLocation *location) +find_cached_location (GHashTable *cache, GVariant *key, GArray *signals) { - return g_object_new (GCLUE_TYPE_LOCATION, - "latitude", gclue_location_get_latitude (location), - "longitude", gclue_location_get_longitude (location), - "accuracy", gclue_location_get_accuracy (location), - "altitude", gclue_location_get_altitude (location), - "timestamp", 0, - "speed", gclue_location_get_speed (location), - "heading", gclue_location_get_heading (location), - NULL); + g_autofree gchar *key_str = g_variant_print (key, FALSE); + GClueLocation *location = NULL; + LocationCacheValue *value; + GList *l; + + value = g_hash_table_lookup (cache, key); + if (!value) { + g_debug ("Cache miss for key %s", key_str); + return NULL; + } + + g_assert (value->elements); + for (l = value->elements; l; l = l->next) { + LocationCacheElement *element = l->data; + + if (location && + gclue_location_get_accuracy (element->location) >= + gclue_location_get_accuracy (location)) { + /* Have at least as accurate location already, + * don't bother with comparing signals. + */ + continue; + } + + if (!cached_signals_match (element->signals, signals)) + continue; + + location = element->location; + } + + if (location) { + g_debug ("Cache hit for key %s: got location %p (%s)", + key_str, location, + gclue_location_get_description (location)); + } else { + g_debug ("Cache had key %s, but with different signals", key_str); + } + + return location; +} + +typedef struct { + GVariant *cache_key; + GArray *signals; +} RefreshTaskData; + +static RefreshTaskData * +refresh_task_data_new (GVariant *cache_key, + GArray *signals) +{ + RefreshTaskData *tdata; + + tdata = g_slice_new (RefreshTaskData); + tdata->cache_key = g_variant_ref (cache_key); + tdata->signals = signals; + return tdata; +} + +static void refresh_task_data_free (gpointer data) +{ + RefreshTaskData *rdata = data; + + g_clear_pointer (&rdata->cache_key, g_variant_unref); + if (rdata->signals) + g_array_free (rdata->signals, TRUE); + g_slice_free (RefreshTaskData, rdata); } static void @@ -1212,40 +1418,59 @@ { GClueWifi *wifi = GCLUE_WIFI (source); g_autoptr(GTask) task = g_task_new (source, cancellable, callback, user_data); - g_autoptr(GVariant) cache_key = get_location_cache_key (wifi); - g_autofree gchar *cache_key_str = g_variant_print (cache_key, FALSE); - GClueLocation *cached_location = g_hash_table_lookup (wifi->priv->location_cache, cache_key); + g_autoptr(GPtrArray) bss_array = get_location_cache_bss_array (wifi); + g_autoptr(GVariant) cache_key = get_location_cache_hashtable_key (wifi, bss_array); + g_autoptr(GArray) signal_array = get_location_cache_signal_array (wifi, bss_array); + GClueLocation *cached_location = find_cached_location (wifi->priv->location_cache, + cache_key, signal_array); + RefreshTaskData *tdata; g_task_set_source_tag (task, gclue_wifi_refresh_async); - g_task_set_task_data (task, g_steal_pointer (&cache_key), (GDestroyNotify) g_variant_unref); if (gclue_location_source_get_active (GCLUE_LOCATION_SOURCE (source))) { /* Try the cache. */ if (cached_location != NULL) { g_autoptr(GClueLocation) new_location = NULL; - g_debug ("Cache hit for key %s: got location %p (%s)", - cache_key_str, cached_location, - gclue_location_get_description (cached_location)); wifi->priv->cache_hits++; /* Duplicate the location so its timestamp is updated. */ - new_location = duplicate_location_new_timestamp (cached_location); + new_location = gclue_location_duplicate_fresh (cached_location); gclue_location_source_set_location (GCLUE_LOCATION_SOURCE (source), new_location); g_task_return_pointer (task, g_steal_pointer (&new_location), g_object_unref); return; } - g_debug ("Cache miss for key %s; querying web service", cache_key_str); wifi->priv->cache_misses++; } + tdata = refresh_task_data_new (cache_key, g_steal_pointer (&signal_array)); + g_task_set_task_data (task, tdata, refresh_task_data_free); + /* Fall back to querying the web service. */ GCLUE_WEB_SOURCE_CLASS (gclue_wifi_parent_class)->refresh_async (source, cancellable, refresh_cb, g_steal_pointer (&task)); } static void +add_cached_location (GHashTable *cache, + GVariant *key, GArray **signals, + GClueLocation *location) +{ + LocationCacheValue *value; + LocationCacheElement *element; + + value = g_hash_table_lookup (cache, key); + if (!value) { + value = location_cache_value_new (); + g_hash_table_insert (cache, g_variant_ref (key), value); + } + + element = location_cache_element_new (g_steal_pointer (signals), location); + value->elements = g_list_prepend (value->elements, element); +} + +static void refresh_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) @@ -1255,7 +1480,7 @@ g_autoptr(GTask) task = g_steal_pointer (&user_data); g_autoptr(GClueLocation) location = NULL; g_autoptr(GError) local_error = NULL; - GVariant *cache_key; + RefreshTaskData *tdata; g_autofree gchar *cache_key_str = NULL; double cache_hit_ratio; @@ -1268,9 +1493,11 @@ } /* Cache the result. */ - cache_key = g_task_get_task_data (task); - cache_key_str = g_variant_print (cache_key, FALSE); - g_hash_table_replace (wifi->priv->location_cache, g_variant_ref (cache_key), g_object_ref (location)); + tdata = g_task_get_task_data (task); + cache_key_str = g_variant_print (tdata->cache_key, FALSE); + add_cached_location (wifi->priv->location_cache, + tdata->cache_key, &tdata->signals, + location); if (wifi->priv->cache_hits || wifi->priv->cache_misses) { double cache_attempts; diff -Nurw geoclue-2.6.0/src/gclue-wifi.h geoclue-2.6.0-soup-3.2/src/gclue-wifi.h --- geoclue-2.6.0/src/gclue-wifi.h 2022-02-10 21:03:30.000000000 +0300 +++ geoclue-2.6.0-soup-3.2/src/gclue-wifi.h 2022-11-10 23:30:31.822463754 +0300 @@ -63,7 +63,8 @@ }; GClueWifi * gclue_wifi_get_singleton (GClueAccuracyLevel level); -GClueAccuracyLevel gclue_wifi_get_accuracy_level (GClueWifi *wifi); +gboolean gclue_wifi_should_skip_bsss (GClueAccuracyLevel level); +GList *gclue_wifi_get_bss_list (GClueWifi *wifi); G_END_DECLS diff -Nurw geoclue-2.6.0/src/meson.build geoclue-2.6.0-soup-3.2/src/meson.build --- geoclue-2.6.0/src/meson.build 2022-02-10 21:03:30.000000000 +0300 +++ geoclue-2.6.0-soup-3.2/src/meson.build 2022-11-10 23:30:31.822463754 +0300 @@ -1,5 +1,5 @@ geoclue_deps = base_deps + [ dependency('json-glib-1.0', version: '>= 0.14.0'), - dependency('libsoup-2.4', version: '>= 2.42.0') ] + dependency('libsoup-3.0', version: '>= 3.0.0') ] sources = [ libgeoclue_public_api_gen_sources[1], geoclue_iface_sources, @@ -28,7 +28,8 @@ 'gclue-wifi.h', 'gclue-wifi.c', 'gclue-mozilla.h', 'gclue-mozilla.c', 'gclue-min-uint.h', 'gclue-min-uint.c', - 'gclue-location.h', 'gclue-location.c' ] + 'gclue-location.h', 'gclue-location.c', + 'gclue-utils.h' ] if get_option('3g-source') or get_option('cdma-source') or get_option('modem-gps-source') geoclue_deps += [ dependency('mm-glib', version: '>= 1.10') ]