From 8ac3dfb9557d01e5a60b61081ec9da7b3b7911fd Mon Sep 17 00:00:00 2001 From: Andrey Skvortsov Date: Fri, 2 Jan 2026 23:09:11 +0300 Subject: [PATCH] location-cache: add class to store cached location This class allows to preserve cached position across ModemManager restarts. Cached location can be used to improve time to first fix after device start by injecting coarse position into location engine. Since there wasn't any fix yet, position in ModemManager is unknown. To fix this cached location is saved on disk (/var/lib/ModemManager/location.ini) when location session is stopped or ModemManager is stopped and loaded on the next start from the file. install_emptydir is introduced in meson 0.60. Distributions has to create directory to store cached location in packaging, because ModemManager runs under systemd isolation with limited rights and it can't create this directory by itself. Signed-off-by: Andrey Skvortsov --- meson.build | 4 +- src/meson.build | 11 ++ src/mm-location-cache.c | 241 ++++++++++++++++++++++++++++++++++++++++ src/mm-location-cache.h | 57 ++++++++++ 4 files changed, 312 insertions(+), 1 deletion(-) create mode 100644 src/mm-location-cache.c create mode 100644 src/mm-location-cache.h diff --git a/meson.build b/meson.build index f81e5974..71b3c3c1 100644 --- a/meson.build +++ b/meson.build @@ -10,7 +10,7 @@ project( 'c_std=gnu89', 'warning_level=2', ], - meson_version: '>= 0.53.0', + meson_version: '>= 0.60.0', ) meson_version = meson.version() @@ -31,11 +31,13 @@ mm_includedir = get_option('includedir') mm_libdir = get_option('libdir') mm_sbindir = get_option('sbindir') mm_sysconfdir = get_option('sysconfdir') +mm_sharedstatedir = get_option('sharedstatedir') mm_pkgdatadir = mm_datadir / mm_name mm_pkgincludedir = mm_includedir / mm_name mm_pkglibdir = mm_libdir / mm_name mm_pkgsysconfdir = mm_sysconfdir / mm_name +mm_pkgsharedstatedir = mm_sharedstatedir / mm_name mm_glib_name = 'libmm-glib' mm_glib_pkgincludedir = mm_includedir / mm_glib_name diff --git a/src/meson.build b/src/meson.build index 04d199d1..78d8794d 100644 --- a/src/meson.build +++ b/src/meson.build @@ -46,6 +46,7 @@ sources = files( 'mm-cbm-part.c', 'mm-charsets.c', 'mm-error-helpers.c', + 'mm-location-cache.c', 'mm-log.c', 'mm-log-object.c', 'mm-modem-helpers.c', @@ -80,11 +81,16 @@ if enable_systemd_journal private_deps += libsystemd_dep endif +c_args = [ + '-DPKGSTATEDIR="@0@"'.format(mm_pkgsharedstatedir), +] + libhelpers = static_library( 'helpers', sources: sources + enums_sources, include_directories: incs, dependencies: deps + private_deps, + c_args: c_args, ) libhelpers_dep = declare_dependency( @@ -506,6 +512,11 @@ install_data( install_dir: udev_rulesdir, ) +# empty directory for cached position +install_emptydir( + mm_pkgsharedstatedir, +) + if enable_tests subdir('tests') endif diff --git a/src/mm-location-cache.c b/src/mm-location-cache.c new file mode 100644 index 00000000..e27921ff --- /dev/null +++ b/src/mm-location-cache.c @@ -0,0 +1,241 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program 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. + * + * This program 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: + * + * Copyright (C) 2026 Andrey Skvortsov + */ + + +#include +#include + +#define _LIBMM_INSIDE_MM +#include + +#include "mm-location-cache.h" +#include "mm-log-object.h" + +#if !defined PKGSTATEDIR +# error PKGSTATEDIR is not defined +#endif + +#define LOCATION_STATE_FILE "location.ini" +#define LOCATION_LAST_POS_GROUP "last_position" +#define LOCATION_LAST_POS_LATITUDE_KEY "latitude" +#define LOCATION_LAST_POS_LONGITUDE_KEY "longitude" + +/*****************************************************************************/ + +static void log_object_iface_init (MMLogObjectInterface *iface); + +G_DEFINE_TYPE_EXTENDED (MMLocationCache, mm_location_cache, G_TYPE_OBJECT, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_LOG_OBJECT, log_object_iface_init)) + +struct _MMLocationCachePrivate { + MMLocationGpsRaw *gps_raw; + gdouble last_latitude; + gdouble last_longitude; + gchar* filename; +}; + +/*****************************************************************************/ + +static gchar * +log_object_build_id (MMLogObject *_self) +{ + return g_strdup ("location-cache"); +} + +gboolean +mm_location_cache_load_from_file (MMLocationCache *self, + const gchar *file, + GError **error) +{ + g_autoptr(GKeyFile) key_file = g_key_file_new (); + gdouble lat; + gdouble lon; + + self->priv->last_latitude = MM_LOCATION_LATITUDE_UNKNOWN; + self->priv->last_longitude = MM_LOCATION_LONGITUDE_UNKNOWN; + + if (!g_key_file_load_from_file (key_file, file, G_KEY_FILE_NONE, error)) { + g_prefix_error (error, "Error loading cached location from %s: ", file); + return FALSE; + } + + lat = g_key_file_get_double (key_file, + LOCATION_LAST_POS_GROUP, + LOCATION_LAST_POS_LATITUDE_KEY, + error); + if (*error) { + g_prefix_error (error, "Error finding latitude in file: "); + return FALSE; + } + + lon = g_key_file_get_double (key_file, + LOCATION_LAST_POS_GROUP, + LOCATION_LAST_POS_LONGITUDE_KEY, + error); + if (*error) { + g_prefix_error (error, "Error finding longitude in file: "); + return FALSE; + } + + self->priv->last_latitude = lat; + self->priv->last_longitude = lon; + return TRUE; +} + +gboolean +mm_location_cache_load (MMLocationCache *self, + GError **error) +{ + return mm_location_cache_load_from_file (self, self->priv->filename, error); +} + +gboolean +mm_location_cache_save_to_file (MMLocationCache *self, + const gchar *file, + GError **error) +{ + g_autoptr(GKeyFile) key_file = g_key_file_new (); + + if ((self->priv->last_latitude == MM_LOCATION_LATITUDE_UNKNOWN) || + (self->priv->last_longitude == MM_LOCATION_LONGITUDE_UNKNOWN)) { + return TRUE; + } + + key_file = g_key_file_new (); + g_key_file_set_double (key_file, + LOCATION_LAST_POS_GROUP, + LOCATION_LAST_POS_LATITUDE_KEY, + self->priv->last_latitude); + g_key_file_set_double (key_file, + LOCATION_LAST_POS_GROUP, + LOCATION_LAST_POS_LONGITUDE_KEY, + self->priv->last_longitude); + + if (!g_key_file_save_to_file (key_file, file, error)) { + g_prefix_error (error, "Error saving cached location to %s", file); + return FALSE; + } + + return TRUE; +} + +gboolean +mm_location_cache_save (MMLocationCache *self, + GError **error) +{ + return mm_location_cache_save_to_file (self, self->priv->filename, error); +} + +void +mm_location_cache_set_filename (MMLocationCache *self, + const gchar *file) +{ + g_free (self->priv->filename); + self->priv->filename = g_strdup (file); +} + +void +mm_location_cache_update_from_lat_lon (MMLocationCache *self, + const double lat, + const double lon) +{ + if (lat != MM_LOCATION_LATITUDE_UNKNOWN && + lon != MM_LOCATION_LONGITUDE_UNKNOWN) { + self->priv->last_latitude = lat; + self->priv->last_longitude = lon; + } +} + +void +mm_location_cache_update_from_nmea (MMLocationCache *self, + const gchar *trace) +{ + gdouble lat; + gdouble lon; + + g_assert (self->priv->gps_raw != NULL); + if (mm_location_gps_raw_add_trace (self->priv->gps_raw, trace)) { + lat = mm_location_gps_raw_get_latitude (self->priv->gps_raw); + lon = mm_location_gps_raw_get_longitude (self->priv->gps_raw); + mm_location_cache_update_from_lat_lon (self, lat, lon); + } +} + +void +mm_location_cache_get_lat_lon (MMLocationCache *self, + double *lat, + double *lon) +{ + *lat = self->priv->last_latitude; + *lon = self->priv->last_longitude; +} + +MMLocationCache * +mm_location_cache_new (void) +{ + return MM_LOCATION_CACHE (g_object_new (MM_TYPE_LOCATION_CACHE, NULL)); +} + +static void +mm_location_cache_init (MMLocationCache *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MM_TYPE_LOCATION_CACHE, MMLocationCachePrivate); + + self->priv->last_longitude = MM_LOCATION_LONGITUDE_UNKNOWN; + self->priv->last_latitude = MM_LOCATION_LATITUDE_UNKNOWN; + self->priv->gps_raw = mm_location_gps_raw_new (); + + self->priv->filename = g_build_path (G_DIR_SEPARATOR_S, PKGSTATEDIR, LOCATION_STATE_FILE, NULL); +} + +static void +finalize (GObject *object) +{ + MMLocationCache *self = MM_LOCATION_CACHE (object); + + g_object_unref (self->priv->gps_raw); + g_free (self->priv->filename); + + G_OBJECT_CLASS (mm_location_cache_parent_class)->finalize (object); +} + +static void +dispose (GObject *object) +{ + g_autoptr(GError) error = NULL; + MMLocationCache *self = MM_LOCATION_CACHE (object); + + if (!mm_location_cache_save (self, &error)) + mm_obj_warn (self, "%s", error->message); + + G_OBJECT_CLASS (mm_location_cache_parent_class)->dispose (object); +} + +static void +log_object_iface_init (MMLogObjectInterface *iface) +{ + iface->build_id = log_object_build_id; +} + +static void +mm_location_cache_class_init (MMLocationCacheClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMLocationCachePrivate)); + + object_class->finalize = finalize; + object_class->dispose = dispose; +} diff --git a/src/mm-location-cache.h b/src/mm-location-cache.h new file mode 100644 index 00000000..6c8ed291 --- /dev/null +++ b/src/mm-location-cache.h @@ -0,0 +1,57 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program 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. + * + * This program 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: + * + * Copyright (C) 2026 Andrey Skvortsov + */ + +#ifndef MM_LOCATION_CACHE_H +#define MM_LOCATION_CACHE_H + +#include +#include + +#define MM_TYPE_LOCATION_CACHE (mm_location_cache_get_type ()) +#define MM_LOCATION_CACHE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_LOCATION_CACHE, MMLocationCache)) +#define MM_LOCATION_CACHE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_LOCATION_CACHE, MMLocationCacheClass)) +#define MM_IS_LOCATION_CACHE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_LOCATION_CACHE)) +#define MM_IS_LOCATION_CACHE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_LOCATION_CACHE)) +#define MM_LOCATION_CACHE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_LOCATION_CACHE, MMLocationCacheClass)) + +typedef struct _MMLocationCache MMLocationCache; +typedef struct _MMLocationCacheClass MMLocationCacheClass; +typedef struct _MMLocationCachePrivate MMLocationCachePrivate; + +struct _MMLocationCache { + GObject parent; + MMLocationCachePrivate *priv; +}; + +struct _MMLocationCacheClass { + GObjectClass parent; +}; + +GType mm_location_cache_get_type (void); +G_DEFINE_AUTOPTR_CLEANUP_FUNC (MMLocationCache, g_object_unref) + +MMLocationCache *mm_location_cache_new (void); + +gboolean mm_location_cache_load_from_file (MMLocationCache *self, const gchar *file, GError **error); +gboolean mm_location_cache_load (MMLocationCache *self, GError **error); +gboolean mm_location_cache_save_to_file (MMLocationCache *self, const gchar *file, GError **error); +gboolean mm_location_cache_save (MMLocationCache *self, GError **error); +void mm_location_cache_set_filename (MMLocationCache *self, const gchar *file); +void mm_location_cache_update_from_lat_lon (MMLocationCache *self, const double lat, const double lon); +void mm_location_cache_update_from_nmea (MMLocationCache *self, const gchar *nmea); +void mm_location_cache_get_lat_lon (MMLocationCache *self, double *lat, double *lon); + +#endif /* MM_LOCATION_CACHE_H */ +