diff --git a/medium.go b/medium.go index f83ec9f..da8ff18 100644 --- a/medium.go +++ b/medium.go @@ -4,7 +4,6 @@ package store import ( "bytes" - goio "io" core "dappco.re/go/core" "dappco.re/go/core/io" @@ -13,24 +12,11 @@ import ( // Medium is the minimal storage transport used by the go-store workspace // import and export helpers and by Compact when writing cold archives. // -// Any `dappco.re/go/core/io.Medium` implementation (local, memory, S3, cube, -// sftp) satisfies this interface by structural typing — go-store only needs a -// handful of methods to ferry bytes between the workspace buffer and the -// underlying medium. +// This is an alias of `dappco.re/go/core/io.Medium`, so callers can pass any +// upstream medium implementation directly without an adapter. // // Usage example: `medium, _ := local.New("/tmp/exports"); storeInstance, err := store.New(":memory:", store.WithMedium(medium))` -type Medium interface { - Read(path string) (string, error) - Write(path, content string) error - EnsureDir(path string) error - Create(path string) (goio.WriteCloser, error) - Exists(path string) bool -} - -// staticMediumCheck documents that `dappco.re/go/core/io.Medium` satisfies the -// in-package `store.Medium` interface — agents pass an `io.Medium` directly to -// `store.WithMedium` without an adapter. -var _ Medium = io.Medium(nil) +type Medium = io.Medium // Usage example: `medium, _ := local.New("/srv/core"); storeInstance, err := store.NewConfigured(store.StoreConfig{DatabasePath: ":memory:", Medium: medium})` // WithMedium installs an io.Medium-compatible transport on the Store so that diff --git a/medium_test.go b/medium_test.go index 483c597..54d52f5 100644 --- a/medium_test.go +++ b/medium_test.go @@ -43,12 +43,32 @@ func (medium *memoryMedium) Write(path, content string) error { return nil } +func (medium *memoryMedium) WriteMode(path, content string, _ fs.FileMode) error { + return medium.Write(path, content) +} + func (medium *memoryMedium) EnsureDir(string) error { return nil } func (medium *memoryMedium) Create(path string) (goio.WriteCloser, error) { return &memoryWriter{medium: medium, path: path}, nil } +func (medium *memoryMedium) Append(path string) (goio.WriteCloser, error) { + medium.lock.Lock() + defer medium.lock.Unlock() + return &memoryWriter{medium: medium, path: path, buffer: *bytes.NewBufferString(medium.files[path])}, nil +} + +func (medium *memoryMedium) ReadStream(path string) (goio.ReadCloser, error) { + medium.lock.Lock() + defer medium.lock.Unlock() + return goio.NopCloser(bytes.NewReader([]byte(medium.files[path]))), nil +} + +func (medium *memoryMedium) WriteStream(path string) (goio.WriteCloser, error) { + return medium.Create(path) +} + func (medium *memoryMedium) Exists(path string) bool { medium.lock.Lock() defer medium.lock.Unlock() @@ -56,6 +76,56 @@ func (medium *memoryMedium) Exists(path string) bool { return ok } +func (medium *memoryMedium) IsFile(path string) bool { return medium.Exists(path) } + +func (medium *memoryMedium) Delete(path string) error { + medium.lock.Lock() + defer medium.lock.Unlock() + delete(medium.files, path) + return nil +} + +func (medium *memoryMedium) DeleteAll(path string) error { + medium.lock.Lock() + defer medium.lock.Unlock() + for key := range medium.files { + if key == path || core.HasPrefix(key, path+"/") { + delete(medium.files, key) + } + } + return nil +} + +func (medium *memoryMedium) Rename(oldPath, newPath string) error { + medium.lock.Lock() + defer medium.lock.Unlock() + content, ok := medium.files[oldPath] + if !ok { + return core.E("memoryMedium.Rename", "file not found: "+oldPath, nil) + } + medium.files[newPath] = content + delete(medium.files, oldPath) + return nil +} + +func (medium *memoryMedium) List(path string) ([]fs.DirEntry, error) { return nil, nil } + +func (medium *memoryMedium) Stat(path string) (fs.FileInfo, error) { + if !medium.Exists(path) { + return nil, core.E("memoryMedium.Stat", "file not found: "+path, nil) + } + return fileInfoStub{name: core.PathBase(path)}, nil +} + +func (medium *memoryMedium) Open(path string) (fs.File, error) { + if !medium.Exists(path) { + return nil, core.E("memoryMedium.Open", "file not found: "+path, nil) + } + return newMemoryFile(path, medium.files[path]), nil +} + +func (medium *memoryMedium) IsDir(string) bool { return false } + type memoryWriter struct { medium *memoryMedium path string @@ -75,6 +145,31 @@ func (writer *memoryWriter) Close() error { return writer.medium.Write(writer.path, writer.buffer.String()) } +type fileInfoStub struct { + name string +} + +func (fileInfoStub) Size() int64 { return 0 } +func (fileInfoStub) Mode() fs.FileMode { return 0 } +func (fileInfoStub) ModTime() time.Time { return time.Time{} } +func (fileInfoStub) IsDir() bool { return false } +func (fileInfoStub) Sys() any { return nil } +func (info fileInfoStub) Name() string { return info.name } + +type memoryFile struct { + *bytes.Reader + name string +} + +func newMemoryFile(name, content string) *memoryFile { + return &memoryFile{Reader: bytes.NewReader([]byte(content)), name: name} +} + +func (file *memoryFile) Stat() (fs.FileInfo, error) { + return fileInfoStub{name: core.PathBase(file.name)}, nil +} +func (file *memoryFile) Close() error { return nil } + // Ensure memoryMedium still satisfies the internal Medium contract. var _ Medium = (*memoryMedium)(nil)