From bab889e9acb5446f74ddd9d5cbc774d4d56a9573 Mon Sep 17 00:00:00 2001 From: Virgil Date: Mon, 30 Mar 2026 21:39:03 +0000 Subject: [PATCH] refactor(ax): clarify core storage names --- datanode/client.go | 250 ++++++++++++++++++------------------- doc.go | 2 +- io.go | 284 ++++++++++++++++++++---------------------- local/client.go | 103 ++++++++------- node/node.go | 144 ++++++++++----------- s3/s3.go | 162 ++++++++++++------------ sigil/crypto_sigil.go | 52 ++++---- sigil/sigil.go | 4 +- sigil/sigils.go | 32 ++--- sqlite/sqlite.go | 128 +++++++++---------- store/medium.go | 158 +++++++++++------------ store/store.go | 32 ++--- workspace/service.go | 76 +++++------ 13 files changed, 704 insertions(+), 723 deletions(-) diff --git a/datanode/client.go b/datanode/client.go index f94cccf..008b662 100644 --- a/datanode/client.go +++ b/datanode/client.go @@ -62,10 +62,10 @@ func FromTar(data []byte) (*Medium, error) { } // Example: snapshot, _ := medium.Snapshot() -func (m *Medium) Snapshot() ([]byte, error) { - m.mu.RLock() - defer m.mu.RUnlock() - data, err := m.dataNode.ToTar() +func (medium *Medium) Snapshot() ([]byte, error) { + medium.mu.RLock() + defer medium.mu.RUnlock() + data, err := medium.dataNode.ToTar() if err != nil { return nil, core.E("datanode.Snapshot", "tar failed", err) } @@ -73,23 +73,23 @@ func (m *Medium) Snapshot() ([]byte, error) { } // Example: _ = medium.Restore(snapshot) -func (m *Medium) Restore(data []byte) error { +func (medium *Medium) Restore(data []byte) error { dataNode, err := borgdatanode.FromTar(data) if err != nil { return core.E("datanode.Restore", "tar failed", err) } - m.mu.Lock() - defer m.mu.Unlock() - m.dataNode = dataNode - m.directorySet = make(map[string]bool) + medium.mu.Lock() + defer medium.mu.Unlock() + medium.dataNode = dataNode + medium.directorySet = make(map[string]bool) return nil } // Example: dataNode := medium.DataNode() -func (m *Medium) DataNode() *borgdatanode.DataNode { - m.mu.RLock() - defer m.mu.RUnlock() - return m.dataNode +func (medium *Medium) DataNode() *borgdatanode.DataNode { + medium.mu.RLock() + defer medium.mu.RUnlock() + return medium.dataNode } // normaliseEntryPath normalises a path: strips the leading slash and cleans traversal. @@ -104,12 +104,12 @@ func normaliseEntryPath(filePath string) string { // --- io.Medium interface --- -func (m *Medium) Read(filePath string) (string, error) { - m.mu.RLock() - defer m.mu.RUnlock() +func (medium *Medium) Read(filePath string) (string, error) { + medium.mu.RLock() + defer medium.mu.RUnlock() filePath = normaliseEntryPath(filePath) - f, err := m.dataNode.Open(filePath) + f, err := medium.dataNode.Open(filePath) if err != nil { return "", core.E("datanode.Read", core.Concat("not found: ", filePath), fs.ErrNotExist) } @@ -130,42 +130,42 @@ func (m *Medium) Read(filePath string) (string, error) { return string(data), nil } -func (m *Medium) Write(filePath, content string) error { - m.mu.Lock() - defer m.mu.Unlock() +func (medium *Medium) Write(filePath, content string) error { + medium.mu.Lock() + defer medium.mu.Unlock() filePath = normaliseEntryPath(filePath) if filePath == "" { return core.E("datanode.Write", "empty path", fs.ErrInvalid) } - m.dataNode.AddData(filePath, []byte(content)) + medium.dataNode.AddData(filePath, []byte(content)) // ensure parent directories are tracked - m.ensureDirsLocked(path.Dir(filePath)) + medium.ensureDirsLocked(path.Dir(filePath)) return nil } -func (m *Medium) WriteMode(filePath, content string, mode fs.FileMode) error { - return m.Write(filePath, content) +func (medium *Medium) WriteMode(filePath, content string, mode fs.FileMode) error { + return medium.Write(filePath, content) } -func (m *Medium) EnsureDir(filePath string) error { - m.mu.Lock() - defer m.mu.Unlock() +func (medium *Medium) EnsureDir(filePath string) error { + medium.mu.Lock() + defer medium.mu.Unlock() filePath = normaliseEntryPath(filePath) if filePath == "" { return nil } - m.ensureDirsLocked(filePath) + medium.ensureDirsLocked(filePath) return nil } // ensureDirsLocked marks a directory and all ancestors as existing. -// Caller must hold m.mu. -func (m *Medium) ensureDirsLocked(directoryPath string) { +// Caller must hold medium.mu. +func (medium *Medium) ensureDirsLocked(directoryPath string) { for directoryPath != "" && directoryPath != "." { - m.directorySet[directoryPath] = true + medium.directorySet[directoryPath] = true directoryPath = path.Dir(directoryPath) if directoryPath == "." { break @@ -173,26 +173,26 @@ func (m *Medium) ensureDirsLocked(directoryPath string) { } } -func (m *Medium) IsFile(filePath string) bool { - m.mu.RLock() - defer m.mu.RUnlock() +func (medium *Medium) IsFile(filePath string) bool { + medium.mu.RLock() + defer medium.mu.RUnlock() filePath = normaliseEntryPath(filePath) - info, err := m.dataNode.Stat(filePath) + info, err := medium.dataNode.Stat(filePath) return err == nil && !info.IsDir() } -func (m *Medium) FileGet(filePath string) (string, error) { - return m.Read(filePath) +func (medium *Medium) FileGet(filePath string) (string, error) { + return medium.Read(filePath) } -func (m *Medium) FileSet(filePath, content string) error { - return m.Write(filePath, content) +func (medium *Medium) FileSet(filePath, content string) error { + return medium.Write(filePath, content) } -func (m *Medium) Delete(filePath string) error { - m.mu.Lock() - defer m.mu.Unlock() +func (medium *Medium) Delete(filePath string) error { + medium.mu.Lock() + defer medium.mu.Unlock() filePath = normaliseEntryPath(filePath) if filePath == "" { @@ -200,46 +200,46 @@ func (m *Medium) Delete(filePath string) error { } // Check if it's a file in the DataNode - info, err := m.dataNode.Stat(filePath) + info, err := medium.dataNode.Stat(filePath) if err != nil { // Check explicit directories - if m.directorySet[filePath] { + if medium.directorySet[filePath] { // Check if dir is empty - hasChildren, err := m.hasPrefixLocked(filePath + "/") + hasChildren, err := medium.hasPrefixLocked(filePath + "/") if err != nil { return core.E("datanode.Delete", core.Concat("failed to inspect directory: ", filePath), err) } if hasChildren { return core.E("datanode.Delete", core.Concat("directory not empty: ", filePath), fs.ErrExist) } - delete(m.directorySet, filePath) + delete(medium.directorySet, filePath) return nil } return core.E("datanode.Delete", core.Concat("not found: ", filePath), fs.ErrNotExist) } if info.IsDir() { - hasChildren, err := m.hasPrefixLocked(filePath + "/") + hasChildren, err := medium.hasPrefixLocked(filePath + "/") if err != nil { return core.E("datanode.Delete", core.Concat("failed to inspect directory: ", filePath), err) } if hasChildren { return core.E("datanode.Delete", core.Concat("directory not empty: ", filePath), fs.ErrExist) } - delete(m.directorySet, filePath) + delete(medium.directorySet, filePath) return nil } // Remove the file by creating a new DataNode without it - if err := m.removeFileLocked(filePath); err != nil { + if err := medium.removeFileLocked(filePath); err != nil { return core.E("datanode.Delete", core.Concat("failed to delete file: ", filePath), err) } return nil } -func (m *Medium) DeleteAll(filePath string) error { - m.mu.Lock() - defer m.mu.Unlock() +func (medium *Medium) DeleteAll(filePath string) error { + medium.mu.Lock() + defer medium.mu.Unlock() filePath = normaliseEntryPath(filePath) if filePath == "" { @@ -250,22 +250,22 @@ func (m *Medium) DeleteAll(filePath string) error { found := false // Check if filePath itself is a file - info, err := m.dataNode.Stat(filePath) + info, err := medium.dataNode.Stat(filePath) if err == nil && !info.IsDir() { - if err := m.removeFileLocked(filePath); err != nil { + if err := medium.removeFileLocked(filePath); err != nil { return core.E("datanode.DeleteAll", core.Concat("failed to delete file: ", filePath), err) } found = true } // Remove all files under prefix - entries, err := m.collectAllLocked() + entries, err := medium.collectAllLocked() if err != nil { return core.E("datanode.DeleteAll", core.Concat("failed to inspect tree: ", filePath), err) } for _, name := range entries { if name == filePath || core.HasPrefix(name, prefix) { - if err := m.removeFileLocked(name); err != nil { + if err := medium.removeFileLocked(name); err != nil { return core.E("datanode.DeleteAll", core.Concat("failed to delete file: ", name), err) } found = true @@ -273,9 +273,9 @@ func (m *Medium) DeleteAll(filePath string) error { } // Remove explicit directories under prefix - for directoryPath := range m.directorySet { + for directoryPath := range medium.directorySet { if directoryPath == filePath || core.HasPrefix(directoryPath, prefix) { - delete(m.directorySet, directoryPath) + delete(medium.directorySet, directoryPath) found = true } } @@ -286,28 +286,28 @@ func (m *Medium) DeleteAll(filePath string) error { return nil } -func (m *Medium) Rename(oldPath, newPath string) error { - m.mu.Lock() - defer m.mu.Unlock() +func (medium *Medium) Rename(oldPath, newPath string) error { + medium.mu.Lock() + defer medium.mu.Unlock() oldPath = normaliseEntryPath(oldPath) newPath = normaliseEntryPath(newPath) // Check if source is a file - info, err := m.dataNode.Stat(oldPath) + info, err := medium.dataNode.Stat(oldPath) if err != nil { return core.E("datanode.Rename", core.Concat("not found: ", oldPath), fs.ErrNotExist) } if !info.IsDir() { // Read old, write new, delete old - data, err := m.readFileLocked(oldPath) + data, err := medium.readFileLocked(oldPath) if err != nil { return core.E("datanode.Rename", core.Concat("failed to read source file: ", oldPath), err) } - m.dataNode.AddData(newPath, data) - m.ensureDirsLocked(path.Dir(newPath)) - if err := m.removeFileLocked(oldPath); err != nil { + medium.dataNode.AddData(newPath, data) + medium.ensureDirsLocked(path.Dir(newPath)) + if err := medium.removeFileLocked(oldPath); err != nil { return core.E("datanode.Rename", core.Concat("failed to remove source file: ", oldPath), err) } return nil @@ -317,19 +317,19 @@ func (m *Medium) Rename(oldPath, newPath string) error { oldPrefix := oldPath + "/" newPrefix := newPath + "/" - entries, err := m.collectAllLocked() + entries, err := medium.collectAllLocked() if err != nil { return core.E("datanode.Rename", core.Concat("failed to inspect tree: ", oldPath), err) } for _, name := range entries { if core.HasPrefix(name, oldPrefix) { newName := core.Concat(newPrefix, core.TrimPrefix(name, oldPrefix)) - data, err := m.readFileLocked(name) + data, err := medium.readFileLocked(name) if err != nil { return core.E("datanode.Rename", core.Concat("failed to read source file: ", name), err) } - m.dataNode.AddData(newName, data) - if err := m.removeFileLocked(name); err != nil { + medium.dataNode.AddData(newName, data) + if err := medium.removeFileLocked(name); err != nil { return core.E("datanode.Rename", core.Concat("failed to remove source file: ", name), err) } } @@ -337,30 +337,30 @@ func (m *Medium) Rename(oldPath, newPath string) error { // Move explicit directories dirsToMove := make(map[string]string) - for d := range m.directorySet { + for d := range medium.directorySet { if d == oldPath || core.HasPrefix(d, oldPrefix) { newD := core.Concat(newPath, core.TrimPrefix(d, oldPath)) dirsToMove[d] = newD } } for old, nw := range dirsToMove { - delete(m.directorySet, old) - m.directorySet[nw] = true + delete(medium.directorySet, old) + medium.directorySet[nw] = true } return nil } -func (m *Medium) List(filePath string) ([]fs.DirEntry, error) { - m.mu.RLock() - defer m.mu.RUnlock() +func (medium *Medium) List(filePath string) ([]fs.DirEntry, error) { + medium.mu.RLock() + defer medium.mu.RUnlock() filePath = normaliseEntryPath(filePath) - entries, err := m.dataNode.ReadDir(filePath) + entries, err := medium.dataNode.ReadDir(filePath) if err != nil { // Check explicit directories - if filePath == "" || m.directorySet[filePath] { + if filePath == "" || medium.directorySet[filePath] { return []fs.DirEntry{}, nil } return nil, core.E("datanode.List", core.Concat("not found: ", filePath), fs.ErrNotExist) @@ -376,7 +376,7 @@ func (m *Medium) List(filePath string) ([]fs.DirEntry, error) { seen[e.Name()] = true } - for d := range m.directorySet { + for d := range medium.directorySet { if !core.HasPrefix(d, prefix) { continue } @@ -398,43 +398,43 @@ func (m *Medium) List(filePath string) ([]fs.DirEntry, error) { return entries, nil } -func (m *Medium) Stat(filePath string) (fs.FileInfo, error) { - m.mu.RLock() - defer m.mu.RUnlock() +func (medium *Medium) Stat(filePath string) (fs.FileInfo, error) { + medium.mu.RLock() + defer medium.mu.RUnlock() filePath = normaliseEntryPath(filePath) if filePath == "" { return &fileInfo{name: ".", isDir: true, mode: fs.ModeDir | 0755}, nil } - info, err := m.dataNode.Stat(filePath) + info, err := medium.dataNode.Stat(filePath) if err == nil { return info, nil } - if m.directorySet[filePath] { + if medium.directorySet[filePath] { return &fileInfo{name: path.Base(filePath), isDir: true, mode: fs.ModeDir | 0755}, nil } return nil, core.E("datanode.Stat", core.Concat("not found: ", filePath), fs.ErrNotExist) } -func (m *Medium) Open(filePath string) (fs.File, error) { - m.mu.RLock() - defer m.mu.RUnlock() +func (medium *Medium) Open(filePath string) (fs.File, error) { + medium.mu.RLock() + defer medium.mu.RUnlock() filePath = normaliseEntryPath(filePath) - return m.dataNode.Open(filePath) + return medium.dataNode.Open(filePath) } -func (m *Medium) Create(filePath string) (goio.WriteCloser, error) { +func (medium *Medium) Create(filePath string) (goio.WriteCloser, error) { filePath = normaliseEntryPath(filePath) if filePath == "" { return nil, core.E("datanode.Create", "empty path", fs.ErrInvalid) } - return &writeCloser{medium: m, path: filePath}, nil + return &writeCloser{medium: medium, path: filePath}, nil } -func (m *Medium) Append(filePath string) (goio.WriteCloser, error) { +func (medium *Medium) Append(filePath string) (goio.WriteCloser, error) { filePath = normaliseEntryPath(filePath) if filePath == "" { return nil, core.E("datanode.Append", "empty path", fs.ErrInvalid) @@ -442,71 +442,71 @@ func (m *Medium) Append(filePath string) (goio.WriteCloser, error) { // Read existing content var existing []byte - m.mu.RLock() - if m.IsFile(filePath) { - data, err := m.readFileLocked(filePath) + medium.mu.RLock() + if medium.IsFile(filePath) { + data, err := medium.readFileLocked(filePath) if err != nil { - m.mu.RUnlock() + medium.mu.RUnlock() return nil, core.E("datanode.Append", core.Concat("failed to read existing content: ", filePath), err) } existing = data } - m.mu.RUnlock() + medium.mu.RUnlock() - return &writeCloser{medium: m, path: filePath, buf: existing}, nil + return &writeCloser{medium: medium, path: filePath, buf: existing}, nil } -func (m *Medium) ReadStream(filePath string) (goio.ReadCloser, error) { - m.mu.RLock() - defer m.mu.RUnlock() +func (medium *Medium) ReadStream(filePath string) (goio.ReadCloser, error) { + medium.mu.RLock() + defer medium.mu.RUnlock() filePath = normaliseEntryPath(filePath) - f, err := m.dataNode.Open(filePath) + f, err := medium.dataNode.Open(filePath) if err != nil { return nil, core.E("datanode.ReadStream", core.Concat("not found: ", filePath), fs.ErrNotExist) } return f.(goio.ReadCloser), nil } -func (m *Medium) WriteStream(filePath string) (goio.WriteCloser, error) { - return m.Create(filePath) +func (medium *Medium) WriteStream(filePath string) (goio.WriteCloser, error) { + return medium.Create(filePath) } -func (m *Medium) Exists(filePath string) bool { - m.mu.RLock() - defer m.mu.RUnlock() +func (medium *Medium) Exists(filePath string) bool { + medium.mu.RLock() + defer medium.mu.RUnlock() filePath = normaliseEntryPath(filePath) if filePath == "" { return true // root always exists } - _, err := m.dataNode.Stat(filePath) + _, err := medium.dataNode.Stat(filePath) if err == nil { return true } - return m.directorySet[filePath] + return medium.directorySet[filePath] } -func (m *Medium) IsDir(filePath string) bool { - m.mu.RLock() - defer m.mu.RUnlock() +func (medium *Medium) IsDir(filePath string) bool { + medium.mu.RLock() + defer medium.mu.RUnlock() filePath = normaliseEntryPath(filePath) if filePath == "" { return true } - info, err := m.dataNode.Stat(filePath) + info, err := medium.dataNode.Stat(filePath) if err == nil { return info.IsDir() } - return m.directorySet[filePath] + return medium.directorySet[filePath] } // --- internal helpers --- // hasPrefixLocked checks if any file path starts with prefix. Caller holds lock. -func (m *Medium) hasPrefixLocked(prefix string) (bool, error) { - entries, err := m.collectAllLocked() +func (medium *Medium) hasPrefixLocked(prefix string) (bool, error) { + entries, err := medium.collectAllLocked() if err != nil { return false, err } @@ -515,7 +515,7 @@ func (m *Medium) hasPrefixLocked(prefix string) (bool, error) { return true, nil } } - for d := range m.directorySet { + for d := range medium.directorySet { if core.HasPrefix(d, prefix) { return true, nil } @@ -524,9 +524,9 @@ func (m *Medium) hasPrefixLocked(prefix string) (bool, error) { } // collectAllLocked returns all file paths in the DataNode. Caller holds lock. -func (m *Medium) collectAllLocked() ([]string, error) { +func (medium *Medium) collectAllLocked() ([]string, error) { var names []string - err := dataNodeWalkDir(m.dataNode, ".", func(filePath string, entry fs.DirEntry, err error) error { + err := dataNodeWalkDir(medium.dataNode, ".", func(filePath string, entry fs.DirEntry, err error) error { if err != nil { return err } @@ -538,8 +538,8 @@ func (m *Medium) collectAllLocked() ([]string, error) { return names, err } -func (m *Medium) readFileLocked(name string) ([]byte, error) { - f, err := dataNodeOpen(m.dataNode, name) +func (medium *Medium) readFileLocked(name string) ([]byte, error) { + f, err := dataNodeOpen(medium.dataNode, name) if err != nil { return nil, err } @@ -556,9 +556,9 @@ func (m *Medium) readFileLocked(name string) ([]byte, error) { // removeFileLocked removes a single file by rebuilding the DataNode. // This is necessary because Borg's DataNode doesn't expose a Remove method. -// Caller must hold m.mu write lock. -func (m *Medium) removeFileLocked(target string) error { - entries, err := m.collectAllLocked() +// Caller must hold medium.mu write lock. +func (medium *Medium) removeFileLocked(target string) error { + entries, err := medium.collectAllLocked() if err != nil { return err } @@ -567,13 +567,13 @@ func (m *Medium) removeFileLocked(target string) error { if name == target { continue } - data, err := m.readFileLocked(name) + data, err := medium.readFileLocked(name) if err != nil { return err } newDN.AddData(name, data) } - m.dataNode = newDN + medium.dataNode = newDN return nil } diff --git a/doc.go b/doc.go index 14eb1cb..6b938f8 100644 --- a/doc.go +++ b/doc.go @@ -1,4 +1,4 @@ -// Package io gives CoreGO a single storage surface. +// Package io exposes CoreGO's storage surface. // // medium, _ := io.NewSandboxed("/srv/app") // _ = medium.Write("config/app.yaml", "port: 8080") diff --git a/io.go b/io.go index 57362a1..f22e20c 100644 --- a/io.go +++ b/io.go @@ -59,7 +59,7 @@ type Medium interface { IsDir(path string) bool } -// FileInfo is a test helper that satisfies fs.FileInfo. +// Example: info := io.FileInfo{name: "app.yaml", size: 8, mode: 0644} type FileInfo struct { name string size int64 @@ -68,19 +68,19 @@ type FileInfo struct { isDir bool } -func (fi FileInfo) Name() string { return fi.name } +func (info FileInfo) Name() string { return info.name } -func (fi FileInfo) Size() int64 { return fi.size } +func (info FileInfo) Size() int64 { return info.size } -func (fi FileInfo) Mode() fs.FileMode { return fi.mode } +func (info FileInfo) Mode() fs.FileMode { return info.mode } -func (fi FileInfo) ModTime() time.Time { return fi.modTime } +func (info FileInfo) ModTime() time.Time { return info.modTime } -func (fi FileInfo) IsDir() bool { return fi.isDir } +func (info FileInfo) IsDir() bool { return info.isDir } -func (fi FileInfo) Sys() any { return nil } +func (info FileInfo) Sys() any { return nil } -// DirEntry is a test helper that satisfies fs.DirEntry. +// Example: entry := io.DirEntry{name: "app.yaml", mode: 0644} type DirEntry struct { name string isDir bool @@ -88,15 +88,15 @@ type DirEntry struct { info fs.FileInfo } -func (de DirEntry) Name() string { return de.name } +func (entry DirEntry) Name() string { return entry.name } -func (de DirEntry) IsDir() bool { return de.isDir } +func (entry DirEntry) IsDir() bool { return entry.isDir } -func (de DirEntry) Type() fs.FileMode { return de.mode.Type() } +func (entry DirEntry) Type() fs.FileMode { return entry.mode.Type() } -func (de DirEntry) Info() (fs.FileInfo, error) { return de.info, nil } +func (entry DirEntry) Info() (fs.FileInfo, error) { return entry.info, nil } -// Example: io.Local.Read("/etc/hostname") +// Example: _ = io.Local.Read("/etc/hostname") var Local Medium var _ Medium = (*local.Medium)(nil) @@ -115,36 +115,34 @@ func NewSandboxed(root string) (Medium, error) { return local.New(root) } -// --- Helper Functions --- - // Example: content, _ := io.Read(medium, "config/app.yaml") -func Read(m Medium, path string) (string, error) { - return m.Read(path) +func Read(medium Medium, path string) (string, error) { + return medium.Read(path) } // Example: _ = io.Write(medium, "config/app.yaml", "port: 8080") -func Write(m Medium, path, content string) error { - return m.Write(path, content) +func Write(medium Medium, path, content string) error { + return medium.Write(path, content) } // Example: reader, _ := io.ReadStream(medium, "logs/app.log") -func ReadStream(m Medium, path string) (goio.ReadCloser, error) { - return m.ReadStream(path) +func ReadStream(medium Medium, path string) (goio.ReadCloser, error) { + return medium.ReadStream(path) } // Example: writer, _ := io.WriteStream(medium, "logs/app.log") -func WriteStream(m Medium, path string) (goio.WriteCloser, error) { - return m.WriteStream(path) +func WriteStream(medium Medium, path string) (goio.WriteCloser, error) { + return medium.WriteStream(path) } // Example: _ = io.EnsureDir(medium, "config") -func EnsureDir(m Medium, path string) error { - return m.EnsureDir(path) +func EnsureDir(medium Medium, path string) error { + return medium.EnsureDir(path) } // Example: ok := io.IsFile(medium, "config/app.yaml") -func IsFile(m Medium, path string) bool { - return m.IsFile(path) +func IsFile(medium Medium, path string) bool { + return medium.IsFile(path) } // Example: _ = io.Copy(source, "input.txt", destination, "backup/input.txt") @@ -159,8 +157,6 @@ func Copy(source Medium, sourcePath string, destination Medium, destinationPath return nil } -// --- MockMedium --- - // Example: medium := io.NewMockMedium() // _ = medium.Write("config/app.yaml", "port: 8080") type MockMedium struct { @@ -181,94 +177,91 @@ func NewMockMedium() *MockMedium { } } -func (m *MockMedium) Read(path string) (string, error) { - content, ok := m.Files[path] +func (medium *MockMedium) Read(path string) (string, error) { + content, ok := medium.Files[path] if !ok { return "", core.E("io.MockMedium.Read", core.Concat("file not found: ", path), fs.ErrNotExist) } return content, nil } -func (m *MockMedium) Write(path, content string) error { - m.Files[path] = content - m.ModTimes[path] = time.Now() +func (medium *MockMedium) Write(path, content string) error { + medium.Files[path] = content + medium.ModTimes[path] = time.Now() return nil } -func (m *MockMedium) WriteMode(path, content string, mode fs.FileMode) error { - return m.Write(path, content) +func (medium *MockMedium) WriteMode(path, content string, mode fs.FileMode) error { + return medium.Write(path, content) } -func (m *MockMedium) EnsureDir(path string) error { - m.Dirs[path] = true +func (medium *MockMedium) EnsureDir(path string) error { + medium.Dirs[path] = true return nil } -func (m *MockMedium) IsFile(path string) bool { - _, ok := m.Files[path] +func (medium *MockMedium) IsFile(path string) bool { + _, ok := medium.Files[path] return ok } -func (m *MockMedium) FileGet(path string) (string, error) { - return m.Read(path) +func (medium *MockMedium) FileGet(path string) (string, error) { + return medium.Read(path) } -func (m *MockMedium) FileSet(path, content string) error { - return m.Write(path, content) +func (medium *MockMedium) FileSet(path, content string) error { + return medium.Write(path, content) } -func (m *MockMedium) Delete(path string) error { - if _, ok := m.Files[path]; ok { - delete(m.Files, path) +func (medium *MockMedium) Delete(path string) error { + if _, ok := medium.Files[path]; ok { + delete(medium.Files, path) return nil } - if _, ok := m.Dirs[path]; ok { - // Check if directory is empty (no files or subdirs with this prefix) + if _, ok := medium.Dirs[path]; ok { prefix := path if !core.HasSuffix(prefix, "/") { prefix += "/" } - for f := range m.Files { - if core.HasPrefix(f, prefix) { + for filePath := range medium.Files { + if core.HasPrefix(filePath, prefix) { return core.E("io.MockMedium.Delete", core.Concat("directory not empty: ", path), fs.ErrExist) } } - for d := range m.Dirs { - if d != path && core.HasPrefix(d, prefix) { + for directoryPath := range medium.Dirs { + if directoryPath != path && core.HasPrefix(directoryPath, prefix) { return core.E("io.MockMedium.Delete", core.Concat("directory not empty: ", path), fs.ErrExist) } } - delete(m.Dirs, path) + delete(medium.Dirs, path) return nil } return core.E("io.MockMedium.Delete", core.Concat("path not found: ", path), fs.ErrNotExist) } -func (m *MockMedium) DeleteAll(path string) error { +func (medium *MockMedium) DeleteAll(path string) error { found := false - if _, ok := m.Files[path]; ok { - delete(m.Files, path) + if _, ok := medium.Files[path]; ok { + delete(medium.Files, path) found = true } - if _, ok := m.Dirs[path]; ok { - delete(m.Dirs, path) + if _, ok := medium.Dirs[path]; ok { + delete(medium.Dirs, path) found = true } - - // Delete all entries under this path prefix := path if !core.HasSuffix(prefix, "/") { prefix += "/" } - for f := range m.Files { - if core.HasPrefix(f, prefix) { - delete(m.Files, f) + for filePath := range medium.Files { + if core.HasPrefix(filePath, prefix) { + delete(medium.Files, filePath) found = true } } - for d := range m.Dirs { - if core.HasPrefix(d, prefix) { - delete(m.Dirs, d) + for directoryPath := range medium.Dirs { + if core.HasPrefix(directoryPath, prefix) { + delete(medium.Dirs, directoryPath) found = true } } @@ -279,20 +272,19 @@ func (m *MockMedium) DeleteAll(path string) error { return nil } -func (m *MockMedium) Rename(oldPath, newPath string) error { - if content, ok := m.Files[oldPath]; ok { - m.Files[newPath] = content - delete(m.Files, oldPath) - if mt, ok := m.ModTimes[oldPath]; ok { - m.ModTimes[newPath] = mt - delete(m.ModTimes, oldPath) +func (medium *MockMedium) Rename(oldPath, newPath string) error { + if content, ok := medium.Files[oldPath]; ok { + medium.Files[newPath] = content + delete(medium.Files, oldPath) + if modTime, ok := medium.ModTimes[oldPath]; ok { + medium.ModTimes[newPath] = modTime + delete(medium.ModTimes, oldPath) } return nil } - if _, ok := m.Dirs[oldPath]; ok { - // Move directory and all contents - m.Dirs[newPath] = true - delete(m.Dirs, oldPath) + if _, ok := medium.Dirs[oldPath]; ok { + medium.Dirs[newPath] = true + delete(medium.Dirs, oldPath) oldPrefix := oldPath if !core.HasSuffix(oldPrefix, "/") { @@ -303,42 +295,40 @@ func (m *MockMedium) Rename(oldPath, newPath string) error { newPrefix += "/" } - // Collect files to move first (don't mutate during iteration) filesToMove := make(map[string]string) - for f := range m.Files { - if core.HasPrefix(f, oldPrefix) { - newF := core.Concat(newPrefix, core.TrimPrefix(f, oldPrefix)) - filesToMove[f] = newF + for filePath := range medium.Files { + if core.HasPrefix(filePath, oldPrefix) { + newFilePath := core.Concat(newPrefix, core.TrimPrefix(filePath, oldPrefix)) + filesToMove[filePath] = newFilePath } } - for oldF, newF := range filesToMove { - m.Files[newF] = m.Files[oldF] - delete(m.Files, oldF) - if mt, ok := m.ModTimes[oldF]; ok { - m.ModTimes[newF] = mt - delete(m.ModTimes, oldF) + for oldFilePath, newFilePath := range filesToMove { + medium.Files[newFilePath] = medium.Files[oldFilePath] + delete(medium.Files, oldFilePath) + if modTime, ok := medium.ModTimes[oldFilePath]; ok { + medium.ModTimes[newFilePath] = modTime + delete(medium.ModTimes, oldFilePath) } } - // Collect directories to move first dirsToMove := make(map[string]string) - for d := range m.Dirs { - if core.HasPrefix(d, oldPrefix) { - newD := core.Concat(newPrefix, core.TrimPrefix(d, oldPrefix)) - dirsToMove[d] = newD + for directoryPath := range medium.Dirs { + if core.HasPrefix(directoryPath, oldPrefix) { + newDirectoryPath := core.Concat(newPrefix, core.TrimPrefix(directoryPath, oldPrefix)) + dirsToMove[directoryPath] = newDirectoryPath } } - for oldD, newD := range dirsToMove { - m.Dirs[newD] = true - delete(m.Dirs, oldD) + for oldDirectoryPath, newDirectoryPath := range dirsToMove { + medium.Dirs[newDirectoryPath] = true + delete(medium.Dirs, oldDirectoryPath) } return nil } return core.E("io.MockMedium.Rename", core.Concat("path not found: ", oldPath), fs.ErrNotExist) } -func (m *MockMedium) Open(path string) (fs.File, error) { - content, ok := m.Files[path] +func (medium *MockMedium) Open(path string) (fs.File, error) { + content, ok := medium.Files[path] if !ok { return nil, core.E("io.MockMedium.Open", core.Concat("file not found: ", path), fs.ErrNotExist) } @@ -348,28 +338,28 @@ func (m *MockMedium) Open(path string) (fs.File, error) { }, nil } -func (m *MockMedium) Create(path string) (goio.WriteCloser, error) { +func (medium *MockMedium) Create(path string) (goio.WriteCloser, error) { return &MockWriteCloser{ - medium: m, + medium: medium, path: path, }, nil } -func (m *MockMedium) Append(path string) (goio.WriteCloser, error) { - content := m.Files[path] +func (medium *MockMedium) Append(path string) (goio.WriteCloser, error) { + content := medium.Files[path] return &MockWriteCloser{ - medium: m, + medium: medium, path: path, data: []byte(content), }, nil } -func (m *MockMedium) ReadStream(path string) (goio.ReadCloser, error) { - return m.Open(path) +func (medium *MockMedium) ReadStream(path string) (goio.ReadCloser, error) { + return medium.Open(path) } -func (m *MockMedium) WriteStream(path string) (goio.WriteCloser, error) { - return m.Create(path) +func (medium *MockMedium) WriteStream(path string) (goio.WriteCloser, error) { + return medium.Create(path) } // MockFile implements fs.File for MockMedium. @@ -379,23 +369,23 @@ type MockFile struct { offset int64 } -func (f *MockFile) Stat() (fs.FileInfo, error) { +func (file *MockFile) Stat() (fs.FileInfo, error) { return FileInfo{ - name: f.name, - size: int64(len(f.content)), + name: file.name, + size: int64(len(file.content)), }, nil } -func (f *MockFile) Read(b []byte) (int, error) { - if f.offset >= int64(len(f.content)) { +func (file *MockFile) Read(buffer []byte) (int, error) { + if file.offset >= int64(len(file.content)) { return 0, goio.EOF } - n := copy(b, f.content[f.offset:]) - f.offset += int64(n) - return n, nil + readCount := copy(buffer, file.content[file.offset:]) + file.offset += int64(readCount) + return readCount, nil } -func (f *MockFile) Close() error { +func (file *MockFile) Close() error { return nil } @@ -406,34 +396,33 @@ type MockWriteCloser struct { data []byte } -func (w *MockWriteCloser) Write(p []byte) (int, error) { - w.data = append(w.data, p...) - return len(p), nil +func (writeCloser *MockWriteCloser) Write(data []byte) (int, error) { + writeCloser.data = append(writeCloser.data, data...) + return len(data), nil } -func (w *MockWriteCloser) Close() error { - w.medium.Files[w.path] = string(w.data) - w.medium.ModTimes[w.path] = time.Now() +func (writeCloser *MockWriteCloser) Close() error { + writeCloser.medium.Files[writeCloser.path] = string(writeCloser.data) + writeCloser.medium.ModTimes[writeCloser.path] = time.Now() return nil } -func (m *MockMedium) List(path string) ([]fs.DirEntry, error) { - if _, ok := m.Dirs[path]; !ok { - // Check if it's the root or has children +func (medium *MockMedium) List(path string) ([]fs.DirEntry, error) { + if _, ok := medium.Dirs[path]; !ok { hasChildren := false prefix := path if path != "" && !core.HasSuffix(prefix, "/") { prefix += "/" } - for f := range m.Files { - if core.HasPrefix(f, prefix) { + for filePath := range medium.Files { + if core.HasPrefix(filePath, prefix) { hasChildren = true break } } if !hasChildren { - for d := range m.Dirs { - if core.HasPrefix(d, prefix) { + for directoryPath := range medium.Dirs { + if core.HasPrefix(directoryPath, prefix) { hasChildren = true break } @@ -452,16 +441,13 @@ func (m *MockMedium) List(path string) ([]fs.DirEntry, error) { seen := make(map[string]bool) var entries []fs.DirEntry - // Find immediate children (files) - for f, content := range m.Files { - if !core.HasPrefix(f, prefix) { + for filePath, content := range medium.Files { + if !core.HasPrefix(filePath, prefix) { continue } - rest := core.TrimPrefix(f, prefix) + rest := core.TrimPrefix(filePath, prefix) if rest == "" || core.Contains(rest, "/") { - // Skip if it's not an immediate child if idx := bytes.IndexByte([]byte(rest), '/'); idx != -1 { - // This is a subdirectory dirName := rest[:idx] if !seen[dirName] { seen[dirName] = true @@ -494,16 +480,14 @@ func (m *MockMedium) List(path string) ([]fs.DirEntry, error) { } } - // Find immediate subdirectories - for d := range m.Dirs { - if !core.HasPrefix(d, prefix) { + for directoryPath := range medium.Dirs { + if !core.HasPrefix(directoryPath, prefix) { continue } - rest := core.TrimPrefix(d, prefix) + rest := core.TrimPrefix(directoryPath, prefix) if rest == "" { continue } - // Get only immediate child if idx := bytes.IndexByte([]byte(rest), '/'); idx != -1 { rest = rest[:idx] } @@ -525,9 +509,9 @@ func (m *MockMedium) List(path string) ([]fs.DirEntry, error) { return entries, nil } -func (m *MockMedium) Stat(path string) (fs.FileInfo, error) { - if content, ok := m.Files[path]; ok { - modTime, ok := m.ModTimes[path] +func (medium *MockMedium) Stat(path string) (fs.FileInfo, error) { + if content, ok := medium.Files[path]; ok { + modTime, ok := medium.ModTimes[path] if !ok { modTime = time.Now() } @@ -538,7 +522,7 @@ func (m *MockMedium) Stat(path string) (fs.FileInfo, error) { modTime: modTime, }, nil } - if _, ok := m.Dirs[path]; ok { + if _, ok := medium.Dirs[path]; ok { return FileInfo{ name: core.PathBase(path), isDir: true, @@ -548,17 +532,17 @@ func (m *MockMedium) Stat(path string) (fs.FileInfo, error) { return nil, core.E("io.MockMedium.Stat", core.Concat("path not found: ", path), fs.ErrNotExist) } -func (m *MockMedium) Exists(path string) bool { - if _, ok := m.Files[path]; ok { +func (medium *MockMedium) Exists(path string) bool { + if _, ok := medium.Files[path]; ok { return true } - if _, ok := m.Dirs[path]; ok { + if _, ok := medium.Dirs[path]; ok { return true } return false } -func (m *MockMedium) IsDir(path string) bool { - _, ok := m.Dirs[path] +func (medium *MockMedium) IsDir(path string) bool { + _, ok := medium.Dirs[path] return ok } diff --git a/local/client.go b/local/client.go index 81c7f9e..3eebd11 100644 --- a/local/client.go +++ b/local/client.go @@ -177,17 +177,15 @@ func logSandboxEscape(root, path, attempted string) { core.Security("sandbox escape detected", "root", root, "path", path, "attempted", attempted, "user", username) } -// sandboxedPath resolves a path inside the filesystem root. -// Absolute paths are sandboxed under root (unless root is "/"). -func (m *Medium) sandboxedPath(path string) string { +func (medium *Medium) sandboxedPath(path string) string { if path == "" { - return m.filesystemRoot + return medium.filesystemRoot } // If the path is relative and the medium is rooted at "/", // treat it as relative to the current working directory. // This makes io.Local behave more like the standard 'os' package. - if m.filesystemRoot == dirSeparator() && !core.PathIsAbs(normalisePath(path)) { + if medium.filesystemRoot == dirSeparator() && !core.PathIsAbs(normalisePath(path)) { return core.Path(currentWorkingDir(), normalisePath(path)) } @@ -196,23 +194,22 @@ func (m *Medium) sandboxedPath(path string) string { clean := cleanSandboxPath(path) // If root is "/", allow absolute paths through - if m.filesystemRoot == dirSeparator() { + if medium.filesystemRoot == dirSeparator() { return clean } // Join cleaned relative path with root - return core.Path(m.filesystemRoot, core.TrimPrefix(clean, dirSeparator())) + return core.Path(medium.filesystemRoot, core.TrimPrefix(clean, dirSeparator())) } -// validatePath ensures the path is within the sandbox, following symlinks if they exist. -func (m *Medium) validatePath(path string) (string, error) { - if m.filesystemRoot == dirSeparator() { - return m.sandboxedPath(path), nil +func (medium *Medium) validatePath(path string) (string, error) { + if medium.filesystemRoot == dirSeparator() { + return medium.sandboxedPath(path), nil } // Split the cleaned path into components parts := splitPathParts(cleanSandboxPath(path)) - current := m.filesystemRoot + current := medium.filesystemRoot for _, part := range parts { next := core.Path(current, part) @@ -229,9 +226,9 @@ func (m *Medium) validatePath(path string) (string, error) { } // Verify the resolved part is still within the root - if !isWithinRoot(m.filesystemRoot, realNext) { + if !isWithinRoot(medium.filesystemRoot, realNext) { // Security event: sandbox escape attempt - logSandboxEscape(m.filesystemRoot, path, realNext) + logSandboxEscape(medium.filesystemRoot, path, realNext) return "", fs.ErrPermission } current = realNext @@ -240,98 +237,98 @@ func (m *Medium) validatePath(path string) (string, error) { return current, nil } -func (m *Medium) Read(path string) (string, error) { - resolvedPath, err := m.validatePath(path) +func (medium *Medium) Read(path string) (string, error) { + resolvedPath, err := medium.validatePath(path) if err != nil { return "", err } return resultString("local.Read", core.Concat("read failed: ", path), unrestrictedFileSystem.Read(resolvedPath)) } -func (m *Medium) Write(path, content string) error { - return m.WriteMode(path, content, 0644) +func (medium *Medium) Write(path, content string) error { + return medium.WriteMode(path, content, 0644) } -func (m *Medium) WriteMode(path, content string, mode fs.FileMode) error { - resolvedPath, err := m.validatePath(path) +func (medium *Medium) WriteMode(path, content string, mode fs.FileMode) error { + resolvedPath, err := medium.validatePath(path) if err != nil { return err } return resultError("local.WriteMode", core.Concat("write failed: ", path), unrestrictedFileSystem.WriteMode(resolvedPath, content, mode)) } -func (m *Medium) EnsureDir(path string) error { - resolvedPath, err := m.validatePath(path) +func (medium *Medium) EnsureDir(path string) error { + resolvedPath, err := medium.validatePath(path) if err != nil { return err } return resultError("local.EnsureDir", core.Concat("ensure dir failed: ", path), unrestrictedFileSystem.EnsureDir(resolvedPath)) } -func (m *Medium) IsDir(path string) bool { +func (medium *Medium) IsDir(path string) bool { if path == "" { return false } - resolvedPath, err := m.validatePath(path) + resolvedPath, err := medium.validatePath(path) if err != nil { return false } return unrestrictedFileSystem.IsDir(resolvedPath) } -func (m *Medium) IsFile(path string) bool { +func (medium *Medium) IsFile(path string) bool { if path == "" { return false } - resolvedPath, err := m.validatePath(path) + resolvedPath, err := medium.validatePath(path) if err != nil { return false } return unrestrictedFileSystem.IsFile(resolvedPath) } -func (m *Medium) Exists(path string) bool { - resolvedPath, err := m.validatePath(path) +func (medium *Medium) Exists(path string) bool { + resolvedPath, err := medium.validatePath(path) if err != nil { return false } return unrestrictedFileSystem.Exists(resolvedPath) } -func (m *Medium) List(path string) ([]fs.DirEntry, error) { - resolvedPath, err := m.validatePath(path) +func (medium *Medium) List(path string) ([]fs.DirEntry, error) { + resolvedPath, err := medium.validatePath(path) if err != nil { return nil, err } return resultDirEntries("local.List", core.Concat("list failed: ", path), unrestrictedFileSystem.List(resolvedPath)) } -func (m *Medium) Stat(path string) (fs.FileInfo, error) { - resolvedPath, err := m.validatePath(path) +func (medium *Medium) Stat(path string) (fs.FileInfo, error) { + resolvedPath, err := medium.validatePath(path) if err != nil { return nil, err } return resultFileInfo("local.Stat", core.Concat("stat failed: ", path), unrestrictedFileSystem.Stat(resolvedPath)) } -func (m *Medium) Open(path string) (fs.File, error) { - resolvedPath, err := m.validatePath(path) +func (medium *Medium) Open(path string) (fs.File, error) { + resolvedPath, err := medium.validatePath(path) if err != nil { return nil, err } return resultFile("local.Open", core.Concat("open failed: ", path), unrestrictedFileSystem.Open(resolvedPath)) } -func (m *Medium) Create(path string) (goio.WriteCloser, error) { - resolvedPath, err := m.validatePath(path) +func (medium *Medium) Create(path string) (goio.WriteCloser, error) { + resolvedPath, err := medium.validatePath(path) if err != nil { return nil, err } return resultWriteCloser("local.Create", core.Concat("create failed: ", path), unrestrictedFileSystem.Create(resolvedPath)) } -func (m *Medium) Append(path string) (goio.WriteCloser, error) { - resolvedPath, err := m.validatePath(path) +func (medium *Medium) Append(path string) (goio.WriteCloser, error) { + resolvedPath, err := medium.validatePath(path) if err != nil { return nil, err } @@ -339,17 +336,17 @@ func (m *Medium) Append(path string) (goio.WriteCloser, error) { } // Example: reader, _ := medium.ReadStream("logs/app.log") -func (m *Medium) ReadStream(path string) (goio.ReadCloser, error) { - return m.Open(path) +func (medium *Medium) ReadStream(path string) (goio.ReadCloser, error) { + return medium.Open(path) } // Example: writer, _ := medium.WriteStream("logs/app.log") -func (m *Medium) WriteStream(path string) (goio.WriteCloser, error) { - return m.Create(path) +func (medium *Medium) WriteStream(path string) (goio.WriteCloser, error) { + return medium.Create(path) } -func (m *Medium) Delete(path string) error { - resolvedPath, err := m.validatePath(path) +func (medium *Medium) Delete(path string) error { + resolvedPath, err := medium.validatePath(path) if err != nil { return err } @@ -359,8 +356,8 @@ func (m *Medium) Delete(path string) error { return resultError("local.Delete", core.Concat("delete failed: ", path), unrestrictedFileSystem.Delete(resolvedPath)) } -func (m *Medium) DeleteAll(path string) error { - resolvedPath, err := m.validatePath(path) +func (medium *Medium) DeleteAll(path string) error { + resolvedPath, err := medium.validatePath(path) if err != nil { return err } @@ -370,24 +367,24 @@ func (m *Medium) DeleteAll(path string) error { return resultError("local.DeleteAll", core.Concat("delete all failed: ", path), unrestrictedFileSystem.DeleteAll(resolvedPath)) } -func (m *Medium) Rename(oldPath, newPath string) error { - oldResolvedPath, err := m.validatePath(oldPath) +func (medium *Medium) Rename(oldPath, newPath string) error { + oldResolvedPath, err := medium.validatePath(oldPath) if err != nil { return err } - newResolvedPath, err := m.validatePath(newPath) + newResolvedPath, err := medium.validatePath(newPath) if err != nil { return err } return resultError("local.Rename", core.Concat("rename failed: ", oldPath), unrestrictedFileSystem.Rename(oldResolvedPath, newResolvedPath)) } -func (m *Medium) FileGet(path string) (string, error) { - return m.Read(path) +func (medium *Medium) FileGet(path string) (string, error) { + return medium.Read(path) } -func (m *Medium) FileSet(path, content string) error { - return m.Write(path, content) +func (medium *Medium) FileSet(path, content string) error { + return medium.Write(path, content) } func lstat(path string) (*syscall.Stat_t, error) { diff --git a/node/node.go b/node/node.go index 36f491a..82d7dfe 100644 --- a/node/node.go +++ b/node/node.go @@ -39,7 +39,7 @@ func New() *Node { // ---------- Node-specific methods ---------- // AddData stages content in the in-memory filesystem. -func (n *Node) AddData(name string, content []byte) { +func (node *Node) AddData(name string, content []byte) { name = core.TrimPrefix(name, "/") if name == "" { return @@ -48,7 +48,7 @@ func (n *Node) AddData(name string, content []byte) { if core.HasSuffix(name, "/") { return } - n.files[name] = &dataFile{ + node.files[name] = &dataFile{ name: name, content: content, modTime: time.Now(), @@ -56,11 +56,11 @@ func (n *Node) AddData(name string, content []byte) { } // ToTar serialises the entire in-memory tree to a tar archive. -func (n *Node) ToTar() ([]byte, error) { +func (node *Node) ToTar() ([]byte, error) { buf := new(bytes.Buffer) tw := tar.NewWriter(buf) - for _, file := range n.files { + for _, file := range node.files { hdr := &tar.Header{ Name: file.name, Mode: 0600, @@ -92,7 +92,7 @@ func FromTar(data []byte) (*Node, error) { } // LoadTar replaces the in-memory tree with the contents of a tar archive. -func (n *Node) LoadTar(data []byte) error { +func (node *Node) LoadTar(data []byte) error { newFiles := make(map[string]*dataFile) tr := tar.NewReader(bytes.NewReader(data)) @@ -122,12 +122,12 @@ func (n *Node) LoadTar(data []byte) error { } } - n.files = newFiles + node.files = newFiles return nil } -func (n *Node) WalkNode(root string, fn fs.WalkDirFunc) error { - return fs.WalkDir(n, root, fn) +func (node *Node) WalkNode(root string, fn fs.WalkDirFunc) error { + return fs.WalkDir(node, root, fn) } // Example: options := node.WalkOptions{MaxDepth: 1, SkipErrors: true} @@ -147,15 +147,15 @@ type WalkOptions struct { // nodeTree := New() // options := WalkOptions{MaxDepth: 1, SkipErrors: true} // _ = nodeTree.WalkWithOptions(".", func(path string, entry fs.DirEntry, err error) error { return nil }, options) -func (n *Node) WalkWithOptions(root string, fn fs.WalkDirFunc, options WalkOptions) error { +func (node *Node) WalkWithOptions(root string, fn fs.WalkDirFunc, options WalkOptions) error { if options.SkipErrors { // If root doesn't exist, silently return nil. - if _, err := n.Stat(root); err != nil { + if _, err := node.Stat(root); err != nil { return nil } } - return fs.WalkDir(n, root, func(entryPath string, entry fs.DirEntry, err error) error { + return fs.WalkDir(node, root, func(entryPath string, entry fs.DirEntry, err error) error { if options.Filter != nil && err == nil { if !options.Filter(entryPath, entry) { if entry != nil && entry.IsDir() { @@ -182,9 +182,9 @@ func (n *Node) WalkWithOptions(root string, fn fs.WalkDirFunc, options WalkOptio }) } -func (n *Node) ReadFile(name string) ([]byte, error) { +func (node *Node) ReadFile(name string) ([]byte, error) { name = core.TrimPrefix(name, "/") - f, ok := n.files[name] + f, ok := node.files[name] if !ok { return nil, core.E("node.ReadFile", core.Concat("path not found: ", name), fs.ErrNotExist) } @@ -195,12 +195,12 @@ func (n *Node) ReadFile(name string) ([]byte, error) { } // CopyFile copies a file from the in-memory tree to the local filesystem. -func (n *Node) CopyFile(sourcePath, destinationPath string, perm fs.FileMode) error { +func (node *Node) CopyFile(sourcePath, destinationPath string, perm fs.FileMode) error { sourcePath = core.TrimPrefix(sourcePath, "/") - f, ok := n.files[sourcePath] + f, ok := node.files[sourcePath] if !ok { // Check if it's a directory — can't copy directories this way. - info, err := n.Stat(sourcePath) + info, err := node.Stat(sourcePath) if err != nil { return core.E("node.CopyFile", core.Concat("source not found: ", sourcePath), fs.ErrNotExist) } @@ -221,17 +221,17 @@ func (n *Node) CopyFile(sourcePath, destinationPath string, perm fs.FileMode) er // Example usage: // // dst := io.NewMockMedium() -// _ = n.CopyTo(dst, "config", "backup/config") -func (n *Node) CopyTo(target coreio.Medium, sourcePath, destPath string) error { +// _ = node.CopyTo(dst, "config", "backup/config") +func (node *Node) CopyTo(target coreio.Medium, sourcePath, destPath string) error { sourcePath = core.TrimPrefix(sourcePath, "/") - info, err := n.Stat(sourcePath) + info, err := node.Stat(sourcePath) if err != nil { return err } if !info.IsDir() { // Single file copy - f, ok := n.files[sourcePath] + f, ok := node.files[sourcePath] if !ok { return core.E("node.CopyTo", core.Concat("path not found: ", sourcePath), fs.ErrNotExist) } @@ -244,7 +244,7 @@ func (n *Node) CopyTo(target coreio.Medium, sourcePath, destPath string) error { prefix += "/" } - for filePath, f := range n.files { + for filePath, f := range node.files { if !core.HasPrefix(filePath, prefix) && filePath != sourcePath { continue } @@ -262,9 +262,9 @@ func (n *Node) CopyTo(target coreio.Medium, sourcePath, destPath string) error { // ---------- Medium interface: fs.FS methods ---------- -func (n *Node) Open(name string) (fs.File, error) { +func (node *Node) Open(name string) (fs.File, error) { name = core.TrimPrefix(name, "/") - if file, ok := n.files[name]; ok { + if file, ok := node.files[name]; ok { return &dataFileReader{file: file}, nil } // Check if it's a directory @@ -272,7 +272,7 @@ func (n *Node) Open(name string) (fs.File, error) { if name == "." || name == "" { prefix = "" } - for filePath := range n.files { + for filePath := range node.files { if core.HasPrefix(filePath, prefix) { return &dirFile{path: name, modTime: time.Now()}, nil } @@ -280,9 +280,9 @@ func (n *Node) Open(name string) (fs.File, error) { return nil, core.E("node.Open", core.Concat("path not found: ", name), fs.ErrNotExist) } -func (n *Node) Stat(name string) (fs.FileInfo, error) { +func (node *Node) Stat(name string) (fs.FileInfo, error) { name = core.TrimPrefix(name, "/") - if file, ok := n.files[name]; ok { + if file, ok := node.files[name]; ok { return file.Stat() } // Check if it's a directory @@ -290,7 +290,7 @@ func (n *Node) Stat(name string) (fs.FileInfo, error) { if name == "." || name == "" { prefix = "" } - for filePath := range n.files { + for filePath := range node.files { if core.HasPrefix(filePath, prefix) { return &dirInfo{name: path.Base(name), modTime: time.Now()}, nil } @@ -298,14 +298,14 @@ func (n *Node) Stat(name string) (fs.FileInfo, error) { return nil, core.E("node.Stat", core.Concat("path not found: ", name), fs.ErrNotExist) } -func (n *Node) ReadDir(name string) ([]fs.DirEntry, error) { +func (node *Node) ReadDir(name string) ([]fs.DirEntry, error) { name = core.TrimPrefix(name, "/") if name == "." { name = "" } // Disallow reading a file as a directory. - if info, err := n.Stat(name); err == nil && !info.IsDir() { + if info, err := node.Stat(name); err == nil && !info.IsDir() { return nil, &fs.PathError{Op: "readdir", Path: name, Err: fs.ErrInvalid} } @@ -317,7 +317,7 @@ func (n *Node) ReadDir(name string) ([]fs.DirEntry, error) { prefix = name + "/" } - for filePath := range n.files { + for filePath := range node.files { if !core.HasPrefix(filePath, prefix) { continue } @@ -334,7 +334,7 @@ func (n *Node) ReadDir(name string) ([]fs.DirEntry, error) { dir := &dirInfo{name: firstComponent, modTime: time.Now()} entries = append(entries, fs.FileInfoToDirEntry(dir)) } else { - file := n.files[filePath] + file := node.files[filePath] info, _ := file.Stat() entries = append(entries, fs.FileInfoToDirEntry(info)) } @@ -349,52 +349,52 @@ func (n *Node) ReadDir(name string) ([]fs.DirEntry, error) { // ---------- Medium interface: read/write ---------- -func (n *Node) Read(filePath string) (string, error) { +func (node *Node) Read(filePath string) (string, error) { filePath = core.TrimPrefix(filePath, "/") - f, ok := n.files[filePath] + f, ok := node.files[filePath] if !ok { return "", core.E("node.Read", core.Concat("path not found: ", filePath), fs.ErrNotExist) } return string(f.content), nil } -func (n *Node) Write(filePath, content string) error { - n.AddData(filePath, []byte(content)) +func (node *Node) Write(filePath, content string) error { + node.AddData(filePath, []byte(content)) return nil } -func (n *Node) WriteMode(filePath, content string, mode fs.FileMode) error { - return n.Write(filePath, content) +func (node *Node) WriteMode(filePath, content string, mode fs.FileMode) error { + return node.Write(filePath, content) } -func (n *Node) FileGet(filePath string) (string, error) { - return n.Read(filePath) +func (node *Node) FileGet(filePath string) (string, error) { + return node.Read(filePath) } -func (n *Node) FileSet(filePath, content string) error { - return n.Write(filePath, content) +func (node *Node) FileSet(filePath, content string) error { + return node.Write(filePath, content) } // Example: _ = nodeTree.EnsureDir("config") -func (n *Node) EnsureDir(_ string) error { +func (node *Node) EnsureDir(_ string) error { return nil } // ---------- Medium interface: existence checks ---------- -func (n *Node) Exists(filePath string) bool { - _, err := n.Stat(filePath) +func (node *Node) Exists(filePath string) bool { + _, err := node.Stat(filePath) return err == nil } -func (n *Node) IsFile(filePath string) bool { +func (node *Node) IsFile(filePath string) bool { filePath = core.TrimPrefix(filePath, "/") - _, ok := n.files[filePath] + _, ok := node.files[filePath] return ok } -func (n *Node) IsDir(filePath string) bool { - info, err := n.Stat(filePath) +func (node *Node) IsDir(filePath string) bool { + info, err := node.Stat(filePath) if err != nil { return false } @@ -403,28 +403,28 @@ func (n *Node) IsDir(filePath string) bool { // ---------- Medium interface: mutations ---------- -func (n *Node) Delete(filePath string) error { +func (node *Node) Delete(filePath string) error { filePath = core.TrimPrefix(filePath, "/") - if _, ok := n.files[filePath]; ok { - delete(n.files, filePath) + if _, ok := node.files[filePath]; ok { + delete(node.files, filePath) return nil } return core.E("node.Delete", core.Concat("path not found: ", filePath), fs.ErrNotExist) } -func (n *Node) DeleteAll(filePath string) error { +func (node *Node) DeleteAll(filePath string) error { filePath = core.TrimPrefix(filePath, "/") found := false - if _, ok := n.files[filePath]; ok { - delete(n.files, filePath) + if _, ok := node.files[filePath]; ok { + delete(node.files, filePath) found = true } prefix := filePath + "/" - for entryPath := range n.files { + for entryPath := range node.files { if core.HasPrefix(entryPath, prefix) { - delete(n.files, entryPath) + delete(node.files, entryPath) found = true } } @@ -435,56 +435,56 @@ func (n *Node) DeleteAll(filePath string) error { return nil } -func (n *Node) Rename(oldPath, newPath string) error { +func (node *Node) Rename(oldPath, newPath string) error { oldPath = core.TrimPrefix(oldPath, "/") newPath = core.TrimPrefix(newPath, "/") - f, ok := n.files[oldPath] + f, ok := node.files[oldPath] if !ok { return core.E("node.Rename", core.Concat("path not found: ", oldPath), fs.ErrNotExist) } f.name = newPath - n.files[newPath] = f - delete(n.files, oldPath) + node.files[newPath] = f + delete(node.files, oldPath) return nil } -func (n *Node) List(filePath string) ([]fs.DirEntry, error) { +func (node *Node) List(filePath string) ([]fs.DirEntry, error) { filePath = core.TrimPrefix(filePath, "/") if filePath == "" || filePath == "." { - return n.ReadDir(".") + return node.ReadDir(".") } - return n.ReadDir(filePath) + return node.ReadDir(filePath) } // ---------- Medium interface: streams ---------- -func (n *Node) Create(filePath string) (goio.WriteCloser, error) { +func (node *Node) Create(filePath string) (goio.WriteCloser, error) { filePath = core.TrimPrefix(filePath, "/") - return &nodeWriter{node: n, path: filePath}, nil + return &nodeWriter{node: node, path: filePath}, nil } -func (n *Node) Append(filePath string) (goio.WriteCloser, error) { +func (node *Node) Append(filePath string) (goio.WriteCloser, error) { filePath = core.TrimPrefix(filePath, "/") var existing []byte - if f, ok := n.files[filePath]; ok { + if f, ok := node.files[filePath]; ok { existing = make([]byte, len(f.content)) copy(existing, f.content) } - return &nodeWriter{node: n, path: filePath, buf: existing}, nil + return &nodeWriter{node: node, path: filePath, buf: existing}, nil } -func (n *Node) ReadStream(filePath string) (goio.ReadCloser, error) { - f, err := n.Open(filePath) +func (node *Node) ReadStream(filePath string) (goio.ReadCloser, error) { + f, err := node.Open(filePath) if err != nil { return nil, err } return goio.NopCloser(f), nil } -func (n *Node) WriteStream(filePath string) (goio.WriteCloser, error) { - return n.Create(filePath) +func (node *Node) WriteStream(filePath string) (goio.WriteCloser, error) { + return node.Create(filePath) } // ---------- Internal types ---------- diff --git a/s3/s3.go b/s3/s3.go index 055c949..c1b270b 100644 --- a/s3/s3.go +++ b/s3/s3.go @@ -100,16 +100,16 @@ func New(options Options) (*Medium, error) { if options.Client == nil { return nil, core.E("s3.New", "client is required", nil) } - m := &Medium{ + medium := &Medium{ client: options.Client, bucket: options.Bucket, prefix: normalisePrefix(options.Prefix), } - return m, nil + return medium, nil } // objectKey maps a virtual path to the full S3 object key. -func (m *Medium) objectKey(filePath string) string { +func (medium *Medium) objectKey(filePath string) string { // Clean the path using a leading "/" to sandbox traversal attempts, // then strip the "/" prefix. This ensures ".." can't escape. clean := path.Clean("/" + filePath) @@ -118,23 +118,23 @@ func (m *Medium) objectKey(filePath string) string { } clean = core.TrimPrefix(clean, "/") - if m.prefix == "" { + if medium.prefix == "" { return clean } if clean == "" { - return m.prefix + return medium.prefix } - return m.prefix + clean + return medium.prefix + clean } -func (m *Medium) Read(filePath string) (string, error) { - key := m.objectKey(filePath) +func (medium *Medium) Read(filePath string) (string, error) { + key := medium.objectKey(filePath) if key == "" { return "", core.E("s3.Read", "path is required", fs.ErrInvalid) } - out, err := m.client.GetObject(context.Background(), &awss3.GetObjectInput{ - Bucket: aws.String(m.bucket), + out, err := medium.client.GetObject(context.Background(), &awss3.GetObjectInput{ + Bucket: aws.String(medium.bucket), Key: aws.String(key), }) if err != nil { @@ -149,14 +149,14 @@ func (m *Medium) Read(filePath string) (string, error) { return string(data), nil } -func (m *Medium) Write(filePath, content string) error { - key := m.objectKey(filePath) +func (medium *Medium) Write(filePath, content string) error { + key := medium.objectKey(filePath) if key == "" { return core.E("s3.Write", "path is required", fs.ErrInvalid) } - _, err := m.client.PutObject(context.Background(), &awss3.PutObjectInput{ - Bucket: aws.String(m.bucket), + _, err := medium.client.PutObject(context.Background(), &awss3.PutObjectInput{ + Bucket: aws.String(medium.bucket), Key: aws.String(key), Body: core.NewReader(content), }) @@ -167,18 +167,18 @@ func (m *Medium) Write(filePath, content string) error { } // Example: _ = medium.WriteMode("keys/private.key", key, 0600) -func (m *Medium) WriteMode(filePath, content string, _ fs.FileMode) error { - return m.Write(filePath, content) +func (medium *Medium) WriteMode(filePath, content string, _ fs.FileMode) error { + return medium.Write(filePath, content) } // Example: _ = medium.EnsureDir("reports/2026") -func (m *Medium) EnsureDir(_ string) error { +func (medium *Medium) EnsureDir(_ string) error { return nil } // Example: ok := medium.IsFile("reports/daily.txt") -func (m *Medium) IsFile(filePath string) bool { - key := m.objectKey(filePath) +func (medium *Medium) IsFile(filePath string) bool { + key := medium.objectKey(filePath) if key == "" { return false } @@ -186,29 +186,29 @@ func (m *Medium) IsFile(filePath string) bool { if core.HasSuffix(key, "/") { return false } - _, err := m.client.HeadObject(context.Background(), &awss3.HeadObjectInput{ - Bucket: aws.String(m.bucket), + _, err := medium.client.HeadObject(context.Background(), &awss3.HeadObjectInput{ + Bucket: aws.String(medium.bucket), Key: aws.String(key), }) return err == nil } -func (m *Medium) FileGet(filePath string) (string, error) { - return m.Read(filePath) +func (medium *Medium) FileGet(filePath string) (string, error) { + return medium.Read(filePath) } -func (m *Medium) FileSet(filePath, content string) error { - return m.Write(filePath, content) +func (medium *Medium) FileSet(filePath, content string) error { + return medium.Write(filePath, content) } -func (m *Medium) Delete(filePath string) error { - key := m.objectKey(filePath) +func (medium *Medium) Delete(filePath string) error { + key := medium.objectKey(filePath) if key == "" { return core.E("s3.Delete", "path is required", fs.ErrInvalid) } - _, err := m.client.DeleteObject(context.Background(), &awss3.DeleteObjectInput{ - Bucket: aws.String(m.bucket), + _, err := medium.client.DeleteObject(context.Background(), &awss3.DeleteObjectInput{ + Bucket: aws.String(medium.bucket), Key: aws.String(key), }) if err != nil { @@ -218,15 +218,15 @@ func (m *Medium) Delete(filePath string) error { } // Example: _ = medium.DeleteAll("reports/2026") -func (m *Medium) DeleteAll(filePath string) error { - key := m.objectKey(filePath) +func (medium *Medium) DeleteAll(filePath string) error { + key := medium.objectKey(filePath) if key == "" { return core.E("s3.DeleteAll", "path is required", fs.ErrInvalid) } // First, try deleting the exact key - _, err := m.client.DeleteObject(context.Background(), &awss3.DeleteObjectInput{ - Bucket: aws.String(m.bucket), + _, err := medium.client.DeleteObject(context.Background(), &awss3.DeleteObjectInput{ + Bucket: aws.String(medium.bucket), Key: aws.String(key), }) if err != nil { @@ -243,8 +243,8 @@ func (m *Medium) DeleteAll(filePath string) error { var continuationToken *string for paginator { - listOut, err := m.client.ListObjectsV2(context.Background(), &awss3.ListObjectsV2Input{ - Bucket: aws.String(m.bucket), + listOut, err := medium.client.ListObjectsV2(context.Background(), &awss3.ListObjectsV2Input{ + Bucket: aws.String(medium.bucket), Prefix: aws.String(prefix), ContinuationToken: continuationToken, }) @@ -261,8 +261,8 @@ func (m *Medium) DeleteAll(filePath string) error { objects[i] = types.ObjectIdentifier{Key: obj.Key} } - deleteOut, err := m.client.DeleteObjects(context.Background(), &awss3.DeleteObjectsInput{ - Bucket: aws.String(m.bucket), + deleteOut, err := medium.client.DeleteObjects(context.Background(), &awss3.DeleteObjectsInput{ + Bucket: aws.String(medium.bucket), Delete: &types.Delete{Objects: objects, Quiet: aws.Bool(true)}, }) if err != nil { @@ -283,17 +283,17 @@ func (m *Medium) DeleteAll(filePath string) error { } // Example: _ = medium.Rename("drafts/todo.txt", "archive/todo.txt") -func (m *Medium) Rename(oldPath, newPath string) error { - oldKey := m.objectKey(oldPath) - newKey := m.objectKey(newPath) +func (medium *Medium) Rename(oldPath, newPath string) error { + oldKey := medium.objectKey(oldPath) + newKey := medium.objectKey(newPath) if oldKey == "" || newKey == "" { return core.E("s3.Rename", "both old and new paths are required", fs.ErrInvalid) } - copySource := m.bucket + "/" + oldKey + copySource := medium.bucket + "/" + oldKey - _, err := m.client.CopyObject(context.Background(), &awss3.CopyObjectInput{ - Bucket: aws.String(m.bucket), + _, err := medium.client.CopyObject(context.Background(), &awss3.CopyObjectInput{ + Bucket: aws.String(medium.bucket), CopySource: aws.String(copySource), Key: aws.String(newKey), }) @@ -301,8 +301,8 @@ func (m *Medium) Rename(oldPath, newPath string) error { return core.E("s3.Rename", core.Concat("failed to copy object: ", oldKey, " -> ", newKey), err) } - _, err = m.client.DeleteObject(context.Background(), &awss3.DeleteObjectInput{ - Bucket: aws.String(m.bucket), + _, err = medium.client.DeleteObject(context.Background(), &awss3.DeleteObjectInput{ + Bucket: aws.String(medium.bucket), Key: aws.String(oldKey), }) if err != nil { @@ -313,16 +313,16 @@ func (m *Medium) Rename(oldPath, newPath string) error { } // Example: entries, _ := medium.List("reports") -func (m *Medium) List(filePath string) ([]fs.DirEntry, error) { - prefix := m.objectKey(filePath) +func (medium *Medium) List(filePath string) ([]fs.DirEntry, error) { + prefix := medium.objectKey(filePath) if prefix != "" && !core.HasSuffix(prefix, "/") { prefix += "/" } var entries []fs.DirEntry - listOut, err := m.client.ListObjectsV2(context.Background(), &awss3.ListObjectsV2Input{ - Bucket: aws.String(m.bucket), + listOut, err := medium.client.ListObjectsV2(context.Background(), &awss3.ListObjectsV2Input{ + Bucket: aws.String(medium.bucket), Prefix: aws.String(prefix), Delimiter: aws.String("/"), }) @@ -386,14 +386,14 @@ func (m *Medium) List(filePath string) ([]fs.DirEntry, error) { } // Example: info, _ := medium.Stat("reports/daily.txt") -func (m *Medium) Stat(filePath string) (fs.FileInfo, error) { - key := m.objectKey(filePath) +func (medium *Medium) Stat(filePath string) (fs.FileInfo, error) { + key := medium.objectKey(filePath) if key == "" { return nil, core.E("s3.Stat", "path is required", fs.ErrInvalid) } - out, err := m.client.HeadObject(context.Background(), &awss3.HeadObjectInput{ - Bucket: aws.String(m.bucket), + out, err := medium.client.HeadObject(context.Background(), &awss3.HeadObjectInput{ + Bucket: aws.String(medium.bucket), Key: aws.String(key), }) if err != nil { @@ -418,14 +418,14 @@ func (m *Medium) Stat(filePath string) (fs.FileInfo, error) { }, nil } -func (m *Medium) Open(filePath string) (fs.File, error) { - key := m.objectKey(filePath) +func (medium *Medium) Open(filePath string) (fs.File, error) { + key := medium.objectKey(filePath) if key == "" { return nil, core.E("s3.Open", "path is required", fs.ErrInvalid) } - out, err := m.client.GetObject(context.Background(), &awss3.GetObjectInput{ - Bucket: aws.String(m.bucket), + out, err := medium.client.GetObject(context.Background(), &awss3.GetObjectInput{ + Bucket: aws.String(medium.bucket), Key: aws.String(key), }) if err != nil { @@ -456,27 +456,27 @@ func (m *Medium) Open(filePath string) (fs.File, error) { } // Example: writer, _ := medium.Create("reports/daily.txt") -func (m *Medium) Create(filePath string) (goio.WriteCloser, error) { - key := m.objectKey(filePath) +func (medium *Medium) Create(filePath string) (goio.WriteCloser, error) { + key := medium.objectKey(filePath) if key == "" { return nil, core.E("s3.Create", "path is required", fs.ErrInvalid) } return &s3WriteCloser{ - medium: m, + medium: medium, key: key, }, nil } // Example: writer, _ := medium.Append("reports/daily.txt") -func (m *Medium) Append(filePath string) (goio.WriteCloser, error) { - key := m.objectKey(filePath) +func (medium *Medium) Append(filePath string) (goio.WriteCloser, error) { + key := medium.objectKey(filePath) if key == "" { return nil, core.E("s3.Append", "path is required", fs.ErrInvalid) } var existing []byte - out, err := m.client.GetObject(context.Background(), &awss3.GetObjectInput{ - Bucket: aws.String(m.bucket), + out, err := medium.client.GetObject(context.Background(), &awss3.GetObjectInput{ + Bucket: aws.String(medium.bucket), Key: aws.String(key), }) if err == nil { @@ -485,20 +485,20 @@ func (m *Medium) Append(filePath string) (goio.WriteCloser, error) { } return &s3WriteCloser{ - medium: m, + medium: medium, key: key, data: existing, }, nil } -func (m *Medium) ReadStream(filePath string) (goio.ReadCloser, error) { - key := m.objectKey(filePath) +func (medium *Medium) ReadStream(filePath string) (goio.ReadCloser, error) { + key := medium.objectKey(filePath) if key == "" { return nil, core.E("s3.ReadStream", "path is required", fs.ErrInvalid) } - out, err := m.client.GetObject(context.Background(), &awss3.GetObjectInput{ - Bucket: aws.String(m.bucket), + out, err := medium.client.GetObject(context.Background(), &awss3.GetObjectInput{ + Bucket: aws.String(medium.bucket), Key: aws.String(key), }) if err != nil { @@ -507,20 +507,20 @@ func (m *Medium) ReadStream(filePath string) (goio.ReadCloser, error) { return out.Body, nil } -func (m *Medium) WriteStream(filePath string) (goio.WriteCloser, error) { - return m.Create(filePath) +func (medium *Medium) WriteStream(filePath string) (goio.WriteCloser, error) { + return medium.Create(filePath) } // Example: ok := medium.Exists("reports/daily.txt") -func (m *Medium) Exists(filePath string) bool { - key := m.objectKey(filePath) +func (medium *Medium) Exists(filePath string) bool { + key := medium.objectKey(filePath) if key == "" { return false } // Check as an exact object - _, err := m.client.HeadObject(context.Background(), &awss3.HeadObjectInput{ - Bucket: aws.String(m.bucket), + _, err := medium.client.HeadObject(context.Background(), &awss3.HeadObjectInput{ + Bucket: aws.String(medium.bucket), Key: aws.String(key), }) if err == nil { @@ -532,8 +532,8 @@ func (m *Medium) Exists(filePath string) bool { if !core.HasSuffix(prefix, "/") { prefix += "/" } - listOut, err := m.client.ListObjectsV2(context.Background(), &awss3.ListObjectsV2Input{ - Bucket: aws.String(m.bucket), + listOut, err := medium.client.ListObjectsV2(context.Background(), &awss3.ListObjectsV2Input{ + Bucket: aws.String(medium.bucket), Prefix: aws.String(prefix), MaxKeys: aws.Int32(1), }) @@ -544,8 +544,8 @@ func (m *Medium) Exists(filePath string) bool { } // Example: ok := medium.IsDir("reports") -func (m *Medium) IsDir(filePath string) bool { - key := m.objectKey(filePath) +func (medium *Medium) IsDir(filePath string) bool { + key := medium.objectKey(filePath) if key == "" { return false } @@ -555,8 +555,8 @@ func (m *Medium) IsDir(filePath string) bool { prefix += "/" } - listOut, err := m.client.ListObjectsV2(context.Background(), &awss3.ListObjectsV2Input{ - Bucket: aws.String(m.bucket), + listOut, err := medium.client.ListObjectsV2(context.Background(), &awss3.ListObjectsV2Input{ + Bucket: aws.String(medium.bucket), Prefix: aws.String(prefix), MaxKeys: aws.Int32(1), }) diff --git a/sigil/crypto_sigil.go b/sigil/crypto_sigil.go index 802878a..52e4ba5 100644 --- a/sigil/crypto_sigil.go +++ b/sigil/crypto_sigil.go @@ -45,25 +45,25 @@ type PreObfuscator interface { type XORObfuscator struct{} // Obfuscate XORs the data with a key stream derived from the entropy. -func (x *XORObfuscator) Obfuscate(data []byte, entropy []byte) []byte { +func (obfuscator *XORObfuscator) Obfuscate(data []byte, entropy []byte) []byte { if len(data) == 0 { return data } - return x.transform(data, entropy) + return obfuscator.transform(data, entropy) } // Deobfuscate reverses the XOR transformation (XOR is symmetric). -func (x *XORObfuscator) Deobfuscate(data []byte, entropy []byte) []byte { +func (obfuscator *XORObfuscator) Deobfuscate(data []byte, entropy []byte) []byte { if len(data) == 0 { return data } - return x.transform(data, entropy) + return obfuscator.transform(data, entropy) } // transform applies XOR with an entropy-derived key stream. -func (x *XORObfuscator) transform(data []byte, entropy []byte) []byte { +func (obfuscator *XORObfuscator) transform(data []byte, entropy []byte) []byte { result := make([]byte, len(data)) - keyStream := x.deriveKeyStream(entropy, len(data)) + keyStream := obfuscator.deriveKeyStream(entropy, len(data)) for i := range data { result[i] = data[i] ^ keyStream[i] } @@ -71,7 +71,7 @@ func (x *XORObfuscator) transform(data []byte, entropy []byte) []byte { } // deriveKeyStream creates a deterministic key stream from entropy. -func (x *XORObfuscator) deriveKeyStream(entropy []byte, length int) []byte { +func (obfuscator *XORObfuscator) deriveKeyStream(entropy []byte, length int) []byte { stream := make([]byte, length) h := sha256.New() @@ -98,7 +98,7 @@ func (x *XORObfuscator) deriveKeyStream(entropy []byte, length int) []byte { type ShuffleMaskObfuscator struct{} // Obfuscate shuffles bytes and applies a mask derived from entropy. -func (s *ShuffleMaskObfuscator) Obfuscate(data []byte, entropy []byte) []byte { +func (obfuscator *ShuffleMaskObfuscator) Obfuscate(data []byte, entropy []byte) []byte { if len(data) == 0 { return data } @@ -107,8 +107,8 @@ func (s *ShuffleMaskObfuscator) Obfuscate(data []byte, entropy []byte) []byte { copy(result, data) // Generate permutation and mask from entropy - perm := s.generatePermutation(entropy, len(data)) - mask := s.deriveMask(entropy, len(data)) + perm := obfuscator.generatePermutation(entropy, len(data)) + mask := obfuscator.deriveMask(entropy, len(data)) // Apply mask first, then shuffle for i := range result { @@ -125,7 +125,7 @@ func (s *ShuffleMaskObfuscator) Obfuscate(data []byte, entropy []byte) []byte { } // Deobfuscate reverses the shuffle and mask operations. -func (s *ShuffleMaskObfuscator) Deobfuscate(data []byte, entropy []byte) []byte { +func (obfuscator *ShuffleMaskObfuscator) Deobfuscate(data []byte, entropy []byte) []byte { if len(data) == 0 { return data } @@ -133,8 +133,8 @@ func (s *ShuffleMaskObfuscator) Deobfuscate(data []byte, entropy []byte) []byte result := make([]byte, len(data)) // Generate permutation and mask from entropy - perm := s.generatePermutation(entropy, len(data)) - mask := s.deriveMask(entropy, len(data)) + perm := obfuscator.generatePermutation(entropy, len(data)) + mask := obfuscator.deriveMask(entropy, len(data)) // Unshuffle first for i, p := range perm { @@ -150,7 +150,7 @@ func (s *ShuffleMaskObfuscator) Deobfuscate(data []byte, entropy []byte) []byte } // generatePermutation creates a deterministic permutation from entropy. -func (s *ShuffleMaskObfuscator) generatePermutation(entropy []byte, length int) []int { +func (obfuscator *ShuffleMaskObfuscator) generatePermutation(entropy []byte, length int) []int { perm := make([]int, length) for i := range perm { perm[i] = i @@ -178,7 +178,7 @@ func (s *ShuffleMaskObfuscator) generatePermutation(entropy []byte, length int) } // deriveMask creates a mask byte array from entropy. -func (s *ShuffleMaskObfuscator) deriveMask(entropy []byte, length int) []byte { +func (obfuscator *ShuffleMaskObfuscator) deriveMask(entropy []byte, length int) []byte { mask := make([]byte, length) h := sha256.New() @@ -247,22 +247,22 @@ func NewChaChaPolySigilWithObfuscator(key []byte, obfuscator PreObfuscator) (*Ch } // In encrypts plaintext with the configured pre-obfuscator. -func (s *ChaChaPolySigil) In(data []byte) ([]byte, error) { - if s.Key == nil { +func (sigil *ChaChaPolySigil) In(data []byte) ([]byte, error) { + if sigil.Key == nil { return nil, NoKeyConfiguredError } if data == nil { return nil, nil } - aead, err := chacha20poly1305.NewX(s.Key) + aead, err := chacha20poly1305.NewX(sigil.Key) if err != nil { return nil, core.E("sigil.ChaChaPolySigil.In", "create cipher", err) } // Generate nonce nonce := make([]byte, aead.NonceSize()) - reader := s.randomReader + reader := sigil.randomReader if reader == nil { reader = rand.Reader } @@ -273,8 +273,8 @@ func (s *ChaChaPolySigil) In(data []byte) ([]byte, error) { // Pre-obfuscate the plaintext using nonce as entropy // This ensures CPU encryption routines never see raw plaintext obfuscated := data - if s.Obfuscator != nil { - obfuscated = s.Obfuscator.Obfuscate(data, nonce) + if sigil.Obfuscator != nil { + obfuscated = sigil.Obfuscator.Obfuscate(data, nonce) } // Encrypt the obfuscated data @@ -285,15 +285,15 @@ func (s *ChaChaPolySigil) In(data []byte) ([]byte, error) { } // Out decrypts ciphertext and reverses the pre-obfuscation step. -func (s *ChaChaPolySigil) Out(data []byte) ([]byte, error) { - if s.Key == nil { +func (sigil *ChaChaPolySigil) Out(data []byte) ([]byte, error) { + if sigil.Key == nil { return nil, NoKeyConfiguredError } if data == nil { return nil, nil } - aead, err := chacha20poly1305.NewX(s.Key) + aead, err := chacha20poly1305.NewX(sigil.Key) if err != nil { return nil, core.E("sigil.ChaChaPolySigil.Out", "create cipher", err) } @@ -315,8 +315,8 @@ func (s *ChaChaPolySigil) Out(data []byte) ([]byte, error) { // Deobfuscate using the same nonce as entropy plaintext := obfuscated - if s.Obfuscator != nil { - plaintext = s.Obfuscator.Deobfuscate(obfuscated, nonce) + if sigil.Obfuscator != nil { + plaintext = sigil.Obfuscator.Deobfuscate(obfuscated, nonce) } if len(plaintext) == 0 { diff --git a/sigil/sigil.go b/sigil/sigil.go index e12d847..77df934 100644 --- a/sigil/sigil.go +++ b/sigil/sigil.go @@ -20,8 +20,8 @@ type Sigil interface { // Example: encoded, _ := sigil.Transmute([]byte("payload"), []sigil.Sigil{hexSigil, gzipSigil}) func Transmute(data []byte, sigils []Sigil) ([]byte, error) { var err error - for _, s := range sigils { - data, err = s.In(data) + for _, sigilValue := range sigils { + data, err = sigilValue.In(data) if err != nil { return nil, core.E("sigil.Transmute", "sigil in failed", err) } diff --git a/sigil/sigils.go b/sigil/sigils.go index 38cb994..36d82df 100644 --- a/sigil/sigils.go +++ b/sigil/sigils.go @@ -25,7 +25,7 @@ import ( type ReverseSigil struct{} // In reverses the bytes of the data. -func (s *ReverseSigil) In(data []byte) ([]byte, error) { +func (sigil *ReverseSigil) In(data []byte) ([]byte, error) { if data == nil { return nil, nil } @@ -37,8 +37,8 @@ func (s *ReverseSigil) In(data []byte) ([]byte, error) { } // Out reverses the bytes of the data. -func (s *ReverseSigil) Out(data []byte) ([]byte, error) { - return s.In(data) +func (sigil *ReverseSigil) Out(data []byte) ([]byte, error) { + return sigil.In(data) } // HexSigil is a Sigil that encodes/decodes data to/from hexadecimal. @@ -46,7 +46,7 @@ func (s *ReverseSigil) Out(data []byte) ([]byte, error) { type HexSigil struct{} // In encodes the data to hexadecimal. -func (s *HexSigil) In(data []byte) ([]byte, error) { +func (sigil *HexSigil) In(data []byte) ([]byte, error) { if data == nil { return nil, nil } @@ -56,7 +56,7 @@ func (s *HexSigil) In(data []byte) ([]byte, error) { } // Out decodes the data from hexadecimal. -func (s *HexSigil) Out(data []byte) ([]byte, error) { +func (sigil *HexSigil) Out(data []byte) ([]byte, error) { if data == nil { return nil, nil } @@ -70,7 +70,7 @@ func (s *HexSigil) Out(data []byte) ([]byte, error) { type Base64Sigil struct{} // In encodes the data to base64. -func (s *Base64Sigil) In(data []byte) ([]byte, error) { +func (sigil *Base64Sigil) In(data []byte) ([]byte, error) { if data == nil { return nil, nil } @@ -80,7 +80,7 @@ func (s *Base64Sigil) In(data []byte) ([]byte, error) { } // Out decodes the data from base64. -func (s *Base64Sigil) Out(data []byte) ([]byte, error) { +func (sigil *Base64Sigil) Out(data []byte) ([]byte, error) { if data == nil { return nil, nil } @@ -96,12 +96,12 @@ type GzipSigil struct { } // In compresses the data using gzip. -func (s *GzipSigil) In(data []byte) ([]byte, error) { +func (sigil *GzipSigil) In(data []byte) ([]byte, error) { if data == nil { return nil, nil } var b bytes.Buffer - outputWriter := s.outputWriter + outputWriter := sigil.outputWriter if outputWriter == nil { outputWriter = &b } @@ -116,7 +116,7 @@ func (s *GzipSigil) In(data []byte) ([]byte, error) { } // Out decompresses the data using gzip. -func (s *GzipSigil) Out(data []byte) ([]byte, error) { +func (sigil *GzipSigil) Out(data []byte) ([]byte, error) { if data == nil { return nil, nil } @@ -137,7 +137,7 @@ func (s *GzipSigil) Out(data []byte) ([]byte, error) { type JSONSigil struct{ Indent bool } // In compacts or indents the JSON data. -func (s *JSONSigil) In(data []byte) ([]byte, error) { +func (sigil *JSONSigil) In(data []byte) ([]byte, error) { if data == nil { return nil, nil } @@ -152,14 +152,14 @@ func (s *JSONSigil) In(data []byte) ([]byte, error) { } compact := core.JSONMarshalString(decoded) - if s.Indent { + if sigil.Indent { return []byte(indentJSON(compact)), nil } return []byte(compact), nil } // Out is a no-op for JSONSigil. -func (s *JSONSigil) Out(data []byte) ([]byte, error) { +func (sigil *JSONSigil) Out(data []byte) ([]byte, error) { // For simplicity, Out is a no-op. The primary use is formatting. return data, nil } @@ -179,9 +179,9 @@ func NewHashSigil(h crypto.Hash) *HashSigil { } // In hashes the data. -func (s *HashSigil) In(data []byte) ([]byte, error) { +func (sigil *HashSigil) In(data []byte) ([]byte, error) { var hasher goio.Writer - switch s.Hash { + switch sigil.Hash { case crypto.MD4: hasher = md4.New() case crypto.MD5: @@ -228,7 +228,7 @@ func (s *HashSigil) In(data []byte) ([]byte, error) { } // Out is a no-op for HashSigil. -func (s *HashSigil) Out(data []byte) ([]byte, error) { +func (sigil *HashSigil) Out(data []byte) ([]byte, error) { return data, nil } diff --git a/sqlite/sqlite.go b/sqlite/sqlite.go index 87a5d99..bd02cca 100644 --- a/sqlite/sqlite.go +++ b/sqlite/sqlite.go @@ -79,9 +79,9 @@ func New(options Options) (*Medium, error) { } // Close closes the underlying database connection. -func (m *Medium) Close() error { - if m.database != nil { - return m.database.Close() +func (medium *Medium) Close() error { + if medium.database != nil { + return medium.database.Close() } return nil } @@ -96,7 +96,7 @@ func normaliseEntryPath(filePath string) string { return core.TrimPrefix(clean, "/") } -func (m *Medium) Read(filePath string) (string, error) { +func (medium *Medium) Read(filePath string) (string, error) { key := normaliseEntryPath(filePath) if key == "" { return "", core.E("sqlite.Read", "path is required", fs.ErrInvalid) @@ -104,8 +104,8 @@ func (m *Medium) Read(filePath string) (string, error) { var content []byte var isDir bool - err := m.database.QueryRow( - `SELECT content, is_dir FROM `+m.table+` WHERE path = ?`, key, + err := medium.database.QueryRow( + `SELECT content, is_dir FROM `+medium.table+` WHERE path = ?`, key, ).Scan(&content, &isDir) if err == sql.ErrNoRows { return "", core.E("sqlite.Read", core.Concat("file not found: ", key), fs.ErrNotExist) @@ -119,19 +119,19 @@ func (m *Medium) Read(filePath string) (string, error) { return string(content), nil } -func (m *Medium) Write(filePath, content string) error { - return m.WriteMode(filePath, content, 0644) +func (medium *Medium) Write(filePath, content string) error { + return medium.WriteMode(filePath, content, 0644) } // Example: _ = medium.WriteMode("keys/private.key", key, 0600) -func (m *Medium) WriteMode(filePath, content string, mode fs.FileMode) error { +func (medium *Medium) WriteMode(filePath, content string, mode fs.FileMode) error { key := normaliseEntryPath(filePath) if key == "" { return core.E("sqlite.WriteMode", "path is required", fs.ErrInvalid) } - _, err := m.database.Exec( - `INSERT INTO `+m.table+` (path, content, mode, is_dir, mtime) VALUES (?, ?, ?, FALSE, ?) + _, err := medium.database.Exec( + `INSERT INTO `+medium.table+` (path, content, mode, is_dir, mtime) VALUES (?, ?, ?, FALSE, ?) ON CONFLICT(path) DO UPDATE SET content = excluded.content, mode = excluded.mode, is_dir = FALSE, mtime = excluded.mtime`, key, []byte(content), int(mode), time.Now().UTC(), ) @@ -142,15 +142,15 @@ func (m *Medium) WriteMode(filePath, content string, mode fs.FileMode) error { } // Example: _ = medium.EnsureDir("config") -func (m *Medium) EnsureDir(filePath string) error { +func (medium *Medium) EnsureDir(filePath string) error { key := normaliseEntryPath(filePath) if key == "" { // Root always "exists" return nil } - _, err := m.database.Exec( - `INSERT INTO `+m.table+` (path, content, mode, is_dir, mtime) VALUES (?, '', 493, TRUE, ?) + _, err := medium.database.Exec( + `INSERT INTO `+medium.table+` (path, content, mode, is_dir, mtime) VALUES (?, '', 493, TRUE, ?) ON CONFLICT(path) DO NOTHING`, key, time.Now().UTC(), ) @@ -160,15 +160,15 @@ func (m *Medium) EnsureDir(filePath string) error { return nil } -func (m *Medium) IsFile(filePath string) bool { +func (medium *Medium) IsFile(filePath string) bool { key := normaliseEntryPath(filePath) if key == "" { return false } var isDir bool - err := m.database.QueryRow( - `SELECT is_dir FROM `+m.table+` WHERE path = ?`, key, + err := medium.database.QueryRow( + `SELECT is_dir FROM `+medium.table+` WHERE path = ?`, key, ).Scan(&isDir) if err != nil { return false @@ -176,16 +176,16 @@ func (m *Medium) IsFile(filePath string) bool { return !isDir } -func (m *Medium) FileGet(filePath string) (string, error) { - return m.Read(filePath) +func (medium *Medium) FileGet(filePath string) (string, error) { + return medium.Read(filePath) } -func (m *Medium) FileSet(filePath, content string) error { - return m.Write(filePath, content) +func (medium *Medium) FileSet(filePath, content string) error { + return medium.Write(filePath, content) } // Example: _ = medium.Delete("config/app.yaml") -func (m *Medium) Delete(filePath string) error { +func (medium *Medium) Delete(filePath string) error { key := normaliseEntryPath(filePath) if key == "" { return core.E("sqlite.Delete", "path is required", fs.ErrInvalid) @@ -193,8 +193,8 @@ func (m *Medium) Delete(filePath string) error { // Check if it's a directory with children var isDir bool - err := m.database.QueryRow( - `SELECT is_dir FROM `+m.table+` WHERE path = ?`, key, + err := medium.database.QueryRow( + `SELECT is_dir FROM `+medium.table+` WHERE path = ?`, key, ).Scan(&isDir) if err == sql.ErrNoRows { return core.E("sqlite.Delete", core.Concat("path not found: ", key), fs.ErrNotExist) @@ -207,8 +207,8 @@ func (m *Medium) Delete(filePath string) error { // Check for children prefix := key + "/" var count int - err := m.database.QueryRow( - `SELECT COUNT(*) FROM `+m.table+` WHERE path LIKE ? AND path != ?`, prefix+"%", key, + err := medium.database.QueryRow( + `SELECT COUNT(*) FROM `+medium.table+` WHERE path LIKE ? AND path != ?`, prefix+"%", key, ).Scan(&count) if err != nil { return core.E("sqlite.Delete", core.Concat("count failed: ", key), err) @@ -218,7 +218,7 @@ func (m *Medium) Delete(filePath string) error { } } - res, err := m.database.Exec(`DELETE FROM `+m.table+` WHERE path = ?`, key) + res, err := medium.database.Exec(`DELETE FROM `+medium.table+` WHERE path = ?`, key) if err != nil { return core.E("sqlite.Delete", core.Concat("delete failed: ", key), err) } @@ -230,7 +230,7 @@ func (m *Medium) Delete(filePath string) error { } // Example: _ = medium.DeleteAll("config") -func (m *Medium) DeleteAll(filePath string) error { +func (medium *Medium) DeleteAll(filePath string) error { key := normaliseEntryPath(filePath) if key == "" { return core.E("sqlite.DeleteAll", "path is required", fs.ErrInvalid) @@ -239,8 +239,8 @@ func (m *Medium) DeleteAll(filePath string) error { prefix := key + "/" // Delete the exact path and all children - res, err := m.database.Exec( - `DELETE FROM `+m.table+` WHERE path = ? OR path LIKE ?`, + res, err := medium.database.Exec( + `DELETE FROM `+medium.table+` WHERE path = ? OR path LIKE ?`, key, prefix+"%", ) if err != nil { @@ -254,14 +254,14 @@ func (m *Medium) DeleteAll(filePath string) error { } // Example: _ = medium.Rename("drafts/todo.txt", "archive/todo.txt") -func (m *Medium) Rename(oldPath, newPath string) error { +func (medium *Medium) Rename(oldPath, newPath string) error { oldKey := normaliseEntryPath(oldPath) newKey := normaliseEntryPath(newPath) if oldKey == "" || newKey == "" { return core.E("sqlite.Rename", "both old and new paths are required", fs.ErrInvalid) } - tx, err := m.database.Begin() + tx, err := medium.database.Begin() if err != nil { return core.E("sqlite.Rename", "begin tx failed", err) } @@ -273,7 +273,7 @@ func (m *Medium) Rename(oldPath, newPath string) error { var isDir bool var mtime time.Time err = tx.QueryRow( - `SELECT content, mode, is_dir, mtime FROM `+m.table+` WHERE path = ?`, oldKey, + `SELECT content, mode, is_dir, mtime FROM `+medium.table+` WHERE path = ?`, oldKey, ).Scan(&content, &mode, &isDir, &mtime) if err == sql.ErrNoRows { return core.E("sqlite.Rename", core.Concat("source not found: ", oldKey), fs.ErrNotExist) @@ -284,7 +284,7 @@ func (m *Medium) Rename(oldPath, newPath string) error { // Insert or replace at new path _, err = tx.Exec( - `INSERT INTO `+m.table+` (path, content, mode, is_dir, mtime) VALUES (?, ?, ?, ?, ?) + `INSERT INTO `+medium.table+` (path, content, mode, is_dir, mtime) VALUES (?, ?, ?, ?, ?) ON CONFLICT(path) DO UPDATE SET content = excluded.content, mode = excluded.mode, is_dir = excluded.is_dir, mtime = excluded.mtime`, newKey, content, mode, isDir, mtime, ) @@ -293,7 +293,7 @@ func (m *Medium) Rename(oldPath, newPath string) error { } // Delete old path - _, err = tx.Exec(`DELETE FROM `+m.table+` WHERE path = ?`, oldKey) + _, err = tx.Exec(`DELETE FROM `+medium.table+` WHERE path = ?`, oldKey) if err != nil { return core.E("sqlite.Rename", core.Concat("delete old path failed: ", oldKey), err) } @@ -304,7 +304,7 @@ func (m *Medium) Rename(oldPath, newPath string) error { newPrefix := newKey + "/" rows, err := tx.Query( - `SELECT path, content, mode, is_dir, mtime FROM `+m.table+` WHERE path LIKE ?`, + `SELECT path, content, mode, is_dir, mtime FROM `+medium.table+` WHERE path LIKE ?`, oldPrefix+"%", ) if err != nil { @@ -332,7 +332,7 @@ func (m *Medium) Rename(oldPath, newPath string) error { for _, c := range children { newChildPath := core.Concat(newPrefix, core.TrimPrefix(c.path, oldPrefix)) _, err = tx.Exec( - `INSERT INTO `+m.table+` (path, content, mode, is_dir, mtime) VALUES (?, ?, ?, ?, ?) + `INSERT INTO `+medium.table+` (path, content, mode, is_dir, mtime) VALUES (?, ?, ?, ?, ?) ON CONFLICT(path) DO UPDATE SET content = excluded.content, mode = excluded.mode, is_dir = excluded.is_dir, mtime = excluded.mtime`, newChildPath, c.content, c.mode, c.isDir, c.mtime, ) @@ -342,7 +342,7 @@ func (m *Medium) Rename(oldPath, newPath string) error { } // Delete old children - _, err = tx.Exec(`DELETE FROM `+m.table+` WHERE path LIKE ?`, oldPrefix+"%") + _, err = tx.Exec(`DELETE FROM `+medium.table+` WHERE path LIKE ?`, oldPrefix+"%") if err != nil { return core.E("sqlite.Rename", "delete old children failed", err) } @@ -352,15 +352,15 @@ func (m *Medium) Rename(oldPath, newPath string) error { } // Example: entries, _ := medium.List("config") -func (m *Medium) List(filePath string) ([]fs.DirEntry, error) { +func (medium *Medium) List(filePath string) ([]fs.DirEntry, error) { prefix := normaliseEntryPath(filePath) if prefix != "" { prefix += "/" } // Query all paths under the prefix - rows, err := m.database.Query( - `SELECT path, content, mode, is_dir, mtime FROM `+m.table+` WHERE path LIKE ? OR path LIKE ?`, + rows, err := medium.database.Query( + `SELECT path, content, mode, is_dir, mtime FROM `+medium.table+` WHERE path LIKE ? OR path LIKE ?`, prefix+"%", prefix+"%", ) if err != nil { @@ -427,7 +427,7 @@ func (m *Medium) List(filePath string) ([]fs.DirEntry, error) { return entries, rows.Err() } -func (m *Medium) Stat(filePath string) (fs.FileInfo, error) { +func (medium *Medium) Stat(filePath string) (fs.FileInfo, error) { key := normaliseEntryPath(filePath) if key == "" { return nil, core.E("sqlite.Stat", "path is required", fs.ErrInvalid) @@ -437,8 +437,8 @@ func (m *Medium) Stat(filePath string) (fs.FileInfo, error) { var mode int var isDir bool var mtime time.Time - err := m.database.QueryRow( - `SELECT content, mode, is_dir, mtime FROM `+m.table+` WHERE path = ?`, key, + err := medium.database.QueryRow( + `SELECT content, mode, is_dir, mtime FROM `+medium.table+` WHERE path = ?`, key, ).Scan(&content, &mode, &isDir, &mtime) if err == sql.ErrNoRows { return nil, core.E("sqlite.Stat", core.Concat("path not found: ", key), fs.ErrNotExist) @@ -457,7 +457,7 @@ func (m *Medium) Stat(filePath string) (fs.FileInfo, error) { }, nil } -func (m *Medium) Open(filePath string) (fs.File, error) { +func (medium *Medium) Open(filePath string) (fs.File, error) { key := normaliseEntryPath(filePath) if key == "" { return nil, core.E("sqlite.Open", "path is required", fs.ErrInvalid) @@ -467,8 +467,8 @@ func (m *Medium) Open(filePath string) (fs.File, error) { var mode int var isDir bool var mtime time.Time - err := m.database.QueryRow( - `SELECT content, mode, is_dir, mtime FROM `+m.table+` WHERE path = ?`, key, + err := medium.database.QueryRow( + `SELECT content, mode, is_dir, mtime FROM `+medium.table+` WHERE path = ?`, key, ).Scan(&content, &mode, &isDir, &mtime) if err == sql.ErrNoRows { return nil, core.E("sqlite.Open", core.Concat("file not found: ", key), fs.ErrNotExist) @@ -488,39 +488,39 @@ func (m *Medium) Open(filePath string) (fs.File, error) { }, nil } -func (m *Medium) Create(filePath string) (goio.WriteCloser, error) { +func (medium *Medium) Create(filePath string) (goio.WriteCloser, error) { key := normaliseEntryPath(filePath) if key == "" { return nil, core.E("sqlite.Create", "path is required", fs.ErrInvalid) } return &sqliteWriteCloser{ - medium: m, + medium: medium, path: key, }, nil } -func (m *Medium) Append(filePath string) (goio.WriteCloser, error) { +func (medium *Medium) Append(filePath string) (goio.WriteCloser, error) { key := normaliseEntryPath(filePath) if key == "" { return nil, core.E("sqlite.Append", "path is required", fs.ErrInvalid) } var existing []byte - err := m.database.QueryRow( - `SELECT content FROM `+m.table+` WHERE path = ? AND is_dir = FALSE`, key, + err := medium.database.QueryRow( + `SELECT content FROM `+medium.table+` WHERE path = ? AND is_dir = FALSE`, key, ).Scan(&existing) if err != nil && err != sql.ErrNoRows { return nil, core.E("sqlite.Append", core.Concat("query failed: ", key), err) } return &sqliteWriteCloser{ - medium: m, + medium: medium, path: key, data: existing, }, nil } -func (m *Medium) ReadStream(filePath string) (goio.ReadCloser, error) { +func (medium *Medium) ReadStream(filePath string) (goio.ReadCloser, error) { key := normaliseEntryPath(filePath) if key == "" { return nil, core.E("sqlite.ReadStream", "path is required", fs.ErrInvalid) @@ -528,8 +528,8 @@ func (m *Medium) ReadStream(filePath string) (goio.ReadCloser, error) { var content []byte var isDir bool - err := m.database.QueryRow( - `SELECT content, is_dir FROM `+m.table+` WHERE path = ?`, key, + err := medium.database.QueryRow( + `SELECT content, is_dir FROM `+medium.table+` WHERE path = ?`, key, ).Scan(&content, &isDir) if err == sql.ErrNoRows { return nil, core.E("sqlite.ReadStream", core.Concat("file not found: ", key), fs.ErrNotExist) @@ -544,11 +544,11 @@ func (m *Medium) ReadStream(filePath string) (goio.ReadCloser, error) { return goio.NopCloser(bytes.NewReader(content)), nil } -func (m *Medium) WriteStream(filePath string) (goio.WriteCloser, error) { - return m.Create(filePath) +func (medium *Medium) WriteStream(filePath string) (goio.WriteCloser, error) { + return medium.Create(filePath) } -func (m *Medium) Exists(filePath string) bool { +func (medium *Medium) Exists(filePath string) bool { key := normaliseEntryPath(filePath) if key == "" { // Root always exists @@ -556,8 +556,8 @@ func (m *Medium) Exists(filePath string) bool { } var count int - err := m.database.QueryRow( - `SELECT COUNT(*) FROM `+m.table+` WHERE path = ?`, key, + err := medium.database.QueryRow( + `SELECT COUNT(*) FROM `+medium.table+` WHERE path = ?`, key, ).Scan(&count) if err != nil { return false @@ -565,15 +565,15 @@ func (m *Medium) Exists(filePath string) bool { return count > 0 } -func (m *Medium) IsDir(filePath string) bool { +func (medium *Medium) IsDir(filePath string) bool { key := normaliseEntryPath(filePath) if key == "" { return false } var isDir bool - err := m.database.QueryRow( - `SELECT is_dir FROM `+m.table+` WHERE path = ?`, key, + err := medium.database.QueryRow( + `SELECT is_dir FROM `+medium.table+` WHERE path = ?`, key, ).Scan(&isDir) if err != nil { return false diff --git a/store/medium.go b/store/medium.go index 1d5feca..7461264 100644 --- a/store/medium.go +++ b/store/medium.go @@ -31,18 +31,18 @@ func NewMedium(options Options) (*Medium, error) { } // Example: medium := keyValueStore.AsMedium() -func (s *Store) AsMedium() *Medium { - return &Medium{store: s} +func (store *Store) AsMedium() *Medium { + return &Medium{store: store} } // Example: keyValueStore := medium.Store() -func (m *Medium) Store() *Store { - return m.store +func (medium *Medium) Store() *Store { + return medium.store } // Example: _ = medium.Close() -func (m *Medium) Close() error { - return m.store.Close() +func (medium *Medium) Close() error { + return medium.store.Close() } // splitGroupKeyPath splits a group/key path into store components. @@ -59,56 +59,56 @@ func splitGroupKeyPath(entryPath string) (group, key string) { return parts[0], parts[1] } -func (m *Medium) Read(entryPath string) (string, error) { +func (medium *Medium) Read(entryPath string) (string, error) { group, key := splitGroupKeyPath(entryPath) if key == "" { return "", core.E("store.Read", "path must include group/key", fs.ErrInvalid) } - return m.store.Get(group, key) + return medium.store.Get(group, key) } -func (m *Medium) Write(entryPath, content string) error { +func (medium *Medium) Write(entryPath, content string) error { group, key := splitGroupKeyPath(entryPath) if key == "" { return core.E("store.Write", "path must include group/key", fs.ErrInvalid) } - return m.store.Set(group, key, content) + return medium.store.Set(group, key, content) } // Example: _ = medium.WriteMode("app/theme", "midnight", 0600) -func (m *Medium) WriteMode(entryPath, content string, _ fs.FileMode) error { - return m.Write(entryPath, content) +func (medium *Medium) WriteMode(entryPath, content string, _ fs.FileMode) error { + return medium.Write(entryPath, content) } // Example: _ = medium.EnsureDir("app") -func (m *Medium) EnsureDir(_ string) error { +func (medium *Medium) EnsureDir(_ string) error { return nil } -func (m *Medium) IsFile(entryPath string) bool { +func (medium *Medium) IsFile(entryPath string) bool { group, key := splitGroupKeyPath(entryPath) if key == "" { return false } - _, err := m.store.Get(group, key) + _, err := medium.store.Get(group, key) return err == nil } -func (m *Medium) FileGet(entryPath string) (string, error) { - return m.Read(entryPath) +func (medium *Medium) FileGet(entryPath string) (string, error) { + return medium.Read(entryPath) } -func (m *Medium) FileSet(entryPath, content string) error { - return m.Write(entryPath, content) +func (medium *Medium) FileSet(entryPath, content string) error { + return medium.Write(entryPath, content) } -func (m *Medium) Delete(entryPath string) error { +func (medium *Medium) Delete(entryPath string) error { group, key := splitGroupKeyPath(entryPath) if group == "" { return core.E("store.Delete", "path is required", fs.ErrInvalid) } if key == "" { - entryCount, err := m.store.Count(group) + entryCount, err := medium.store.Count(group) if err != nil { return err } @@ -117,42 +117,42 @@ func (m *Medium) Delete(entryPath string) error { } return nil } - return m.store.Delete(group, key) + return medium.store.Delete(group, key) } -func (m *Medium) DeleteAll(entryPath string) error { +func (medium *Medium) DeleteAll(entryPath string) error { group, key := splitGroupKeyPath(entryPath) if group == "" { return core.E("store.DeleteAll", "path is required", fs.ErrInvalid) } if key == "" { - return m.store.DeleteGroup(group) + return medium.store.DeleteGroup(group) } - return m.store.Delete(group, key) + return medium.store.Delete(group, key) } -func (m *Medium) Rename(oldPath, newPath string) error { +func (medium *Medium) Rename(oldPath, newPath string) error { oldGroup, oldKey := splitGroupKeyPath(oldPath) newGroup, newKey := splitGroupKeyPath(newPath) if oldKey == "" || newKey == "" { return core.E("store.Rename", "both paths must include group/key", fs.ErrInvalid) } - val, err := m.store.Get(oldGroup, oldKey) + val, err := medium.store.Get(oldGroup, oldKey) if err != nil { return err } - if err := m.store.Set(newGroup, newKey, val); err != nil { + if err := medium.store.Set(newGroup, newKey, val); err != nil { return err } - return m.store.Delete(oldGroup, oldKey) + return medium.store.Delete(oldGroup, oldKey) } // Example: entries, _ := medium.List("app") -func (m *Medium) List(entryPath string) ([]fs.DirEntry, error) { +func (medium *Medium) List(entryPath string) ([]fs.DirEntry, error) { group, key := splitGroupKeyPath(entryPath) if group == "" { - rows, err := m.store.database.Query("SELECT DISTINCT grp FROM kv ORDER BY grp") + rows, err := medium.store.database.Query("SELECT DISTINCT grp FROM kv ORDER BY grp") if err != nil { return nil, core.E("store.List", "query groups", err) } @@ -173,7 +173,7 @@ func (m *Medium) List(entryPath string) ([]fs.DirEntry, error) { return nil, nil // leaf node, nothing beneath } - all, err := m.store.GetAll(group) + all, err := medium.store.GetAll(group) if err != nil { return nil, err } @@ -185,13 +185,13 @@ func (m *Medium) List(entryPath string) ([]fs.DirEntry, error) { } // Example: info, _ := medium.Stat("app/theme") -func (m *Medium) Stat(entryPath string) (fs.FileInfo, error) { +func (medium *Medium) Stat(entryPath string) (fs.FileInfo, error) { group, key := splitGroupKeyPath(entryPath) if group == "" { return nil, core.E("store.Stat", "path is required", fs.ErrInvalid) } if key == "" { - entryCount, err := m.store.Count(group) + entryCount, err := medium.store.Count(group) if err != nil { return nil, err } @@ -200,77 +200,77 @@ func (m *Medium) Stat(entryPath string) (fs.FileInfo, error) { } return &keyValueFileInfo{name: group, isDir: true}, nil } - val, err := m.store.Get(group, key) + value, err := medium.store.Get(group, key) if err != nil { return nil, err } - return &keyValueFileInfo{name: key, size: int64(len(val))}, nil + return &keyValueFileInfo{name: key, size: int64(len(value))}, nil } -func (m *Medium) Open(entryPath string) (fs.File, error) { +func (medium *Medium) Open(entryPath string) (fs.File, error) { group, key := splitGroupKeyPath(entryPath) if key == "" { return nil, core.E("store.Open", "path must include group/key", fs.ErrInvalid) } - val, err := m.store.Get(group, key) + value, err := medium.store.Get(group, key) if err != nil { return nil, err } - return &keyValueFile{name: key, content: []byte(val)}, nil + return &keyValueFile{name: key, content: []byte(value)}, nil } -func (m *Medium) Create(entryPath string) (goio.WriteCloser, error) { +func (medium *Medium) Create(entryPath string) (goio.WriteCloser, error) { group, key := splitGroupKeyPath(entryPath) if key == "" { return nil, core.E("store.Create", "path must include group/key", fs.ErrInvalid) } - return &keyValueWriteCloser{store: m.store, group: group, key: key}, nil + return &keyValueWriteCloser{store: medium.store, group: group, key: key}, nil } -func (m *Medium) Append(entryPath string) (goio.WriteCloser, error) { +func (medium *Medium) Append(entryPath string) (goio.WriteCloser, error) { group, key := splitGroupKeyPath(entryPath) if key == "" { return nil, core.E("store.Append", "path must include group/key", fs.ErrInvalid) } - existing, _ := m.store.Get(group, key) - return &keyValueWriteCloser{store: m.store, group: group, key: key, data: []byte(existing)}, nil + existingValue, _ := medium.store.Get(group, key) + return &keyValueWriteCloser{store: medium.store, group: group, key: key, data: []byte(existingValue)}, nil } -func (m *Medium) ReadStream(entryPath string) (goio.ReadCloser, error) { +func (medium *Medium) ReadStream(entryPath string) (goio.ReadCloser, error) { group, key := splitGroupKeyPath(entryPath) if key == "" { return nil, core.E("store.ReadStream", "path must include group/key", fs.ErrInvalid) } - val, err := m.store.Get(group, key) + val, err := medium.store.Get(group, key) if err != nil { return nil, err } return goio.NopCloser(core.NewReader(val)), nil } -func (m *Medium) WriteStream(entryPath string) (goio.WriteCloser, error) { - return m.Create(entryPath) +func (medium *Medium) WriteStream(entryPath string) (goio.WriteCloser, error) { + return medium.Create(entryPath) } -func (m *Medium) Exists(entryPath string) bool { +func (medium *Medium) Exists(entryPath string) bool { group, key := splitGroupKeyPath(entryPath) if group == "" { return false } if key == "" { - entryCount, err := m.store.Count(group) + entryCount, err := medium.store.Count(group) return err == nil && entryCount > 0 } - _, err := m.store.Get(group, key) + _, err := medium.store.Get(group, key) return err == nil } -func (m *Medium) IsDir(entryPath string) bool { +func (medium *Medium) IsDir(entryPath string) bool { group, key := splitGroupKeyPath(entryPath) if key != "" || group == "" { return false } - entryCount, err := m.store.Count(group) + entryCount, err := medium.store.Count(group) return err == nil && entryCount > 0 } @@ -282,22 +282,22 @@ type keyValueFileInfo struct { isDir bool } -func (fi *keyValueFileInfo) Name() string { return fi.name } +func (fileInfo *keyValueFileInfo) Name() string { return fileInfo.name } -func (fi *keyValueFileInfo) Size() int64 { return fi.size } +func (fileInfo *keyValueFileInfo) Size() int64 { return fileInfo.size } -func (fi *keyValueFileInfo) Mode() fs.FileMode { - if fi.isDir { +func (fileInfo *keyValueFileInfo) Mode() fs.FileMode { + if fileInfo.isDir { return fs.ModeDir | 0755 } return 0644 } -func (fi *keyValueFileInfo) ModTime() time.Time { return time.Time{} } +func (fileInfo *keyValueFileInfo) ModTime() time.Time { return time.Time{} } -func (fi *keyValueFileInfo) IsDir() bool { return fi.isDir } +func (fileInfo *keyValueFileInfo) IsDir() bool { return fileInfo.isDir } -func (fi *keyValueFileInfo) Sys() any { return nil } +func (fileInfo *keyValueFileInfo) Sys() any { return nil } type keyValueDirEntry struct { name string @@ -305,19 +305,19 @@ type keyValueDirEntry struct { size int64 } -func (de *keyValueDirEntry) Name() string { return de.name } +func (entry *keyValueDirEntry) Name() string { return entry.name } -func (de *keyValueDirEntry) IsDir() bool { return de.isDir } +func (entry *keyValueDirEntry) IsDir() bool { return entry.isDir } -func (de *keyValueDirEntry) Type() fs.FileMode { - if de.isDir { +func (entry *keyValueDirEntry) Type() fs.FileMode { + if entry.isDir { return fs.ModeDir } return 0 } -func (de *keyValueDirEntry) Info() (fs.FileInfo, error) { - return &keyValueFileInfo{name: de.name, size: de.size, isDir: de.isDir}, nil +func (entry *keyValueDirEntry) Info() (fs.FileInfo, error) { + return &keyValueFileInfo{name: entry.name, size: entry.size, isDir: entry.isDir}, nil } type keyValueFile struct { @@ -326,20 +326,20 @@ type keyValueFile struct { offset int64 } -func (f *keyValueFile) Stat() (fs.FileInfo, error) { - return &keyValueFileInfo{name: f.name, size: int64(len(f.content))}, nil +func (file *keyValueFile) Stat() (fs.FileInfo, error) { + return &keyValueFileInfo{name: file.name, size: int64(len(file.content))}, nil } -func (f *keyValueFile) Read(b []byte) (int, error) { - if f.offset >= int64(len(f.content)) { +func (file *keyValueFile) Read(buffer []byte) (int, error) { + if file.offset >= int64(len(file.content)) { return 0, goio.EOF } - n := copy(b, f.content[f.offset:]) - f.offset += int64(n) - return n, nil + readCount := copy(buffer, file.content[file.offset:]) + file.offset += int64(readCount) + return readCount, nil } -func (f *keyValueFile) Close() error { return nil } +func (file *keyValueFile) Close() error { return nil } type keyValueWriteCloser struct { store *Store @@ -348,11 +348,11 @@ type keyValueWriteCloser struct { data []byte } -func (w *keyValueWriteCloser) Write(p []byte) (int, error) { - w.data = append(w.data, p...) - return len(p), nil +func (writer *keyValueWriteCloser) Write(data []byte) (int, error) { + writer.data = append(writer.data, data...) + return len(data), nil } -func (w *keyValueWriteCloser) Close() error { - return w.store.Set(w.group, w.key, string(w.data)) +func (writer *keyValueWriteCloser) Close() error { + return writer.store.Set(writer.group, writer.key, string(writer.data)) } diff --git a/store/store.go b/store/store.go index 4d43f30..f82216c 100644 --- a/store/store.go +++ b/store/store.go @@ -52,14 +52,14 @@ func New(options Options) (*Store, error) { } // Example: _ = keyValueStore.Close() -func (s *Store) Close() error { - return s.database.Close() +func (store *Store) Close() error { + return store.database.Close() } // Example: theme, _ := keyValueStore.Get("app", "theme") -func (s *Store) Get(group, key string) (string, error) { +func (store *Store) Get(group, key string) (string, error) { var value string - err := s.database.QueryRow("SELECT value FROM kv WHERE grp = ? AND key = ?", group, key).Scan(&value) + err := store.database.QueryRow("SELECT value FROM kv WHERE grp = ? AND key = ?", group, key).Scan(&value) if err == sql.ErrNoRows { return "", core.E("store.Get", core.Concat("not found: ", group, "/", key), NotFoundError) } @@ -70,8 +70,8 @@ func (s *Store) Get(group, key string) (string, error) { } // Example: _ = keyValueStore.Set("app", "theme", "midnight") -func (s *Store) Set(group, key, value string) error { - _, err := s.database.Exec( +func (store *Store) Set(group, key, value string) error { + _, err := store.database.Exec( `INSERT INTO kv (grp, key, value) VALUES (?, ?, ?) ON CONFLICT(grp, key) DO UPDATE SET value = excluded.value`, group, key, value, @@ -83,8 +83,8 @@ func (s *Store) Set(group, key, value string) error { } // Example: _ = keyValueStore.Delete("app", "theme") -func (s *Store) Delete(group, key string) error { - _, err := s.database.Exec("DELETE FROM kv WHERE grp = ? AND key = ?", group, key) +func (store *Store) Delete(group, key string) error { + _, err := store.database.Exec("DELETE FROM kv WHERE grp = ? AND key = ?", group, key) if err != nil { return core.E("store.Delete", "exec", err) } @@ -92,9 +92,9 @@ func (s *Store) Delete(group, key string) error { } // Example: count, _ := keyValueStore.Count("app") -func (s *Store) Count(group string) (int, error) { +func (store *Store) Count(group string) (int, error) { var count int - err := s.database.QueryRow("SELECT COUNT(*) FROM kv WHERE grp = ?", group).Scan(&count) + err := store.database.QueryRow("SELECT COUNT(*) FROM kv WHERE grp = ?", group).Scan(&count) if err != nil { return 0, core.E("store.Count", "query", err) } @@ -102,8 +102,8 @@ func (s *Store) Count(group string) (int, error) { } // Example: _ = keyValueStore.DeleteGroup("app") -func (s *Store) DeleteGroup(group string) error { - _, err := s.database.Exec("DELETE FROM kv WHERE grp = ?", group) +func (store *Store) DeleteGroup(group string) error { + _, err := store.database.Exec("DELETE FROM kv WHERE grp = ?", group) if err != nil { return core.E("store.DeleteGroup", "exec", err) } @@ -111,8 +111,8 @@ func (s *Store) DeleteGroup(group string) error { } // Example: values, _ := keyValueStore.GetAll("app") -func (s *Store) GetAll(group string) (map[string]string, error) { - rows, err := s.database.Query("SELECT key, value FROM kv WHERE grp = ?", group) +func (store *Store) GetAll(group string) (map[string]string, error) { + rows, err := store.database.Query("SELECT key, value FROM kv WHERE grp = ?", group) if err != nil { return nil, core.E("store.GetAll", "query", err) } @@ -135,8 +135,8 @@ func (s *Store) GetAll(group string) (map[string]string, error) { // Example: keyValueStore, _ := store.New(store.Options{Path: ":memory:"}) // _ = keyValueStore.Set("user", "name", "alice") // out, _ := keyValueStore.Render("hello {{ .name }}", "user") -func (s *Store) Render(templateText, group string) (string, error) { - rows, err := s.database.Query("SELECT key, value FROM kv WHERE grp = ?", group) +func (store *Store) Render(templateText, group string) (string, error) { + rows, err := store.database.Query("SELECT key, value FROM kv WHERE grp = ?", group) if err != nil { return "", core.E("store.Render", "query", err) } diff --git a/workspace/service.go b/workspace/service.go index a55c8c1..d382d3a 100644 --- a/workspace/service.go +++ b/workspace/service.go @@ -57,55 +57,55 @@ func New(options Options) (*Service, error) { return nil, core.E("workspace.New", "core is required", fs.ErrInvalid) } - s := &Service{ + service := &Service{ core: options.Core, rootPath: rootPath, medium: io.Local, } if options.Crypt != nil { - s.crypt = options.Crypt + service.crypt = options.Crypt } - if err := s.medium.EnsureDir(rootPath); err != nil { + if err := service.medium.EnsureDir(rootPath); err != nil { return nil, core.E("workspace.New", "failed to ensure root directory", err) } - return s, nil + return service, nil } // Example: workspaceID, _ := service.CreateWorkspace("alice", "pass123") -func (s *Service) CreateWorkspace(identifier, password string) (string, error) { - s.mu.Lock() - defer s.mu.Unlock() +func (service *Service) CreateWorkspace(identifier, password string) (string, error) { + service.mu.Lock() + defer service.mu.Unlock() - if s.crypt == nil { + if service.crypt == nil { return "", core.E("workspace.CreateWorkspace", "crypt service not available", nil) } hash := sha256.Sum256([]byte(identifier)) workspaceID := hex.EncodeToString(hash[:]) - workspaceDirectory, err := s.resolveWorkspaceDirectory("workspace.CreateWorkspace", workspaceID) + workspaceDirectory, err := service.resolveWorkspaceDirectory("workspace.CreateWorkspace", workspaceID) if err != nil { return "", err } - if s.medium.Exists(workspaceDirectory) { + if service.medium.Exists(workspaceDirectory) { return "", core.E("workspace.CreateWorkspace", "workspace already exists", nil) } for _, d := range []string{"config", "log", "data", "files", "keys"} { - if err := s.medium.EnsureDir(core.Path(workspaceDirectory, d)); err != nil { + if err := service.medium.EnsureDir(core.Path(workspaceDirectory, d)); err != nil { return "", core.E("workspace.CreateWorkspace", core.Concat("failed to create directory: ", d), err) } } - privKey, err := s.crypt.CreateKeyPair(identifier, password) + privKey, err := service.crypt.CreateKeyPair(identifier, password) if err != nil { return "", core.E("workspace.CreateWorkspace", "failed to generate keys", err) } - if err := s.medium.WriteMode(core.Path(workspaceDirectory, "keys", "private.key"), privKey, 0600); err != nil { + if err := service.medium.WriteMode(core.Path(workspaceDirectory, "keys", "private.key"), privKey, 0600); err != nil { return "", core.E("workspace.CreateWorkspace", "failed to save private key", err) } @@ -113,29 +113,29 @@ func (s *Service) CreateWorkspace(identifier, password string) (string, error) { } // Example: _ = service.SwitchWorkspace(workspaceID) -func (s *Service) SwitchWorkspace(workspaceID string) error { - s.mu.Lock() - defer s.mu.Unlock() +func (service *Service) SwitchWorkspace(workspaceID string) error { + service.mu.Lock() + defer service.mu.Unlock() - workspaceDirectory, err := s.resolveWorkspaceDirectory("workspace.SwitchWorkspace", workspaceID) + workspaceDirectory, err := service.resolveWorkspaceDirectory("workspace.SwitchWorkspace", workspaceID) if err != nil { return err } - if !s.medium.IsDir(workspaceDirectory) { + if !service.medium.IsDir(workspaceDirectory) { return core.E("workspace.SwitchWorkspace", core.Concat("workspace not found: ", workspaceID), nil) } - s.activeWorkspaceID = core.PathBase(workspaceDirectory) + service.activeWorkspaceID = core.PathBase(workspaceDirectory) return nil } // resolveActiveWorkspaceFilePath resolves a file path inside the active workspace files root. // It rejects empty names and traversal outside the workspace root. -func (s *Service) resolveActiveWorkspaceFilePath(operation, workspaceFilePath string) (string, error) { - if s.activeWorkspaceID == "" { +func (service *Service) resolveActiveWorkspaceFilePath(operation, workspaceFilePath string) (string, error) { + if service.activeWorkspaceID == "" { return "", core.E(operation, "no active workspace", nil) } - filesRoot := core.Path(s.rootPath, s.activeWorkspaceID, "files") + filesRoot := core.Path(service.rootPath, service.activeWorkspaceID, "files") filePath, err := joinPathWithinRoot(filesRoot, workspaceFilePath) if err != nil { return "", core.E(operation, "file path escapes workspace files", fs.ErrPermission) @@ -147,27 +147,27 @@ func (s *Service) resolveActiveWorkspaceFilePath(operation, workspaceFilePath st } // Example: content, _ := service.WorkspaceFileGet("notes/todo.txt") -func (s *Service) WorkspaceFileGet(workspaceFilePath string) (string, error) { - s.mu.RLock() - defer s.mu.RUnlock() +func (service *Service) WorkspaceFileGet(workspaceFilePath string) (string, error) { + service.mu.RLock() + defer service.mu.RUnlock() - filePath, err := s.resolveActiveWorkspaceFilePath("workspace.WorkspaceFileGet", workspaceFilePath) + filePath, err := service.resolveActiveWorkspaceFilePath("workspace.WorkspaceFileGet", workspaceFilePath) if err != nil { return "", err } - return s.medium.Read(filePath) + return service.medium.Read(filePath) } // Example: _ = service.WorkspaceFileSet("notes/todo.txt", "ship it") -func (s *Service) WorkspaceFileSet(workspaceFilePath, content string) error { - s.mu.Lock() - defer s.mu.Unlock() +func (service *Service) WorkspaceFileSet(workspaceFilePath, content string) error { + service.mu.Lock() + defer service.mu.Unlock() - filePath, err := s.resolveActiveWorkspaceFilePath("workspace.WorkspaceFileSet", workspaceFilePath) + filePath, err := service.resolveActiveWorkspaceFilePath("workspace.WorkspaceFileSet", workspaceFilePath) if err != nil { return err } - return s.medium.Write(filePath, content) + return service.medium.Write(filePath, content) } // service, _ := workspace.New(workspace.Options{Core: core.New(), Crypt: myCryptProvider}) @@ -185,7 +185,7 @@ func (s *Service) WorkspaceFileSet(workspaceFilePath, content string) error { // // _ = createResult.OK // _ = switchResult.OK -func (s *Service) HandleIPCEvents(_ *core.Core, message core.Message) core.Result { +func (service *Service) HandleIPCEvents(_ *core.Core, message core.Message) core.Result { switch payload := message.(type) { case map[string]any: action, _ := payload["action"].(string) @@ -193,14 +193,14 @@ func (s *Service) HandleIPCEvents(_ *core.Core, message core.Message) core.Resul case "workspace.create": identifier, _ := payload["identifier"].(string) password, _ := payload["password"].(string) - workspaceID, err := s.CreateWorkspace(identifier, password) + workspaceID, err := service.CreateWorkspace(identifier, password) if err != nil { return core.Result{}.New(err) } return core.Result{Value: workspaceID, OK: true} case "workspace.switch": workspaceID, _ := payload["workspaceID"].(string) - if err := s.SwitchWorkspace(workspaceID); err != nil { + if err := service.SwitchWorkspace(workspaceID); err != nil { return core.Result{}.New(err) } return core.Result{OK: true} @@ -228,15 +228,15 @@ func joinPathWithinRoot(root string, parts ...string) (string, error) { return "", fs.ErrPermission } -func (s *Service) resolveWorkspaceDirectory(operation, workspaceID string) (string, error) { +func (service *Service) resolveWorkspaceDirectory(operation, workspaceID string) (string, error) { if workspaceID == "" { return "", core.E(operation, "workspace id is required", fs.ErrInvalid) } - workspaceDirectory, err := joinPathWithinRoot(s.rootPath, workspaceID) + workspaceDirectory, err := joinPathWithinRoot(service.rootPath, workspaceID) if err != nil { return "", core.E(operation, "workspace path escapes root", err) } - if core.PathDir(workspaceDirectory) != s.rootPath { + if core.PathDir(workspaceDirectory) != service.rootPath { return "", core.E(operation, core.Concat("invalid workspace id: ", workspaceID), fs.ErrPermission) } return workspaceDirectory, nil