denote/<id> becomes denote/n/<id>
This commit is contained in:
parent
5238bf2bb9
commit
b1cb5ee6b8
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
8
main.go
8
main.go
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue