From d1ee082142608488338afe25f082b68c9d1fc79d Mon Sep 17 00:00:00 2001 From: Florian Brosch Date: Sun, 15 Oct 2017 21:58:57 +0200 Subject: [PATCH] weather: Re-use old weather predictions on errors --- src/gcal-application.c | 6 +- src/gcal-weather-service.c | 156 ++++++++++++++++++++++++++++++++----- src/gcal-weather-service.h | 5 +- 3 files changed, 144 insertions(+), 23 deletions(-) diff --git a/src/gcal-application.c b/src/gcal-application.c index efc452eb..a598627e 100644 --- a/src/gcal-application.c +++ b/src/gcal-application.c @@ -35,7 +35,8 @@ #include #include -#define WEATHER_CHECK_INTERVAL 7200 /* seconds */ +#define WEATHER_CHECK_INTERVAL (2 * 60 * 60) /* seconds */ +#define WEATHER_VALID_TIMESPAN (24 * 60 * 60) /* seconds */ #define FORECAST_MAX_DAYS 3 struct _GcalApplication @@ -424,7 +425,8 @@ gcal_application_init (GcalApplication *self) g_signal_connect_swapped (self->manager, "source-changed", G_CALLBACK (process_sources), self); self->weather_service = gcal_weather_service_new (NULL, /* in prep. for configurable time zones */ FORECAST_MAX_DAYS, - WEATHER_CHECK_INTERVAL); + WEATHER_CHECK_INTERVAL, + WEATHER_VALID_TIMESPAN); self->search_provider = gcal_shell_search_provider_new (); gcal_shell_search_provider_connect (self->search_provider, self->manager); } diff --git a/src/gcal-weather-service.c b/src/gcal-weather-service.c index 8c7cea62..63932193 100644 --- a/src/gcal-weather-service.c +++ b/src/gcal-weather-service.c @@ -51,6 +51,8 @@ typedef struct * @location_cancellable: Used to deal with async location service construction. * @locaton_running: Whether location service is active. * @weather_infos: List of #GcalWeatherInfo objects. + * @weather_infos_upated: The monotonic time @weather_info was set at. + * @valid_timespan: Amount of seconds weather information are considered valid. * @gweather_info: The weather info to query. * @max_days: Number of days we want weather information for. * @weather_service_running: True if weather service is active. @@ -84,6 +86,8 @@ struct _GcalWeatherService /* weather: */ GSList *weather_infos; /* owned[owned] */ + gint64 weather_infos_upated; + gint64 valid_timespan; GWeatherInfo *gweather_info; /* owned, nullable */ guint max_days; gboolean weather_service_running; @@ -97,6 +101,7 @@ enum PROP_MAX_DAYS, PROP_TIME_ZONE, PROP_CHECK_INTERVAL, + PROP_VALID_TIMESPAN, PROP_NUM, }; @@ -139,7 +144,14 @@ static void on_gclue_client_stop (GClueClient static void gcal_weather_service_set_max_days (GcalWeatherService *self, guint days); -static void gcal_weather_service_update_weather (GWeatherInfo *info, +static void gcal_weather_service_set_valid_timespan (GcalWeatherService *self, + gint64 timespan); + +static void gcal_weather_service_update_weather (GcalWeatherService *self, + GWeatherInfo *info, + gboolean reuse_old_on_error); + +static void on_gweather_update (GWeatherInfo *info, GcalWeatherService *self); /* Internal weather update timer API and callbacks */ @@ -234,6 +246,9 @@ gcal_weather_service_get_property (GObject *object, case PROP_CHECK_INTERVAL: g_value_set_uint (value, gcal_weather_service_get_check_interval (self)); break; + case PROP_VALID_TIMESPAN: + g_value_set_int64 (value, gcal_weather_service_get_valid_timespan (self)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -262,6 +277,9 @@ gcal_weather_service_set_property (GObject *object, case PROP_CHECK_INTERVAL: gcal_weather_service_set_check_interval (self, g_value_get_uint (value)); break; + case PROP_VALID_TIMESPAN: + gcal_weather_service_set_valid_timespan (self, g_value_get_int64 (value)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -281,7 +299,7 @@ gcal_weather_service_class_init (GcalWeatherServiceClass *klass) object_class->set_property = gcal_weather_service_set_property; /** - * GcalWeatherServiceClass:max-days: + * GcalWeatherService:max-days: * * Maximal number of days to fetch forecasts for. */ @@ -293,7 +311,7 @@ gcal_weather_service_class_init (GcalWeatherServiceClass *klass) G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); /** - * GcalWeatherServiceClass:time-zone: + * GcalWeatherService:time-zone: * * The time zone to use. */ @@ -304,7 +322,7 @@ gcal_weather_service_class_init (GcalWeatherServiceClass *klass) G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE)); /** - * GcalWeatherServiceClass:check-interval: + * GcalWeatherService:check-interval: * * Amount of seconds to wait before re-fetching weather infos. * Use %0 to disable timers. @@ -316,6 +334,19 @@ gcal_weather_service_class_init (GcalWeatherServiceClass *klass) 0, G_MAXUINT, 0, G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + /** + * GcalWeatherService:valid_timespan: + * + * Amount of seconds fetched weather information are considered as valid. + */ + g_object_class_install_property + (G_OBJECT_CLASS (klass), + PROP_VALID_TIMESPAN, + g_param_spec_int64 ("valid-timespan", "valid-timespan", "valid-timespan", + 0, G_MAXINT64, 0, + G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + /** * GcalWeatherService::weather-changed: * @sender: The #GcalWeatherService @@ -349,6 +380,8 @@ gcal_weather_service_init (GcalWeatherService *self) self->location_service_running = FALSE; self->location_service = NULL; self->weather_infos = NULL; + self->weather_infos_upated = -1; + self->valid_timespan = -1; self->gweather_info = NULL; self->weather_service_running = FALSE; self->max_days = 0; @@ -869,7 +902,7 @@ gcal_weather_service_update_location (GcalWeatherService *self, if (location == NULL) { g_debug ("Could not retrieve current location"); - gcal_weather_service_update_weather (NULL, self); + gcal_weather_service_update_weather (self, NULL, FALSE); } else { @@ -883,7 +916,16 @@ gcal_weather_service_update_location (GcalWeatherService *self, * what is going on. */ gweather_info_set_enabled_providers (self->gweather_info, GWEATHER_PROVIDER_METAR | GWEATHER_PROVIDER_OWM | GWEATHER_PROVIDER_YR_NO); - g_signal_connect (self->gweather_info, "updated", (GCallback) gcal_weather_service_update_weather, self); + g_signal_connect (self->gweather_info, "updated", (GCallback) on_gweather_update, self); + + /* gweather_info_update might or might not trigger a + * GWeatherInfo::update() signal. Therefore, we have to + * remove weather information before querying new one. + * This might result in icon flickering on screen. + * We probably want to introduce a "unknown" or "loading" + * state in gweather-info to soften the effect. + */ + gcal_weather_service_update_weather (self, NULL, FALSE); gweather_info_update (self->gweather_info); gcal_weather_service_timer_start (self); @@ -1112,6 +1154,7 @@ gcal_weather_service_set_max_days (GcalWeatherService *self, guint days) { g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self)); + g_return_if_fail (days >= 1); self->max_days = days; @@ -1120,23 +1163,42 @@ gcal_weather_service_set_max_days (GcalWeatherService *self, -/* gcal_weather_service_update_weather: - * @info: (nullable): Newly received weather information or %NULL. +/* gcal_weather_service_set_valid_timespan: * @self: A #GcalWeatherService instance. + * @timespan: Amount of seconds we consider weather information as valid. + */ +static void +gcal_weather_service_set_valid_timespan (GcalWeatherService *self, + gint64 timespan) +{ + g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self)); + g_return_if_fail (timespan >= 0); + + self->valid_timespan = timespan; + + g_object_notify ((GObject*) self, "valid-timespan"); +} + + +/* gcal_weather_service_update_weather: + * @self: A #GcalWeatherService instance. + * @info: (nullable): Newly received weather information or %NULL. + * @reuse_old_on_error: Whether to re-use old but not outdated weather + * information in case we could not fetch new data. * * Retrieves weather information for @location and triggers * #GcalWeatherService::weather-changed. */ static void -gcal_weather_service_update_weather (GWeatherInfo *info, - GcalWeatherService *self) +gcal_weather_service_update_weather (GcalWeatherService *self, + GWeatherInfo *info, + gboolean reuse_old_on_error) { + GSList *gwforecast = NULL; /* unowned */ + + g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self)); g_return_if_fail (info == NULL || GWEATHER_IS_INFO (info)); - g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self)); - /* Free previously received weather infos */ - g_slist_free_full (self->weather_infos, g_object_unref); - self->weather_infos = NULL; /* Compute a list of newly received weather infos. */ if (info == NULL) @@ -1145,12 +1207,8 @@ gcal_weather_service_update_weather (GWeatherInfo *info, } else if (gweather_info_is_valid (info)) { - GSList *gwforecast; /* unowned */ - g_debug ("Received valid weather information"); - gwforecast = gweather_info_get_forecast_list (info); - self->weather_infos = preprocess_gweather_reports (self, gwforecast); } else { @@ -1158,7 +1216,48 @@ gcal_weather_service_update_weather (GWeatherInfo *info, g_debug ("Could not retrieve valid weather for location '%s'", location_name); } - g_signal_emit (self, gcal_weather_service_signals[SIG_WEATHER_CHANGED], 0); + if (gwforecast == NULL && self->weather_infos_upated >= 0) + { + gint64 now = g_get_monotonic_time (); + gboolean in_valid_timespan; + + in_valid_timespan = (now - self->weather_infos_upated) / 1000000 <= self->valid_timespan; + if (!reuse_old_on_error || !in_valid_timespan) + { + g_slist_free_full (self->weather_infos, g_object_unref); + self->weather_infos = NULL; + self->weather_infos_upated = -1; + + g_signal_emit (self, gcal_weather_service_signals[SIG_WEATHER_CHANGED], 0); + } + } + else if (gwforecast != NULL) + { + g_slist_free_full (self->weather_infos, g_object_unref); + self->weather_infos = preprocess_gweather_reports (self, gwforecast); + self->weather_infos_upated = g_get_monotonic_time (); + + g_signal_emit (self, gcal_weather_service_signals[SIG_WEATHER_CHANGED], 0); + } +} + + + +/* on_gweather_update: + * @self: A #GcalWeatherService instance. + * @timespan: Amount of seconds we consider weather information as valid. + * + * Triggered on weather updates with previously handled or no + * location changes. + */ +static void +on_gweather_update (GWeatherInfo *info, + GcalWeatherService *self) +{ + g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self)); + g_return_if_fail (info == NULL || GWEATHER_IS_INFO (info)); + + gcal_weather_service_update_weather (self, info, TRUE); } @@ -1270,12 +1369,14 @@ on_timer_timeout (GcalWeatherService *self) GcalWeatherService * gcal_weather_service_new (GTimeZone *time_zone, guint max_days, - guint check_interval) + guint check_interval, + gint64 valid_timespan) { return g_object_new (GCAL_TYPE_WEATHER_SERVICE, "time-zone", time_zone, "max-days", max_days, "check-interval", check_interval, + "valid-timespan", valid_timespan, NULL); } @@ -1387,6 +1488,21 @@ gcal_weather_service_get_max_days (GcalWeatherService *self) +/** + * gcal_weather_service_get_valid_timespan: + * @self: The #GcalWeatherService instance. + * + * Getter for #GcalWeatherService:valid-interval. + */ +gint64 +gcal_weather_service_get_valid_timespan (GcalWeatherService *self) +{ + g_return_val_if_fail (GCAL_IS_WEATHER_SERVICE (self), 0); + return self->valid_timespan; +} + + + /** * gcal_weather_service_get_time_zone: * @self: The #GcalWeatherService instance. diff --git a/src/gcal-weather-service.h b/src/gcal-weather-service.h index f0ecf664..693b8a6c 100644 --- a/src/gcal-weather-service.h +++ b/src/gcal-weather-service.h @@ -34,7 +34,8 @@ G_DECLARE_FINAL_TYPE (GcalWeatherService, gcal_weather_service, GCAL, WEATHER_SE GcalWeatherService* gcal_weather_service_new (GTimeZone *time_zone, guint max_days, - guint check_interval); + guint check_interval, + gint64 valid_timespan); GTimeZone* gcal_weather_service_get_time_zone (GcalWeatherService *self); @@ -48,6 +49,8 @@ void gcal_weather_service_stop (GcalWeatherService guint gcal_weather_service_get_max_days (GcalWeatherService *self); +gint64 gcal_weather_service_get_valid_timespan (GcalWeatherService *self); + guint gcal_weather_service_get_check_interval (GcalWeatherService *self); GSList* gcal_weather_service_get_weather_infos (GcalWeatherService *self);