2026-03-06 13:14:32 +00:00
|
|
|
package store
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
goio "io"
|
|
|
|
|
"io/fs"
|
|
|
|
|
"path"
|
2026-04-05 12:22:25 +01:00
|
|
|
"slices"
|
2026-03-06 13:14:32 +00:00
|
|
|
"time"
|
2026-03-16 18:16:14 +00:00
|
|
|
|
2026-03-26 16:23:45 +00:00
|
|
|
core "dappco.re/go/core"
|
2026-03-30 19:36:30 +00:00
|
|
|
coreio "dappco.re/go/core/io"
|
2026-03-06 13:14:32 +00:00
|
|
|
)
|
|
|
|
|
|
2026-04-05 13:37:12 +01:00
|
|
|
// ErrNotDirectory is returned by List when the path resolves to a key rather than a group.
|
|
|
|
|
// Example: _, err := medium.List("app/theme") // err == store.ErrNotDirectory
|
|
|
|
|
var ErrNotDirectory = core.E("store", "path is a key, not a directory", fs.ErrInvalid)
|
|
|
|
|
|
2026-03-30 21:23:35 +00:00
|
|
|
// Example: medium, _ := store.NewMedium(store.Options{Path: "config.db"})
|
2026-03-31 05:10:35 +00:00
|
|
|
// Example: _ = medium.Write("app/theme", "midnight")
|
|
|
|
|
// Example: entries, _ := medium.List("")
|
|
|
|
|
// Example: entries, _ := medium.List("app")
|
2026-03-06 13:14:32 +00:00
|
|
|
type Medium struct {
|
2026-03-31 13:54:58 +00:00
|
|
|
keyValueStore *KeyValueStore
|
2026-03-06 13:14:32 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-30 19:36:30 +00:00
|
|
|
var _ coreio.Medium = (*Medium)(nil)
|
|
|
|
|
|
2026-03-30 21:23:35 +00:00
|
|
|
// Example: medium, _ := store.NewMedium(store.Options{Path: "config.db"})
|
2026-03-31 05:10:35 +00:00
|
|
|
// Example: _ = medium.Write("app/theme", "midnight")
|
2026-03-30 20:52:34 +00:00
|
|
|
func NewMedium(options Options) (*Medium, error) {
|
2026-03-31 13:54:58 +00:00
|
|
|
keyValueStore, err := New(options)
|
2026-03-06 13:14:32 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2026-03-31 13:54:58 +00:00
|
|
|
return &Medium{keyValueStore: keyValueStore}, nil
|
2026-03-06 13:14:32 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-30 21:04:19 +00:00
|
|
|
// Example: medium := keyValueStore.AsMedium()
|
2026-03-31 13:54:58 +00:00
|
|
|
func (keyValueStore *KeyValueStore) AsMedium() *Medium {
|
|
|
|
|
return &Medium{keyValueStore: keyValueStore}
|
2026-03-06 13:14:32 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-31 13:54:58 +00:00
|
|
|
// Example: keyValueStore := medium.KeyValueStore()
|
|
|
|
|
func (medium *Medium) KeyValueStore() *KeyValueStore {
|
|
|
|
|
return medium.keyValueStore
|
2026-03-06 13:14:32 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-30 20:31:12 +00:00
|
|
|
// Example: _ = medium.Close()
|
2026-03-30 21:39:03 +00:00
|
|
|
func (medium *Medium) Close() error {
|
2026-03-31 13:54:58 +00:00
|
|
|
return medium.keyValueStore.Close()
|
2026-03-06 13:14:32 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-30 20:58:10 +00:00
|
|
|
func splitGroupKeyPath(entryPath string) (group, key string) {
|
2026-03-30 20:18:30 +00:00
|
|
|
clean := path.Clean(entryPath)
|
2026-03-26 16:23:45 +00:00
|
|
|
clean = core.TrimPrefix(clean, "/")
|
2026-03-06 13:14:32 +00:00
|
|
|
if clean == "" || clean == "." {
|
|
|
|
|
return "", ""
|
|
|
|
|
}
|
2026-03-26 16:23:45 +00:00
|
|
|
parts := core.SplitN(clean, "/", 2)
|
2026-03-06 13:14:32 +00:00
|
|
|
if len(parts) == 1 {
|
|
|
|
|
return parts[0], ""
|
|
|
|
|
}
|
|
|
|
|
return parts[0], parts[1]
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 21:39:03 +00:00
|
|
|
func (medium *Medium) Read(entryPath string) (string, error) {
|
2026-03-30 20:58:10 +00:00
|
|
|
group, key := splitGroupKeyPath(entryPath)
|
2026-03-06 13:14:32 +00:00
|
|
|
if key == "" {
|
2026-03-26 16:23:45 +00:00
|
|
|
return "", core.E("store.Read", "path must include group/key", fs.ErrInvalid)
|
2026-03-06 13:14:32 +00:00
|
|
|
}
|
2026-03-31 13:54:58 +00:00
|
|
|
return medium.keyValueStore.Get(group, key)
|
2026-03-06 13:14:32 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-30 21:39:03 +00:00
|
|
|
func (medium *Medium) Write(entryPath, content string) error {
|
2026-03-30 20:58:10 +00:00
|
|
|
group, key := splitGroupKeyPath(entryPath)
|
2026-03-06 13:14:32 +00:00
|
|
|
if key == "" {
|
2026-03-26 16:23:45 +00:00
|
|
|
return core.E("store.Write", "path must include group/key", fs.ErrInvalid)
|
2026-03-06 13:14:32 +00:00
|
|
|
}
|
2026-03-31 13:54:58 +00:00
|
|
|
return medium.keyValueStore.Set(group, key, content)
|
2026-03-06 13:14:32 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-30 21:23:35 +00:00
|
|
|
// Example: _ = medium.WriteMode("app/theme", "midnight", 0600)
|
2026-04-05 12:39:57 +01:00
|
|
|
// Note: mode is not persisted — the SQLite store has no entry_mode column.
|
|
|
|
|
// Use Write when mode is irrelevant; WriteMode satisfies the Medium interface only.
|
2026-03-31 05:10:35 +00:00
|
|
|
func (medium *Medium) WriteMode(entryPath, content string, mode fs.FileMode) error {
|
2026-03-30 21:39:03 +00:00
|
|
|
return medium.Write(entryPath, content)
|
2026-03-30 19:36:30 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-30 21:23:35 +00:00
|
|
|
// Example: _ = medium.EnsureDir("app")
|
2026-03-31 05:10:35 +00:00
|
|
|
func (medium *Medium) EnsureDir(entryPath string) error {
|
2026-03-06 13:14:32 +00:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 21:39:03 +00:00
|
|
|
func (medium *Medium) IsFile(entryPath string) bool {
|
2026-03-30 20:58:10 +00:00
|
|
|
group, key := splitGroupKeyPath(entryPath)
|
2026-03-06 13:14:32 +00:00
|
|
|
if key == "" {
|
|
|
|
|
return false
|
|
|
|
|
}
|
2026-03-31 13:54:58 +00:00
|
|
|
_, err := medium.keyValueStore.Get(group, key)
|
2026-03-06 13:14:32 +00:00
|
|
|
return err == nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 21:39:03 +00:00
|
|
|
func (medium *Medium) Delete(entryPath string) error {
|
2026-03-30 20:58:10 +00:00
|
|
|
group, key := splitGroupKeyPath(entryPath)
|
2026-03-06 13:14:32 +00:00
|
|
|
if group == "" {
|
2026-03-26 16:23:45 +00:00
|
|
|
return core.E("store.Delete", "path is required", fs.ErrInvalid)
|
2026-03-06 13:14:32 +00:00
|
|
|
}
|
|
|
|
|
if key == "" {
|
2026-03-31 13:54:58 +00:00
|
|
|
entryCount, err := medium.keyValueStore.Count(group)
|
2026-03-06 13:14:32 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2026-03-30 20:18:30 +00:00
|
|
|
if entryCount > 0 {
|
2026-03-26 16:23:45 +00:00
|
|
|
return core.E("store.Delete", core.Concat("group not empty: ", group), fs.ErrExist)
|
2026-03-06 13:14:32 +00:00
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2026-03-31 13:54:58 +00:00
|
|
|
return medium.keyValueStore.Delete(group, key)
|
2026-03-06 13:14:32 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-30 21:39:03 +00:00
|
|
|
func (medium *Medium) DeleteAll(entryPath string) error {
|
2026-03-30 20:58:10 +00:00
|
|
|
group, key := splitGroupKeyPath(entryPath)
|
2026-03-06 13:14:32 +00:00
|
|
|
if group == "" {
|
2026-03-26 16:23:45 +00:00
|
|
|
return core.E("store.DeleteAll", "path is required", fs.ErrInvalid)
|
2026-03-06 13:14:32 +00:00
|
|
|
}
|
|
|
|
|
if key == "" {
|
2026-03-31 13:54:58 +00:00
|
|
|
return medium.keyValueStore.DeleteGroup(group)
|
2026-03-06 13:14:32 +00:00
|
|
|
}
|
2026-03-31 13:54:58 +00:00
|
|
|
return medium.keyValueStore.Delete(group, key)
|
2026-03-06 13:14:32 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-30 21:39:03 +00:00
|
|
|
func (medium *Medium) Rename(oldPath, newPath string) error {
|
2026-03-30 20:58:10 +00:00
|
|
|
oldGroup, oldKey := splitGroupKeyPath(oldPath)
|
|
|
|
|
newGroup, newKey := splitGroupKeyPath(newPath)
|
2026-03-30 19:36:30 +00:00
|
|
|
if oldKey == "" || newKey == "" {
|
2026-03-26 16:23:45 +00:00
|
|
|
return core.E("store.Rename", "both paths must include group/key", fs.ErrInvalid)
|
2026-03-06 13:14:32 +00:00
|
|
|
}
|
2026-04-05 13:20:21 +01:00
|
|
|
if oldGroup == newGroup && oldKey == newKey {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2026-03-31 13:54:58 +00:00
|
|
|
value, err := medium.keyValueStore.Get(oldGroup, oldKey)
|
2026-03-06 13:14:32 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2026-03-31 13:54:58 +00:00
|
|
|
if err := medium.keyValueStore.Set(newGroup, newKey, value); err != nil {
|
2026-03-06 13:14:32 +00:00
|
|
|
return err
|
|
|
|
|
}
|
2026-03-31 13:54:58 +00:00
|
|
|
return medium.keyValueStore.Delete(oldGroup, oldKey)
|
2026-03-06 13:14:32 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-30 21:23:35 +00:00
|
|
|
// Example: entries, _ := medium.List("app")
|
2026-03-30 21:39:03 +00:00
|
|
|
func (medium *Medium) List(entryPath string) ([]fs.DirEntry, error) {
|
2026-03-30 20:58:10 +00:00
|
|
|
group, key := splitGroupKeyPath(entryPath)
|
2026-03-06 13:14:32 +00:00
|
|
|
|
|
|
|
|
if group == "" {
|
2026-04-05 12:39:57 +01:00
|
|
|
groups, err := medium.keyValueStore.ListGroups()
|
2026-03-06 13:14:32 +00:00
|
|
|
if err != nil {
|
2026-04-05 12:39:57 +01:00
|
|
|
return nil, err
|
2026-03-06 13:14:32 +00:00
|
|
|
}
|
2026-04-05 12:39:57 +01:00
|
|
|
entries := make([]fs.DirEntry, 0, len(groups))
|
|
|
|
|
for _, groupName := range groups {
|
2026-03-30 21:04:19 +00:00
|
|
|
entries = append(entries, &keyValueDirEntry{name: groupName, isDir: true})
|
2026-03-06 13:14:32 +00:00
|
|
|
}
|
2026-03-30 22:39:50 +00:00
|
|
|
return entries, nil
|
2026-03-06 13:14:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if key != "" {
|
2026-04-05 13:37:12 +01:00
|
|
|
return nil, ErrNotDirectory
|
2026-03-06 13:14:32 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-31 13:54:58 +00:00
|
|
|
all, err := medium.keyValueStore.GetAll(group)
|
2026-03-06 13:14:32 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2026-04-05 12:22:25 +01:00
|
|
|
// Sort keys so that List returns entries in a deterministic order.
|
|
|
|
|
keys := make([]string, 0, len(all))
|
|
|
|
|
for k := range all {
|
|
|
|
|
keys = append(keys, k)
|
|
|
|
|
}
|
|
|
|
|
slices.Sort(keys)
|
2026-03-06 13:14:32 +00:00
|
|
|
var entries []fs.DirEntry
|
2026-04-05 12:22:25 +01:00
|
|
|
for _, k := range keys {
|
|
|
|
|
entries = append(entries, &keyValueDirEntry{name: k, size: int64(len(all[k]))})
|
2026-03-06 13:14:32 +00:00
|
|
|
}
|
|
|
|
|
return entries, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 21:23:35 +00:00
|
|
|
// Example: info, _ := medium.Stat("app/theme")
|
2026-03-30 21:39:03 +00:00
|
|
|
func (medium *Medium) Stat(entryPath string) (fs.FileInfo, error) {
|
2026-03-30 20:58:10 +00:00
|
|
|
group, key := splitGroupKeyPath(entryPath)
|
2026-03-06 13:14:32 +00:00
|
|
|
if group == "" {
|
2026-03-26 16:23:45 +00:00
|
|
|
return nil, core.E("store.Stat", "path is required", fs.ErrInvalid)
|
2026-03-06 13:14:32 +00:00
|
|
|
}
|
|
|
|
|
if key == "" {
|
2026-03-31 13:54:58 +00:00
|
|
|
entryCount, err := medium.keyValueStore.Count(group)
|
2026-03-06 13:14:32 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2026-03-30 20:18:30 +00:00
|
|
|
if entryCount == 0 {
|
2026-03-26 16:23:45 +00:00
|
|
|
return nil, core.E("store.Stat", core.Concat("group not found: ", group), fs.ErrNotExist)
|
2026-03-06 13:14:32 +00:00
|
|
|
}
|
2026-03-30 21:04:19 +00:00
|
|
|
return &keyValueFileInfo{name: group, isDir: true}, nil
|
2026-03-06 13:14:32 +00:00
|
|
|
}
|
2026-03-31 13:54:58 +00:00
|
|
|
value, err := medium.keyValueStore.Get(group, key)
|
2026-03-06 13:14:32 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2026-03-30 21:39:03 +00:00
|
|
|
return &keyValueFileInfo{name: key, size: int64(len(value))}, nil
|
2026-03-06 13:14:32 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-30 21:39:03 +00:00
|
|
|
func (medium *Medium) Open(entryPath string) (fs.File, error) {
|
2026-03-30 20:58:10 +00:00
|
|
|
group, key := splitGroupKeyPath(entryPath)
|
2026-03-06 13:14:32 +00:00
|
|
|
if key == "" {
|
2026-03-26 16:23:45 +00:00
|
|
|
return nil, core.E("store.Open", "path must include group/key", fs.ErrInvalid)
|
2026-03-06 13:14:32 +00:00
|
|
|
}
|
2026-03-31 13:54:58 +00:00
|
|
|
value, err := medium.keyValueStore.Get(group, key)
|
2026-03-06 13:14:32 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2026-03-30 21:39:03 +00:00
|
|
|
return &keyValueFile{name: key, content: []byte(value)}, nil
|
2026-03-06 13:14:32 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-30 21:39:03 +00:00
|
|
|
func (medium *Medium) Create(entryPath string) (goio.WriteCloser, error) {
|
2026-03-30 20:58:10 +00:00
|
|
|
group, key := splitGroupKeyPath(entryPath)
|
2026-03-06 13:14:32 +00:00
|
|
|
if key == "" {
|
2026-03-26 16:23:45 +00:00
|
|
|
return nil, core.E("store.Create", "path must include group/key", fs.ErrInvalid)
|
2026-03-06 13:14:32 +00:00
|
|
|
}
|
2026-03-31 13:54:58 +00:00
|
|
|
return &keyValueWriteCloser{keyValueStore: medium.keyValueStore, group: group, key: key}, nil
|
2026-03-06 13:14:32 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-30 21:39:03 +00:00
|
|
|
func (medium *Medium) Append(entryPath string) (goio.WriteCloser, error) {
|
2026-03-30 20:58:10 +00:00
|
|
|
group, key := splitGroupKeyPath(entryPath)
|
2026-03-06 13:14:32 +00:00
|
|
|
if key == "" {
|
2026-03-26 16:23:45 +00:00
|
|
|
return nil, core.E("store.Append", "path must include group/key", fs.ErrInvalid)
|
2026-03-06 13:14:32 +00:00
|
|
|
}
|
2026-04-05 13:20:21 +01:00
|
|
|
existingValue, err := medium.keyValueStore.Get(group, key)
|
|
|
|
|
if err != nil && !core.Is(err, NotFoundError) {
|
|
|
|
|
return nil, core.E("store.Append", core.Concat("failed to read existing content: ", entryPath), err)
|
|
|
|
|
}
|
2026-03-31 13:54:58 +00:00
|
|
|
return &keyValueWriteCloser{keyValueStore: medium.keyValueStore, group: group, key: key, data: []byte(existingValue)}, nil
|
2026-03-06 13:14:32 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-30 21:39:03 +00:00
|
|
|
func (medium *Medium) ReadStream(entryPath string) (goio.ReadCloser, error) {
|
2026-03-30 20:58:10 +00:00
|
|
|
group, key := splitGroupKeyPath(entryPath)
|
2026-03-06 13:14:32 +00:00
|
|
|
if key == "" {
|
2026-03-26 16:23:45 +00:00
|
|
|
return nil, core.E("store.ReadStream", "path must include group/key", fs.ErrInvalid)
|
2026-03-06 13:14:32 +00:00
|
|
|
}
|
2026-03-31 13:54:58 +00:00
|
|
|
value, err := medium.keyValueStore.Get(group, key)
|
2026-03-06 13:14:32 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2026-03-31 13:25:00 +00:00
|
|
|
return goio.NopCloser(core.NewReader(value)), nil
|
2026-03-06 13:14:32 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-30 21:39:03 +00:00
|
|
|
func (medium *Medium) WriteStream(entryPath string) (goio.WriteCloser, error) {
|
|
|
|
|
return medium.Create(entryPath)
|
2026-03-06 13:14:32 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-30 21:39:03 +00:00
|
|
|
func (medium *Medium) Exists(entryPath string) bool {
|
2026-03-30 20:58:10 +00:00
|
|
|
group, key := splitGroupKeyPath(entryPath)
|
2026-03-06 13:14:32 +00:00
|
|
|
if group == "" {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
if key == "" {
|
2026-03-31 13:54:58 +00:00
|
|
|
entryCount, err := medium.keyValueStore.Count(group)
|
2026-03-30 20:18:30 +00:00
|
|
|
return err == nil && entryCount > 0
|
2026-03-06 13:14:32 +00:00
|
|
|
}
|
2026-03-31 13:54:58 +00:00
|
|
|
_, err := medium.keyValueStore.Get(group, key)
|
2026-03-06 13:14:32 +00:00
|
|
|
return err == nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 21:39:03 +00:00
|
|
|
func (medium *Medium) IsDir(entryPath string) bool {
|
2026-03-30 20:58:10 +00:00
|
|
|
group, key := splitGroupKeyPath(entryPath)
|
2026-03-06 13:14:32 +00:00
|
|
|
if key != "" || group == "" {
|
|
|
|
|
return false
|
|
|
|
|
}
|
2026-03-31 13:54:58 +00:00
|
|
|
entryCount, err := medium.keyValueStore.Count(group)
|
2026-03-30 20:18:30 +00:00
|
|
|
return err == nil && entryCount > 0
|
2026-03-06 13:14:32 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-30 21:04:19 +00:00
|
|
|
type keyValueFileInfo struct {
|
2026-03-06 13:14:32 +00:00
|
|
|
name string
|
|
|
|
|
size int64
|
|
|
|
|
isDir bool
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 21:39:03 +00:00
|
|
|
func (fileInfo *keyValueFileInfo) Name() string { return fileInfo.name }
|
2026-03-26 16:23:45 +00:00
|
|
|
|
2026-03-30 21:39:03 +00:00
|
|
|
func (fileInfo *keyValueFileInfo) Size() int64 { return fileInfo.size }
|
2026-03-26 16:23:45 +00:00
|
|
|
|
2026-03-30 21:39:03 +00:00
|
|
|
func (fileInfo *keyValueFileInfo) Mode() fs.FileMode {
|
|
|
|
|
if fileInfo.isDir {
|
2026-03-26 10:54:10 +00:00
|
|
|
return fs.ModeDir | 0755
|
|
|
|
|
}
|
|
|
|
|
return 0644
|
|
|
|
|
}
|
2026-03-26 16:23:45 +00:00
|
|
|
|
2026-03-30 21:39:03 +00:00
|
|
|
func (fileInfo *keyValueFileInfo) ModTime() time.Time { return time.Time{} }
|
2026-03-26 16:23:45 +00:00
|
|
|
|
2026-03-30 21:39:03 +00:00
|
|
|
func (fileInfo *keyValueFileInfo) IsDir() bool { return fileInfo.isDir }
|
2026-03-26 16:23:45 +00:00
|
|
|
|
2026-03-30 21:39:03 +00:00
|
|
|
func (fileInfo *keyValueFileInfo) Sys() any { return nil }
|
2026-03-06 13:14:32 +00:00
|
|
|
|
2026-03-30 21:04:19 +00:00
|
|
|
type keyValueDirEntry struct {
|
2026-03-06 13:14:32 +00:00
|
|
|
name string
|
|
|
|
|
isDir bool
|
|
|
|
|
size int64
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 21:39:03 +00:00
|
|
|
func (entry *keyValueDirEntry) Name() string { return entry.name }
|
2026-03-26 16:23:45 +00:00
|
|
|
|
2026-03-30 21:39:03 +00:00
|
|
|
func (entry *keyValueDirEntry) IsDir() bool { return entry.isDir }
|
2026-03-26 16:23:45 +00:00
|
|
|
|
2026-03-30 21:39:03 +00:00
|
|
|
func (entry *keyValueDirEntry) Type() fs.FileMode {
|
|
|
|
|
if entry.isDir {
|
2026-03-26 10:54:10 +00:00
|
|
|
return fs.ModeDir
|
|
|
|
|
}
|
|
|
|
|
return 0
|
|
|
|
|
}
|
2026-03-26 16:23:45 +00:00
|
|
|
|
2026-03-30 21:39:03 +00:00
|
|
|
func (entry *keyValueDirEntry) Info() (fs.FileInfo, error) {
|
|
|
|
|
return &keyValueFileInfo{name: entry.name, size: entry.size, isDir: entry.isDir}, nil
|
2026-03-06 13:14:32 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-30 21:04:19 +00:00
|
|
|
type keyValueFile struct {
|
2026-03-06 13:14:32 +00:00
|
|
|
name string
|
|
|
|
|
content []byte
|
|
|
|
|
offset int64
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 21:39:03 +00:00
|
|
|
func (file *keyValueFile) Stat() (fs.FileInfo, error) {
|
|
|
|
|
return &keyValueFileInfo{name: file.name, size: int64(len(file.content))}, nil
|
2026-03-06 13:14:32 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-30 21:39:03 +00:00
|
|
|
func (file *keyValueFile) Read(buffer []byte) (int, error) {
|
|
|
|
|
if file.offset >= int64(len(file.content)) {
|
2026-03-06 13:14:32 +00:00
|
|
|
return 0, goio.EOF
|
|
|
|
|
}
|
2026-03-30 21:39:03 +00:00
|
|
|
readCount := copy(buffer, file.content[file.offset:])
|
|
|
|
|
file.offset += int64(readCount)
|
|
|
|
|
return readCount, nil
|
2026-03-06 13:14:32 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-30 21:39:03 +00:00
|
|
|
func (file *keyValueFile) Close() error { return nil }
|
2026-03-06 13:14:32 +00:00
|
|
|
|
2026-03-30 21:04:19 +00:00
|
|
|
type keyValueWriteCloser struct {
|
2026-03-31 13:54:58 +00:00
|
|
|
keyValueStore *KeyValueStore
|
2026-03-06 13:14:32 +00:00
|
|
|
group string
|
|
|
|
|
key string
|
|
|
|
|
data []byte
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 21:39:03 +00:00
|
|
|
func (writer *keyValueWriteCloser) Write(data []byte) (int, error) {
|
|
|
|
|
writer.data = append(writer.data, data...)
|
|
|
|
|
return len(data), nil
|
2026-03-06 13:14:32 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-30 21:39:03 +00:00
|
|
|
func (writer *keyValueWriteCloser) Close() error {
|
2026-03-31 13:54:58 +00:00
|
|
|
return writer.keyValueStore.Set(writer.group, writer.key, string(writer.data))
|
2026-03-06 13:14:32 +00:00
|
|
|
}
|