From 1feb752996b404965a2f58b29a569a273d4374fa Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Sat, 13 Aug 2011 08:55:20 -0400 Subject: [PATCH] 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(+) create mode 100644 glib/tests/glib-clock.c 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 g_date_time_format + + +g_date_time_source_new
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. + * + * This function currently does not detect time zone + * changes. On Linux, your program should also monitor the + * /etc/timezone file using + * #GFileMonitor. +* + * Clock exampleFIXME: MISSING XINCLUDE CONTENT + * + * 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 +#include 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 + +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 (); } -- 2.7.4