[libgweather] Add serialization capabilities to GWeatherLocation
- From: Giovanni Campagna <gcampagna src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [libgweather] Add serialization capabilities to GWeatherLocation
- Date: Sun, 2 Dec 2012 03:14:58 +0000 (UTC)
commit c522bea3e49c37a0878d654ded454b182afb1144
Author: Giovanni Campagna <gcampagna src gnome org>
Date: Sun Dec 2 03:55:31 2012 +0100
Add serialization capabilities to GWeatherLocation
Applications using libgweather want to store locations in GSettings
or data files, and then be able to retrieve them, in a way that minimizes
the difference between acquiring the location from a GWeatherLocationEntry
or from storage.
To do so, a new location level is introduced, GWEATHER_LOCATION_DETACHED,
which is used as a fallback in case we cannot recover the right location
from the database.
libgweather/gweather-location.c | 278 ++++++++++++++++++++++++++++-
libgweather/gweather-location.h | 12 +-
libgweather/gweather-xml.c | 3 +
libgweather/location-entry.c | 58 +++----
libgweather/parser.c | 8 +-
libgweather/weather-priv.h | 6 +
schemas/org.gnome.GWeather.gschema.xml.in | 2 +-
7 files changed, 325 insertions(+), 42 deletions(-)
---
diff --git a/libgweather/gweather-location.c b/libgweather/gweather-location.c
index 0729ed9..2929b66 100644
--- a/libgweather/gweather-location.c
+++ b/libgweather/gweather-location.c
@@ -34,6 +34,9 @@
#include "parser.h"
#include "weather-priv.h"
+/* This is the precision of coordinates in the database */
+#define EPSILON 0.000001
+
/**
* SECTION:gweather-location
* @Title: GWeatherLocation
@@ -60,6 +63,10 @@
* @GWEATHER_LOCATION_CITY: A location representing a city
* @GWEATHER_LOCATION_WEATHER_STATION: A location representing a
* weather station.
+ * @GWEATHER_LOCATION_DETACHED: A location that is detached from the
+ * database, for example because it was loaded from external storage
+ * and could not be fully recovered. The parent of this location is
+ * the nearest weather station.
*
* The size/scope of a particular #GWeatherLocation.
*
@@ -69,6 +76,9 @@
* or may not be divided into "adm2"s. A city will have at least one,
* and possibly several, weather stations inside it. Weather stations
* will never appear outside of cities.
+ *
+ * Building a database with gweather_location_new_world() will never
+ * create detached instances, but deserializing might.
**/
static int
@@ -255,7 +265,12 @@ location_new_from_xml (GWeatherParser *parser, GWeatherLocationLevel level,
if (level == GWEATHER_LOCATION_WEATHER_STATION) {
/* Cache weather stations by METAR code */
- g_hash_table_replace (parser->metar_code_cache, loc->station_code, gweather_location_ref (loc));
+ GList *a, *b;
+
+ a = g_hash_table_lookup (parser->metar_code_cache, loc->station_code);
+ b = g_list_append (a, gweather_location_ref (loc));
+ if (b != a)
+ g_hash_table_replace (parser->metar_code_cache, loc->station_code, b);
}
if (children->len) {
@@ -706,9 +721,9 @@ gweather_location_get_code (GWeatherLocation *loc)
* gweather_location_get_city_name:
* @loc: a #GWeatherLocation
*
- * For a %GWEATHER_LOCATION_CITY location, this is equivalent to
- * gweather_location_get_name(). For a
- * %GWEATHER_LOCATION_WEATHER_STATION location, it is equivalent to
+ * For a %GWEATHER_LOCATION_CITY or %GWEATHER_LOCATION_DETACHED location,
+ * this is equivalent to gweather_location_get_name().
+ * For a %GWEATHER_LOCATION_WEATHER_STATION location, it is equivalent to
* calling gweather_location_get_name() on the location's parent. For
* other locations it will return %NULL.
*
@@ -719,7 +734,8 @@ gweather_location_get_city_name (GWeatherLocation *loc)
{
g_return_val_if_fail (loc != NULL, NULL);
- if (loc->level == GWEATHER_LOCATION_CITY)
+ if (loc->level == GWEATHER_LOCATION_CITY ||
+ loc->level == GWEATHER_LOCATION_DETACHED)
return g_strdup (loc->name);
else if (loc->level == GWEATHER_LOCATION_WEATHER_STATION &&
loc->parent &&
@@ -772,11 +788,29 @@ _weather_location_from_gweather_location (GWeatherLocation *gloc, const gchar *n
return wloc;
}
+/**
+ * gweather_location_find_by_station_code:
+ * @world: a #GWeatherLocation at the world level
+ * @station_code: a 4 letter METAR code
+ *
+ * Retrieves the weather station identifier by @station_code.
+ * Note that multiple instances of the same weather station can exist
+ * in the database, and this function will return any of them, so this
+ * not usually what you want.
+ *
+ * See gweather_location_deserialize() to recover a stored #GWeatherLocation.
+ *
+ * Returns: a weather station level #GWeatherLocation for @station_code,
+ * or %NULL if none exists in the database.
+ */
GWeatherLocation *
gweather_location_find_by_station_code (GWeatherLocation *world,
const gchar *station_code)
{
- return g_hash_table_lookup (world->metar_code_cache, station_code);
+ GList *l;
+
+ l = g_hash_table_lookup (world->metar_code_cache, station_code);
+ return l ? l->data : NULL;
}
GWeatherLocation *
@@ -790,3 +824,235 @@ gweather_location_ref_world (GWeatherLocation *loc)
gweather_location_ref (loc);
return loc;
}
+
+/**
+ * gweather_location_equals:
+ * @one: a #GWeatherLocation
+ * @two: another #GWeatherLocation
+ *
+ * Compares two #GWeatherLocation and sees if they represent the same
+ * place.
+ * It is only legal to call this for cities, weather stations or
+ * detached locations.
+ * Note that this function only checks for geographical characteristics,
+ * such as coordinates and METAR code. It is still possible that the two
+ * locations belong to different worlds (in which case care must be
+ * taken when passing them GWeatherLocationEntry and GWeatherInfo), or
+ * if one is them is detached it could have a custom name.
+ *
+ * Returns: %TRUE if the two locations represent the same place as
+ * far as libgweather can tell, and %FALSE otherwise.
+ */
+gboolean
+gweather_location_equal (GWeatherLocation *one,
+ GWeatherLocation *two)
+{
+ int level;
+
+ if (one == two)
+ return TRUE;
+
+ if (one->level != two->level &&
+ one->level != GWEATHER_LOCATION_DETACHED &&
+ two->level != GWEATHER_LOCATION_DETACHED)
+ return FALSE;
+
+ level = one->level;
+ if (level == GWEATHER_LOCATION_DETACHED)
+ level = two->level;
+
+ if (level == GWEATHER_LOCATION_COUNTRY)
+ return g_strcmp0 (one->country_code, two->country_code);
+
+ if (level == GWEATHER_LOCATION_ADM1 ||
+ level == GWEATHER_LOCATION_ADM2) {
+ if (g_strcmp0 (one->sort_name, two->sort_name) != 0)
+ return FALSE;
+
+ return one->parent && two->parent &&
+ gweather_location_equal (one->parent, two->parent);
+ }
+
+ if (g_strcmp0 (one->station_code, two->station_code) != 0)
+ return FALSE;
+
+ if (one->level != GWEATHER_LOCATION_DETACHED &&
+ two->level != GWEATHER_LOCATION_DETACHED &&
+ !gweather_location_equal (one->parent, two->parent))
+ return FALSE;
+
+ return ABS(one->latitude - two->latitude) < EPSILON &&
+ ABS(one->longitude - two->longitude) < EPSILON;
+}
+
+/* ------------------- serialization ------------------------------- */
+
+#define FORMAT 1
+
+static GVariant *
+gweather_location_format_one_serialize (GWeatherLocation *location)
+{
+ const char *name;
+ gboolean is_city;
+
+ name = location->name;
+
+ /* Normalize location to be a weather station or detached */
+ if (location->level == GWEATHER_LOCATION_CITY) {
+ location = location->children[0];
+ is_city = TRUE;
+ } else {
+ is_city = FALSE;
+ }
+
+ return g_variant_new ("(ssbm(dd)m(dd))",
+ name, location->station_code, is_city,
+ location->latlon_valid,
+ location->latitude,
+ location->longitude,
+ location->parent && location->parent->latlon_valid,
+ location->parent ? location->parent->latitude : 0.0d,
+ location->parent ? location->parent->longitude : 0.0d);
+}
+
+GWeatherLocation *
+_gweather_location_new_detached (GWeatherLocation *nearest_station,
+ const char *name,
+ gboolean latlon_valid,
+ gdouble latitude,
+ gdouble longitude)
+{
+ GWeatherLocation *self;
+ char *normalized;
+
+ self = g_slice_new0 (GWeatherLocation);
+ self->level = GWEATHER_LOCATION_DETACHED;
+ self->name = g_strdup (name);
+
+ normalized = g_utf8_normalize (name, -1, G_NORMALIZE_ALL);
+ self->sort_name = g_utf8_casefold (normalized, -1);
+ g_free (normalized);
+
+ self->parent = nearest_station;
+ self->children = NULL;
+
+ if (nearest_station)
+ self->station_code = g_strdup (nearest_station->station_code);
+
+ g_assert (nearest_station || latlon_valid);
+
+ if (latlon_valid) {
+ self->latlon_valid = TRUE;
+ self->latitude = latitude;
+ self->longitude = longitude;
+ } else {
+ self->latlon_valid = nearest_station->latlon_valid;
+ self->latitude = nearest_station->latitude;
+ self->longitude = nearest_station->longitude;
+ }
+
+ return self;
+}
+
+static GWeatherLocation *
+gweather_location_format_one_deserialize (GWeatherLocation *world,
+ GVariant *variant)
+{
+ const char *name;
+ const char *station_code;
+ gboolean is_city, latlon_valid, parent_latlon_valid;
+ gdouble latitude, longitude, parent_latitude, parent_longitude;
+ GList *candidates, *l;
+ GWeatherLocation *found;
+
+ g_variant_get (variant, "(&s&sbm(dd)m(dd))", &name, &station_code, &is_city,
+ &latlon_valid, &latitude, &longitude,
+ &parent_latlon_valid, &parent_latitude, &parent_longitude);
+
+ /* First find the list of candidate locations */
+ candidates = g_hash_table_lookup (world->metar_code_cache, station_code);
+
+ /* If we don't have coordinates, fallback immediately to making up
+ * a location
+ */
+ if (!latlon_valid)
+ return candidates ? _gweather_location_new_detached (candidates->data, name, FALSE, 0, 0) : NULL;
+
+ found = NULL;
+
+ /* First try weather stations directly. */
+ for (l = candidates; l; l = l->next) {
+ GWeatherLocation *ws, *city;
+
+ ws = l->data;
+
+ if (!ws->latlon_valid ||
+ ABS(ws->latitude - latitude) >= EPSILON ||
+ ABS(ws->longitude - longitude) >= EPSILON)
+ /* Not what we're looking for... */
+ continue;
+
+ /* If we can't check for the latitude and longitude
+ of the parent, we just assume we found what we needed
+ */
+ if ((!parent_latlon_valid || !ws->parent || !ws->parent->latlon_valid) ||
+ (ABS(parent_latitude - ws->parent->latitude) < EPSILON &&
+ ABS(parent_longitude - ws->parent->longitude) < EPSILON)) {
+
+ /* Found! Now check which one we want (ws or city) and the name... */
+ if (is_city)
+ city = ws->parent;
+ else
+ city = ws;
+
+ if (city == NULL) {
+ /* Oops! There is no city for this weather station! */
+ continue;
+ }
+
+ if (g_strcmp0 (name, city->name) == 0)
+ found = gweather_location_ref (city);
+ else
+ found = _gweather_location_new_detached (city, name, FALSE, 0, 0);
+
+ break;
+ }
+ }
+
+ if (found)
+ return found;
+
+ /* No weather station matches the serialized data, let's pick
+ one at random from the station code list */
+ if (candidates)
+ return _gweather_location_new_detached (candidates->data,
+ name, TRUE, latitude, longitude);
+ else
+ return NULL;
+}
+
+GVariant *
+gweather_location_serialize (GWeatherLocation *location)
+{
+ return g_variant_new ("(uv)", FORMAT,
+ gweather_location_format_one_serialize (location));
+}
+
+GWeatherLocation *
+gweather_location_deserialize (GWeatherLocation *world,
+ GVariant *serialized)
+{
+ GVariant *v;
+ GWeatherLocation *loc;
+ int format;
+
+ g_variant_get (serialized, "(uv)", &format, &v);
+
+ if (format == FORMAT)
+ loc = gweather_location_format_one_deserialize (world, v);
+ else
+ loc = NULL;
+
+ g_variant_unref (v);
+ return loc;
+}
diff --git a/libgweather/gweather-location.h b/libgweather/gweather-location.h
index fb3b7ef..7efcd94 100644
--- a/libgweather/gweather-location.h
+++ b/libgweather/gweather-location.h
@@ -41,7 +41,8 @@ typedef enum { /*< underscore_name=gweather_location_level >*/
/* ADM2 = second-order division = county, etc */
GWEATHER_LOCATION_ADM2,
GWEATHER_LOCATION_CITY,
- GWEATHER_LOCATION_WEATHER_STATION
+ GWEATHER_LOCATION_WEATHER_STATION,
+ GWEATHER_LOCATION_DETACHED
} GWeatherLocationLevel;
GType gweather_location_get_type (void);
@@ -80,10 +81,17 @@ const char *gweather_location_get_code (GWeatherLocation *loc)
char *gweather_location_get_city_name (GWeatherLocation *loc);
GWeatherLocation *gweather_location_find_by_station_code (GWeatherLocation *world,
- const gchar *code);
+ const gchar *station_code);
GWeatherLocation *gweather_location_ref_world (GWeatherLocation *loc);
+gboolean gweather_location_equal (GWeatherLocation *one,
+ GWeatherLocation *two);
+
+GVariant *gweather_location_serialize (GWeatherLocation *loc);
+GWeatherLocation *gweather_location_deserialize (GWeatherLocation *world,
+ GVariant *serialized);
+
G_END_DECLS
#endif /* __GWEATHER_LOCATIONS_H__ */
diff --git a/libgweather/gweather-xml.c b/libgweather/gweather-xml.c
index 3e72d5e..fa39618 100644
--- a/libgweather/gweather-xml.c
+++ b/libgweather/gweather-xml.c
@@ -114,6 +114,9 @@ gweather_xml_parse_node (GWeatherLocation *gloc,
-1);
_weather_location_free (wloc);
+ case GWEATHER_LOCATION_DETACHED:
+ g_assert_not_reached ();
+
break;
}
diff --git a/libgweather/location-entry.c b/libgweather/location-entry.c
index 132fa58..864ccdf 100644
--- a/libgweather/location-entry.c
+++ b/libgweather/location-entry.c
@@ -22,10 +22,11 @@
#include <config.h>
#endif
+#include <string.h>
+
#define GWEATHER_I_KNOW_THIS_IS_UNSTABLE
#include "location-entry.h"
-
-#include <string.h>
+#include "weather-priv.h"
/**
* SECTION:location-entry
@@ -175,14 +176,16 @@ entry_changed (GWeatherLocationEntry *entry)
static void
set_location_internal (GWeatherLocationEntry *entry,
GtkTreeModel *model,
- GtkTreeIter *iter)
+ GtkTreeIter *iter,
+ GWeatherLocation *loc)
{
- GWeatherLocation *loc;
char *name;
if (entry->location)
gweather_location_unref (entry->location);
+ g_assert (iter == NULL || loc == NULL);
+
if (iter) {
gtk_tree_model_get (model, iter,
GWEATHER_LOCATION_ENTRY_COL_DISPLAY_NAME, &name,
@@ -192,6 +195,10 @@ set_location_internal (GWeatherLocationEntry *entry,
gtk_entry_set_text (GTK_ENTRY (entry), name);
entry->custom_text = FALSE;
g_free (name);
+ } else if (loc) {
+ entry->location = gweather_location_ref (loc);
+ gtk_entry_set_text (GTK_ENTRY (entry), loc->name);
+ entry->custom_text = TRUE;
} else {
entry->location = NULL;
gtk_entry_set_text (GTK_ENTRY (entry), "");
@@ -210,6 +217,8 @@ set_location_internal (GWeatherLocationEntry *entry,
*
* Sets @entry's location to @loc, and updates the text of the
* entry accordingly.
+ * Note that if the database contains a location that compares
+ * equal to @loc, that will be chosen in place of @loc.
**/
void
gweather_location_entry_set_location (GWeatherLocationEntry *entry,
@@ -230,13 +239,13 @@ gweather_location_entry_set_location (GWeatherLocationEntry *entry,
gtk_tree_model_get (model, &iter,
GWEATHER_LOCATION_ENTRY_COL_LOCATION, &cmploc,
-1);
- if (loc == cmploc) {
- set_location_internal (entry, model, &iter);
+ if (gweather_location_equal (loc, cmploc)) {
+ set_location_internal (entry, model, &iter, NULL);
return;
}
} while (gtk_tree_model_iter_next (model, &iter));
- set_location_internal (entry, model, NULL);
+ set_location_internal (entry, model, NULL, loc);
}
/**
@@ -330,11 +339,11 @@ gweather_location_entry_set_city (GWeatherLocationEntry *entry,
g_free (cmpname);
}
- set_location_internal (entry, model, &iter);
+ set_location_internal (entry, model, &iter, NULL);
return TRUE;
} while (gtk_tree_model_iter_next (model, &iter));
- set_location_internal (entry, model, NULL);
+ set_location_internal (entry, model, NULL, NULL);
return FALSE;
}
@@ -413,29 +422,11 @@ fill_location_entry_model (GtkTreeStore *store, GWeatherLocation *loc,
g_free (display_name);
g_free (compare_name);
}
- } else if (children[0]) {
- /* Else there's only one location. This is a mix of the
- * city-with-multiple-location case above and the
- * location-with-no-city case below.
- */
- display_name = g_strdup_printf ("%s, %s",
- gweather_location_get_name (loc),
- parent_display_name);
- compare_name = g_strdup_printf ("%s, %s",
- gweather_location_get_sort_name (loc),
- parent_compare_name);
-
- gtk_tree_store_append (store, &iter, NULL);
- gtk_tree_store_set (store, &iter,
- GWEATHER_LOCATION_ENTRY_COL_LOCATION, children[0],
- GWEATHER_LOCATION_ENTRY_COL_DISPLAY_NAME, display_name,
- GWEATHER_LOCATION_ENTRY_COL_COMPARE_NAME, compare_name,
- -1);
-
- g_free (display_name);
- g_free (compare_name);
+
+ break;
}
- break;
+
+ /* fall through */
case GWEATHER_LOCATION_WEATHER_STATION:
/* <location> with no parent <city>, or <city> with a single
@@ -458,6 +449,9 @@ fill_location_entry_model (GtkTreeStore *store, GWeatherLocation *loc,
g_free (display_name);
g_free (compare_name);
break;
+
+ case GWEATHER_LOCATION_DETACHED:
+ g_assert_not_reached ();
}
gweather_location_free_children (loc, children);
@@ -569,7 +563,7 @@ match_selected (GtkEntryCompletion *completion,
GtkTreeIter *iter,
gpointer entry)
{
- set_location_internal (entry, model, iter);
+ set_location_internal (entry, model, iter, NULL);
return TRUE;
}
diff --git a/libgweather/parser.c b/libgweather/parser.c
index ffefcab..837444b 100644
--- a/libgweather/parser.c
+++ b/libgweather/parser.c
@@ -153,6 +153,12 @@ gweather_parser_get_localized_value (GWeatherParser *parser)
return name;
}
+static void
+gweather_location_list_free (gpointer list)
+{
+ g_list_free_full (list, (GDestroyNotify) gweather_location_unref);
+}
+
GWeatherParser *
gweather_parser_new (gboolean use_regions)
{
@@ -247,7 +253,7 @@ gweather_parser_new (gboolean use_regions)
tm.tm_year++;
parser->year_end = mktime (&tm);
- parser->metar_code_cache = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify) gweather_location_unref);
+ parser->metar_code_cache = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, gweather_location_list_free);
return parser;
diff --git a/libgweather/weather-priv.h b/libgweather/weather-priv.h
index e0cd51e..985114c 100644
--- a/libgweather/weather-priv.h
+++ b/libgweather/weather-priv.h
@@ -218,5 +218,11 @@ void free_forecast_list (GWeatherInfo *info);
GWeatherInfo *_gweather_info_new_clone (GWeatherInfo *info);
+GWeatherLocation *_gweather_location_new_detached (GWeatherLocation *nearest_station,
+ const char *name,
+ gboolean latlon_valid,
+ gdouble latitude,
+ gdouble longitude);
+
#endif /* __WEATHER_PRIV_H_ */
diff --git a/schemas/org.gnome.GWeather.gschema.xml.in b/schemas/org.gnome.GWeather.gschema.xml.in
index c1972d7..113854b 100644
--- a/schemas/org.gnome.GWeather.gschema.xml.in
+++ b/schemas/org.gnome.GWeather.gschema.xml.in
@@ -52,7 +52,7 @@
</_description>
</key>
<key name="default-location" type="(ssm(dd))">
- <!-- TRANSLATORS: pick a _default location to use in the weather applet. This should
+ <!-- TRANSLATORS: pick a default location to use in the weather applet. This should
usually be the largest city or the capital of your country. If you're not picking
a <location> in the database, don't forget to set name and coordinates. -->
<_default>('', 'KNYC', nothing)</_default>
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]