[gnome-clocks/systemd-timers: 5/5] background: Save alarms to systemd user .timer units




commit 9f9865f0a615f27a538f790ca17794a45165698a
Author: Julian Sparber <julian sparber net>
Date:   Thu Nov 12 16:42:05 2020 +0100

    background: Save alarms to systemd user .timer units

 meson.build            |   1 +
 src/alarm-face.vala    |  12 ++++
 src/alarm-item.vala    |  19 +++++
 src/config.vapi        |   2 +
 src/meson.build        |   1 +
 src/systemd-utils.vala | 187 +++++++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 222 insertions(+)
---
diff --git a/meson.build b/meson.build
index f30fd0b..3b911cb 100644
--- a/meson.build
+++ b/meson.build
@@ -75,6 +75,7 @@ conf.set_quoted('PACKAGE_VERSION', meson.project_version())
 conf.set_quoted('PROFILE', profile)
 conf.set_quoted('VERSION', meson.project_version())
 conf.set_quoted('GETTEXT_PACKAGE', meson.project_name())
+conf.set_quoted('BINDIR', join_paths(get_option('prefix'), get_option('bindir')))
 conf.set_quoted('DATADIR', join_paths(get_option('prefix'), get_option('datadir')))
 conf.set_quoted('GNOMELOCALEDIR', join_paths(get_option('prefix'), get_option('localedir')))
 if have_first_weekday
diff --git a/src/alarm-face.vala b/src/alarm-face.vala
index 2caa602..3f37440 100644
--- a/src/alarm-face.vala
+++ b/src/alarm-face.vala
@@ -104,7 +104,17 @@ public class Face : Gtk.Stack, Clocks.Clock {
     }
 
     private void save () {
+        var systemd_timer = SystemdUtils.Timer.get_default ();
         settings.set_value ("alarms", alarms.serialize ());
+        // Update systemd files
+        systemd_timer.clear ();
+        alarms.foreach ((item) => {
+          var alarm = item as Alarm.Item;
+          if (alarm != null)
+              ((!) alarm).save_to_systemd (systemd_timer);
+        });
+        // FIXME: this could cause a race condition when save () is called multible times
+        systemd_timer.commit.begin ();
     }
 
     internal void edit (Item alarm) {
@@ -115,6 +125,7 @@ public class Face : Gtk.Stack, Clocks.Clock {
                 ((SetupDialog) dialog).apply_to_alarm ();
                 save ();
             } else if (response == DELETE_ALARM) {
+                alarm.active = false;
                 alarms.delete_item (alarm);
                 save ();
             }
@@ -124,6 +135,7 @@ public class Face : Gtk.Stack, Clocks.Clock {
     }
 
     internal void delete (Item alarm) {
+        alarm.active = false;
         alarms.delete_item (alarm);
         save ();
     }
diff --git a/src/alarm-item.vala b/src/alarm-item.vala
index bed3067..0e96d13 100644
--- a/src/alarm-item.vala
+++ b/src/alarm-item.vala
@@ -132,6 +132,25 @@ private class Item : Object, ContentItem {
                 days: days);
     }
 
