aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--internal/notify/files.go67
-rw-r--r--internal/notify/files_test.go57
2 files changed, 124 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)
+}
diff --git a/internal/notify/files_test.go b/internal/notify/files_test.go
new file mode 100644
index 0000000..b17a4f6
--- /dev/null
+++ b/internal/notify/files_test.go
@@ -0,0 +1,57 @@
+// 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"
+ "testing"
+)
+
+func TestPublishWritesBothFiles(t *testing.T) {
+ dir := t.TempDir()
+ live := []Popup{{ID: 1, App: "a", Urgency: Normal, Created: 1, Expires: 2}}
+ history := []Popup{{ID: 2, App: "b", Urgency: Low, Created: 3}}
+ if err := Publish(dir, live, history); err != nil {
+ t.Fatalf("Publish: %v", err)
+ }
+ var gotLive []Popup
+ data, err := os.ReadFile(filepath.Join(dir, "queue.json"))
+ if err != nil {
+ t.Fatalf("read queue: %v", err)
+ }
+ if err := json.Unmarshal(data, &gotLive); err != nil {
+ t.Fatalf("queue not JSON: %v", err)
+ }
+ if len(gotLive) != 1 || gotLive[0].ID != 1 {
+ t.Errorf("queue = %+v, want one id 1", gotLive)
+ }
+ if _, err := os.Stat(filepath.Join(dir, "history.json")); err != nil {
+ t.Errorf("history.json missing: %v", err)
+ }
+}
+
+func TestPublishEmptyIsAnEmptyArray(t *testing.T) {
+ dir := t.TempDir()
+ if err := Publish(dir, nil, nil); err != nil {
+ t.Fatalf("Publish: %v", err)
+ }
+ data, _ := os.ReadFile(filepath.Join(dir, "queue.json"))
+ var got []Popup
+ if err := json.Unmarshal(data, &got); err != nil {
+ t.Fatalf("empty queue not JSON array: %v (%s)", err, data)
+ }
+ if string(data) != "[]" {
+ t.Errorf("empty queue encoded as %q, want []", data)
+ }
+}