DirStore: add sub-store mounting (Mount/Unmount/Child)
All methods delegate to children when path contains '/'. Children appear as directory entries in List(). Enables hierarchical namespace composition.
This commit is contained in:
parent
190ad4bcdf
commit
30e171c1ba
|
|
@ -9,7 +9,8 @@ import (
|
|||
|
||||
// DirStore is a configurable filesystem-backed Store.
|
||||
// It supports single or multiple directories (union), read-only mode,
|
||||
// a synthetic index entry, subdirectory operations, and custom path resolution.
|
||||
// a synthetic index entry, subdirectory operations, custom path resolution,
|
||||
// and mounting child DirStores at names (sub-stores).
|
||||
type DirStore struct {
|
||||
dirs []string
|
||||
perm os.FileMode
|
||||
|
|
@ -21,6 +22,7 @@ type DirStore struct {
|
|||
deleteFn func([]string, string) error
|
||||
renameFn func([]string, string, string) error
|
||||
openFn func([]string, string) (StoreEntry, error)
|
||||
children map[string]*DirStore
|
||||
}
|
||||
|
||||
// DirOption configures a DirStore.
|
||||
|
|
@ -75,6 +77,56 @@ func NewDirStore(dirs []string, perm os.FileMode, opts ...DirOption) *DirStore {
|
|||
return d
|
||||
}
|
||||
|
||||
// Mount attaches a child DirStore at the given name.
|
||||
func (d *DirStore) Mount(name string, child *DirStore) {
|
||||
if d.children == nil {
|
||||
d.children = make(map[string]*DirStore)
|
||||
}
|
||||
d.children[name] = child
|
||||
}
|
||||
|
||||
// Unmount removes a mounted child.
|
||||
func (d *DirStore) Unmount(name string) {
|
||||
delete(d.children, name)
|
||||
}
|
||||
|
||||
// Child returns the child mounted at name, or nil.
|
||||
func (d *DirStore) Child(name string) *DirStore {
|
||||
if d.children == nil {
|
||||
return nil
|
||||
}
|
||||
return d.children[name]
|
||||
}
|
||||
|
||||
// split checks if name routes to a child store. Returns (child, rest, true)
|
||||
// if a child handles this path, or (nil, "", false) otherwise.
|
||||
func (d *DirStore) split(name string) (*DirStore, string, bool) {
|
||||
if d.children == nil {
|
||||
return nil, "", false
|
||||
}
|
||||
// Check for exact child match (directory itself).
|
||||
if child, ok := d.children[name]; ok {
|
||||
return child, "", true
|
||||
}
|
||||
// Check for child prefix: "childname/rest"
|
||||
if i := indexOf(name, '/'); i >= 0 {
|
||||
prefix := name[:i]
|
||||
if child, ok := d.children[prefix]; ok {
|
||||
return child, name[i+1:], true
|
||||
}
|
||||
}
|
||||
return nil, "", false
|
||||
}
|
||||
|
||||
func indexOf(s string, b byte) int {
|
||||
for i := range len(s) {
|
||||
if s[i] == b {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func (d *DirStore) resolve(name string) (string, error) {
|
||||
if d.resolveFn != nil {
|
||||
return d.resolveFn(d.dirs, name)
|
||||
|
|
@ -93,6 +145,14 @@ func (d *DirStore) writePath(name string) string {
|
|||
}
|
||||
|
||||
func (d *DirStore) Stat(name string) (os.FileInfo, error) {
|
||||
// Check children first.
|
||||
if child, rest, ok := d.split(name); ok {
|
||||
if rest == "" {
|
||||
// The child directory itself.
|
||||
return &SyntheticFileInfo{Name_: name, Mode_: 0555, IsDir_: true}, nil
|
||||
}
|
||||
return child.Stat(rest)
|
||||
}
|
||||
if name == "idx" && d.indexFn != nil {
|
||||
return &SyntheticFileInfo{Name_: "idx", Mode_: 0444}, nil
|
||||
}
|
||||
|
|
@ -105,7 +165,6 @@ func (d *DirStore) Stat(name string) (os.FileInfo, error) {
|
|||
return nil, err
|
||||
}
|
||||
if d.resolveFn != nil {
|
||||
// Virtual name may differ from physical filename.
|
||||
return &SyntheticFileInfo{Name_: name, Mode_: info.Mode(), Size_: info.Size()}, nil
|
||||
}
|
||||
return info, nil
|
||||
|
|
@ -121,7 +180,10 @@ func (d *DirStore) List() ([]os.DirEntry, error) {
|
|||
result := make([]os.DirEntry, 0, len(entries)+1)
|
||||
result = append(result, FileEntry("idx", 0444))
|
||||
result = append(result, entries...)
|
||||
return result, nil
|
||||
entries = result
|
||||
}
|
||||
for name := range d.children {
|
||||
entries = append(entries, DirEntry(name, 0555))
|
||||
}
|
||||
return entries, nil
|
||||
}
|
||||
|
|
@ -143,10 +205,22 @@ func (d *DirStore) List() ([]os.DirEntry, error) {
|
|||
}
|
||||
}
|
||||
}
|
||||
for name := range d.children {
|
||||
if !seen[name] {
|
||||
result = append(result, DirEntry(name, 0555))
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (d *DirStore) Open(name string) (StoreEntry, error) {
|
||||
// Delegate to child stores.
|
||||
if child, rest, ok := d.split(name); ok {
|
||||
if rest == "" {
|
||||
return nil, fmt.Errorf("%s: is a directory", name)
|
||||
}
|
||||
return child.Open(rest)
|
||||
}
|
||||
notBlocking := func(context.Context, string) ([]byte, string, error) {
|
||||
return nil, "", fmt.Errorf("blocking read not supported")
|
||||
}
|
||||
|
|
@ -188,6 +262,9 @@ func (d *DirStore) Open(name string) (StoreEntry, error) {
|
|||
}
|
||||
|
||||
func (d *DirStore) Create(name string) error {
|
||||
if child, rest, ok := d.split(name); ok && rest != "" {
|
||||
return child.Create(rest)
|
||||
}
|
||||
if d.readOnly {
|
||||
return fmt.Errorf("read-only store")
|
||||
}
|
||||
|
|
@ -202,6 +279,9 @@ func (d *DirStore) Create(name string) error {
|
|||
}
|
||||
|
||||
func (d *DirStore) Delete(name string) error {
|
||||
if child, rest, ok := d.split(name); ok && rest != "" {
|
||||
return child.Delete(rest)
|
||||
}
|
||||
if d.readOnly {
|
||||
return fmt.Errorf("read-only store")
|
||||
}
|
||||
|
|
@ -216,6 +296,9 @@ func (d *DirStore) Delete(name string) error {
|
|||
}
|
||||
|
||||
func (d *DirStore) Rename(old, new string) error {
|
||||
if child, rest, ok := d.split(old); ok && rest != "" {
|
||||
return child.Rename(rest, new)
|
||||
}
|
||||
if d.readOnly {
|
||||
return fmt.Errorf("read-only store")
|
||||
}
|
||||
|
|
@ -229,15 +312,31 @@ func (d *DirStore) Rename(old, new string) error {
|
|||
return os.Rename(p, filepath.Join(filepath.Dir(p), new))
|
||||
}
|
||||
|
||||
// ListDir lists entries in a subdirectory relative to the first backing dir.
|
||||
// ListDir lists entries in a subdirectory.
|
||||
func (d *DirStore) ListDir(rel string) ([]os.DirEntry, error) {
|
||||
// Check if rel routes to a child.
|
||||
if child, rest, ok := d.split(rel); ok {
|
||||
if rest == "" {
|
||||
return child.List()
|
||||
}
|
||||
return child.ListDir(rest)
|
||||
}
|
||||
if len(d.dirs) == 0 {
|
||||
return nil, fmt.Errorf("%s: not found", rel)
|
||||
}
|
||||
return os.ReadDir(filepath.Join(d.dirs[0], rel))
|
||||
}
|
||||
|
||||
// MkdirAll creates a directory relative to the first backing dir.
|
||||
func (d *DirStore) MkdirAll(rel string) error {
|
||||
if child, rest, ok := d.split(rel); ok && rest != "" {
|
||||
return child.MkdirAll(rest)
|
||||
}
|
||||
if d.readOnly {
|
||||
return fmt.Errorf("read-only store")
|
||||
}
|
||||
if len(d.dirs) == 0 {
|
||||
return fmt.Errorf("no backing directory")
|
||||
}
|
||||
return os.MkdirAll(filepath.Join(d.dirs[0], rel), 0755)
|
||||
}
|
||||
|
|
|
|||
Reference in New Issue