diff --git a/internal/denote/denote.go b/internal/denote/denote.go index 3b768f6..34b2c4a 100644 --- a/internal/denote/denote.go +++ b/internal/denote/denote.go @@ -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 } diff --git a/internal/fs/fs.go b/internal/fs/fs.go index 318d09e..bbe3ad3 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -4,19 +4,21 @@ denote metadata for all files in the denote directory. The filesystem is organiz follows: denote/ (directory) Root filesystem - / (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 + / (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//ctl to delete a denote file +- Write 'd' to denote/n//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 { diff --git a/internal/sync/sync.go b/internal/sync/sync.go index 6f2f33d..a03b213 100644 --- a/internal/sync/sync.go +++ b/internal/sync/sync.go @@ -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 } diff --git a/main.go b/main.go index 03941e1..e97b19a 100644 --- a/main.go +++ b/main.go @@ -142,9 +142,9 @@ func main() { break } - // Write to denote//ctl via 9P + // Write to denote/n//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)