package filesystem
import (
"fmt"
"io"
)
type BaseFileSystem struct {
FS FileSystem
}
func NewBaseFileSystem(fs FileSystem) *BaseFileSystem {
return &BaseFileSystem{FS: fs}
}
func (b *BaseFileSystem) WriteAt(path string, data []byte, offset int64) (int64, error) {
if offset < 0 {
return 0, fmt.Errorf("invalid offset: %d", offset)
}
stat, err := b.FS.Stat(path)
if err != nil {
if offset > 0 {
padded := make([]byte, offset+int64(len(data)))
copy(padded[offset:], data)
_, err = b.FS.Write(path, padded, -1, WriteFlagCreate|WriteFlagTruncate)
} else {
_, err = b.FS.Write(path, data, -1, WriteFlagCreate|WriteFlagTruncate)
}
if err != nil {
return 0, err
}
return int64(len(data)), nil
}
currentData, err := b.FS.Read(path, 0, -1)
if err != nil && err != io.EOF {
return 0, err
}
newSize := offset + int64(len(data))
if newSize < stat.Size {
newSize = stat.Size
}
newData := make([]byte, newSize)
copy(newData, currentData)
copy(newData[offset:], data)
_, err = b.FS.Write(path, newData, -1, WriteFlagTruncate)
if err != nil {
return 0, err
}
return int64(len(data)), nil
}
func (b *BaseFileSystem) Truncate(path string, size int64) error {
if size < 0 {
return fmt.Errorf("invalid size: %d", size)
}
stat, err := b.FS.Stat(path)
if err != nil {
return err
}
if stat.IsDir {
return fmt.Errorf("is a directory: %s", path)
}
currentData, err := b.FS.Read(path, 0, -1)
if err != nil && err != io.EOF {
return err
}
var newData []byte
if size <= int64(len(currentData)) {
newData = currentData[:size]
} else {
newData = make([]byte, size)
copy(newData, currentData)
}
_, err = b.FS.Write(path, newData, -1, WriteFlagTruncate)
return err
}
func (b *BaseFileSystem) Touch(path string) error {
stat, err := b.FS.Stat(path)
if err != nil {
_, err = b.FS.Write(path, []byte{}, -1, WriteFlagCreate)
return err
}
if stat.IsDir {
return fmt.Errorf("cannot touch directory: %s", path)
}
data, err := b.FS.Read(path, 0, -1)
if err != nil && err != io.EOF {
return err
}
_, err = b.FS.Write(path, data, -1, WriteFlagNone)
return err
}
func (b *BaseFileSystem) Sync(path string) error {
return nil
}
func (b *BaseFileSystem) GetCapabilities() Capabilities {
return DefaultCapabilities()
}
func (b *BaseFileSystem) GetPathCapabilities(path string) Capabilities {
return b.GetCapabilities()
}
type BaseFileHandle struct {
id int64
path string
flags OpenFlag
fs FileSystem
position int64
closed bool
}
func NewBaseFileHandle(id int64, path string, flags OpenFlag, fs FileSystem) *BaseFileHandle {
return &BaseFileHandle{
id: id,
path: path,
flags: flags,
fs: fs,
position: 0,
closed: false,
}
}
func (h *BaseFileHandle) ID() int64 {
return h.id
}
func (h *BaseFileHandle) Path() string {
return h.path
}
func (h *BaseFileHandle) Flags() OpenFlag {
return h.flags
}
func (h *BaseFileHandle) Read(buf []byte) (int, error) {
if h.closed {
return 0, fmt.Errorf("handle is closed")
}
if h.flags&O_WRONLY != 0 {
return 0, fmt.Errorf("handle not open for reading")
}
data, err := h.fs.Read(h.path, h.position, int64(len(buf)))
if err != nil && err != io.EOF {
return 0, err
}
n := copy(buf, data)
h.position += int64(n)
if err == io.EOF {
return n, io.EOF
}
return n, nil
}
func (h *BaseFileHandle) ReadAt(buf []byte, offset int64) (int, error) {
if h.closed {
return 0, fmt.Errorf("handle is closed")
}
if h.flags&O_WRONLY != 0 {
return 0, fmt.Errorf("handle not open for reading")
}
data, err := h.fs.Read(h.path, offset, int64(len(buf)))
if err != nil && err != io.EOF {
return 0, err
}
n := copy(buf, data)
if err == io.EOF {
return n, io.EOF
}
return n, nil
}
func (h *BaseFileHandle) Write(data []byte) (int, error) {
if h.closed {
return 0, fmt.Errorf("handle is closed")
}
if h.flags == O_RDONLY {
return 0, fmt.Errorf("handle not open for writing")
}
var offset int64 = h.position
var flags WriteFlag = WriteFlagNone
if h.flags&O_APPEND != 0 {
flags |= WriteFlagAppend
offset = -1
}
n, err := h.fs.Write(h.path, data, offset, flags)
if err != nil {
return 0, err
}
if h.flags&O_APPEND == 0 {
h.position += n
}
return int(n), nil
}
func (h *BaseFileHandle) WriteAt(data []byte, offset int64) (int, error) {
if h.closed {
return 0, fmt.Errorf("handle is closed")
}
if h.flags == O_RDONLY {
return 0, fmt.Errorf("handle not open for writing")
}
n, err := h.fs.Write(h.path, data, offset, WriteFlagNone)
if err != nil {
return 0, err
}
return int(n), nil
}
func (h *BaseFileHandle) Seek(offset int64, whence int) (int64, error) {
if h.closed {
return 0, fmt.Errorf("handle is closed")
}
stat, err := h.fs.Stat(h.path)
if err != nil {
return 0, err
}
var newPos int64
switch whence {
case 0:
newPos = offset
case 1:
newPos = h.position + offset
case 2:
newPos = stat.Size + offset
default:
return 0, fmt.Errorf("invalid whence: %d", whence)
}
if newPos < 0 {
return 0, fmt.Errorf("negative position: %d", newPos)
}
h.position = newPos
return h.position, nil
}
func (h *BaseFileHandle) Sync() error {
if h.closed {
return fmt.Errorf("handle is closed")
}
if syncer, ok := h.fs.(Syncer); ok {
return syncer.Sync(h.path)
}
return nil
}
func (h *BaseFileHandle) Close() error {
if h.closed {
return nil
}
h.closed = true
return nil
}
func (h *BaseFileHandle) Stat() (*FileInfo, error) {
if h.closed {
return nil, fmt.Errorf("handle is closed")
}
return h.fs.Stat(h.path)
}
var _ FileHandle = (*BaseFileHandle)(nil)