Files
2026-05-26 17:49:31 +08:00

179 lines
3.4 KiB
Go

package inbox
import (
"bufio"
"encoding/json"
"errors"
"os"
"path/filepath"
)
type Store struct {
path string
}
func DefaultStorePath() string {
return filepath.Join(os.Getenv("HOME"), ".local", "state", "agent-notify", "inbox.jsonl")
}
func NewStore(path string) Store {
if path == "" {
path = DefaultStorePath()
}
return Store{path: path}
}
func (s Store) Path() string {
return s.path
}
func (s Store) Append(rec Record) error {
if err := os.MkdirAll(filepath.Dir(s.path), 0755); err != nil {
return err
}
f, err := os.OpenFile(s.path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
return err
}
defer f.Close()
if err := json.NewEncoder(f).Encode(rec); err != nil {
return err
}
return nil
}
func (s Store) List() ([]Record, error) {
f, err := os.Open(s.path)
if errors.Is(err, os.ErrNotExist) {
return nil, nil
}
if err != nil {
return nil, err
}
defer f.Close()
var records []Record
scanner := bufio.NewScanner(f)
for scanner.Scan() {
var rec Record
if err := json.Unmarshal(scanner.Bytes(), &rec); err != nil {
continue
}
records = append(records, rec)
}
if err := scanner.Err(); err != nil {
return nil, err
}
return records, nil
}
func (s Store) Pending() ([]Record, error) {
records, err := s.List()
if err != nil {
return nil, err
}
var pending []Record
for _, rec := range records {
if rec.Status == StatusPending {
pending = append(pending, rec)
}
}
return pending, nil
}
func (s Store) MarkDone(ids []string) (int, error) {
idSet := makeSet(ids)
return s.rewrite(func(rec Record) (Record, bool, bool) {
if _, ok := idSet[rec.ID]; !ok {
return rec, true, false
}
if rec.Status == StatusDone {
return rec, true, false
}
rec.Status = StatusDone
return rec, true, true
})
}
func (s Store) Remove(ids []string) (int, error) {
idSet := makeSet(ids)
return s.rewrite(func(rec Record) (Record, bool, bool) {
if _, ok := idSet[rec.ID]; ok {
return rec, false, true
}
return rec, true, false
})
}
func (s Store) ClearDone() (int, error) {
return s.rewrite(func(rec Record) (Record, bool, bool) {
if rec.Status == StatusDone {
return rec, false, true
}
return rec, true, false
})
}
func (s Store) ClearAll() (int, error) {
records, err := s.List()
if err != nil {
return 0, err
}
if err := os.MkdirAll(filepath.Dir(s.path), 0755); err != nil {
return 0, err
}
if err := os.WriteFile(s.path, nil, 0644); err != nil {
return 0, err
}
return len(records), nil
}
func (s Store) rewrite(fn func(Record) (Record, bool, bool)) (int, error) {
records, err := s.List()
if err != nil {
return 0, err
}
if err := os.MkdirAll(filepath.Dir(s.path), 0755); err != nil {
return 0, err
}
tmp, err := os.CreateTemp(filepath.Dir(s.path), "inbox-*.jsonl")
if err != nil {
return 0, err
}
tmpPath := tmp.Name()
changed := 0
enc := json.NewEncoder(tmp)
for _, rec := range records {
next, keep, didChange := fn(rec)
if didChange {
changed++
}
if !keep {
continue
}
if err := enc.Encode(next); err != nil {
tmp.Close()
os.Remove(tmpPath)
return 0, err
}
}
if err := tmp.Close(); err != nil {
os.Remove(tmpPath)
return 0, err
}
if err := os.Rename(tmpPath, s.path); err != nil {
os.Remove(tmpPath)
return 0, err
}
return changed, nil
}
func makeSet(values []string) map[string]struct{} {
set := make(map[string]struct{}, len(values))
for _, value := range values {
set[value] = struct{}{}
}
return set
}