[geary/bug/728002-webkit2: 57/140] Add Geary.TimeoutManager as a high-level interface to GLib.Timeout.



commit 271b9460ec8f7e721f45e2577ce4a582441a1c45
Author: Michael James Gratton <mike vee net>
Date:   Tue Jan 3 20:16:06 2017 +1100

    Add Geary.TimeoutManager as a high-level interface to GLib.Timeout.

 src/CMakeLists.txt                         |    1 +
 src/engine/util/util-timeout-manager.vala  |  114 ++++++++++++++++++++++++++++
 test/CMakeLists.txt                        |    1 +
 test/engine/util-timeout-manager-test.vala |   64 ++++++++++++++++
 test/main.vala                             |    1 +
 5 files changed, 181 insertions(+), 0 deletions(-)
---
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index dfab7bf..a91beda 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -307,6 +307,7 @@ engine/util/util-stream.vala
 engine/util/util-string.vala
 engine/util/util-synchronization.vala
 engine/util/util-time.vala
+engine/util/util-timeout-manager.vala
 engine/util/util-trillian.vala
 
 ${CMAKE_BINARY_DIR}/geary-version.vala
diff --git a/src/engine/util/util-timeout-manager.vala b/src/engine/util/util-timeout-manager.vala
new file mode 100644
index 0000000..92adfdc
--- /dev/null
+++ b/src/engine/util/util-timeout-manager.vala
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2017 Michael Gratton <mike vee net>
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later). See the COPYING file in this distribution.
+ */
+
+/**
+ * Executes a function after a certain period of time has elapsed.
+ *
+ * This class is a convenience API for the GLib main loop and source
+ * infrastructure, automatically performing cleanup when destroyed.
+ *
+ * Note this class is not thread safe and should only be invoked from
+ * the main loop.
+ */
+public class Geary.TimeoutManager : BaseObject {
+
+
+    /** Specifies the priority the timeout should be given. */
+    public enum Priority {
+        HIGH = GLib.Priority.HIGH,
+        DEFAULT = GLib.Priority.DEFAULT,
+        HIGH_IDLE = GLib.Priority.HIGH_IDLE,
+        DEFAULT_IDLE = GLib.Priority.DEFAULT_IDLE,
+        LOW = GLib.Priority.LOW;
+    }
+
+    /** Specifies if the timeout should fire once or continuously. */
+    public enum Repeat { ONCE, FOREVER; }
+
+    /** The timeout callback function prototype. */
+    public delegate void TimeoutFunc(TimeoutManager manager);
+
+
+    /** Determines if {@link interval} represent seconds. */
+    public bool use_seconds;
+
+    /** The interval after which the timeout is fired, in seconds or milliseconds */
+    public uint interval;
+
+    /** Determines if this timeout will continue to fire after the first time. */
+    public Repeat repetition = Repeat.ONCE;
+
+    /** Determines the priority this timeout will receive on the main loop. */
+    public Priority priority = Priority.DEFAULT;
+
+    /** Determines if the timeout is waiting to fire or not. */
+    public bool is_running {
+        get { return this.source_id >= 0; }
+    }
+
+    private TimeoutFunc callback;
+    private int source_id = -1;
+
+
+    /**
+     * Constructs a new timeout with an interval in seconds.
+     *
+     * The timeout will be by default not running, and hence needs to be
+     * started by a call to {@link start}.
+     */
+    public TimeoutManager.seconds(uint interval, TimeoutFunc callback) {
+        this.use_seconds = true;
+        this.interval = interval;
+        this.callback = callback;
+    }
+
+    ~TimeoutManager() {
+        reset();
+    }
+
+    /**
+     * Schedules the timeout to fire after the given interval.
+     *
+     * If the timeout is already running, it will first be reset.
+     */
+    public void start() {
+        reset();
+        this.source_id = (int) (
+            (this.use_seconds)
+            ? GLib.Timeout.add_seconds(this.interval, on_trigger, this.priority)
+            : GLib.Timeout.add(this.interval, on_trigger, this.priority)
+        );
+    }
+
+    /**
+     * Prevents the timeout from firing.
+     *
+     * After a call to this timeout will not fire again, regardless of
+     * the specified repetition for the timeout.
+     *
+     * @return `true` if the timeout was already running, else `false`
+     */
+    public bool reset() {
+        bool is_running = this.is_running;
+        if (is_running) {
+            Source.remove(this.source_id);
+            this.source_id = -1;
+        }
+        return is_running;
+    }
+
+    private bool on_trigger() {
+        callback(this);
+        bool ret = Source.CONTINUE;
+        if (this.repetition == Repeat.ONCE) {
+            this.source_id = -1;
+            ret = Source.REMOVE;
+        }
+        return ret;
+    }
+
+}
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 87a977e..92af56e 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -11,6 +11,7 @@ set(TEST_SRC
   engine/rfc822-message-data-test.vala
   engine/rfc822-utils-test.vala
   engine/util-html-test.vala
+  engine/util-timeout-manager-test.vala
 
   client/application/geary-configuration-test.vala
   client/components/client-web-view-test-case.vala
diff --git a/test/engine/util-timeout-manager-test.vala b/test/engine/util-timeout-manager-test.vala
new file mode 100644
index 0000000..99851d6
--- /dev/null
+++ b/test/engine/util-timeout-manager-test.vala
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2016 Michael Gratton <mike vee net>
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later). See the COPYING file in this distribution.
+ */
+
+class Geary.TimeoutManagerTest : Gee.TestCase {
+
+    // add_seconds seems to vary wildly, so needs a large epsilon
+    private const double SECONDS_EPSILON = 1.8;
+
+    public TimeoutManagerTest() {
+        base("Geary.TimeoutManagerTest");
+        add_test("start_reset", start_reset);
+        add_test("test_seconds", test_seconds);
+        add_test("test_repeat_forever", test_repeat_forever);
+    }
+
+    public void start_reset() {
+        TimeoutManager test = new TimeoutManager.seconds(1, () => { /* noop */ });
+        assert(!test.is_running);
+        test.start();
+        assert(test.is_running);
+        test.reset();
+        assert(!test.is_running);
+    }
+
+    public void test_seconds() {
+        Timer timer = new Timer();
+
+        TimeoutManager test = new TimeoutManager.seconds(1, () => { timer.stop(); });
+        test.start();
+
+        timer.start();
+        while (test.is_running && timer.elapsed() < SECONDS_EPSILON) {
+            Gtk.main_iteration();
+        }
+
+        assert_epsilon(timer.elapsed(), 1.0, SECONDS_EPSILON);
+    }
+
+    public void test_repeat_forever() {
+        Timer timer = new Timer();
+        int count = 0;
+
+        TimeoutManager test = new TimeoutManager.seconds(1, () => { count++; });
+        test.repetition = TimeoutManager.Repeat.FOREVER;
+        test.start();
+
+        timer.start();
+        while (count < 2 && timer.elapsed() < SECONDS_EPSILON * 2) {
+            Gtk.main_iteration();
+        }
+        timer.stop();
+
+        assert_epsilon(timer.elapsed(), 2.0, SECONDS_EPSILON * 2);
+    }
+
+    private inline void assert_epsilon(double actual, double expected, double epsilon) {
+        assert(actual + epsilon >= expected && actual - epsilon <= expected);
+    }
+
+}
diff --git a/test/main.vala b/test/main.vala
index c82e569..7fb7842 100644
--- a/test/main.vala
+++ b/test/main.vala
@@ -42,6 +42,7 @@ int main(string[] args) {
     engine.add_suite(new Geary.RFC822.MessageTest().get_suite());
     engine.add_suite(new Geary.RFC822.MessageDataTest().get_suite());
     engine.add_suite(new Geary.RFC822.Utils.Test().get_suite());
+    engine.add_suite(new Geary.TimeoutManagerTest().get_suite());
 
     TestSuite client = new TestSuite("client");
 


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