denote/<id> becomes denote/n/<id>

This commit is contained in:
Levi Neely 2025-11-18 12:15:54 +01:00
parent 5238bf2bb9
commit b1cb5ee6b8
4 changed files with 91 additions and 61 deletions

View File

@ -48,7 +48,7 @@ func readFile(f *client.Fsys, path string) (string, error) {
func read9PFields(f *client.Fsys, identifier string, fields ...string) (map[string]string, error) {
result := make(map[string]string)
for _, field := range fields {
val, err := readFile(f, identifier+"/"+field)
val, err := readFile(f, "n/"+identifier+"/"+field)
if err != nil {
return nil, fmt.Errorf("failed to read %s: %w", field, err)
}
@ -85,13 +85,13 @@ func Search(filters []*fs.Filter) (fs.Results, error) {
}
// Read remaining fields from note directory
if path, err := readFile(f, identifier+"/path"); err == nil {
if path, err := readFile(f, "n/"+identifier+"/path"); err == nil {
meta.Path = path
}
if ext, err := readFile(f, identifier+"/extension"); err == nil {
if ext, err := readFile(f, "n/"+identifier+"/extension"); err == nil {
meta.Extension = ext
}
if keywords, err := readFile(f, identifier+"/keywords"); err == nil && keywords != "" {
if keywords, err := readFile(f, "n/"+identifier+"/keywords"); err == nil && keywords != "" {
meta.Tags = strings.Split(keywords, ",")
for i := range meta.Tags {
meta.Tags[i] = strings.TrimSpace(meta.Tags[i])
@ -100,7 +100,7 @@ func Search(filters []*fs.Filter) (fs.Results, error) {
// Read title - 9P server already returns file content title if available,
// otherwise filename-based title
if title, err := readFile(f, identifier+"/title"); err == nil {
if title, err := readFile(f, "n/"+identifier+"/title"); err == nil {
meta.Title = title
}

View File

@ -4,19 +4,21 @@ denote metadata for all files in the denote directory. The filesystem is organiz
follows:
denote/ (directory) Root filesystem
<identifier>/ (directory) Unique denote file identifier
ctl (write-only) Control file (publish rename, update, and delete events)
extension (read-only) Underlying file extension (e.g., org, md, txt)
keywords (read-write) File tags
path (read-only) Path on underlying filesystem
title (read-write) Denote document title
event (read-only) Global event bus (listen & react to rename, update, and delete events)
index (read-only) Metadata index, supports search functionality
new (write-only) Create new note (write "'title' tag1,tag2")
n/ (directory) Notes directory
<identifier>/ (directory) Unique denote file identifier
ctl (write-only) Control file (publish rename, update, and delete events)
extension (read-only) Underlying file extension (e.g., org, md, txt)
keywords (read-write) File tags
path (read-only) Path on underlying filesystem
title (read-write) Denote document title
Notes:
- Messages written to the ctl file may generate events that are broadcast over the global event bus.
- Writing to title or keywords triggers an update ('u') event and a rename ('r') event
- Write 'd' to denote/<identifier>/ctl to delete a denote file
- Write 'd' to denote/n/<identifier>/ctl to delete a denote file
- Write "'document title' tag1,tag2,(...)" to denote/new to create a new denote file
*/
package fs
@ -272,6 +274,7 @@ const (
qidIndex = 999999
qidEvent = 999998
qidNew = 999997
qidNDir = 999996
)
// File types within a note directory
@ -686,7 +689,7 @@ func (s *server) walk(cs *connState, fc *plan9.Fcall) *plan9.Fcall {
for _, name := range fc.Wname {
if path == "/" {
// Walking from root - should be a note ID
// Walking from root
found := false
if "index" == name {
qid := plan9.Qid{
@ -712,18 +715,31 @@ func (s *server) walk(cs *connState, fc *plan9.Fcall) *plan9.Fcall {
qids = append(qids, qid)
path = "/new"
found = true
} else {
for i, note := range s.notes {
if note.Identifier == name {
qid := plan9.Qid{
Type: QTDir,
Path: uint64(qidNoteBase + i),
}
qids = append(qids, qid)
path = "/" + name
found = true
break
} else if "n" == name {
qid := plan9.Qid{
Type: QTDir,
Path: uint64(qidNDir),
}
qids = append(qids, qid)
path = "/n"
found = true
}
if !found {
return errorFcall(fc, "file not found")
}
} else if path == "/n" {
// Walking from /n - should be a note identifier
found := false
for i, note := range s.notes {
if note.Identifier == name {
qid := plan9.Qid{
Type: QTDir,
Path: uint64(qidNoteBase + i),
}
qids = append(qids, qid)
path = "/n/" + name
found = true
break
}
}
if !found {
@ -938,12 +954,12 @@ func (s *server) write(cs *connState, fc *plan9.Fcall) *plan9.Fcall {
// Extract note identifier and field name from path
parts := strings.Split(strings.Trim(f.path, "/"), "/")
if len(parts) != 2 {
if len(parts) != 3 || parts[0] != "n" {
return errorFcall(fc, "invalid path")
}
noteID := parts[0]
fieldName := parts[1]
noteID := parts[1]
fieldName := parts[2]
note, err := findNote(noteID)
if err != nil {
@ -1047,19 +1063,6 @@ func (s *server) readDir(path string, offset int64, count uint32) []byte {
var dirs []plan9.Dir
if path == "/" {
// add index node
dirs = append(dirs, plan9.Dir{
Qid: plan9.Qid{
Type: QTFile,
Path: uint64(qidIndex),
},
Mode: 0444,
Name: "index",
Uid: "denote",
Gid: "denote",
Muid: "denote",
Length: 0,
})
// add event node
dirs = append(dirs, plan9.Dir{
Qid: plan9.Qid{
@ -1073,6 +1076,19 @@ func (s *server) readDir(path string, offset int64, count uint32) []byte {
Muid: "denote",
Length: 0,
})
// add index node
dirs = append(dirs, plan9.Dir{
Qid: plan9.Qid{
Type: QTFile,
Path: uint64(qidIndex),
},
Mode: 0444,
Name: "index",
Uid: "denote",
Gid: "denote",
Muid: "denote",
Length: 0,
})
// add new node
dirs = append(dirs, plan9.Dir{
Qid: plan9.Qid{
@ -1086,6 +1102,22 @@ func (s *server) readDir(path string, offset int64, count uint32) []byte {
Muid: "denote",
Length: 0,
})
// add n directory
dirs = append(dirs, plan9.Dir{
Qid: plan9.Qid{
Type: QTDir,
Path: uint64(qidNDir),
},
Mode: plan9.DMDIR | 0555,
Name: "n",
Uid: "denote",
Gid: "denote",
Muid: "denote",
Length: 0,
})
} else if path == "/index" {
} else if path == "/n" {
// List all note IDs
for i, note := range s.notes {
dirs = append(dirs, plan9.Dir{
@ -1101,8 +1133,6 @@ func (s *server) readDir(path string, offset int64, count uint32) []byte {
Length: 0,
})
}
} else if path == "/index" {
} else {
// List files in a note directory
noteIdx := s.pathToNoteIndex(path)
@ -1182,11 +1212,11 @@ func (s *server) pathToDir(path string, qid plan9.Qid) plan9.Dir {
func (s *server) pathToNoteIndex(path string) int {
parts := strings.Split(strings.Trim(path, "/"), "/")
if len(parts) == 0 {
if len(parts) < 2 || parts[0] != "n" {
return -1
}
noteID := parts[0]
noteID := parts[1]
for i, note := range s.notes {
if note.Identifier == noteID {
return i
@ -1205,12 +1235,12 @@ func (s *server) getFileContent(path string) string {
}
parts := strings.Split(strings.Trim(path, "/"), "/")
if len(parts) != 2 {
if len(parts) != 3 || parts[0] != "n" {
return ""
}
noteID := parts[0]
fileName := parts[1]
noteID := parts[1]
fileName := parts[2]
note, err := findNote(noteID)
if err != nil {

View File

@ -94,20 +94,20 @@ func syncFrontMatter(path, identifier string) error {
// Write to 9P filesystem
return fs.With9P(func(f *client.Fsys) error {
// Write title (triggers 'u' event)
titlePath := identifier + "/title"
titlePath := "n/" + identifier + "/title"
if err := fs.WriteFile(f, titlePath, fm.Title); err != nil {
return err
}
// Write keywords (triggers 'u' event)
keywords := strings.Join(fm.Tags, ",")
keywordsPath := identifier + "/keywords"
keywordsPath := "n/" + identifier + "/keywords"
if err := fs.WriteFile(f, keywordsPath, keywords); err != nil {
return err
}
// Trigger rename event
ctlPath := identifier + "/ctl"
ctlPath := "n/" + identifier + "/ctl"
if err := fs.WriteFile(f, ctlPath, "r"); err != nil {
return err
}
@ -159,7 +159,7 @@ func SyncAll() error {
}
// Read path and extension
pathFid, err := f.Open(identifier+"/path", 0)
pathFid, err := f.Open("n/"+identifier+"/path", 0)
if err != nil {
log.Printf("sync: failed to read path for %s: %v", identifier, err)
continue
@ -169,7 +169,7 @@ func SyncAll() error {
pathFid.Close()
path := strings.TrimSpace(string(pathBuf[:n]))
extFid, err := f.Open(identifier+"/extension", 0)
extFid, err := f.Open("n/"+identifier+"/extension", 0)
if err != nil {
log.Printf("sync: failed to read extension for %s: %v", identifier, err)
continue
@ -204,20 +204,20 @@ func syncDenoteFile(f *client.Fsys, path, identifier string) error {
}
// Write title (triggers 'u' event)
titlePath := identifier + "/title"
titlePath := "n/" + identifier + "/title"
if err := fs.WriteFile(f, titlePath, fm.Title); err != nil {
return err
}
// Write keywords (triggers 'u' event)
keywords := strings.Join(fm.Tags, ",")
keywordsPath := identifier + "/keywords"
keywordsPath := "n/" + identifier + "/keywords"
if err := fs.WriteFile(f, keywordsPath, keywords); err != nil {
return err
}
// Trigger rename event
ctlPath := identifier + "/ctl"
ctlPath := "n/" + identifier + "/ctl"
if err := fs.WriteFile(f, ctlPath, "r"); err != nil {
return err
}
@ -231,14 +231,14 @@ func syncNonDenoteFile(f *client.Fsys, path, identifier string) error {
meta := fs.ExtractMetadata(path)
// Write title (triggers 'u' event)
titlePath := identifier + "/title"
titlePath := "n/" + identifier + "/title"
if err := fs.WriteFile(f, titlePath, meta.Title); err != nil {
return err
}
// Write keywords (triggers 'u' event)
keywords := strings.Join(meta.Tags, ",")
keywordsPath := identifier + "/keywords"
keywordsPath := "n/" + identifier + "/keywords"
if err := fs.WriteFile(f, keywordsPath, keywords); err != nil {
return err
}

View File

@ -142,9 +142,9 @@ func main() {
break
}
// Write to denote/<identifier>/ctl via 9P
// Write to denote/n/<identifier>/ctl via 9P
if err := fs.With9P(func(f *client.Fsys) error {
return fs.WriteFile(f, filepath.Join(input, "ctl"), "d")
return fs.WriteFile(f, filepath.Join("n", input, "ctl"), "d")
}); err != nil {
log.Printf("failed to delete file: %v", err)
}
@ -348,12 +348,12 @@ func applyIndexChanges(f *client.Fsys, current, updated fs.Results) error {
if titleChanged || tagsChanged {
// Write both title and keywords
titlePath := upd.Identifier + "/title"
titlePath := "n/" + upd.Identifier + "/title"
if err := fs.WriteFile(f, titlePath, upd.Title); err != nil {
return fmt.Errorf("failed to write title for %s: %w", upd.Identifier, err)
}
keywordsPath := upd.Identifier + "/keywords"
keywordsPath := "n/" + upd.Identifier + "/keywords"
keywords := strings.Join(upd.Tags, ",")
if err := fs.WriteFile(f, keywordsPath, keywords); err != nil {
return fmt.Errorf("failed to write keywords for %s: %w", upd.Identifier, err)