[gnome-calendar: 49/49] weather-service: major reorganization and cleanup
- From: Georges Basile Stavracas Neto <gbsneto src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-calendar: 49/49] weather-service: major reorganization and cleanup
- Date: Tue, 12 Dec 2017 01:12:38 +0000 (UTC)
commit 23a9de1108a0a7a0f833188230fa671c50705231
Author: Georges Basile Stavracas Neto <georges stavracas gmail com>
Date: Mon Dec 11 23:10:55 2017 -0200
weather-service: major reorganization and cleanup
To make it more like the actual code style of GNOME Calendar.
Hope Florian doesn't get (too) mad of me removing and modifying
so much on his code. I'm sorry dude, my OCD didn't allow me to
just flood the gate.
src/gcal-weather-service.c | 1835 ++++++++++++++++++--------------------------
1 file changed, 739 insertions(+), 1096 deletions(-)
---
diff --git a/src/gcal-weather-service.c b/src/gcal-weather-service.c
index aa39598c..28429f3e 100644
--- a/src/gcal-weather-service.c
+++ b/src/gcal-weather-service.c
@@ -16,7 +16,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#define G_LOG_DOMAIN "Weather"
+#define G_LOG_DOMAIN "GcalWeatherService"
+
#define DESKTOP_FILE_NAME "org.gnome.Calendar"
#include <geocode-glib/geocode-glib.h>
@@ -28,8 +29,7 @@
#include "gcal-timer.h"
-G_BEGIN_DECLS
-
+#define DAY_SECONDS (24 * 60 * 60)
/**
* Internal structure used to manage known
@@ -75,10 +75,7 @@ struct _GcalWeatherService
{
GObjectClass parent;
- /* <public> */
-
- /* <private> */
- GTimeZone *time_zone; /* owned, nullable */
+ GTimeZone *timezone; /* owned, nullable */
/* timer: */
guint check_interval_new;
@@ -103,101 +100,23 @@ struct _GcalWeatherService
gboolean weather_service_running;
};
-
-/* Auxiliary methods: */
-static void on_network_change (GNetworkMonitor *monitor,
- gboolean available,
- GcalWeatherService *self);
-
-static void on_gclue_simple_creation (GClueSimple *source,
- GAsyncResult *result,
- GcalWeatherService *data);
-
-static void on_gclue_location_changed (GClueLocation *location,
- GcalWeatherService *self);
-
-static void on_gclue_client_activity_changed (GClueClient *client,
- GcalWeatherService *self);
-
-static void on_gclue_client_stop (GClueClient *client,
- GAsyncResult *res,
- GClueSimple *simple);
-
-static void on_gweather_update (GWeatherInfo *info,
- GcalWeatherService *self);
-
-static void on_duration_timer_timeout (GcalTimer *timer,
- GcalWeatherService *self);
-
-static void on_midnight_timer_timeout (GcalTimer *timer,
+static void on_gweather_update_cb (GWeatherInfo *info,
GcalWeatherService *self);
-
-/* Timer Helpers: */
-static void update_timeout_interval (GcalWeatherService *self);
-
static void schedule_midnight (GcalWeatherService *self);
static void start_timer (GcalWeatherService *self);
static void stop_timer (GcalWeatherService *self);
-
-/* Internal location API and callbacks: */
-
-static void update_location (GcalWeatherService *self,
- GWeatherLocation *location);
-
-static void update_gclue_location (GcalWeatherService *self,
- GClueLocation *location);
-
-
-/* Internal Weather API */
-static void set_max_days (GcalWeatherService *self,
- guint days);
-
-static void set_valid_timespan (GcalWeatherService *self,
- gint64 timespan);
-
static gboolean has_valid_weather_infos (GcalWeatherService *self);
static void update_weather (GcalWeatherService *self,
GWeatherInfo *info,
gboolean reuse_old_on_error);
-
-/* Internal weather update API and callbacks */
-static void set_check_interval_new (GcalWeatherService *self,
- guint check_interval);
-
-static void set_check_interval_renew (GcalWeatherService *self,
- guint check_interval);
-
static gssize get_normalized_icon_name_len (const gchar *str);
-static gchar* get_normalized_icon_name (GWeatherInfo *gwi,
- gboolean is_night_icon);
-
-static gint get_icon_name_sortkey (const gchar *icon_name,
- gboolean *supports_night_icon);
-
-static gboolean get_time_day_start (GcalWeatherService *self,
- GDate *ret_date,
- gint64 *ret_unix,
- gint64 *ret_unix_exact);
-
-static inline gboolean get_gweather_temperature (GWeatherInfo *gwi,
- gdouble *temp);
-
-static gboolean compute_weather_info_data (GSList *samples,
- gboolean is_today,
- gchar **icon_name,
- gchar **temperature);
-
-static GSList* preprocess_gweather_reports (GcalWeatherService *self,
- GSList *samples);
-
-
G_DEFINE_TYPE (GcalWeatherService, gcal_weather_service, G_TYPE_OBJECT)
enum
@@ -214,253 +133,16 @@ enum
enum
{
SIG_WEATHER_CHANGED,
- SIG_NUM,
+ N_SIGNALS,
};
-static guint gcal_weather_service_signals[SIG_NUM] = { 0 };
-
-G_END_DECLS
-
-
-/********************
- * < gobject setup >
- *******************/
-
-static void
-gcal_weather_service_finalize (GObject *object)
-{
- GcalWeatherService *self; /* unowned */
-
- self = (GcalWeatherService *) object;
-
- gcal_timer_free (self->duration_timer);
- self->duration_timer = NULL;
-
- gcal_timer_free (self->midnight_timer);
- self->midnight_timer = NULL;
-
- if (self->time_zone != NULL)
- {
- g_time_zone_unref (self->time_zone);
- self->time_zone = NULL;
- }
-
- if (self->location_service != NULL)
- g_clear_object (&self->location_service);
-
- g_cancellable_cancel (self->location_cancellable);
- g_clear_object (&self->location_cancellable);
-
- g_slist_free_full (self->weather_infos, g_object_unref);
-
- if (self->gweather_info != NULL)
- g_clear_object (&self->gweather_info);
-
- if (self->network_changed_sid > 0)
- {
- GNetworkMonitor *monitor; /* unowned */
-
- monitor = g_network_monitor_get_default ();
- g_signal_handler_disconnect (monitor, self->network_changed_sid);
- }
-
- G_OBJECT_CLASS (gcal_weather_service_parent_class)->finalize (object);
-}
-
-static void
-gcal_weather_service_get_property (GObject *object,
- guint prop_id,
- GValue *value,
- GParamSpec *pspec)
-{
- GcalWeatherService* self; /* unowned */
-
- self = GCAL_WEATHER_SERVICE (object);
- switch (prop_id)
- {
- case PROP_MAX_DAYS:
- g_value_set_uint (value, gcal_weather_service_get_max_days (self));
- break;
- case PROP_TIME_ZONE:
- g_value_set_pointer (value, gcal_weather_service_get_time_zone (self));
- break;
- case PROP_CHECK_INTERVAL_NEW:
- g_value_set_uint (value, gcal_weather_service_get_check_interval_new (self));
- break;
- case PROP_CHECK_INTERVAL_RENEW:
- g_value_set_uint (value, gcal_weather_service_get_check_interval_renew (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;
- }
-}
-
-static void
-gcal_weather_service_set_property (GObject *object,
- guint prop_id,
- const GValue *value,
- GParamSpec *pspec)
-{
- GcalWeatherService* self; /* unowned */
-
- self = GCAL_WEATHER_SERVICE (object);
- switch (prop_id)
- {
- case PROP_MAX_DAYS:
- set_max_days (self, g_value_get_uint (value));
- break;
- case PROP_TIME_ZONE:
- gcal_weather_service_set_time_zone (self, g_value_get_pointer (value));
- break;
- case PROP_CHECK_INTERVAL_NEW:
- set_check_interval_new (self, g_value_get_uint (value));
- break;
- case PROP_CHECK_INTERVAL_RENEW:
- set_check_interval_renew (self, g_value_get_uint (value));
- break;
- case PROP_VALID_TIMESPAN:
- set_valid_timespan (self, g_value_get_int64 (value));
- break;
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
- break;
- }
-}
-
-static void
-gcal_weather_service_class_init (GcalWeatherServiceClass *klass)
-{
- GObjectClass *object_class; /* unowned */
-
- object_class = G_OBJECT_CLASS (klass);
- object_class->finalize = gcal_weather_service_finalize;
- object_class->get_property = gcal_weather_service_get_property;
- object_class->set_property = gcal_weather_service_set_property;
-
- /**
- * GcalWeatherService:max-days:
- *
- * Maximal number of days to fetch forecasts for.
- */
- g_object_class_install_property
- (G_OBJECT_CLASS (klass),
- PROP_MAX_DAYS,
- g_param_spec_uint ("max-days", "max-days", "max-days",
- 1, G_MAXUINT, 3,
- G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
-
- /**
- * GcalWeatherService:time-zone:
- *
- * The time zone to use.
- */
- g_object_class_install_property
- (G_OBJECT_CLASS (klass),
- PROP_TIME_ZONE,
- g_param_spec_pointer ("time-zone", "time-zone", "time-zone",
- G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE));
-
- /**
- * GcalWeatherService:check-interval-new:
- *
- * Amount of seconds to wait before re-fetching weather infos.
- * This interval is used when no valid weather reports are available.
- */
- g_object_class_install_property
- (G_OBJECT_CLASS (klass),
- PROP_CHECK_INTERVAL_NEW,
- g_param_spec_uint ("check-interval-new", "check-interval-new", "check-interval-new",
- 0, G_MAXUINT, GCAL_WEATHER_CHECK_INTERVAL_NEW_DFLT,
- G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
-
- /**
- * GcalWeatherService:check-interval-renew:
- *
- * Amount of seconds to wait before re-fetching weather information.
- * This interval is used when valid weather reports are available.
- */
- g_object_class_install_property
- (G_OBJECT_CLASS (klass),
- PROP_CHECK_INTERVAL_RENEW,
- g_param_spec_uint ("check-interval-renew", "check-interval-renew", "check-interval-renew",
- 0, G_MAXUINT, GCAL_WEATHER_CHECK_INTERVAL_RENEW_DFLT,
- 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, GCAL_WEATHER_VALID_TIMESPAN_DFLT,
- G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
-
-
- /**
- * GcalWeatherService::weather-changed:
- * @sender: The #GcalWeatherService
- * @self: data pointer.
- *
- * Triggered on weather changes. Call
- * gcal_weather_service_get_weather_infos() to
- * retrieve predictions.
- */
- gcal_weather_service_signals[SIG_WEATHER_CHANGED]
- = g_signal_new ("weather-changed",
- GCAL_TYPE_WEATHER_SERVICE,
- G_SIGNAL_RUN_LAST,
- 0,
- NULL,
- NULL,
- g_cclosure_marshal_VOID__VOID,
- G_TYPE_NONE,
- 0);
-}
-
-static void
-gcal_weather_service_init (GcalWeatherService *self)
-{
- GNetworkMonitor *monitor; /* unowned */
-
- self->duration_timer = gcal_timer_new (GCAL_WEATHER_CHECK_INTERVAL_NEW_DFLT);
- gcal_timer_set_callback (self->duration_timer, (GCalTimerFunc) on_duration_timer_timeout, self, NULL);
- self->midnight_timer = gcal_timer_new (24*60*60);
- gcal_timer_set_callback (self->midnight_timer, (GCalTimerFunc) on_midnight_timer_timeout, self, NULL);
- self->time_zone = NULL;
- self->check_interval_new = GCAL_WEATHER_CHECK_INTERVAL_NEW_DFLT;
- self->check_interval_renew = GCAL_WEATHER_CHECK_INTERVAL_RENEW_DFLT;
- self->location_cancellable = g_cancellable_new ();
- 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;
+static guint signals[N_SIGNALS] = { 0 };
- monitor = g_network_monitor_get_default ();
- self->network_changed_sid = g_signal_connect (monitor,
- "network-changed",
- (GCallback) on_network_change,
- self);
-}
-/* get_normalized_icon_name:
- * @str: A icon name to normalize.
- *
- * Translates a given weather icon name to the
- * one to display for folded weather reports.
- *
- * Returns: (transfer full): A normalized icon name.
+/*
+ * Auxiliary methods
*/
+
static gchar*
get_normalized_icon_name (GWeatherInfo* wi,
gboolean is_night_icon)
@@ -472,21 +154,21 @@ get_normalized_icon_name (GWeatherInfo* wi,
const gsize sym_pfx_size = G_N_ELEMENTS (sym_pfx) - 1;
const gchar *str; /* unowned */
- gssize normalized_size;
- gchar *buffer = NULL; /* owned */
- gchar *bufpos = NULL; /* unowned */
- gsize buffer_size;
+ gssize normalized_size;
+ gchar *buffer = NULL; /* owned */
+ gchar *bufpos = NULL; /* unowned */
+ gsize buffer_size;
g_return_val_if_fail (wi != NULL, NULL);
-
+
str = gweather_info_get_icon_name (wi);
- if (str == NULL)
+ if (!str)
return NULL;
-
+
normalized_size = get_normalized_icon_name_len (str);
g_return_val_if_fail (normalized_size >= 0, NULL);
- if (is_night_icon)
+ if (is_night_icon)
buffer_size = normalized_size + night_pfx_size + sym_pfx_size + 1;
else
buffer_size = normalized_size + sym_pfx_size + 1;
@@ -504,350 +186,311 @@ get_normalized_icon_name (GWeatherInfo* wi,
}
memcpy (bufpos, sym_pfx, sym_pfx_size);
- buffer[buffer_size - 1] = '\0';
+ buffer[buffer_size - 1] = '\0';
return buffer;
}
+static void
+update_timeout_interval (GcalWeatherService *self)
+{
+ guint interval;
-/**************
- * < private >
- **************/
+ if (has_valid_weather_infos (self))
+ interval = self->check_interval_renew;
+ else
+ interval = self->check_interval_new;
-/*
- * Auxiliary methods:
- */
+ gcal_timer_set_default_duration (self->duration_timer, interval);
+}
-/* on_network_change:
- * @monitor: The emitting #GNetworkMonitor
- * @available: The current value of “network-available”
- * @self: The #GcalWeatherService instance.
- *
- * Starts and stops timer based on monitored network
- * changes.
- */
static void
-on_network_change (GNetworkMonitor *monitor,
- gboolean available,
- GcalWeatherService *self)
+schedule_midnight (GcalWeatherService *self)
{
- gboolean is_running;
+ g_autoptr (GTimeZone) zone = NULL;
+ g_autoptr (GDateTime) now = NULL;
+ g_autoptr (GDateTime) tom = NULL;
+ g_autoptr (GDateTime) mid = NULL;
+ gint64 real_now;
+ gint64 real_mid;
- g_return_if_fail (G_IS_NETWORK_MONITOR (monitor));
g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self));
- g_debug ("network changed, available = %d", available);
+ zone = !self->timezone ? g_time_zone_new_local () : g_time_zone_ref (self->timezone);
- is_running = gcal_timer_is_running (self->duration_timer);
- if (available && !is_running)
- {
- if (self->gweather_info != NULL)
- gweather_info_update (self->gweather_info);
+ now = g_date_time_new_now (zone);
+ tom = g_date_time_add_days (now, 1);
+ mid = g_date_time_new (zone,
+ g_date_time_get_year (tom),
+ g_date_time_get_month (tom),
+ g_date_time_get_day_of_month (tom),
+ 0, 0, 0);
- start_timer (self);
- }
- else if (!available && is_running)
- {
- stop_timer (self);
- }
+ real_mid = g_date_time_to_unix (mid);
+ real_now = g_date_time_to_unix (now);
+
+ gcal_timer_set_default_duration (self->midnight_timer,
+ real_mid - real_now);
}
-/* on_gclue_simple_creation:
- * @source:
- * @result: Result of gclue_simple_new().
- * @self: (transfer full): A GcalWeatherService reference.
- *
- * Callback used in gcal_weather_service_run().
- */
static void
-on_gclue_simple_creation (GClueSimple *_source,
- GAsyncResult *result,
- GcalWeatherService *self)
+start_timer (GcalWeatherService *self)
{
- GClueSimple *location_service; /* owned */
- GClueLocation *location; /* unowned */
- GClueClient *client; /* unowned */
- g_autoptr (GError) error = NULL;
-
- g_return_if_fail (G_IS_ASYNC_RESULT (result));
- g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self));
+ GNetworkMonitor *monitor; /* unowned */
- /* make sure we do not touch self->location_service
- * if the current operation was cancelled.
- */
- location_service = gclue_simple_new_finish (result, &error);
- if (error != NULL)
+ monitor = g_network_monitor_get_default ();
+ if (g_network_monitor_get_network_available (monitor))
{
- g_assert (location_service == NULL);
-
- if (error->domain == G_IO_ERROR && error->code == G_IO_ERROR_CANCELLED)
- /* Cancelled during creation. Silently fail. */;
- else
- g_warning ("Could not create GCLueSimple: %s", error->message);
-
- g_object_unref (self);
- return;
- }
-
- g_assert (self->location_service == NULL);
- g_assert (location_service != NULL);
-
- self->location_service = g_steal_pointer (&location_service);
-
- location = gclue_simple_get_location (self->location_service);
- client = gclue_simple_get_client (self->location_service);
-
- if (location != NULL)
- {
- update_gclue_location (self, location);
+ update_timeout_interval (self);
+ gcal_timer_start (self->duration_timer);
- g_signal_connect_object (location,
- "notify::location",
- G_CALLBACK (on_gclue_location_changed),
- self,
- 0);
+ schedule_midnight (self);
+ gcal_timer_start (self->midnight_timer);
}
-
- g_signal_connect_object (client,
- "notify::active",
- G_CALLBACK (on_gclue_client_activity_changed),
- self,
- 0);
-
- g_object_unref (self);
}
-/* on_gclue_location_changed:
- * @location: #GClueLocation owned by @self
- * @self: The #GcalWeatherService
- *
- * Handles location changes.
- */
static void
-on_gclue_location_changed (GClueLocation *location,
- GcalWeatherService *self)
+stop_timer (GcalWeatherService *self)
{
- g_return_if_fail (GCLUE_IS_LOCATION (location));
- g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self));
-
- update_gclue_location (self, location);
+ gcal_timer_stop (self->duration_timer);
+ gcal_timer_stop (self->midnight_timer);
}
-/* on_gclue_client_activity_changed:
- * @client: The #GClueclient ownd by @self
- * @self: The #GcalWeatherService
- *
- * Handles location client activity changes.
- */
static void
-on_gclue_client_activity_changed (GClueClient *client,
- GcalWeatherService *self)
+set_check_interval_new (GcalWeatherService *self,
+ guint interval)
{
- g_return_if_fail (GCLUE_IS_CLIENT (client));
- g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self));
+ self->check_interval_new = interval;
+ update_timeout_interval (self);
- /* Notify listeners about unknown locations: */
- update_location (self, NULL);
+ g_object_notify ((GObject*) self, "check-interval-new");
}
-/* on_gclue_client_stop:
- * @source_object: A #GClueClient.
- * @res: Result of gclue_client_call_stop().
- * @simple: (transfer full): A #GClueSimple.
- *
- * Helper-callback used in gcal_weather_service_stop().
- */
static void
-on_gclue_client_stop (GClueClient *client,
- GAsyncResult *res,
- GClueSimple *simple)
+set_check_interval_renew (GcalWeatherService *self,
+ guint interval)
{
- g_autoptr(GError) error = NULL; /* owned */
- gboolean stopped;
-
- g_return_if_fail (GCLUE_IS_CLIENT (client));
- g_return_if_fail (G_IS_ASYNC_RESULT (res));
- g_return_if_fail (GCLUE_IS_SIMPLE (simple));
-
- stopped = gclue_client_call_stop_finish (client,
- res,
- &error);
- if (error != NULL)
- g_warning ("Could not stop location service: %s", error->message);
- else if (!stopped)
- g_warning ("Could not stop location service");
+ self->check_interval_renew = interval;
+ update_timeout_interval (self);
- g_object_unref (simple);
+ g_object_notify ((GObject*) self, "check-interval-renew");
}
-/* 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)
+static gssize
+get_normalized_icon_name_len (const gchar *str)
{
- g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self));
- g_return_if_fail (info == NULL || GWEATHER_IS_INFO (info));
-
- update_weather (self, info, TRUE);
-}
+ const gchar *suffix1 = "-symbolic";
+ const gssize suffix1_len = strlen (suffix1);
-/* on_duration_timer_timeout
- * @self: A #GcalWeatherService.
- *
- * Handles scheduled weather report updates.
- */
-static void
-on_duration_timer_timeout (GcalTimer *timer,
- GcalWeatherService *self)
-{
- g_return_if_fail (timer != NULL);
- g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self));
+ const gchar *suffix2 = "-night";
+ const gssize suffix2_len = strlen (suffix2);
- if (self->gweather_info != NULL)
- gweather_info_update (self->gweather_info);
-}
+ gssize clean_len;
+ gssize str_len;
-/* on_midnight_timer_timeout
- * @self: A #GcalWeatherService.
- *
- * Handles scheduled weather report updates.
- */
-static void
-on_midnight_timer_timeout (GcalTimer *timer,
- GcalWeatherService *self)
-{
- g_return_if_fail (timer != NULL);
- g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self));
+ str_len = strlen (str);
- if (self->gweather_info != NULL)
- gweather_info_update (self->gweather_info);
+ clean_len = str_len - suffix1_len;
+ if (clean_len >= 0 && memcmp (suffix1, str + clean_len, suffix1_len) == 0)
+ str_len = clean_len;
- if (gcal_timer_is_running (self->duration_timer))
- gcal_timer_reset (self->duration_timer);
+ clean_len = str_len - suffix2_len;
+ if (clean_len >= 0 && memcmp (suffix2, str + clean_len, suffix2_len) == 0)
+ str_len = clean_len;
- schedule_midnight (self);
+ return str_len;
}
+static gint
+get_icon_name_sortkey (const gchar *icon_name,
+ gboolean *supports_night_icon)
+{
+ /* Note that we can't use gweathers condition
+ * field as it is not necessarily holds valid values.
+ * libgweather uses its own heuristic to determine
+ * the icon to use. String matching is still better
+ * than copying their algorithm, I guess.
+ */
+
+ gssize normalized_name_len;
+ const GcalWeatherIconInfo icons[] =
+ { {"weather-clear", TRUE},
+ {"weather-few-clouds", TRUE},
+ {"weather-overcast", FALSE},
+ {"weather-fog", FALSE},
+ {"weather-showers-scattered", FALSE},
+ {"weather-showers", FALSE},
+ {"weather-snow", FALSE},
+ {"weather-storm", FALSE},
+ {"weather-severe-alert", FALSE}
+ };
-/*
- * Intenral timer Helpers:
- */
+ g_return_val_if_fail (icon_name != NULL, -1);
+ g_return_val_if_fail (supports_night_icon != NULL, -1);
-/* update_timeout_interval:
- * @self: The #GcalWeatherService instance.
- *
- * Selects the right duration timer timeout based
- * on locally-stored weather information.
- */
-static void
-update_timeout_interval (GcalWeatherService *self)
-{
- guint interval;
+ *supports_night_icon = FALSE;
- g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self));
+ normalized_name_len = get_normalized_icon_name_len (icon_name);
+ g_return_val_if_fail (normalized_name_len >= 0, -1);
- if (has_valid_weather_infos (self))
- interval = self->check_interval_renew;
- else
- interval = self->check_interval_new;
+ for (int i = 0; i < G_N_ELEMENTS (icons); i++)
+ {
+ if (icons[i].name[normalized_name_len] == '\0' && strncmp (icon_name, icons[i].name,
normalized_name_len) == 0)
+ {
+ *supports_night_icon = icons[i].night_support;
+ return i;
+ }
+ }
- gcal_timer_set_default_duration (self->duration_timer, interval);
+ g_warning ("Unknown weather icon '%s'", icon_name);
+
+ return -1;
}
-/* schedule_midnight:
- * @self: The #GcalWeatherService instance.
- *
- * Sets the midnight timer timeout to midnight.
- * The timer needs to be reset when it
- * emits.
- */
-static void
-schedule_midnight (GcalWeatherService *self)
+static gboolean
+get_time_day_start (GcalWeatherService *self,
+ GDate *ret_date,
+ gint64 *ret_unix,
+ gint64 *ret_unix_exact)
{
g_autoptr (GTimeZone) zone = NULL;
g_autoptr (GDateTime) now = NULL;
- g_autoptr (GDateTime) tom = NULL;
- g_autoptr (GDateTime) mid = NULL;
- gint64 real_now;
- gint64 real_mid;
+ g_autoptr (GDateTime) day = NULL;
- g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self));
+ g_return_val_if_fail (self != NULL, FALSE);
+ g_return_val_if_fail (ret_date != NULL, FALSE);
+ g_return_val_if_fail (ret_unix != NULL, FALSE);
+ g_return_val_if_fail (ret_unix_exact != NULL, FALSE);
- zone = (self->time_zone == NULL)
- ? g_time_zone_new_local ()
- : g_time_zone_ref (self->time_zone);
+ zone = !self->timezone ? g_time_zone_new_local () : g_time_zone_ref (self->timezone);
now = g_date_time_new_now (zone);
- tom = g_date_time_add_days (now, 1);
- mid = g_date_time_new (zone,
- g_date_time_get_year (tom),
- g_date_time_get_month (tom),
- g_date_time_get_day_of_month (tom),
+ day = g_date_time_new (zone,
+ g_date_time_get_year (now),
+ g_date_time_get_month (now),
+ g_date_time_get_day_of_month (now),
0, 0, 0);
- real_mid = g_date_time_to_unix (mid);
- real_now = g_date_time_to_unix (now);
+ g_date_set_dmy (ret_date,
+ g_date_time_get_day_of_month (day),
+ g_date_time_get_month (day),
+ g_date_time_get_year (day));
- gcal_timer_set_default_duration (self->midnight_timer,
- real_mid - real_now);
+ *ret_unix = g_date_time_to_unix (day);
+ *ret_unix_exact = g_date_time_to_unix (now);
+ return TRUE;
}
-/* start_timer:
- * @self: The #GcalWeatherService instance.
- *
- * Starts weather timer in case it makes sense.
- */
-static void
-start_timer (GcalWeatherService *self)
+static inline gboolean
+get_gweather_temperature (GWeatherInfo *gwi,
+ gdouble *temp)
{
- GNetworkMonitor *monitor; /* unowned */
+ gboolean valid;
+ gdouble value;
- g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self));
+ *temp = NAN;
- monitor = g_network_monitor_get_default ();
- if (g_network_monitor_get_network_available (monitor))
- {
- update_timeout_interval (self);
- gcal_timer_start (self->duration_timer);
+ g_return_val_if_fail (gwi != NULL, FALSE);
+ g_return_val_if_fail (temp != NULL, FALSE);
- schedule_midnight (self);
- gcal_timer_start (self->midnight_timer);
+ valid = gweather_info_get_value_temp (gwi,
+ GWEATHER_TEMP_UNIT_DEFAULT,
+ &value);
+
+ /* TODO: Extract temperatures in Celsius and catch implausible cases */
+ if (valid)
+ {
+ *temp = value;
+ return TRUE;
+ }
+ else
+ {
+ *temp = NAN;
+ return FALSE;
}
}
-/* stop_timer:
- * @self: The #GcalWeatherService instance.
- *
- * Stops the timer.
- */
-static void
-stop_timer (GcalWeatherService *self)
+static gboolean
+compute_weather_info_data (GSList *samples,
+ gboolean is_today,
+ gchar **icon_name,
+ gchar **temperature)
{
- g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self));
+ GWeatherInfo *phenomenon_gwi = NULL;
+ GWeatherInfo *temp_gwi = NULL;
+ GSList *iter;
+ gboolean phenomenon_supports_night_icon;
+ gboolean has_daytime;
+ gdouble temp_val;
+ gint phenomenon_val;
+
+ temp_val = NAN;
+ has_daytime = FALSE;
+ phenomenon_val = -1;
+ phenomenon_supports_night_icon = FALSE;
- gcal_timer_stop (self->duration_timer);
- gcal_timer_stop (self->midnight_timer);
-}
+ /* Note: I checked three different gweather consumers
+ * and they all pick different values. So here is my
+ * take: I pick up the worst weather for icons and
+ * the highest temperature. I basically want to know
+ * whether I need my umbrella for my appointment.
+ * Not sure about the right temperature. It is probably
+ * better to pick-up the median of all predictions
+ * during daytime.
+ */
+
+ for (iter = samples; iter; iter = iter->next)
+ {
+ GWeatherInfo *gwi; /* unowned */
+ const gchar *icon_name; /* unowned */
+ gboolean supports_night_icon;
+ gboolean valid_temp;
+ gdouble temp;
+ gint phenomenon;
+ gwi = GWEATHER_INFO (iter->data);
+ phenomenon = -1;
+
+ icon_name = gweather_info_get_icon_name (gwi);
+ if (icon_name)
+ phenomenon = get_icon_name_sortkey (icon_name, &supports_night_icon);
+
+ valid_temp = get_gweather_temperature (gwi, &temp);
+
+ if (phenomenon >= 0 && (phenomenon_gwi == NULL || phenomenon > phenomenon_val))
+ {
+ phenomenon_supports_night_icon = supports_night_icon;
+ phenomenon_val = phenomenon;
+ phenomenon_gwi = gwi;
+ }
+
+ if (valid_temp && (!temp_gwi || temp > temp_val))
+ {
+ temp_val = temp;
+ temp_gwi = gwi;
+ }
+
+ if (gweather_info_is_daytime (gwi))
+ has_daytime = TRUE;
+ }
+
+ if (phenomenon_gwi && temp_gwi)
+ {
+ *icon_name = get_normalized_icon_name (phenomenon_gwi, is_today && !has_daytime &&
phenomenon_supports_night_icon);
+ *temperature = gweather_info_get_temp (temp_gwi);
+ return TRUE;
+ }
+ else
+ {
+ /* empty list */
+ *icon_name = NULL;
+ *temperature = NULL;
+ return FALSE;
+ }
+}
-/*
- * Internal location API:
- */
-/**
- * update_location:
- * @self: The #GcalWeatherService instance.
- * @location: (nullable): The location we want weather information for.
- *
- * Registers the location to retrieve weather information from.
- */
static void
update_location (GcalWeatherService *self,
GWeatherLocation *location)
@@ -877,7 +520,7 @@ 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) on_gweather_update, self);
+ g_signal_connect (self->gweather_info, "updated", (GCallback) on_gweather_update_cb, self);
/* gweather_info_update might or might not trigger a
* GWeatherInfo::update() signal. Therefore, we have to
@@ -893,23 +536,13 @@ update_location (GcalWeatherService *self,
}
}
-/**
- * update_gclue_location:
- * @self: The #GcalWeatherService instance.
- * @location: (nullable): The location we want weather information for.
- *
- * Registers the location to retrieve weather information from.
- */
static void
update_gclue_location (GcalWeatherService *self,
GClueLocation *location)
{
GWeatherLocation *wlocation = NULL; /* owned */
- g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self));
- g_return_if_fail (location == NULL || GCLUE_IS_LOCATION (location));
-
- if (location != NULL)
+ if (location)
{
GWeatherLocation *wworld; /* unowned */
gdouble latitude;
@@ -923,593 +556,609 @@ update_gclue_location (GcalWeatherService *self,
wlocation = gweather_location_find_nearest_city (wworld, latitude, longitude);
}
-
update_location (self, wlocation);
- if (wlocation != NULL)
- gweather_location_unref (wlocation);
+ g_clear_pointer (&wlocation, gweather_location_unref);
}
+static GSList*
+preprocess_gweather_reports (GcalWeatherService *self,
+ GSList *samples)
+{
+ GWeatherInfo *first_tomorrow = NULL; /* unowned */
+ GSList **days = NULL; /* owned[owned[unowned]] */
+ GSList *result = NULL; /* owned[owned] */
+ GSList *iter = NULL; /* unowned */
+ GDate cur_gdate;
+ glong first_tomorrow_dtime = -1;
+ glong today_unix;
+ glong unix_now;
+ /* This function basically separates samples by date and calls compute_weather_info_data
+ * for every bucket to build weather infos. Each bucket represents a single day.
+ *
+ * All gweather consumers I reviewed presume sorted samples. However, there is no documented
+ * order. Lets assume the worst.
+ */
-/*
- * Internal Weather API
- */
+ if (self->max_days <= 0)
+ return NULL;
-#if PRINT_WEATHER_DATA
-static gchar*
-gwc2str (GWeatherInfo *gwi)
-{
- g_autoptr (GDateTime) date = NULL;
- g_autofree gchar *date_str = NULL;
- glong update;
+ if (!get_time_day_start (self, &cur_gdate, &today_unix, &unix_now))
+ return NULL;
- gchar *icon_name; /* unowned */
- gdouble temp;
+ days = g_malloc0 (sizeof (GSList*) * self->max_days);
- if (!gweather_info_get_value_update (gwi, &update))
- return g_strdup ("<null>");
+ /* Split samples to max_days buckets: */
+ for (iter = samples; iter != NULL; iter = iter->next)
+ {
+ GWeatherInfo *gwi; /* unowned */
+ gboolean valid_date;
+ glong gwi_dtime;
+ gsize bucket;
- date = g_date_time_new_from_unix_local (update);
- date_str = g_date_time_format (date, "%F %T"),
+ gwi = GWEATHER_INFO (iter->data);
+ valid_date = gweather_info_get_value_update (gwi, &gwi_dtime);
+ if (!valid_date)
+ continue;
- get_gweather_temperature (gwi, &temp);
- icon_name = gweather_info_get_symbolic_icon_name (gwi);
+ #if PRINT_WEATHER_DATA
+ {
+ g_autofree gchar* dbg_str = gwc2str (gwi);
+ g_message ("WEATHER READING POINT: %s", dbg_str);
+ }
+ #endif
- return g_strdup_printf ("(%s: t:%f, w:%s)",
- date_str,
- temp,
- icon_name);
-}
-#endif
+ if (gwi_dtime >= 0 && gwi_dtime >= today_unix)
+ {
+ bucket = (gwi_dtime - today_unix) / DAY_SECONDS;
+ if (bucket < self->max_days)
+ days[bucket] = g_slist_prepend (days[bucket], gwi);
-/* set_max_days:
- * @self: The #GcalWeatherService instance.
- * @days: Number of days.
- *
- * Setter for #GcalWeatherInfos:max-days.
- */
-static void
-set_max_days (GcalWeatherService *self,
- guint days)
-{
- g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self));
- g_return_if_fail (days >= 1);
+ if (bucket == 1 && (first_tomorrow == NULL || first_tomorrow_dtime > gwi_dtime))
+ {
+ first_tomorrow_dtime = gwi_dtime;
+ first_tomorrow = gwi;
+ }
+ }
+ else
+ {
+ g_debug ("Encountered historic weather information");
+ }
+ }
- self->max_days = days;
+ if (!days[0] && first_tomorrow)
+ {
- g_object_notify ((GObject*) self, "max-days");
-}
+ glong secs_left_today;
+ glong secs_between;
-/* set_valid_timespan:
- * @self: A #GcalWeatherService instance.
- * @timespan: Amount of seconds we consider weather information as valid.
- */
-static void
-set_valid_timespan (GcalWeatherService *self,
- gint64 timespan)
-{
- g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self));
- g_return_if_fail (timespan >= 0);
+ /* There is no data point left for today. Lets borrow one. */
+ secs_left_today = DAY_SECONDS - (unix_now - today_unix);
+ secs_between = first_tomorrow_dtime - unix_now;
- self->valid_timespan = timespan;
+ if (secs_left_today < 90*60 && secs_between <= 180*60)
+ days[0] = g_slist_prepend (days[0], first_tomorrow);
+ }
- g_object_notify ((GObject*) self, "valid-timespan");
-}
+ /* Produce GcalWeatherInfo for each bucket: */
+ for (int i = 0; i < self->max_days; i++)
+ {
+ g_autofree gchar *icon_name;
+ g_autofree gchar *temperature;
-/* has_valid_weather_infos
- * @self: A #GcalWeatherService instance.
- *
- * Checks whether weather information are available
- * and up-to-date.
- */
-static gboolean
-has_valid_weather_infos (GcalWeatherService *self)
-{
- gint64 now;
+ if (compute_weather_info_data (days[i], i == 0, &icon_name, &temperature))
+ {
+ GcalWeatherInfo* gcwi;
- g_return_val_if_fail (GCAL_IS_WEATHER_SERVICE (self), FALSE);
+ gcwi = gcal_weather_info_new (&cur_gdate, icon_name, temperature);
+ result = g_slist_prepend (result, g_steal_pointer (&gcwi));
+ }
- if (self->gweather_info == NULL || self->weather_infos_upated < 0)
- return FALSE;
+ g_date_add_days (&cur_gdate, 1);
+ }
- now = g_get_monotonic_time ();
- return (now - self->weather_infos_upated) / 1000000 <= self->valid_timespan;
+ /* Cleanup */
+ for (int i = 0; i < self->max_days; i++)
+ g_slist_free (days[i]);
+ g_free (days);
+
+ return result;
}
-/* 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.
+
+/*
+ * Callbacks
*/
+
static void
-update_weather (GcalWeatherService *self,
- GWeatherInfo *info,
- gboolean reuse_old_on_error)
+on_network_changed_cb (GNetworkMonitor *monitor,
+ gboolean available,
+ GcalWeatherService *self)
{
- GSList *gwforecast = NULL; /* unowned */
-
- g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self));
- g_return_if_fail (info == NULL || GWEATHER_IS_INFO (info));
-
+ gboolean is_running;
- /* Compute a list of newly received weather infos. */
- if (info == NULL)
- {
- g_debug ("Could not retrieve valid weather");
- }
- else if (gweather_info_is_valid (info))
- {
- g_debug ("Received valid weather information");
- gwforecast = gweather_info_get_forecast_list (info);
- }
- else
- {
- g_autofree gchar* location_name = gweather_info_get_location_name (info);
- g_debug ("Could not retrieve valid weather for location '%s'", location_name);
- }
+ is_running = gcal_timer_is_running (self->duration_timer);
- if (gwforecast == NULL && self->weather_infos_upated >= 0)
+ if (available && !is_running)
{
- if (!reuse_old_on_error || !has_valid_weather_infos (self))
- {
- g_slist_free_full (self->weather_infos, g_object_unref);
- self->weather_infos = NULL;
- self->weather_infos_upated = -1;
+ if (self->gweather_info != NULL)
+ gweather_info_update (self->gweather_info);
- g_signal_emit (self, gcal_weather_service_signals[SIG_WEATHER_CHANGED], 0);
- }
+ start_timer (self);
}
- else if (gwforecast != NULL)
+ else if (!available && is_running)
{
- 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);
+ stop_timer (self);
}
}
-
-
-/*
- * Internal weather update API and callbacks
- */
-
-/* set_check_interval_new:
- * @self: The #GcalWeatherService instance.
- * @days: Number of days.
- *
- * Setter for GcalWeatherInfos:check-interval-new.
- */
static void
-set_check_interval_new (GcalWeatherService *self,
- guint interval)
+on_gclue_location_changed_cb (GClueLocation *location,
+ GcalWeatherService *self)
{
- g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self));
- g_return_if_fail (interval > 0);
-
- self->check_interval_new = interval;
- update_timeout_interval (self);
+ update_gclue_location (self, location);
+}
- g_object_notify ((GObject*) self, "check-interval-new");
+static void
+on_gclue_client_activity_changed_cb (GClueClient *client,
+ GcalWeatherService *self)
+{
+ /* Notify listeners about unknown locations: */
+ update_location (self, NULL);
}
-/* set_check_interval_renew:
- * @self: The #GcalWeatherService instance.
- * @days: Number of days.
- *
- * Setter for GcalWeatherInfos:check-interval-renew.
- */
static void
-set_check_interval_renew (GcalWeatherService *self,
- guint interval)
+on_gclue_simple_creation_cb (GClueSimple *_source,
+ GAsyncResult *result,
+ GcalWeatherService *self)
{
- g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self));
- g_return_if_fail (interval > 0);
+ g_autoptr (GError) error = NULL;
+ GClueLocation *location; /* unowned */
+ GClueSimple *location_service; /* owned */
+ GClueClient *client; /* unowned */
- self->check_interval_renew = interval;
- update_timeout_interval (self);
+ /* make sure we do not touch self->location_service
+ * if the current operation was cancelled.
+ */
+ location_service = gclue_simple_new_finish (result, &error);
- g_object_notify ((GObject*) self, "check-interval-renew");
-}
+ if (error)
+ {
+ g_assert_null (location_service);
-/* drop_suffix:
- * @str: A icon name to normalize.
- *
- * Translates a given weather icon name to the
- * one to display for folded weather reports.
- *
- * Returns: Number of initial characters that
- * belong to the normalized name.
- */
-static gssize
-get_normalized_icon_name_len (const gchar *str)
-{
- const gchar suffix1[] = "-symbolic";
- const gssize suffix1_len = G_N_ELEMENTS (suffix1) - 1;
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ /* Cancelled during creation. Silently fail. */;
+ else
+ g_warning ("Could not create GCLueSimple: %s", error->message);
- const gchar suffix2[] = "-night";
- const gssize suffix2_len = G_N_ELEMENTS (suffix2) - 1;
+ g_object_unref (self);
+ return;
+ }
- gssize clean_len;
- gssize str_len;
+ self->location_service = g_steal_pointer (&location_service);
- g_return_val_if_fail (str != NULL, -1);
+ location = gclue_simple_get_location (self->location_service);
+ client = gclue_simple_get_client (self->location_service);
- str_len = strlen (str);
+ if (location)
+ {
+ update_gclue_location (self, location);
- clean_len = str_len - suffix1_len;
- if (clean_len >= 0 && memcmp (suffix1, str + clean_len, suffix1_len) == 0)
- str_len = clean_len;
+ g_signal_connect_object (location,
+ "notify::location",
+ G_CALLBACK (on_gclue_location_changed_cb),
+ self,
+ 0);
+ }
- clean_len = str_len - suffix2_len;
- if (clean_len >= 0 && memcmp (suffix2, str + clean_len, suffix2_len) == 0)
- str_len = clean_len;
+ g_signal_connect_object (client,
+ "notify::active",
+ G_CALLBACK (on_gclue_client_activity_changed_cb),
+ self,
+ 0);
- return str_len;
+ g_object_unref (self);
}
-/* get_icon_name_sortkey:
- *
- * Returns a sort key for a given weather
- * icon name and -1 for unknown ones.
+/* on_gclue_client_stop:
+ * @source_object: A #GClueClient.
+ * @res: Result of gclue_client_call_stop().
+ * @simple: (transfer full): A #GClueSimple.
*
- * The lower the key, the better the weather.
+ * Helper-callback used in gcal_weather_service_stop().
*/
-static gint
-get_icon_name_sortkey (const gchar *icon_name,
- gboolean *supports_night_icon)
+static void
+on_gclue_client_stop (GClueClient *client,
+ GAsyncResult *res,
+ GClueSimple *simple)
{
- /* Note that we can't use gweathers condition
- * field as it is not necessarily holds valid values.
- * libgweather uses its own heuristic to determine
- * the icon to use. String matching is still better
- * than copying their algorithm, I guess.
- */
-
- gssize normalized_name_len;
+ g_autoptr(GError) error = NULL; /* owned */
+ gboolean stopped;
- const GcalWeatherIconInfo icons[] =
- { {"weather-clear", TRUE},
- {"weather-few-clouds", TRUE},
- {"weather-overcast", FALSE},
- {"weather-fog", FALSE},
- {"weather-showers-scattered", FALSE},
- {"weather-showers", FALSE},
- {"weather-snow", FALSE},
- {"weather-storm", FALSE},
- {"weather-severe-alert", FALSE}
- };
+ stopped = gclue_client_call_stop_finish (client,
+ res,
+ &error);
+ if (error != NULL)
+ g_warning ("Could not stop location service: %s", error->message);
+ else if (!stopped)
+ g_warning ("Could not stop location service");
- g_return_val_if_fail (icon_name != NULL, -1);
- g_return_val_if_fail (supports_night_icon != NULL, -1);
+ g_object_unref (simple);
+}
- *supports_night_icon = FALSE;
+static void
+on_gweather_update_cb (GWeatherInfo *info,
+ GcalWeatherService *self)
+{
+ update_weather (self, info, TRUE);
+}
- normalized_name_len = get_normalized_icon_name_len (icon_name);
- g_return_val_if_fail (normalized_name_len >= 0, -1);
+/* on_duration_timer_timeout
+ * @self: A #GcalWeatherService.
+ *
+ * Handles scheduled weather report updates.
+ */
+static void
+on_duration_timer_timeout (GcalTimer *timer,
+ GcalWeatherService *self)
+{
+ if (self->gweather_info)
+ gweather_info_update (self->gweather_info);
+}
- for (int i = 0; i < G_N_ELEMENTS (icons); i++)
- {
- if (icons[i].name[normalized_name_len] == '\0' && strncmp (icon_name, icons[i].name,
normalized_name_len) == 0)
- {
- *supports_night_icon = icons[i].night_support;
- return i;
- }
- }
+/* on_midnight_timer_timeout
+ * @self: A #GcalWeatherService.
+ *
+ * Handles scheduled weather report updates.
+ */
+static void
+on_midnight_timer_timeout (GcalTimer *timer,
+ GcalWeatherService *self)
+{
+ if (self->gweather_info)
+ gweather_info_update (self->gweather_info);
- g_warning ("Unknown weather icon '%s'", icon_name);
+ if (gcal_timer_is_running (self->duration_timer))
+ gcal_timer_reset (self->duration_timer);
- return -1;
+ schedule_midnight (self);
}
-/* get_time_day_start:
- * @self: The #GcalWeatherService instance.
- * @date: (out) (not nullable): A #GDate that should be set to today.
- * @unix: (out) (not nullable): A UNIX time stamp that should be set to today.
- * @ret_unix_exact (out) (not nullable): The exact date time this data point predicts.
- *
- * Provides current date in two different forms.
- *
- * Returns: %TRUE on success.
- */
-static gboolean
-get_time_day_start (GcalWeatherService *self,
- GDate *ret_date,
- gint64 *ret_unix,
- gint64 *ret_unix_exact)
+#if PRINT_WEATHER_DATA
+static gchar*
+gwc2str (GWeatherInfo *gwi)
{
- g_autoptr (GTimeZone) zone = NULL;
- g_autoptr (GDateTime) now = NULL;
- g_autoptr (GDateTime) day = NULL;
+ g_autoptr (GDateTime) date = NULL;
+ g_autofree gchar *date_str = NULL;
+ glong update;
- g_return_val_if_fail (self != NULL, FALSE);
- g_return_val_if_fail (ret_date != NULL, FALSE);
- g_return_val_if_fail (ret_unix != NULL, FALSE);
- g_return_val_if_fail (ret_unix_exact != NULL, FALSE);
+ gchar *icon_name; /* unowned */
+ gdouble temp;
- zone = (self->time_zone == NULL)
- ? g_time_zone_new_local ()
- : g_time_zone_ref (self->time_zone);
+ if (!gweather_info_get_value_update (gwi, &update))
+ return g_strdup ("<null>");
- now = g_date_time_new_now (zone);
- day = g_date_time_new (zone,
- g_date_time_get_year (now),
- g_date_time_get_month (now),
- g_date_time_get_day_of_month (now),
- 0, 0, 0);
+ date = g_date_time_new_from_unix_local (update);
+ date_str = g_date_time_format (date, "%F %T"),
- g_date_set_dmy (ret_date,
- g_date_time_get_day_of_month (day),
- g_date_time_get_month (day),
- g_date_time_get_year (day));
+ get_gweather_temperature (gwi, &temp);
+ icon_name = gweather_info_get_symbolic_icon_name (gwi);
- *ret_unix = g_date_time_to_unix (day);
- *ret_unix_exact = g_date_time_to_unix (now);
- return TRUE;
+ return g_strdup_printf ("(%s: t:%f, w:%s)",
+ date_str,
+ temp,
+ icon_name);
}
+#endif
-/* get_gweather_temperature:
- * @gwi: #GWeatherInfo to extract temperatures from.
- * @temp: (out): extracted temperature or %NAN.
- *
- * Returns sanitized temperatures. Returned values should only
- * be used as sort key.
- *
- * Returns: %TRUE for valid temperatures.
- */
-static inline gboolean
-get_gweather_temperature (GWeatherInfo *gwi,
- gdouble *temp)
+static void
+set_max_days (GcalWeatherService *self,
+ guint days)
{
- gboolean valid;
- gdouble value;
+ g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self));
+ g_return_if_fail (days >= 1);
- *temp = NAN;
+ self->max_days = days;
- g_return_val_if_fail (gwi != NULL, FALSE);
- g_return_val_if_fail (temp != NULL, FALSE);
+ g_object_notify ((GObject*) self, "max-days");
+}
- valid = gweather_info_get_value_temp (gwi,
- GWEATHER_TEMP_UNIT_DEFAULT,
- &value);
+static void
+set_valid_timespan (GcalWeatherService *self,
+ gint64 timespan)
+{
+ g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self));
+ g_return_if_fail (timespan >= 0);
- /* TODO: Extract temperatures in Celsius and catch
- * implausible cases.
- */
- if (valid)
- {
- *temp = value;
- return TRUE;
- }
- else
- {
- *temp = NAN;
- return FALSE;
- }
+ self->valid_timespan = timespan;
+
+ g_object_notify ((GObject*) self, "valid-timespan");
}
-/* compute_weather_info_data:
- * @samples: List of received #GWeatherInfos.
- * @icon_name: (out): (transfer full): weather icon name or %NULL.
- * @temperature: (out): (transfer full): temperature and unit or %NULL.
- *
- * Computes a icon name and temperature representing @samples.
- *
- * Returns: %TRUE if there is a valid icon name and temperature.
- */
static gboolean
-compute_weather_info_data (GSList *samples,
- gboolean is_today,
- gchar **icon_name,
- gchar **temperature)
+has_valid_weather_infos (GcalWeatherService *self)
{
- gboolean phenomenon_supports_night_icon = FALSE;
- gint phenomenon_val = -1;
- GWeatherInfo *phenomenon_gwi = NULL; /* unowned */
- gdouble temp_val = NAN;
- GWeatherInfo *temp_gwi = NULL; /* unowned */
- gboolean has_daytime = FALSE;
- GSList *iter; /* unowned */
+ gint64 now;
- /* Note: I checked three different gweather consumers
- * and they all pick different values. So here is my
- * take: I pick up the worst weather for icons and
- * the highest temperature. I basically want to know
- * whether I need my umbrella for my appointment.
- * Not sure about the right temperature. It is probably
- * better to pick-up the median of all predictions
- * during daytime.
- */
+ g_return_val_if_fail (GCAL_IS_WEATHER_SERVICE (self), FALSE);
+
+ if (self->gweather_info == NULL || self->weather_infos_upated < 0)
+ return FALSE;
- g_return_val_if_fail (icon_name != NULL, FALSE);
- g_return_val_if_fail (temperature != NULL, FALSE);
+ now = g_get_monotonic_time ();
+ return (now - self->weather_infos_upated) / 1000000 <= self->valid_timespan;
+}
- for (iter = samples; iter != NULL; iter = iter->next)
- {
- GWeatherInfo *gwi; /* unowned */
- const gchar *icon_name; /* unowned */
- gint phenomenon = -1;
- gboolean supports_night_icon;
- gdouble temp;
- gboolean valid_temp;
+/* 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
+update_weather (GcalWeatherService *self,
+ GWeatherInfo *info,
+ gboolean reuse_old_on_error)
+{
+ GSList *gwforecast = NULL; /* unowned */
- gwi = GWEATHER_INFO (iter->data);
+ g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self));
+ g_return_if_fail (info == NULL || GWEATHER_IS_INFO (info));
- icon_name = gweather_info_get_icon_name (gwi);
- if (icon_name != NULL)
- phenomenon = get_icon_name_sortkey (icon_name, &supports_night_icon);
- valid_temp = get_gweather_temperature (gwi, &temp);
+ /* Compute a list of newly received weather infos. */
+ if (!info)
+ {
+ g_debug ("Could not retrieve valid weather");
+ }
+ else if (gweather_info_is_valid (info))
+ {
+ g_debug ("Received valid weather information");
+ gwforecast = gweather_info_get_forecast_list (info);
+ }
+ else
+ {
+ g_autofree gchar* location_name = gweather_info_get_location_name (info);
+ g_debug ("Could not retrieve valid weather for location '%s'", location_name);
+ }
- if (phenomenon >= 0 && (phenomenon_gwi == NULL || phenomenon > phenomenon_val))
+ if (!gwforecast && self->weather_infos_upated >= 0)
+ {
+ if (!reuse_old_on_error || !has_valid_weather_infos (self))
{
- phenomenon_supports_night_icon = supports_night_icon;
- phenomenon_val = phenomenon;
- phenomenon_gwi = gwi;
- }
+ g_slist_free_full (self->weather_infos, g_object_unref);
+ self->weather_infos = NULL;
+ self->weather_infos_upated = -1;
- if (valid_temp && (temp_gwi == NULL || temp > temp_val))
- {
- temp_val = temp;
- temp_gwi = gwi;
+ g_signal_emit (self, signals[SIG_WEATHER_CHANGED], 0);
}
-
- if (gweather_info_is_daytime (gwi))
- has_daytime = TRUE;
- }
-
- if (phenomenon_gwi != NULL && temp_gwi != NULL)
- {
- *icon_name = get_normalized_icon_name (phenomenon_gwi, is_today && !has_daytime &&
phenomenon_supports_night_icon);
- *temperature = gweather_info_get_temp (temp_gwi);
- return TRUE;
}
- else
+ else if (gwforecast)
{
- /* empty list */
- *icon_name = NULL;
- *temperature = NULL;
- return FALSE;
+ 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, signals[SIG_WEATHER_CHANGED], 0);
}
}
-/* preprocess_gweather_reports:
- * @self: The #GcalWeatherService instance.
- * @samples: Received list of #GWeatherInfos
- *
- * Computes weather info objects representing specific days
- * by combining given #GWeatherInfos.
- *
- * Returns: (transfer full): List of up to $self->max_days #GcalWeatherInfos.
+
+/*
+ * GObject overrides
*/
-static GSList*
-preprocess_gweather_reports (GcalWeatherService *self,
- GSList *samples)
+
+static void
+gcal_weather_service_finalize (GObject *object)
{
- const glong DAY_SECONDS = 24*60*60;
- GSList *result = NULL; /* owned[owned] */
- GSList **days; /* owned[owned[unowned]] */
- GSList *iter; /* unowned */
- GDate cur_gdate;
- glong today_unix;
- glong unix_now;
+ GcalWeatherService *self = (GcalWeatherService *) object;
- GWeatherInfo *first_tomorrow = NULL; /* unowned */
- glong first_tomorrow_dtime = -1;
+ g_cancellable_cancel (self->location_cancellable);
- g_return_val_if_fail (GCAL_IS_WEATHER_SERVICE (self), NULL);
+ g_clear_pointer (&self->duration_timer, gcal_timer_free);
+ g_clear_pointer (&self->midnight_timer, gcal_timer_free);
+ g_clear_pointer (&self->timezone, g_time_zone_unref);
- /* This function basically separates samples by
- * date and calls compute_weather_info_data for
- * every bucket to build weather infos.
- * Each bucket represents a single day.
- */
+ g_clear_object (&self->gweather_info);
+ g_clear_object (&self->location_service);
+ g_clear_object (&self->location_cancellable);
- /* All gweather consumers I reviewed presume
- * sorted samples. However, there is no documented
- * order. Lets assume the worst.
- */
+ g_slist_free_full (self->weather_infos, g_object_unref);
- if (self->max_days <= 0)
- return NULL;
+ if (self->network_changed_sid > 0)
+ g_signal_handler_disconnect (g_network_monitor_get_default (), self->network_changed_sid);
- if (!get_time_day_start (self, &cur_gdate, &today_unix, &unix_now))
- return NULL;
+ G_OBJECT_CLASS (gcal_weather_service_parent_class)->finalize (object);
+}
- days = g_malloc0 (sizeof (GSList*) * self->max_days);
+static void
+gcal_weather_service_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GcalWeatherService *self = (GcalWeatherService *) object;
- /* Split samples to max_days buckets: */
- for (iter = samples; iter != NULL; iter = iter->next)
+ switch (prop_id)
{
- GWeatherInfo *gwi; /* unowned */
- gboolean valid_date;
- glong gwi_dtime;
- gsize bucket;
+ case PROP_MAX_DAYS:
+ g_value_set_uint (value, gcal_weather_service_get_max_days (self));
+ break;
- gwi = GWEATHER_INFO (iter->data);
- valid_date = gweather_info_get_value_update (gwi, &gwi_dtime);
- if (!valid_date)
- continue;
+ case PROP_TIME_ZONE:
+ g_value_set_pointer (value, gcal_weather_service_get_time_zone (self));
+ break;
- #if PRINT_WEATHER_DATA
- {
- g_autofree gchar* dbg_str = gwc2str (gwi);
- g_message ("WEATHER READING POINT: %s", dbg_str);
- }
- #endif
+ case PROP_CHECK_INTERVAL_NEW:
+ g_value_set_uint (value, gcal_weather_service_get_check_interval_new (self));
+ break;
- if (gwi_dtime >= 0 && gwi_dtime >= today_unix)
- {
- bucket = (gwi_dtime - today_unix) / DAY_SECONDS;
- if (bucket >= 0 && bucket < self->max_days)
- days[bucket] = g_slist_prepend (days[bucket], gwi);
+ case PROP_CHECK_INTERVAL_RENEW:
+ g_value_set_uint (value, gcal_weather_service_get_check_interval_renew (self));
+ break;
- if (bucket == 1 && (first_tomorrow == NULL || first_tomorrow_dtime > gwi_dtime))
- {
- first_tomorrow_dtime = gwi_dtime;
- first_tomorrow = gwi;
- }
- }
- else
- {
- g_debug ("Encountered historic weather information");
- }
+ 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;
}
+}
+
+static void
+gcal_weather_service_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GcalWeatherService *self = (GcalWeatherService *) object;
- if (days[0] == NULL && first_tomorrow != NULL)
+ switch (prop_id)
{
- /* There is no data point left for today.
- * Lets borrow one.
- */
- glong secs_left_today;
- glong secs_between;
+ case PROP_MAX_DAYS:
+ set_max_days (self, g_value_get_uint (value));
+ break;
+
+ case PROP_TIME_ZONE:
+ gcal_weather_service_set_time_zone (self, g_value_get_pointer (value));
+ break;
+
+ case PROP_CHECK_INTERVAL_NEW:
+ set_check_interval_new (self, g_value_get_uint (value));
+ break;
- secs_left_today = DAY_SECONDS - (unix_now - today_unix);
- secs_between = first_tomorrow_dtime - unix_now;
+ case PROP_CHECK_INTERVAL_RENEW:
+ set_check_interval_renew (self, g_value_get_uint (value));
+ break;
- if (secs_left_today < 90*60 && secs_between <= 180*60)
- days[0] = g_slist_prepend (days[0], first_tomorrow);
+ case PROP_VALID_TIMESPAN:
+ set_valid_timespan (self, g_value_get_int64 (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
}
+}
- /* Produce GcalWeatherInfo for each bucket: */
- for (int i = 0; i < self->max_days; i++)
- {
- g_autofree gchar *icon_name;
- g_autofree gchar *temperature;
+static void
+gcal_weather_service_class_init (GcalWeatherServiceClass *klass)
+{
+ GObjectClass *object_class;
- if (compute_weather_info_data (days[i], i == 0, &icon_name, &temperature))
- {
- GcalWeatherInfo* gcwi; /* owned */
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->finalize = gcal_weather_service_finalize;
+ object_class->get_property = gcal_weather_service_get_property;
+ object_class->set_property = gcal_weather_service_set_property;
- gcwi = gcal_weather_info_new (&cur_gdate,
- icon_name,
- temperature);
+ /**
+ * GcalWeatherService:max-days:
+ *
+ * Maximal number of days to fetch forecasts for.
+ */
+ g_object_class_install_property
+ (G_OBJECT_CLASS (klass),
+ PROP_MAX_DAYS,
+ g_param_spec_uint ("max-days", "max-days", "max-days",
+ 1, G_MAXUINT, 3,
+ G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
- result = g_slist_prepend (result,
- g_steal_pointer (&gcwi));
- }
+ /**
+ * GcalWeatherService:time-zone:
+ *
+ * The time zone to use.
+ */
+ g_object_class_install_property
+ (G_OBJECT_CLASS (klass),
+ PROP_TIME_ZONE,
+ g_param_spec_pointer ("time-zone", "time-zone", "time-zone",
+ G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE));
- g_date_add_days (&cur_gdate, 1);
- }
+ /**
+ * GcalWeatherService:check-interval-new:
+ *
+ * Amount of seconds to wait before re-fetching weather infos.
+ * This interval is used when no valid weather reports are available.
+ */
+ g_object_class_install_property
+ (G_OBJECT_CLASS (klass),
+ PROP_CHECK_INTERVAL_NEW,
+ g_param_spec_uint ("check-interval-new", "check-interval-new", "check-interval-new",
+ 0, G_MAXUINT, GCAL_WEATHER_CHECK_INTERVAL_NEW_DFLT,
+ G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
- /* Cleanup: */
- for (int i = 0; i < self->max_days; i++)
- g_slist_free (days[i]);
- g_free (days);
+ /**
+ * GcalWeatherService:check-interval-renew:
+ *
+ * Amount of seconds to wait before re-fetching weather information.
+ * This interval is used when valid weather reports are available.
+ */
+ g_object_class_install_property
+ (G_OBJECT_CLASS (klass),
+ PROP_CHECK_INTERVAL_RENEW,
+ g_param_spec_uint ("check-interval-renew", "check-interval-renew", "check-interval-renew",
+ 0, G_MAXUINT, GCAL_WEATHER_CHECK_INTERVAL_RENEW_DFLT,
+ G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
- return result;
+ /**
+ * 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, GCAL_WEATHER_VALID_TIMESPAN_DFLT,
+ G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+
+ /**
+ * GcalWeatherService::weather-changed:
+ * @sender: The #GcalWeatherService
+ * @self: data pointer.
+ *
+ * Triggered on weather changes. Call
+ * gcal_weather_service_get_weather_infos() to
+ * retrieve predictions.
+ */
+ signals[SIG_WEATHER_CHANGED] = g_signal_new ("weather-changed",
+ GCAL_TYPE_WEATHER_SERVICE,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL,
+ NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
}
+static void
+gcal_weather_service_init (GcalWeatherService *self)
+{
+ self->check_interval_new = GCAL_WEATHER_CHECK_INTERVAL_NEW_DFLT;
+ self->check_interval_renew = GCAL_WEATHER_CHECK_INTERVAL_RENEW_DFLT;
+ self->location_cancellable = g_cancellable_new ();
+ self->weather_infos_upated = -1;
+ self->valid_timespan = -1;
+
+ self->duration_timer = gcal_timer_new (GCAL_WEATHER_CHECK_INTERVAL_NEW_DFLT);
+ gcal_timer_set_callback (self->duration_timer, (GCalTimerFunc) on_duration_timer_timeout, self, NULL);
+
+ self->midnight_timer = gcal_timer_new (24 * 60 * 60);
+ gcal_timer_set_callback (self->midnight_timer, (GCalTimerFunc) on_midnight_timer_timeout, self, NULL);
-/*************
- * < public >
- *************/
+ self->network_changed_sid = g_signal_connect (g_network_monitor_get_default (),
+ "network-changed",
+ G_CALLBACK (on_network_changed_cb),
+ self);
+}
/**
* gcal_weather_service_new:
@@ -1548,7 +1197,8 @@ GTimeZone*
gcal_weather_service_get_time_zone (GcalWeatherService *self)
{
g_return_val_if_fail (self != NULL, NULL);
- return self->time_zone;
+
+ return self->timezone;
}
/**
@@ -1564,25 +1214,19 @@ gcal_weather_service_set_time_zone (GcalWeatherService *self,
{
g_return_if_fail (self != NULL);
- if (self->time_zone != value)
- {
- if (self->time_zone != NULL)
- {
- g_time_zone_unref (self->time_zone);
- self->time_zone = NULL;
- }
+ if (self->timezone == value)
+ return;
- if (value != NULL)
- self->time_zone = g_time_zone_ref (value);
+ g_clear_pointer (&self->timezone, g_time_zone_unref);
+ self->timezone = value ? g_time_zone_ref (value) : NULL;
- /* make sure we provide correct weather infos */
- gweather_info_update (self->gweather_info);
+ /* make sure we provide correct weather infos */
+ gweather_info_update (self->gweather_info);
- /* make sure midnight is timed correctly: */
- schedule_midnight (self);
+ /* make sure midnight is timed correctly: */
+ schedule_midnight (self);
- g_object_notify ((GObject*) self, "time-zone");
- }
+ g_object_notify ((GObject*) self, "time-zone");
}
/**
@@ -1688,6 +1332,7 @@ gcal_weather_service_update (GcalWeatherService *self)
{
gweather_info_update (self->gweather_info);
update_timeout_interval (self);
+
if (gcal_timer_is_running (self->duration_timer))
gcal_timer_reset (self->duration_timer);
}
@@ -1707,22 +1352,24 @@ gcal_weather_service_run (GcalWeatherService *self,
{
g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self));
- g_debug ("Start weather service");
+ g_debug ("Starting weather service");
if (self->location_service_running || self->weather_service_running)
gcal_weather_service_stop (self);
- if (location == NULL)
+ if (!location)
{
/* Start location and weather service: */
self->location_service_running = TRUE;
self->weather_service_running = TRUE;
+
g_cancellable_cancel (self->location_cancellable);
g_cancellable_reset (self->location_cancellable);
+
gclue_simple_new (DESKTOP_FILE_NAME,
GCLUE_ACCURACY_LEVEL_EXACT,
self->location_cancellable,
- (GAsyncReadyCallback) on_gclue_simple_creation,
+ (GAsyncReadyCallback) on_gclue_simple_creation_cb,
g_object_ref (self));
}
else
@@ -1756,21 +1403,17 @@ gcal_weather_service_stop (GcalWeatherService *self)
self->location_service_running = FALSE;
self->weather_service_running = FALSE;
- /* Notify all listeners about unknown location: */
+ /* Notify all listeners about unknown location */
update_location (self, NULL);
- if (self->location_service == NULL)
+ if (!self->location_service)
{
/* location service is under construction. Cancel creation. */
g_cancellable_cancel (self->location_cancellable);
}
else
{
- GClueClient *client; /* unowned */
-
- client = gclue_simple_get_client (self->location_service);
-
- gclue_client_call_stop (client,
+ gclue_client_call_stop (gclue_simple_get_client (self->location_service),
NULL,
(GAsyncReadyCallback) on_gclue_client_stop,
g_steal_pointer (&self->location_service));
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]