From 83d6eaef9a47a1f3dcc6d48e89c4799de49a0806 Mon Sep 17 00:00:00 2001 From: Florian Brosch Date: Mon, 23 Oct 2017 22:39:27 +0200 Subject: [PATCH] weather: Introduce a proper timer for forecasts. --- src/gcal-timer.c | 332 +++++++++++++++++++++++++++++++++++++ src/gcal-timer.h | 52 ++++++ src/gcal-weather-service.c | 113 +++++-------- src/meson.build | 1 + 4 files changed, 425 insertions(+), 73 deletions(-) create mode 100644 src/gcal-timer.c create mode 100644 src/gcal-timer.h diff --git a/src/gcal-timer.c b/src/gcal-timer.c new file mode 100644 index 00000000..7baa0245 --- /dev/null +++ b/src/gcal-timer.c @@ -0,0 +1,332 @@ +/* + * gcal-timer.c + * + * Copyright (C) 2017 - Florian Brosch + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gcal-timer.h" + + +/** + * The #GcalTimer object structure. + * + * Do not use this one as #GSource. + * Only use it in combination with its + * methods. + */ +typedef struct _GcalTimer +{ + GSource parent; + gint64 last_event; + gint64 default_duration; +} GcalTimer; + + +/* CallbackWrapper: + * @timer: The timer the callback applies to. + * @callback: Original callback. + * @destroy_notify: Original user-data destroy function. + * @data: Original user data. + * + * Internal struct used to hide GSource internals + * from API users. + */ +typedef struct +{ + GcalTimer *timer; /* unowned */ + GCalTimerFunc callback; + GDestroyNotify destroy_notify; + gpointer data; +} CallbackWrapper; + + +static void timer_func_destroy_notify_wrapper (CallbackWrapper *wrapper); + +static gboolean timer_func_wrapper (CallbackWrapper *wrapper); + +static gboolean timer_source_dispatch (GcalTimer *self, + GSourceFunc callback, + CallbackWrapper *user_data); + +static void schedule_next (GcalTimer *self); + +static void timer_source_finalize (GcalTimer *self); + + + + +/*< private >*/ +static void +timer_func_destroy_notify_wrapper (CallbackWrapper *wrapper) +{ + g_return_if_fail (wrapper != NULL); + + if (wrapper->destroy_notify != NULL && wrapper->data != NULL) + wrapper->destroy_notify (wrapper->data); + + g_free (wrapper); +} + + + +static gboolean +timer_func_wrapper (CallbackWrapper *wrapper) +{ + g_return_val_if_fail (wrapper != NULL, G_SOURCE_REMOVE); + + if (wrapper->callback != NULL) + wrapper->callback (wrapper->timer, wrapper->data); + + return G_SOURCE_CONTINUE; +} + + + +static gboolean +timer_source_dispatch (GcalTimer *self, + GSourceFunc user_callback, + CallbackWrapper *user_data) +{ + gboolean result = G_SOURCE_CONTINUE; + + g_return_val_if_fail (self != NULL, G_SOURCE_REMOVE); + + if (user_callback != NULL) + result = user_callback (user_data); + + self->last_event = g_source_get_time ((GSource*) self); + schedule_next (self); + + return result; +} + + + +static void +schedule_next (GcalTimer *self) +{ + gint64 now; + gint64 next; + + g_return_if_fail (self != NULL); + g_return_if_fail (self->last_event >= 0); + + now = g_source_get_time ((GSource*) self); + next = self->last_event + self->default_duration*G_GUINT64_CONSTANT(1000000); + + if (next > now) + g_source_set_ready_time ((GSource*) self, next); + else + g_source_set_ready_time ((GSource*) self, 0); +} + + + +static void +timer_source_finalize (GcalTimer *self) +{ +} + + + +/*< public >*/ +/** + * gcal_timer_new: + * @default_duration: The duration (>0) between two events in seconds. + * + * Creates a new #GcalTimer object. + * + * While #GcalTimers are proper #GSources, they are not intended + * to be used directly. + * + * Returns: (transfer full): A new timer object. + */ +GcalTimer* +gcal_timer_new (gint64 default_duration) +{ + GMainContext *cntxt; /* unowned */ + GcalTimer *self; /* owned */ + + g_return_val_if_fail (default_duration > 0, NULL); + + static GSourceFuncs source_funcs = + { NULL, /* prepare */ + NULL, /* check */ + (gboolean (*) (GSource*, GSourceFunc, gpointer)) timer_source_dispatch, + (void (*) (GSource*)) timer_source_finalize + }; + + self = (GcalTimer*) g_source_new (&source_funcs, sizeof (GcalTimer)); + self->default_duration = default_duration; + self->last_event = -1; + + cntxt = g_main_context_default (); + g_source_set_ready_time ((GSource*) self, -1); + g_source_attach ((GSource*) self, cntxt); + + return g_steal_pointer (&self); +} + + + +/** + * gcal_timer_set_start: + * @self: The #GcalTimer. + * + * Starts this timer. + */ +void +gcal_timer_start (GcalTimer *self) +{ + g_return_if_fail (self != NULL); + g_return_if_fail (!gcal_timer_is_running (self)); + + self->last_event = g_source_get_time ((GSource*) self); + schedule_next (self); +} + + + +/** + * gcal_timer_set_reset: + * @self: The #GcalTimer. + * + * Re-schedules next event. + */ +void +gcal_timer_reset (GcalTimer *self) +{ + g_return_if_fail (self != NULL); + g_return_if_fail (gcal_timer_is_running (self)); + + if (g_source_get_ready_time ((GSource*) self) >= 0) + { + self->last_event = g_source_get_time ((GSource*) self); + schedule_next (self); + } +} + + + +/** + * gcal_timer_set_stop: + * @self: The #GcalTimer. + * + * Stops a previously started timer. + */ +void +gcal_timer_stop (GcalTimer *self) +{ + g_return_if_fail (self != NULL); + g_return_if_fail (gcal_timer_is_running (self)); + + g_source_set_ready_time ((GSource*) self, -1); +} + + + +/** + * gcal_timer_is_running + * @self: The #GcalTimer. + * + * Whether this timer is running. + */ +gboolean +gcal_timer_is_running (GcalTimer *self) +{ + g_return_val_if_fail (self != NULL, FALSE); + + return g_source_get_ready_time ((GSource*) self) >= 0; +} + + + +/** + * gcal_timer_set_duration: + * @self: The #GcalTimer. + * @duration: The new duration + * + * Changes the duration between two events. + */ +void +gcal_timer_set_duration (GcalTimer *self, + gint64 duration) +{ + gint64 now; + + g_return_if_fail (self != NULL); + g_return_if_fail (duration > 0); + + now = g_source_get_time ((GSource*) self); + + self->default_duration = duration; + if (!gcal_timer_is_running (self)) + /* nothing to do (not running) */; + else if (self->last_event + duration < now) + g_source_set_ready_time ((GSource*) self, 0); + else + schedule_next (self); +} + + + +/** + * gcal_timer_set_callback: + * @self: The #GcalTimer. + * @func: The event handler callback function. + * @data: (nullable): User data passed to @func. + * @notify: (nullable): free or unref function for @data. + * + * Sets the callback to be triggered after each duration + * or given day-times. + */ +void +gcal_timer_set_callback (GcalTimer *self, + GCalTimerFunc func, + gpointer data, + GDestroyNotify notify) +{ + CallbackWrapper *wrapper; /* owned */ + + g_return_if_fail (self != NULL); + g_return_if_fail (func != NULL); + + wrapper = g_new0 (CallbackWrapper, 1); + wrapper->timer = self; + wrapper->callback = func; + wrapper->destroy_notify = notify; + wrapper->data = data; + + g_source_set_callback ((GSource*) self, + (GSourceFunc) timer_func_wrapper, + g_steal_pointer (&wrapper), + (GDestroyNotify) timer_func_destroy_notify_wrapper); +} + + +/** + * gcal_timer_free: + * @self: The #GcalTimer. + * + * Frees #GcalTimer. + */ +void +gcal_timer_free (GcalTimer *self) +{ + g_return_if_fail (self != NULL); + g_source_destroy ((GSource *) self); + g_source_unref ((GSource *) self); +} diff --git a/src/gcal-timer.h b/src/gcal-timer.h new file mode 100644 index 00000000..3f7666ba --- /dev/null +++ b/src/gcal-timer.h @@ -0,0 +1,52 @@ +/* + * gcal-timer.h + * + * Copyright (C) 2017 - Florian Brosch + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GCAL_TIMER_H__ +#define __GCAL_TIMER_H__ + +#include + +struct _GcalTimer; +typedef struct _GcalTimer GcalTimer; + +typedef void (*GCalTimerFunc) (GcalTimer *self, gpointer data); + + +GcalTimer* gcal_timer_new (gint64 default_duration); + +void gcal_timer_start (GcalTimer *self); + +void gcal_timer_reset (GcalTimer *self); + +void gcal_timer_stop (GcalTimer *self); + +gboolean gcal_timer_is_running (GcalTimer *self); + +void gcal_timer_set_duration (GcalTimer *self, + gint64 duration); + +void gcal_timer_set_callback (GcalTimer *self, + GCalTimerFunc func, + gpointer data, + GDestroyNotify notify); + +void gcal_timer_free (GcalTimer *self); + + +#endif /* __GCAL_TIMER_H__ */ diff --git a/src/gcal-weather-service.c b/src/gcal-weather-service.c index 2a6cd6ac..49c7adc6 100644 --- a/src/gcal-weather-service.c +++ b/src/gcal-weather-service.c @@ -24,6 +24,7 @@ #include #include "gcal-weather-service.h" +#include "gcal-timer.h" G_BEGIN_DECLS @@ -44,7 +45,6 @@ typedef struct * * @time_zone: The current time zone * @check_interval: Amount of seconds to wait before re-fetching weather infos. - * @timeout_id: Time-out event ID or %0. Timer is used to periodically update weather infos. * @location_service: Used to monitor location changes. * Initialized by gcal_weather_service_run(), * freed by gcal_weather_service_stop(). @@ -77,7 +77,7 @@ struct _GcalWeatherService /* timer: */ guint check_interval; - guint timeout_id; + GcalTimer *timer; /* locations: */ GClueSimple *location_service; /* owned, nullable */ @@ -117,6 +117,10 @@ static guint gcal_weather_service_signals[SIG_NUM] = { 0 }; G_DEFINE_TYPE (GcalWeatherService, gcal_weather_service, G_TYPE_OBJECT) +/* Timer Helpers: */ +static void update_timeout_interval (GcalWeatherService *self); + + /* Internal location API and callbacks: */ static void gcal_weather_service_update_location (GcalWeatherService *self, GWeatherLocation *location); @@ -165,11 +169,8 @@ static gchar* get_normalized_icon_name (GWeatherInfo static gint get_icon_name_sortkey (const gchar *icon_name, gboolean *supports_night_icon); -static void gcal_weather_service_timer_stop (GcalWeatherService *self); - -static void gcal_weather_service_timer_start (GcalWeatherService *self); - -static gboolean on_timer_timeout (GcalWeatherService *self); +static void on_timer_timeout (GcalTimer *timer, + GcalWeatherService *self); static gboolean get_time_day_start (GcalWeatherService *self, GDate *ret_date, @@ -202,6 +203,9 @@ gcal_weather_service_finalize (GObject *object) self = (GcalWeatherService *) object; + gcal_timer_free (self->timer); + self->timer = NULL; + if (self->time_zone != NULL) { g_time_zone_unref (self->time_zone); @@ -371,9 +375,10 @@ gcal_weather_service_class_init (GcalWeatherServiceClass *klass) static void gcal_weather_service_init (GcalWeatherService *self) { + self->timer = gcal_timer_new (600); + gcal_timer_set_callback (self->timer, (GCalTimerFunc) on_timer_timeout, self, NULL); self->time_zone = NULL; self->check_interval = 0; - self->timeout_id = 0; self->location_cancellable = g_cancellable_new (); self->location_service_running = FALSE; self->location_service = NULL; @@ -893,7 +898,7 @@ gcal_weather_service_update_location (GcalWeatherService *self, if (self->gweather_info != NULL) { - gcal_weather_service_timer_stop (self); + gcal_timer_stop (self->timer); g_clear_object (&self->gweather_info); } @@ -926,7 +931,8 @@ gcal_weather_service_update_location (GcalWeatherService *self, gcal_weather_service_update_weather (self, NULL, FALSE); gweather_info_update (self->gweather_info); - gcal_weather_service_timer_start (self); + update_timeout_interval (self); + gcal_timer_start (self->timer); } } @@ -1225,6 +1231,15 @@ on_gweather_update (GWeatherInfo *info, +static void +update_timeout_interval (GcalWeatherService *self) +{ + g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self)); + gcal_timer_set_duration (self->timer, self->check_interval); +} + + + /* gcal_weather_service_set_max_days: * @self: The #GcalWeatherService instance. * @days: Number of days. @@ -1238,79 +1253,28 @@ gcal_weather_service_set_check_interval (GcalWeatherService *self, g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self)); self->check_interval = interval; + update_timeout_interval (self); g_object_notify ((GObject*) self, "check-interval"); } -/* gcal_weather_service_timer_stop: - * @self: The #GcalWeatherService instance. - * - * Stops the internal timer. Does nothing if - * timer is not running. - * - * This timer is used to update weather information - * every :check-interval seconds - */ -static void -gcal_weather_service_timer_stop (GcalWeatherService *self) -{ - g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self)); - - if (self->timeout_id > 0) - { - g_source_remove (self->timeout_id); - self->timeout_id = 0; - } -} - - - -/* gcal_weather_service_timer_start - * @self: The #GcalWeatherService instance. - * - * Starts the internal timer. Does nothing if - * timer is already running. - * - * Timer is used to update weather information - * every :check-interval seconds. - */ -static void -gcal_weather_service_timer_start (GcalWeatherService *self) -{ - g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self)); - - if (self->check_interval == 0) - /* timer is disabled */ - return; - - if (self->timeout_id > 0) - /* timer is already running */ - return; - - self->timeout_id = g_timeout_add_seconds (self->check_interval, - (GSourceFunc) on_timer_timeout, - self); -} - - /* on_timer_timeout * @self: A #GcalWeatherService. * * Handles scheduled weather report updates. - * - * Returns: %G_SOURCE_CONTINUE */ -static gboolean -on_timer_timeout (GcalWeatherService *self) +static void +on_timer_timeout (GcalTimer *timer, + GcalWeatherService *self) { - g_return_val_if_fail (GCAL_IS_WEATHER_SERVICE (self), G_SOURCE_REMOVE); + g_return_if_fail (timer != NULL); + g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self)); - gcal_weather_service_update (self); - - return G_SOURCE_CONTINUE; + if (self->gweather_info) + gweather_info_update (self->gweather_info); } @@ -1383,8 +1347,8 @@ gcal_weather_service_run (GcalWeatherService *self, self->location_service_running = FALSE; self->weather_service_running = TRUE; + /*_update_location starts timer if necessary */ gcal_weather_service_update_location (self, location); - gcal_weather_service_timer_start (self); } } @@ -1410,8 +1374,6 @@ gcal_weather_service_stop (GcalWeatherService *self) self->location_service_running = FALSE; self->weather_service_running = FALSE; - gcal_weather_service_timer_stop (self); - /* Notify all listeners about unknown location: */ gcal_weather_service_update_location (self, NULL); @@ -1580,5 +1542,10 @@ gcal_weather_service_update (GcalWeatherService *self) g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self)); if (self->gweather_info != NULL) - gweather_info_update (self->gweather_info); + { + gweather_info_update (self->gweather_info); + update_timeout_interval (self); + if (gcal_timer_is_running (self->timer)) + gcal_timer_reset (self->timer); + } } diff --git a/src/meson.build b/src/meson.build index 24b1da43..1ac51b38 100644 --- a/src/meson.build +++ b/src/meson.build @@ -52,6 +52,7 @@ sources = files( 'gcal-window.c', 'gcal-weather-service.c', 'gcal-weather-info.c', + 'gcal-timer.c' ) gnome.mkenums(