664 lines
18 KiB
Go
664 lines
18 KiB
Go
package io
|
|
|
|
import (
|
|
"bytes"
|
|
"cmp"
|
|
goio "io"
|
|
"io/fs"
|
|
"path"
|
|
"slices"
|
|
"time"
|
|
|
|
core "dappco.re/go/core"
|
|
"dappco.re/go/core/io/local"
|
|
)
|
|
|
|
// Example: medium, _ := io.NewSandboxed("/srv/app")
|
|
// Example: _ = medium.Write("config/app.yaml", "port: 8080")
|
|
// Example: backup, _ := io.NewSandboxed("/srv/backup")
|
|
// Example: _ = io.Copy(medium, "data/report.json", backup, "daily/report.json")
|
|
type Medium interface {
|
|
// Example: content, _ := medium.Read("config/app.yaml")
|
|
Read(path string) (string, error)
|
|
|
|
// Example: _ = medium.Write("config/app.yaml", "port: 8080")
|
|
Write(path, content string) error
|
|
|
|
// Example: _ = medium.WriteMode("keys/private.key", key, 0600)
|
|
WriteMode(path, content string, mode fs.FileMode) error
|
|
|
|
// Example: _ = medium.EnsureDir("config/app")
|
|
EnsureDir(path string) error
|
|
|
|
// Example: isFile := medium.IsFile("config/app.yaml")
|
|
IsFile(path string) bool
|
|
|
|
// Example: _ = medium.Delete("config/app.yaml")
|
|
Delete(path string) error
|
|
|
|
// Example: _ = medium.DeleteAll("logs/archive")
|
|
DeleteAll(path string) error
|
|
|
|
// Example: _ = medium.Rename("drafts/todo.txt", "archive/todo.txt")
|
|
Rename(oldPath, newPath string) error
|
|
|
|
// Example: entries, _ := medium.List("config")
|
|
List(path string) ([]fs.DirEntry, error)
|
|
|
|
// Example: info, _ := medium.Stat("config/app.yaml")
|
|
Stat(path string) (fs.FileInfo, error)
|
|
|
|
// Example: file, _ := medium.Open("config/app.yaml")
|
|
Open(path string) (fs.File, error)
|
|
|
|
// Example: writer, _ := medium.Create("logs/app.log")
|
|
Create(path string) (goio.WriteCloser, error)
|
|
|
|
// Example: writer, _ := medium.Append("logs/app.log")
|
|
Append(path string) (goio.WriteCloser, error)
|
|
|
|
// Example: reader, _ := medium.ReadStream("logs/app.log")
|
|
ReadStream(path string) (goio.ReadCloser, error)
|
|
|
|
// Example: writer, _ := medium.WriteStream("logs/app.log")
|
|
WriteStream(path string) (goio.WriteCloser, error)
|
|
|
|
// Example: exists := medium.Exists("config/app.yaml")
|
|
Exists(path string) bool
|
|
|
|
// Example: isDirectory := medium.IsDir("config")
|
|
IsDir(path string) bool
|
|
}
|
|
|
|
// Example: info := io.NewFileInfo("app.yaml", 8, 0644, time.Unix(0, 0), false)
|
|
type FileInfo struct {
|
|
name string
|
|
size int64
|
|
mode fs.FileMode
|
|
modTime time.Time
|
|
isDir bool
|
|
}
|
|
|
|
var _ fs.FileInfo = FileInfo{}
|
|
|
|
func (info FileInfo) Name() string { return info.name }
|
|
|
|
func (info FileInfo) Size() int64 { return info.size }
|
|
|
|
func (info FileInfo) Mode() fs.FileMode { return info.mode }
|
|
|
|
func (info FileInfo) ModTime() time.Time { return info.modTime }
|
|
|
|
func (info FileInfo) IsDir() bool { return info.isDir }
|
|
|
|
func (info FileInfo) Sys() any { return nil }
|
|
|
|
// Example: info := io.NewFileInfo("app.yaml", 8, 0644, time.Unix(0, 0), false)
|
|
// Example: entry := io.NewDirEntry("app.yaml", false, 0644, info)
|
|
type DirEntry struct {
|
|
name string
|
|
isDir bool
|
|
mode fs.FileMode
|
|
info fs.FileInfo
|
|
}
|
|
|
|
var _ fs.DirEntry = DirEntry{}
|
|
|
|
func (entry DirEntry) Name() string { return entry.name }
|
|
|
|
func (entry DirEntry) IsDir() bool { return entry.isDir }
|
|
|
|
func (entry DirEntry) Type() fs.FileMode { return entry.mode.Type() }
|
|
|
|
func (entry DirEntry) Info() (fs.FileInfo, error) { return entry.info, nil }
|
|
|
|
// Example: info := io.NewFileInfo("app.yaml", 8, 0644, time.Unix(0, 0), false)
|
|
func NewFileInfo(name string, size int64, mode fs.FileMode, modTime time.Time, isDir bool) FileInfo {
|
|
return FileInfo{
|
|
name: name,
|
|
size: size,
|
|
mode: mode,
|
|
modTime: modTime,
|
|
isDir: isDir,
|
|
}
|
|
}
|
|
|
|
// Example: info := io.NewFileInfo("app.yaml", 8, 0644, time.Unix(0, 0), false)
|
|
// Example: entry := io.NewDirEntry("app.yaml", false, 0644, info)
|
|
func NewDirEntry(name string, isDir bool, mode fs.FileMode, info fs.FileInfo) DirEntry {
|
|
return DirEntry{
|
|
name: name,
|
|
isDir: isDir,
|
|
mode: mode,
|
|
info: info,
|
|
}
|
|
}
|
|
|
|
// Example: _ = io.Local.Read("/etc/hostname")
|
|
var Local Medium
|
|
|
|
var _ Medium = (*local.Medium)(nil)
|
|
|
|
func init() {
|
|
var err error
|
|
Local, err = local.New("/")
|
|
if err != nil {
|
|
core.Warn("io.Local init failed", "error", err)
|
|
}
|
|
}
|
|
|
|
// Example: medium, _ := io.NewSandboxed("/srv/app")
|
|
// Example: _ = medium.Write("config/app.yaml", "port: 8080")
|
|
func NewSandboxed(root string) (Medium, error) {
|
|
return local.New(root)
|
|
}
|
|
|
|
// Example: content, _ := io.Read(medium, "config/app.yaml")
|
|
func Read(medium Medium, path string) (string, error) {
|
|
return medium.Read(path)
|
|
}
|
|
|
|
// Example: _ = io.Write(medium, "config/app.yaml", "port: 8080")
|
|
func Write(medium Medium, path, content string) error {
|
|
return medium.Write(path, content)
|
|
}
|
|
|
|
// Example: reader, _ := io.ReadStream(medium, "logs/app.log")
|
|
func ReadStream(medium Medium, path string) (goio.ReadCloser, error) {
|
|
return medium.ReadStream(path)
|
|
}
|
|
|
|
// Example: writer, _ := io.WriteStream(medium, "logs/app.log")
|
|
func WriteStream(medium Medium, path string) (goio.WriteCloser, error) {
|
|
return medium.WriteStream(path)
|
|
}
|
|
|
|
// Example: _ = io.EnsureDir(medium, "config")
|
|
func EnsureDir(medium Medium, path string) error {
|
|
return medium.EnsureDir(path)
|
|
}
|
|
|
|
// Example: isFile := io.IsFile(medium, "config/app.yaml")
|
|
func IsFile(medium Medium, path string) bool {
|
|
return medium.IsFile(path)
|
|
}
|
|
|
|
// Example: _ = io.Copy(sourceMedium, "input.txt", destinationMedium, "backup/input.txt")
|
|
func Copy(sourceMedium Medium, sourcePath string, destinationMedium Medium, destinationPath string) error {
|
|
content, err := sourceMedium.Read(sourcePath)
|
|
if err != nil {
|
|
return core.E("io.Copy", core.Concat("read failed: ", sourcePath), err)
|
|
}
|
|
if err := destinationMedium.Write(destinationPath, content); err != nil {
|
|
return core.E("io.Copy", core.Concat("write failed: ", destinationPath), err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Example: medium := io.NewMemoryMedium()
|
|
// Example: _ = medium.Write("config/app.yaml", "port: 8080")
|
|
type MemoryMedium struct {
|
|
fileContents map[string]string
|
|
fileModes map[string]fs.FileMode
|
|
directories map[string]bool
|
|
modificationTimes map[string]time.Time
|
|
}
|
|
|
|
var _ Medium = (*MemoryMedium)(nil)
|
|
|
|
// Example: medium := io.NewMemoryMedium()
|
|
// Example: _ = medium.Write("config/app.yaml", "port: 8080")
|
|
func NewMemoryMedium() *MemoryMedium {
|
|
return &MemoryMedium{
|
|
fileContents: make(map[string]string),
|
|
fileModes: make(map[string]fs.FileMode),
|
|
directories: make(map[string]bool),
|
|
modificationTimes: make(map[string]time.Time),
|
|
}
|
|
}
|
|
|
|
func (medium *MemoryMedium) ensureAncestorDirectories(filePath string) {
|
|
parentPath := path.Dir(filePath)
|
|
for parentPath != "." && parentPath != "" {
|
|
medium.directories[parentPath] = true
|
|
nextParentPath := path.Dir(parentPath)
|
|
if nextParentPath == parentPath {
|
|
break
|
|
}
|
|
parentPath = nextParentPath
|
|
}
|
|
}
|
|
|
|
func (medium *MemoryMedium) directoryExists(path string) bool {
|
|
if path == "" {
|
|
return false
|
|
}
|
|
if _, ok := medium.directories[path]; ok {
|
|
return true
|
|
}
|
|
|
|
prefix := path
|
|
if !core.HasSuffix(prefix, "/") {
|
|
prefix += "/"
|
|
}
|
|
|
|
for filePath := range medium.fileContents {
|
|
if core.HasPrefix(filePath, prefix) {
|
|
return true
|
|
}
|
|
}
|
|
for directoryPath := range medium.directories {
|
|
if directoryPath != path && core.HasPrefix(directoryPath, prefix) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// Example: value, _ := io.NewMemoryMedium().Read("notes.txt")
|
|
func (medium *MemoryMedium) Read(path string) (string, error) {
|
|
content, ok := medium.fileContents[path]
|
|
if !ok {
|
|
return "", core.E("io.MemoryMedium.Read", core.Concat("file not found: ", path), fs.ErrNotExist)
|
|
}
|
|
return content, nil
|
|
}
|
|
|
|
// Example: _ = io.NewMemoryMedium().Write("notes.txt", "hello")
|
|
func (medium *MemoryMedium) Write(path, content string) error {
|
|
return medium.WriteMode(path, content, 0644)
|
|
}
|
|
|
|
// Example: _ = io.NewMemoryMedium().WriteMode("keys/private.key", "secret", 0600)
|
|
func (medium *MemoryMedium) WriteMode(path, content string, mode fs.FileMode) error {
|
|
medium.ensureAncestorDirectories(path)
|
|
medium.fileContents[path] = content
|
|
medium.fileModes[path] = mode
|
|
medium.modificationTimes[path] = time.Now()
|
|
return nil
|
|
}
|
|
|
|
// Example: _ = io.NewMemoryMedium().EnsureDir("config/app")
|
|
func (medium *MemoryMedium) EnsureDir(path string) error {
|
|
medium.ensureAncestorDirectories(path)
|
|
medium.directories[path] = true
|
|
return nil
|
|
}
|
|
|
|
// Example: ok := io.NewMemoryMedium().IsFile("notes.txt")
|
|
func (medium *MemoryMedium) IsFile(path string) bool {
|
|
_, ok := medium.fileContents[path]
|
|
return ok
|
|
}
|
|
|
|
// Example: _ = io.NewMemoryMedium().Delete("old.txt")
|
|
func (medium *MemoryMedium) Delete(path string) error {
|
|
if _, ok := medium.fileContents[path]; ok {
|
|
delete(medium.fileContents, path)
|
|
delete(medium.fileModes, path)
|
|
delete(medium.modificationTimes, path)
|
|
return nil
|
|
}
|
|
if medium.directoryExists(path) {
|
|
prefix := path
|
|
if !core.HasSuffix(prefix, "/") {
|
|
prefix += "/"
|
|
}
|
|
hasChildren := false
|
|
for filePath := range medium.fileContents {
|
|
if core.HasPrefix(filePath, prefix) {
|
|
hasChildren = true
|
|
break
|
|
}
|
|
}
|
|
if !hasChildren {
|
|
for directoryPath := range medium.directories {
|
|
if directoryPath != path && core.HasPrefix(directoryPath, prefix) {
|
|
hasChildren = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if hasChildren {
|
|
return core.E("io.MemoryMedium.Delete", core.Concat("directory not empty: ", path), fs.ErrExist)
|
|
}
|
|
delete(medium.directories, path)
|
|
return nil
|
|
}
|
|
return core.E("io.MemoryMedium.Delete", core.Concat("path not found: ", path), fs.ErrNotExist)
|
|
}
|
|
|
|
// Example: _ = io.NewMemoryMedium().DeleteAll("logs")
|
|
func (medium *MemoryMedium) DeleteAll(path string) error {
|
|
found := false
|
|
if _, ok := medium.fileContents[path]; ok {
|
|
delete(medium.fileContents, path)
|
|
delete(medium.fileModes, path)
|
|
delete(medium.modificationTimes, path)
|
|
found = true
|
|
}
|
|
if _, ok := medium.directories[path]; ok {
|
|
delete(medium.directories, path)
|
|
found = true
|
|
}
|
|
prefix := path
|
|
if !core.HasSuffix(prefix, "/") {
|
|
prefix += "/"
|
|
}
|
|
for filePath := range medium.fileContents {
|
|
if core.HasPrefix(filePath, prefix) {
|
|
delete(medium.fileContents, filePath)
|
|
delete(medium.fileModes, filePath)
|
|
delete(medium.modificationTimes, filePath)
|
|
found = true
|
|
}
|
|
}
|
|
for directoryPath := range medium.directories {
|
|
if core.HasPrefix(directoryPath, prefix) {
|
|
delete(medium.directories, directoryPath)
|
|
found = true
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
return core.E("io.MemoryMedium.DeleteAll", core.Concat("path not found: ", path), fs.ErrNotExist)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Example: _ = io.NewMemoryMedium().Rename("drafts/todo.txt", "archive/todo.txt")
|
|
func (medium *MemoryMedium) Rename(oldPath, newPath string) error {
|
|
if content, ok := medium.fileContents[oldPath]; ok {
|
|
medium.fileContents[newPath] = content
|
|
delete(medium.fileContents, oldPath)
|
|
if mode, ok := medium.fileModes[oldPath]; ok {
|
|
medium.fileModes[newPath] = mode
|
|
delete(medium.fileModes, oldPath)
|
|
}
|
|
if modTime, ok := medium.modificationTimes[oldPath]; ok {
|
|
medium.modificationTimes[newPath] = modTime
|
|
delete(medium.modificationTimes, oldPath)
|
|
}
|
|
return nil
|
|
}
|
|
if medium.directoryExists(oldPath) {
|
|
medium.directories[newPath] = true
|
|
if _, ok := medium.directories[oldPath]; ok {
|
|
delete(medium.directories, oldPath)
|
|
}
|
|
|
|
oldPrefix := oldPath
|
|
if !core.HasSuffix(oldPrefix, "/") {
|
|
oldPrefix += "/"
|
|
}
|
|
newPrefix := newPath
|
|
if !core.HasSuffix(newPrefix, "/") {
|
|
newPrefix += "/"
|
|
}
|
|
|
|
filesToMove := make(map[string]string)
|
|
for filePath := range medium.fileContents {
|
|
if core.HasPrefix(filePath, oldPrefix) {
|
|
newFilePath := core.Concat(newPrefix, core.TrimPrefix(filePath, oldPrefix))
|
|
filesToMove[filePath] = newFilePath
|
|
}
|
|
}
|
|
for oldFilePath, newFilePath := range filesToMove {
|
|
medium.fileContents[newFilePath] = medium.fileContents[oldFilePath]
|
|
delete(medium.fileContents, oldFilePath)
|
|
if modTime, ok := medium.modificationTimes[oldFilePath]; ok {
|
|
medium.modificationTimes[newFilePath] = modTime
|
|
delete(medium.modificationTimes, oldFilePath)
|
|
}
|
|
}
|
|
|
|
dirsToMove := make(map[string]string)
|
|
for directoryPath := range medium.directories {
|
|
if core.HasPrefix(directoryPath, oldPrefix) {
|
|
newDirectoryPath := core.Concat(newPrefix, core.TrimPrefix(directoryPath, oldPrefix))
|
|
dirsToMove[directoryPath] = newDirectoryPath
|
|
}
|
|
}
|
|
for oldDirectoryPath, newDirectoryPath := range dirsToMove {
|
|
medium.directories[newDirectoryPath] = true
|
|
delete(medium.directories, oldDirectoryPath)
|
|
}
|
|
return nil
|
|
}
|
|
return core.E("io.MemoryMedium.Rename", core.Concat("path not found: ", oldPath), fs.ErrNotExist)
|
|
}
|
|
|
|
// Example: file, _ := io.NewMemoryMedium().Open("notes.txt")
|
|
func (medium *MemoryMedium) Open(path string) (fs.File, error) {
|
|
content, ok := medium.fileContents[path]
|
|
if !ok {
|
|
return nil, core.E("io.MemoryMedium.Open", core.Concat("file not found: ", path), fs.ErrNotExist)
|
|
}
|
|
return &MemoryFile{
|
|
name: core.PathBase(path),
|
|
content: []byte(content),
|
|
mode: medium.modeForPath(path),
|
|
modTime: medium.modificationTimeForPath(path),
|
|
}, nil
|
|
}
|
|
|
|
// Example: writer, _ := io.NewMemoryMedium().Create("notes.txt")
|
|
func (medium *MemoryMedium) Create(path string) (goio.WriteCloser, error) {
|
|
return &MemoryWriteCloser{
|
|
medium: medium,
|
|
path: path,
|
|
mode: 0644,
|
|
}, nil
|
|
}
|
|
|
|
// Example: writer, _ := io.NewMemoryMedium().Append("notes.txt")
|
|
func (medium *MemoryMedium) Append(path string) (goio.WriteCloser, error) {
|
|
content := medium.fileContents[path]
|
|
return &MemoryWriteCloser{
|
|
medium: medium,
|
|
path: path,
|
|
data: []byte(content),
|
|
mode: medium.modeForPath(path),
|
|
}, nil
|
|
}
|
|
|
|
// Example: reader, _ := io.NewMemoryMedium().ReadStream("notes.txt")
|
|
func (medium *MemoryMedium) ReadStream(path string) (goio.ReadCloser, error) {
|
|
return medium.Open(path)
|
|
}
|
|
|
|
// Example: writer, _ := io.NewMemoryMedium().WriteStream("notes.txt")
|
|
func (medium *MemoryMedium) WriteStream(path string) (goio.WriteCloser, error) {
|
|
return medium.Create(path)
|
|
}
|
|
|
|
// Example: file, _ := io.NewMemoryMedium().Open("notes.txt")
|
|
type MemoryFile struct {
|
|
name string
|
|
content []byte
|
|
offset int64
|
|
mode fs.FileMode
|
|
modTime time.Time
|
|
}
|
|
|
|
var _ fs.File = (*MemoryFile)(nil)
|
|
var _ goio.ReadCloser = (*MemoryFile)(nil)
|
|
|
|
func (file *MemoryFile) Stat() (fs.FileInfo, error) {
|
|
return NewFileInfo(file.name, int64(len(file.content)), file.mode, file.modTime, false), nil
|
|
}
|
|
|
|
func (file *MemoryFile) Read(buffer []byte) (int, error) {
|
|
if file.offset >= int64(len(file.content)) {
|
|
return 0, goio.EOF
|
|
}
|
|
readCount := copy(buffer, file.content[file.offset:])
|
|
file.offset += int64(readCount)
|
|
return readCount, nil
|
|
}
|
|
|
|
func (file *MemoryFile) Close() error {
|
|
return nil
|
|
}
|
|
|
|
// Example: writer, _ := io.NewMemoryMedium().Create("notes.txt")
|
|
type MemoryWriteCloser struct {
|
|
medium *MemoryMedium
|
|
path string
|
|
data []byte
|
|
mode fs.FileMode
|
|
}
|
|
|
|
var _ goio.WriteCloser = (*MemoryWriteCloser)(nil)
|
|
|
|
func (writeCloser *MemoryWriteCloser) Write(data []byte) (int, error) {
|
|
writeCloser.data = append(writeCloser.data, data...)
|
|
return len(data), nil
|
|
}
|
|
|
|
func (writeCloser *MemoryWriteCloser) Close() error {
|
|
writeCloser.medium.ensureAncestorDirectories(writeCloser.path)
|
|
writeCloser.medium.fileContents[writeCloser.path] = string(writeCloser.data)
|
|
writeCloser.medium.fileModes[writeCloser.path] = writeCloser.mode
|
|
writeCloser.medium.modificationTimes[writeCloser.path] = time.Now()
|
|
return nil
|
|
}
|
|
|
|
func (medium *MemoryMedium) modeForPath(path string) fs.FileMode {
|
|
if mode, ok := medium.fileModes[path]; ok {
|
|
return mode
|
|
}
|
|
return 0644
|
|
}
|
|
|
|
func (medium *MemoryMedium) modificationTimeForPath(path string) time.Time {
|
|
if modTime, ok := medium.modificationTimes[path]; ok {
|
|
return modTime
|
|
}
|
|
return time.Time{}
|
|
}
|
|
|
|
// Example: entries, _ := io.NewMemoryMedium().List("config")
|
|
func (medium *MemoryMedium) List(path string) ([]fs.DirEntry, error) {
|
|
if _, ok := medium.directories[path]; !ok {
|
|
hasChildren := false
|
|
prefix := path
|
|
if path != "" && !core.HasSuffix(prefix, "/") {
|
|
prefix += "/"
|
|
}
|
|
for filePath := range medium.fileContents {
|
|
if core.HasPrefix(filePath, prefix) {
|
|
hasChildren = true
|
|
break
|
|
}
|
|
}
|
|
if !hasChildren {
|
|
for directoryPath := range medium.directories {
|
|
if core.HasPrefix(directoryPath, prefix) {
|
|
hasChildren = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if !hasChildren && path != "" {
|
|
return nil, core.E("io.MemoryMedium.List", core.Concat("directory not found: ", path), fs.ErrNotExist)
|
|
}
|
|
}
|
|
|
|
prefix := path
|
|
if path != "" && !core.HasSuffix(prefix, "/") {
|
|
prefix += "/"
|
|
}
|
|
|
|
seen := make(map[string]bool)
|
|
var entries []fs.DirEntry
|
|
|
|
for filePath, content := range medium.fileContents {
|
|
if !core.HasPrefix(filePath, prefix) {
|
|
continue
|
|
}
|
|
rest := core.TrimPrefix(filePath, prefix)
|
|
if rest == "" || core.Contains(rest, "/") {
|
|
if idx := bytes.IndexByte([]byte(rest), '/'); idx != -1 {
|
|
dirName := rest[:idx]
|
|
if !seen[dirName] {
|
|
seen[dirName] = true
|
|
entries = append(entries, NewDirEntry(
|
|
dirName,
|
|
true,
|
|
fs.ModeDir|0755,
|
|
NewFileInfo(dirName, 0, fs.ModeDir|0755, time.Time{}, true),
|
|
))
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
if !seen[rest] {
|
|
seen[rest] = true
|
|
filePath := core.Concat(prefix, rest)
|
|
entries = append(entries, NewDirEntry(
|
|
rest,
|
|
false,
|
|
medium.modeForPath(filePath),
|
|
NewFileInfo(rest, int64(len(content)), medium.modeForPath(filePath), medium.modificationTimeForPath(filePath), false),
|
|
))
|
|
}
|
|
}
|
|
|
|
for directoryPath := range medium.directories {
|
|
if !core.HasPrefix(directoryPath, prefix) {
|
|
continue
|
|
}
|
|
rest := core.TrimPrefix(directoryPath, prefix)
|
|
if rest == "" {
|
|
continue
|
|
}
|
|
if idx := bytes.IndexByte([]byte(rest), '/'); idx != -1 {
|
|
rest = rest[:idx]
|
|
}
|
|
if !seen[rest] {
|
|
seen[rest] = true
|
|
entries = append(entries, NewDirEntry(
|
|
rest,
|
|
true,
|
|
fs.ModeDir|0755,
|
|
NewFileInfo(rest, 0, fs.ModeDir|0755, time.Time{}, true),
|
|
))
|
|
}
|
|
}
|
|
|
|
slices.SortFunc(entries, func(a, b fs.DirEntry) int {
|
|
return cmp.Compare(a.Name(), b.Name())
|
|
})
|
|
|
|
return entries, nil
|
|
}
|
|
|
|
// Example: info, _ := io.NewMemoryMedium().Stat("notes.txt")
|
|
func (medium *MemoryMedium) Stat(path string) (fs.FileInfo, error) {
|
|
if content, ok := medium.fileContents[path]; ok {
|
|
modTime, ok := medium.modificationTimes[path]
|
|
if !ok {
|
|
modTime = time.Now()
|
|
}
|
|
return NewFileInfo(core.PathBase(path), int64(len(content)), medium.modeForPath(path), modTime, false), nil
|
|
}
|
|
if medium.directoryExists(path) {
|
|
return NewFileInfo(core.PathBase(path), 0, fs.ModeDir|0755, time.Time{}, true), nil
|
|
}
|
|
return nil, core.E("io.MemoryMedium.Stat", core.Concat("path not found: ", path), fs.ErrNotExist)
|
|
}
|
|
|
|
// Example: ok := io.NewMemoryMedium().Exists("notes.txt")
|
|
func (medium *MemoryMedium) Exists(path string) bool {
|
|
if _, ok := medium.fileContents[path]; ok {
|
|
return true
|
|
}
|
|
return medium.directoryExists(path)
|
|
}
|
|
|
|
// Example: ok := io.NewMemoryMedium().IsDir("config")
|
|
func (medium *MemoryMedium) IsDir(path string) bool {
|
|
return medium.directoryExists(path)
|
|
}
|