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
This commit is contained in:
Colin Walters 2011-08-13 08:55:20 -04:00
parent 1dfe332e45
commit 1feb752996
8 changed files with 276 additions and 0 deletions

View File

@ -1551,6 +1551,9 @@ g_date_time_to_utc
<SUBSECTION>
g_date_time_format
<SUBSECTION>
g_date_time_source_new
</SECTION>
<SECTION>

View File

@ -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: */

View File

@ -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__ */

View File

@ -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

View File

@ -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,

View File

@ -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

38
glib/tests/glib-clock.c Normal file
View File

@ -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;
}

View File

@ -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 ();
}