aboutsummaryrefslogtreecommitdiffstats
path: root/internal/notify/files.go
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-09-15 13:48:31 +0200
committerDanilo M. <danix@danix.xyz>2026-09-15 13:48:31 +0200
commitdbecac7041605a5dc8dd88109f71ea00a5692faa (patch)
treec3ef95c8e30d6ee1bc6027cc61488db5a7e514b0 /internal/notify/files.go
parent79c064efe75aa201584a9d618cbde950574f0ca5 (diff)
downloadnotifyd-dbecac7041605a5dc8dd88109f71ea00a5692faa.tar.gz
notifyd-dbecac7041605a5dc8dd88109f71ea00a5692faa.zip
feat: publish the queue and history atomically
Both files are written whole through a temporary file and a rename, so a renderer never reads a half-written value. An empty queue is [] rather than null, because the renderer parses it as an array.
Diffstat (limited to 'internal/notify/files.go')
-rw-r--r--internal/notify/files.go67
1 files changed, 67 insertions, 0 deletions
diff --git a/internal/notify/files.go b/internal/notify/files.go
new file mode 100644
index 0000000..22e0ea9
--- /dev/null
+++ b/internal/notify/files.go
@@ -0,0 +1,67 @@
+// Copyright (C) 2026 Danilo M. <danix@danix.xyz>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+package notify
+
+import (
+ "encoding/json"
+ "os"
+ "path/filepath"
+)
+
+// RuntimeDir is where the daemon publishes. It is tmpfs, so a reboot clears
+// every file and there is no cleanup code.
+func RuntimeDir() string {
+ if d := os.Getenv("XDG_RUNTIME_DIR"); d != "" {
+ return filepath.Join(d, "notifyd")
+ }
+ return filepath.Join(os.TempDir(), "notifyd")
+}
+
+// Publish writes the live queue and the history ring, each whole, through a
+// temporary file and a rename. A reader never sees a half-written value, the
+// same atomic write the status registry relies on.
+func Publish(dir string, live, history []Popup) error {
+ if err := os.MkdirAll(dir, 0o700); err != nil {
+ return err
+ }
+ if live == nil {
+ live = []Popup{}
+ }
+ if history == nil {
+ history = []Popup{}
+ }
+ if err := writeJSON(filepath.Join(dir, "queue.json"), live); err != nil {
+ return err
+ }
+ return writeJSON(filepath.Join(dir, "history.json"), history)
+}
+
+func writeJSON(path string, v any) error {
+ data, err := json.Marshal(v)
+ if err != nil {
+ return err
+ }
+ tmp, err := os.CreateTemp(filepath.Dir(path), ".tmp-*")
+ if err != nil {
+ return err
+ }
+ if _, err := tmp.Write(data); err != nil {
+ tmp.Close()
+ os.Remove(tmp.Name())
+ return err
+ }
+ if err := tmp.Close(); err != nil {
+ os.Remove(tmp.Name())
+ return err
+ }
+ return os.Rename(tmp.Name(), path)
+}