[glib] gdatetime: Add g_date_time_source_new()



commit 1feb752996b404965a2f58b29a569a273d4374fa
Author: Colin Walters <walters verbum org>
Date:   Sat Aug 13 08:55:20 2011 -0400

    gdatetime: Add g_date_time_source_new()
    
    Several different codebases in GNOME want to implement wall clocks.
    While we could pretty easily share a private library, it's not a
    substantial amount of code, and GLib already has a lot of the
    necessary system-specific detection and handling infrastructure.
    
    Note this initial implementation just wakes up once a second in the
    cancel_on_set case; we'll add the Linux-specific handling in a
    subsequent commit.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=655129

 docs/reference/glib/glib-sections.txt |    3 +
 glib/gdatetime.c                      |  156 +++++++++++++++++++++++++++++++++
 glib/gdatetime.h                      |    3 +
 glib/glib.symbols                     |    1 +
 glib/gmain.h                          |    1 +
 glib/tests/Makefile.am                |    4 +
 glib/tests/glib-clock.c               |   38 ++++++++
 glib/tests/timeout.c                  |   70 +++++++++++++++
 8 files changed, 276 insertions(+), 0 deletions(-)
---
diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt
index 6ea7401..e1db395 100644
--- a/docs/reference/glib/glib-sections.txt
+++ b/docs/reference/glib/glib-sections.txt
@@ -1551,6 +1551,9 @@ g_date_time_to_utc
 
 <SUBSECTION>
 g_date_time_format
+
+<SUBSECTION>
+g_date_time_source_new
 </SECTION>
 
 <SECTION>
diff --git a/glib/gdatetime.c b/glib/gdatetime.c
index 5a486b2..d0a8f73 100644
--- a/glib/gdatetime.c
+++ b/glib/gdatetime.c
@@ -2587,6 +2587,162 @@ bad_format:
   return NULL;
 }
 
+typedef struct _GDateTimeSource GDateTimeSource;
+struct _GDateTimeSource
+{
+  GSource     source;
+  
+  gint64      real_expiration;
+  gint64      wakeup_expiration;
+
+  gboolean    cancel_on_set;
+};
+
+static inline void
+g_datetime_source_reschedule (GDateTimeSource *datetime_source,
+			     gint64                from_monotonic)
+{
+  datetime_source->wakeup_expiration = from_monotonic + G_TIME_SPAN_SECOND;
+}
+
+static gboolean
+g_datetime_source_is_expired (GDateTimeSource *datetime_source)
+{
+  gint64 real_now;
+
+  real_now = g_get_real_time ();
+  
+  if (datetime_source->real_expiration <= real_now)
+    return TRUE;
+
+  /* We can't really detect without system support when things change;
+   * so just trigger every second.
+   */
+  if (datetime_source->cancel_on_set)
+    return TRUE;
+
+  return FALSE;
+}
+
+/* In prepare, we're just checking the monotonic time against
+ * our projected wakeup.
+ */
+static gboolean
+g_datetime_source_prepare (GSource *source,
+			  gint    *timeout)
+{
+  GDateTimeSource *datetime_source = (GDateTimeSource*)source;
+  gint64 monotonic_now = g_source_get_time (source);
+
+  if (monotonic_now < datetime_source->wakeup_expiration)
+    {
+      /* Round up to ensure that we don't try again too early */
+      *timeout = (datetime_source->wakeup_expiration - monotonic_now + 999) / 1000;
+      return FALSE;
+    }
+
+  *timeout = 0;
+  return g_datetime_source_is_expired (datetime_source);
+}
+
+/* In check, we're looking at the wall clock.
+ */
+static gboolean 
+g_datetime_source_check (GSource  *source)
+{
+  GDateTimeSource *datetime_source = (GDateTimeSource*)source;
+
+  if (g_datetime_source_is_expired (datetime_source))
+    return TRUE;
+
+  g_datetime_source_reschedule (datetime_source, g_source_get_time (source));
+  
+  return FALSE;
+}
+
+static gboolean
+g_datetime_source_dispatch (GSource    *source, 
+			   GSourceFunc callback,
+			   gpointer    user_data)
+{
+  if (!callback)
+    {
+      g_warning ("Timeout source dispatched without callback\n"
+                 "You must call g_source_set_callback().");
+      return FALSE;
+    }
+  
+  (callback) (user_data);
+
+  /* Always false as this source is documented to run once */
+  return FALSE;
+}
+
+static void
+g_datetime_source_finalize (GSource *source)
+{
+}
+
+static GSourceFuncs g_datetime_source_funcs = {
+  g_datetime_source_prepare,
+  g_datetime_source_check,
+  g_datetime_source_dispatch,
+  g_datetime_source_finalize
+};
+
+/**
+ * g_date_time_source_new:
+ * @datetime: Time to await
+ * @cancel_on_set: Also invoke callback if the system clock changes discontiguously
+ *
+ * This function is designed for programs that want to schedule an
+ * event based on real (wall clock) time, as returned by
+ * g_get_real_time().  For example, HOUR:MINUTE wall-clock displays
+ * and calendaring software.  The callback will be invoked when the
+ * specified wall clock time @datetime is reached.
+ *
+ * Compare versus g_timeout_source_new() which is defined to use
+ * monotonic time as returned by g_get_monotonic_time().
+ *
+ * If @cancel_on_set is given, the callback will also be invoked at
+ * most a second after the system clock is changed.  This includes
+ * being set backwards or forwards, and system
+ * resume from suspend.  Not all operating systems allow detecting all
+ * relevant events efficiently - this function may cause the process
+ * to wake up once a second in those cases.
+ *
+ * A wall clock display should use @cancel_on_set; a calendaring
+ * program shouldn't need to.
+ *
+ * Note that the return value from the associated callback will be
+ * ignored; this is a one time watch.
+ *
+ * <note><para>This function currently does not detect time zone
+ * changes.  On Linux, your program should also monitor the
+ * <literal>/etc/timezone</literal> file using
+ * #GFileMonitor.</para></note>
+*
+ * <example id="gdatetime-example-watch"><title>Clock example</title><programlisting><xi:include xmlns:xi="http://www.w3.org/2001/XInclude"; parse="text" href="../../../../glib/tests/glib-clock.c"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include></programlisting></example>
+ *
+ * Return value: A newly-constructed #GSource
+ *
+ * Since: 2.30
+ **/
+GSource *
+g_date_time_source_new (GDateTime  *datetime,
+			gboolean    cancel_on_set)
+{
+  GDateTimeSource *datetime_source;
+
+  datetime_source = (GDateTimeSource*) g_source_new (&g_datetime_source_funcs, sizeof (GDateTimeSource));
+
+  datetime_source->cancel_on_set = cancel_on_set;
+  datetime_source->real_expiration = g_date_time_to_unix (datetime) * 1000000;
+  g_datetime_source_reschedule (datetime_source, g_get_monotonic_time ());
+
+  return (GSource*)datetime_source;
+}
+
 
 /* Epilogue {{{1 */
 /* vim:set foldmethod=marker: */
