[gnome-calendar/wip/flb/weather-forecast: 116/135] weather: Introduce a proper timer for forecasts.



commit 83d6eaef9a47a1f3dcc6d48e89c4799de49a0806
Author: Florian Brosch <flo brosch gmail com>
Date:   Mon Oct 23 22:39:27 2017 +0200

    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(-)
---
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 <flo brosch gmail com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#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 <flo brosch gmail com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GCAL_TIMER_H__
+#define __GCAL_TIMER_H__
+
+#include <glib.h>
+
+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 <math.h>
 
 #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);
-
-  gcal_weather_service_update (self);
+  g_return_if_fail (timer != NULL);
+  g_return_if_fail (GCAL_IS_WEATHER_SERVICE (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(


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]