+    public void save_to_systemd (SystemdUtils.Timer systemd_timer) {
+        var wallclock = Utils.WallClock.get_default ();
+        var now = wallclock.date_time;
+        if (snooze_time != null) {
+            // Add timer only if the snooze time is in the future
+            if (this.active && now.compare ((!) snooze_time) <= 0) {
+                systemd_timer.add_time (((!) snooze_time).get_hour (),
+                                        ((!) snooze_time).get_minute ());
+            }
+        }
+
+        // Add the timer only if the alarm needs to go off in the future
+        if (this.active && (now.compare (time) <= 0 || recurring)) {
+            systemd_timer.add_time (time.get_hour (),
+                                    time.get_minute (),
+                                    days);
+        }
+    }
+
     private void setup_bell () {
         bell = new Utils.Bell ("alarm-clock-elapsed");
         notification = new GLib.Notification (_("Alarm"));
diff --git a/src/config.vapi b/src/config.vapi
index 1126340..ad092e9 100644
--- a/src/config.vapi
+++ b/src/config.vapi
@@ -1,7 +1,9 @@
 [CCode (cprefix = "", lower_case_cprefix = "", cheader_filename = "config.h")]
 namespace Config {
     public const string VERSION;
+    public const string BINDIR;
     public const string PROFILE;
+    public const string PACKAGE_NAME;
     public const string NAME_PREFIX;
     public const string GETTEXT_PACKAGE;
     public const string GNOMELOCALEDIR;
diff --git a/src/meson.build b/src/meson.build
index 4c51188..d44d253 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -18,6 +18,7 @@ clocks_vala_sources = files(
   'stopwatch-face.vala',
   'stopwatch-lap.vala',
   'stopwatch-laps-row.vala',
+  'systemd-utils.vala',
   'timer-face.vala',
   'timer-item.vala',
   'timer-row.vala',
diff --git a/src/systemd-utils.vala b/src/systemd-utils.vala
new file mode 100644
index 0000000..a7b4ff0
--- /dev/null
+++ b/src/systemd-utils.vala
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2020 Purism SPC
+ *
+ * Authors:
+ * Julian Sparber <julian sparber net>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ */
+
+namespace Clocks {
+namespace SystemdUtils {
+
+    [DBus (name = "org.freedesktop.systemd1.Manager")]
+    interface Systemd1 : Object {
+        public abstract void enable_unit_files (string[] files,
+                                                bool runtime = false,
+                                                bool force = false) throws GLib.Error;
+        public abstract void disable_unit_files (string[] files,
+                                                 bool runtime = false) throws GLib.Error;
+        public abstract void start_unit (string name,
+                                         string mode) throws GLib.Error;
+        public abstract void stop_unit (string name,
+                                        string mode) throws GLib.Error;
+        public abstract void reload () throws GLib.Error;
+    }
+
+    private struct Times {
+        int hour;
+        int minute;
+        Utils.Weekdays? days;
+    }
+
+    public class Timer : GLib.Object {
+        private static Timer? instance;
+        const string TIMER_FILE = Config.PACKAGE_NAME + Config.PROFILE + ".timer";
+        const string SERVICE_FILE = Config.PACKAGE_NAME + Config.PROFILE + ".service";
+        SList<Times?> times = new SList<Times?> ();
+        bool in_progess = false;
+
+        public static Timer get_default () {
+          if (instance == null) {
+            instance = new Timer ();
+          }
+          return (!) instance;
+        }
+
+        private Timer () {
+          ensure_service ();
+        }
+
+        public void clear () {
+          times = new SList<Times?> ();
+        }
+
+        public void add_time (int hour, int minute, Utils.Weekdays? days = null) {
+            times.append ( {hour, minute, days } );
+        }
+
+        public async void commit () throws Error {
+            if (in_progess)
+                return;
+            in_progess = true;
+            File file = get_unit_file ();
+            Systemd1 systemd1 = yield Bus.get_proxy (BusType.SESSION,
+                                                     "org.freedesktop.systemd1",
+                                                     "/org/freedesktop/systemd1");
+
+            if ((SList<Times?>?) times != null) {
+                debug ("Update .timer unit");
+                FileOutputStream os = file.replace (null,
+                                                    false,
+                                                    FileCreateFlags.PRIVATE |
+                                                    FileCreateFlags.REPLACE_DESTINATION);
+                var builder = new StringBuilder ();
+                builder.append ("[Unit]\n");
+                builder.append ("Description=GNOME clocks timer\n");
+                builder.append ("\n");
+                builder.append ("[Timer]\n");
+                builder.append ("Unit=gnome-clocks.service\n");
+                builder.append (build_timers ());
+                builder.append ("Persistent=true\n");
+                builder.append ("\n");
+                builder.append ("[Install]\n");
+                builder.append ("WantedBy=default.target\n");
+                os.write (builder.str.data);
+
+                debug ("Enable and start .timer unit");
+                systemd1.reload ();
+                systemd1.enable_unit_files ({TIMER_FILE});
+                systemd1.start_unit (TIMER_FILE, "replace");
+            } else {
+                if (file.query_exists ()) {
+                    debug ("Stop and disable .timer unit");
+                    systemd1.disable_unit_files ({TIMER_FILE});
+                    systemd1.stop_unit (TIMER_FILE, "replace");
+
+                    debug ("Delete .timer unit");
+                    yield file.delete_async ();
+                }
+            }
+            in_progess = false;
+        }
+
+        private string build_timers () {
+            var builder = new StringBuilder ();
+            times.foreach ( (t) => {
+                if (t != null) {
+                    var time = (!) t;
+                    builder.append_printf ("OnCalendar=%s *-*-* %d:%d\n",
+                                           get_enabled_days_string (time.days),
+                                           time.hour, time.minute);
+                }
+            });
+            return builder.str;
+        }
+
+        private static string get_enabled_days_string (Utils.Weekdays? days) {
+            if (days == null || ((!) days).empty) {
+                return "";
+            }
+            var builder = new StringBuilder ();
+            var first = true;
+            for (var day = 0; day < 7; day++) {
+                if (((!)days).get (day)) {
+                    if (!first)
+                        builder.append (",");
+                    else
+                        first = false;
+                    builder.append (get_day_string (day));
+                }
+            }
+            return builder.str;
+        }
+
+        private static string get_day_string (Utils.Weekdays.Day day) {
+            switch (day) {
+                case Utils.Weekdays.Day.MON:
+                    return "Mon";
+                case Utils.Weekdays.Day.TUE:
+                    return "Tue";
+                case Utils.Weekdays.Day.WED:
+                    return "Wed";
+                case Utils.Weekdays.Day.THU:
+                    return "Thu";
+                case Utils.Weekdays.Day.FRI:
+                    return "Fri";
+                case Utils.Weekdays.Day.SAT:
+                    return "Sat";
+                case Utils.Weekdays.Day.SUN:
+                    return "Sun";
+                default:
+                    return "";
+            }
+        }
+
+        private File get_unit_file () {
+            File file = File.new_build_filename (GLib.Environment.get_user_config_dir (),
+                                                 "/systemd/user/",
+                                                 TIMER_FILE);
+            return file;
+        }
+
+        private void ensure_service () {
+            File file = File.new_build_filename (GLib.Environment.get_user_config_dir (),
+            "/systemd/user/",
+            SERVICE_FILE);
+            if (!file.query_exists ()) {
+                debug ("Add new %s unit", SERVICE_FILE);
+                try {
+                    FileOutputStream os = file.create (FileCreateFlags.PRIVATE);
+                    var builder = new StringBuilder ();
+                    builder.append ("[Unit]\n");
+                    builder.append ("Description=GNOME Clocks (simple clock application)\n");
+                    builder.append ("\n");
+                    builder.append ("[Service]\n");
+                    builder.append ("Type=simple\n");
+                    builder.append_printf ("ExecStart=%s/gnome-clocks\n", Config.BINDIR);
+                    os.write (builder.str.data);
+                } catch (Error error) {
+                    warning ("Couldn't create systemd unit: %s", error.message);
+                }
+            }
+        }
+    }
+}
+}


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