diff --git a/glib/gdatetime.h b/glib/gdatetime.h
index b76df89..b25db9f 100644
--- a/glib/gdatetime.h
+++ b/glib/gdatetime.h
@@ -31,6 +31,7 @@
 #define __G_DATE_TIME_H__
 
 #include <glib/gtimezone.h>
+#include <glib/gmain.h>
 
 G_BEGIN_DECLS
 
@@ -212,6 +213,8 @@ GDateTime *             g_date_time_to_utc                              (GDateTi
 gchar *                 g_date_time_format                              (GDateTime      *datetime,
                                                                          const gchar    *format) G_GNUC_MALLOC;
 
+GSource *               g_date_time_source_new                          (GDateTime      *datetime,
+									 gboolean        cancel_on_set);
 G_END_DECLS
 
 #endif /* __G_DATE_TIME_H__ */
diff --git a/glib/glib.symbols b/glib/glib.symbols
index 20b3b5a..3af1cfa 100644
--- a/glib/glib.symbols
+++ b/glib/glib.symbols
@@ -293,6 +293,7 @@ g_date_time_new_now_local
 g_date_time_new_now_utc
 g_date_time_new_utc
 g_date_time_ref
+g_date_time_source_new
 g_date_time_to_local
 g_date_time_to_timeval
 g_date_time_to_timezone
diff --git a/glib/gmain.h b/glib/gmain.h
index ed130d0..d31b45f 100644
--- a/glib/gmain.h
+++ b/glib/gmain.h
@@ -533,6 +533,7 @@ guint    g_timeout_add_seconds_full (gint            priority,
 guint    g_timeout_add_seconds      (guint           interval,
                                      GSourceFunc     function,
                                      gpointer        data);
+
 guint    g_child_watch_add_full     (gint            priority,
                                      GPid            pid,
                                      GChildWatchFunc function,
diff --git a/glib/tests/Makefile.am b/glib/tests/Makefile.am
index 3f4bd14..e22d552 100644
--- a/glib/tests/Makefile.am
+++ b/glib/tests/Makefile.am
@@ -206,6 +206,10 @@ check-am: gtester-xmllint-check
 
 endif
 
+noinst_PROGRAMS += glib-clock
+glib_clock_CFLAGS = $(INCLUDES)
+glib_clock_LDADD = $(progs_ldadd)
+
 CLEANFILES = \
 	tmpsample.xml
 
diff --git a/glib/tests/glib-clock.c b/glib/tests/glib-clock.c
new file mode 100644
index 0000000..99fa053
--- /dev/null
+++ b/glib/tests/glib-clock.c
@@ -0,0 +1,38 @@
+#include <glib.h>
+
+static gboolean
+redisplay_clock (gpointer data)
+{
+  GSource *source;
+  GDateTime *now, *expiry;
+
+  now = g_date_time_new_now_local ();
+  g_print ("%02d:%02d\n",
+	   g_date_time_get_hour (now),
+	   g_date_time_get_minute (now));
+
+  expiry = g_date_time_add_seconds (now, 60 - g_date_time_get_second (now));
+  source = g_date_time_source_new (expiry, TRUE);
+  g_source_set_callback (source, redisplay_clock, NULL, NULL);
+  g_source_attach (source, NULL);
+  g_source_unref (source);
+
+  g_date_time_unref (expiry);
+  g_date_time_unref (now);
+
+  return FALSE;
+}
+
+int
+main (void)
+{
+  GMainLoop *loop;
+  
+  loop = g_main_loop_new (NULL, FALSE);
+
+  redisplay_clock (NULL);
+
+  g_main_loop_run (loop);
+  
+  return 0;
+}
diff --git a/glib/tests/timeout.c b/glib/tests/timeout.c
index bae2b4f..252f83e 100644
--- a/glib/tests/timeout.c
+++ b/glib/tests/timeout.c
@@ -88,6 +88,74 @@ test_rounding (void)
   g_main_loop_run (loop);
 }
 
+static gboolean
+on_test_date_time_watch_timeout (gpointer user_data)
+{
+  *((gboolean*)user_data) = TRUE;
+
+  g_main_loop_quit (loop);
+
+  return TRUE;
+}
+
+/* This test isn't very useful; it's hard to actually test much of the
+ * functionality of g_date_time_source_new() without a means to set
+ * the system clock (which typically requires system-specific
+ * interfaces as well as elevated privileges).
+ *
+ * But at least we're running the code and ensuring the timer fires.
+ */
+static void
+test_date_time_create_watch (gboolean cancel_on_set)
+{
+  GSource *source;
+  GDateTime *now, *expiry;
+  gboolean fired = FALSE;
+  gint64 orig_time_monotonic, end_time_monotonic;
+  gint64 elapsed_monotonic_seconds;
+  
+  loop = g_main_loop_new (NULL, FALSE);
+
+  orig_time_monotonic = g_get_monotonic_time ();
+
+  now = g_date_time_new_now_local ();
+  expiry = g_date_time_add_seconds (now, 7);
+  g_date_time_unref (now);
+
+  source = g_date_time_source_new (expiry, cancel_on_set);
+  g_source_set_callback (source, on_test_date_time_watch_timeout, &fired, NULL);
+  g_source_attach (source, NULL);
+  g_source_unref (source);
+
+  g_main_loop_run (loop);
+
+  g_assert (fired);
+  if (!cancel_on_set)
+    {
+      end_time_monotonic = g_get_monotonic_time ();
+      
+      elapsed_monotonic_seconds = 1 + (end_time_monotonic - orig_time_monotonic) / G_TIME_SPAN_SECOND;
+      
+      g_assert_cmpint (elapsed_monotonic_seconds, >=, 7);
+    }
+  else
+    {
+      /* We can't really assert much about the cancel_on_set case */
+    }
+}
+
+static void
+test_date_time_create_watch_nocancel_on_set (void)
+{
+  test_date_time_create_watch (FALSE);
+}
+
+static void
+test_date_time_create_watch_cancel_on_set (void)
+{
+  test_date_time_create_watch (TRUE);
+}
+
 int
 main (int argc, char *argv[])
 {
@@ -95,6 +163,8 @@ main (int argc, char *argv[])
 
   g_test_add_func ("/timeout/seconds", test_seconds);
   g_test_add_func ("/timeout/rounding", test_rounding);
+  g_test_add_func ("/timeout/datetime_watch_nocancel_on_set", test_date_time_create_watch_nocancel_on_set);
+  g_test_add_func ("/timeout/datetime_watch_cancel_on_set", test_date_time_create_watch_cancel_on_set);
 
   return g_test_run ();
 }



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