Remove event stream and pubsub bus; add file rename on metadata change

The pubsub event endpoint was only used for physical file sync, which is
no longer needed now that reads/writes go directly through 9P. Removed
the /event file, pubsub message bus, and all Publish calls.

Added renameNote helper that renames the physical file and updates
note.Path whenever title, keywords, signature, or body frontmatter
changes, keeping the denote filename in sync with in-memory metadata.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Levi Neely 2026-03-21 22:10:37 +01:00
parent 6dd103482b
commit 6e1fc063fa
3 changed files with 31 additions and 71 deletions

2
go.mod
View File

@ -3,5 +3,3 @@ module denotesrv
go 1.23.0
require 9fans.net/go v0.0.5
require github.com/simonfxr/pubsub v0.0.5 // indirect

8
go.sum
View File

@ -2,13 +2,7 @@
9fans.net/go v0.0.5/go.mod h1:Rxvbbc1e+1TyGMjAvLthGTyO97t+6JMQ6ly+Lcs9Uf0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/simonfxr/pubsub v0.0.5 h1:DJfvFoglqGvwJriIOC5NI5um34n2YX8KAU5+7jv768w=
github.com/simonfxr/pubsub v0.0.5/go.mod h1:bQ+B2NEEHZ08VY/0xVFGaGL1KMJ7cl3yLaXe8xvyC24=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@ -38,5 +32,3 @@ golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapK
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -51,7 +51,6 @@ import (
"9fans.net/go/plan9"
"9fans.net/go/plan9/client"
ps "github.com/simonfxr/pubsub"
)
// File types in our filesystem
@ -69,7 +68,6 @@ const (
qidNoteBase = 1
qidFileBase = 1000001
qidIndex = 999999
qidEvent = 999998
qidNew = 999997
qidNDir = 999996
qidCtl = 999995
@ -93,7 +91,6 @@ type server struct {
callbacks Callbacks
filterQuery string
filteredNotes []*metadata.Metadata
events *ps.Bus
}
type connState struct {
@ -107,8 +104,6 @@ type fid struct {
offset int64
mode uint8
writeBuf []byte // accumulates Twrite chunks, dispatched on Tclunk
eventCh chan string // for event subscribers
eventCancel func() // unsubscribe callback
}
var srv *server
@ -185,7 +180,6 @@ func NewServer(denoteDir string, callbacks Callbacks) (*Server, error) {
notes: notes,
denoteDir: denoteDir,
callbacks: callbacks,
events: ps.NewBus(),
}
return &Server{s}, nil
}
@ -204,7 +198,6 @@ func StartServer(initialData metadata.Results, denoteDir string, callbacks Callb
notes: initialData,
denoteDir: denoteDir,
callbacks: callbacks,
events: ps.NewBus(),
}
// Get namespace and create Unix socket path
@ -409,14 +402,6 @@ func (s *server) walk(cs *connState, fc *plan9.Fcall) *plan9.Fcall {
qids = append(qids, qid)
path = "/dir"
found = true
} else if name == "event" {
qid := plan9.Qid{
Type: QTFile,
Path: uint64(qidEvent),
}
qids = append(qids, qid)
path = "/event"
found = true
} else if name == "n" {
qid := plan9.Qid{
Type: QTDir,
@ -509,24 +494,6 @@ func (s *server) read(cs *connState, fc *plan9.Fcall) *plan9.Fcall {
return errorFcall(fc, "fid not found")
}
// Handle event stream (blocking read with pubsub)
if f.path == "/event" {
if f.eventCh == nil {
ch := make(chan string, 64)
sub := s.events.SubscribeChan("events", ch, ps.CloseOnUnsubscribe)
f.eventCh = ch
f.eventCancel = func() { s.events.Unsubscribe(sub) }
}
cs.mu.Unlock()
select {
case msg := <-f.eventCh:
data := []byte(msg + "\n")
return &plan9.Fcall{Type: plan9.Rread, Tag: fc.Tag, Count: uint32(len(data)), Data: data}
case <-time.After(30 * time.Second):
return &plan9.Fcall{Type: plan9.Rread, Tag: fc.Tag, Count: 0, Data: []byte{}}
}
}
defer cs.mu.Unlock()
var data []byte
@ -654,7 +621,6 @@ func (s *server) dispatchWrite(f *fid, tag uint16) *plan9.Fcall {
if s.callbacks.OnNew != nil {
go s.callbacks.OnNew(identifier)
}
s.events.Publish("events", "n "+identifier)
return &plan9.Fcall{Type: plan9.Rwrite, Tag: fc.Tag, Count: uint32(len(f.writeBuf))}
}
@ -684,8 +650,9 @@ func (s *server) dispatchWrite(f *fid, tag uint16) *plan9.Fcall {
if s.callbacks.OnRename != nil {
s.callbacks.OnRename(noteID)
}
s.events.Publish("events", "u "+noteID)
s.events.Publish("events", "r "+noteID)
if err := s.renameNote(note); err != nil {
return errorFcall(fc, err.Error())
}
case "keywords":
if input == "" {
note.Tags = []string{}
@ -702,8 +669,9 @@ func (s *server) dispatchWrite(f *fid, tag uint16) *plan9.Fcall {
if s.callbacks.OnRename != nil {
s.callbacks.OnRename(noteID)
}
s.events.Publish("events", "u "+noteID)
s.events.Publish("events", "r "+noteID)
if err := s.renameNote(note); err != nil {
return errorFcall(fc, err.Error())
}
case "signature":
note.Signature = input
if s.callbacks.OnUpdate != nil {
@ -712,15 +680,15 @@ func (s *server) dispatchWrite(f *fid, tag uint16) *plan9.Fcall {
if s.callbacks.OnRename != nil {
s.callbacks.OnRename(noteID)
}
s.events.Publish("events", "u "+noteID)
s.events.Publish("events", "r "+noteID)
if err := s.renameNote(note); err != nil {
return errorFcall(fc, err.Error())
}
case "ctl":
switch input {
case "d":
if s.callbacks.OnDelete != nil {
s.callbacks.OnDelete(noteID)
}
s.events.Publish("events", "d "+noteID)
s.mu.Lock()
for i, n := range s.notes {
if n.Identifier == noteID {
@ -739,7 +707,6 @@ func (s *server) dispatchWrite(f *fid, tag uint16) *plan9.Fcall {
if s.callbacks.OnRename != nil {
s.callbacks.OnRename(noteID)
}
s.events.Publish("events", "r "+noteID)
}
case "body":
if err := s.writeBody(note.Path, input, note); err != nil {
@ -785,10 +752,6 @@ func (s *server) clunk(cs *connState, fc *plan9.Fcall) *plan9.Fcall {
}
cs.mu.Lock()
}
// Clean up event subscription if any
if ok && f.eventCancel != nil {
f.eventCancel()
}
delete(cs.fids, fc.Fid)
cs.mu.Unlock()
@ -854,19 +817,6 @@ func (s *server) readDir(path string, offset int64, count uint32) []byte {
Muid: "denote",
Length: uint64(len(s.denoteDir)),
})
// add event node
dirs = append(dirs, plan9.Dir{
Qid: plan9.Qid{
Type: QTFile,
Path: uint64(qidEvent),
},
Mode: 0444,
Name: "event",
Uid: "denote",
Gid: "denote",
Muid: "denote",
Length: 0,
})
// add n directory
dirs = append(dirs, plan9.Dir{
Qid: plan9.Qid{
@ -1075,6 +1025,26 @@ func (s *server) readBody(path string) string {
return string(data)
}
// renameNote renames the physical file to match current note metadata and updates note.Path.
// It is a no-op when the filename is already correct.
func (s *server) renameNote(note *metadata.Metadata) error {
if note.Path == "" {
return nil
}
ext := filepath.Ext(note.Path)
fm := metadata.NewFrontMatter(note.Title, note.Signature, note.Tags, note.Identifier)
newName := metadata.BuildFilename(fm, ext)
newPath := filepath.Join(filepath.Dir(note.Path), newName)
if newPath == note.Path {
return nil
}
if err := os.Rename(note.Path, newPath); err != nil {
return err
}
note.Path = newPath
return nil
}
// writeBody writes content to file and syncs frontmatter to metadata
func (s *server) writeBody(path, body string, note *metadata.Metadata) error {
if path == "" {
@ -1091,7 +1061,7 @@ func (s *server) writeBody(path, body string, note *metadata.Metadata) error {
note.Tags = fm.Tags
note.Signature = fm.Signature
}
return nil
return s.renameNote(note)
}
func errorFcall(fc *plan9.Fcall, msg string) *plan9.Fcall {