storj/cmd/uplink/ulfs/local_backend_mem.go
Jeff Wendling 54ef1c8ca2 cmd/uplink: use new upload code path
the parallelism and parallelism-chunk-size flags
which used to control how many parts to split a
segment into and many to perform in parallel
are now deprecated and replaced by
maximum-concurrent-pieces and long-tail-margin.

now, for an individual transfer, the total number
of piece uploads that transfer will perform is
controlled by maximum-concurrent-pieces, and
segments within that transfer will automatically
be performed in parallel. so if you used to set
your parallelism to n, a good value for the pieces
might be something approximately like 130*n, and
the parallelism-chunk-size is unnecessary.

Change-Id: Ibe724ca70b07eba89dad551eb612a1db988b18b9
2023-04-13 16:52:38 -04:00

309 lines
6.6 KiB
Go

// Copyright (C) 2022 Storj Labs, Inc.
// See LICENSE for copying information.
package ulfs
import (
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"strings"
"time"
"github.com/zeebo/errs"
)
// LocalBackendMem implements LocalBackend with memory backed files.
type LocalBackendMem struct {
root *memDir
cwd *memDir
}
// NewLocalBackendMem creates a new LocalBackendMem.
func NewLocalBackendMem() *LocalBackendMem {
return &LocalBackendMem{
root: newMemDir("/"),
cwd: newMemDir(""),
}
}
func (l *LocalBackendMem) openRoot(name string) (string, *memDir) {
if strings.HasPrefix(name, string(filepath.Separator)) {
return name, l.root
} else if name == "." {
return name[1:], l.cwd
} else if strings.HasPrefix(name, "./") {
return name[2:], l.cwd
}
return name, l.cwd
}
func (l *LocalBackendMem) openParent(name string) (*memDir, string, error) {
dir := filepath.Dir(name)
fh, err := l.Open(dir)
if err != nil {
return nil, "", err
}
md, ok := fh.(*memDir)
if !ok {
return nil, "", errs.New("parent not a directory: %q", dir)
}
return md, filepath.Base(name), nil
}
// Create creates a new file for the given name.
func (l *LocalBackendMem) Create(name string) (LocalBackendFile, error) {
name = filepath.Clean(name)
md, base, err := l.openParent(name)
if err != nil {
return nil, err
}
if fh, ok := md.children[base]; ok {
if _, ok := fh.(*memDir); ok {
return nil, errs.New("file already exists: %q", name)
}
}
mf := newMemFile(name)
md.children[base] = mf
return mf, nil
}
// MkdirAll recursively creates directories to make name a directory.
func (l *LocalBackendMem) MkdirAll(name string, perm os.FileMode) error {
name = filepath.Clean(name)
name, root := l.openRoot(name)
return iterateComponents(name, func(name, ent string) error {
fh, ok := root.children[ent]
if !ok {
fh = newMemDir(name)
root.children[ent] = fh
}
md, ok := fh.(*memDir)
if !ok {
return errs.New("file already exists: %q", name)
}
root = md
return nil
})
}
// Open opens the file with the given name.
func (l *LocalBackendMem) Open(name string) (LocalBackendFile, error) {
name = filepath.Clean(name)
var root LocalBackendFile
name, root = l.openRoot(name)
err := iterateComponents(name, func(name, ent string) error {
md, ok := root.(*memDir)
if !ok {
return errs.New("not a directory: %q", name)
}
fh, ok := md.children[ent]
if !ok {
return os.ErrNotExist
}
root = fh
return nil
})
if err != nil {
return nil, err
}
return root, nil
}
// Remove deletes the file with the given name.
func (l *LocalBackendMem) Remove(name string) error {
name = filepath.Clean(name)
md, base, err := l.openParent(name)
if err != nil {
return err
}
if _, ok := md.children[base]; !ok {
return errs.New("file does not exists: %q", name)
}
delete(md.children, base)
return nil
}
// Rename causes the file at oldname to be moved to newname.
func (l *LocalBackendMem) Rename(oldname, newname string) error {
oldname = filepath.Clean(oldname)
newname = filepath.Clean(newname)
omd, obase, err := l.openParent(oldname)
if err != nil {
return err
}
nmd, nbase, err := l.openParent(newname)
if err != nil {
return err
}
f, ok := omd.children[obase]
if !ok {
return os.ErrNotExist
}
switch f := f.(type) {
case *memFile:
f.name = newname
case *memDir:
f.name = newname
}
nmd.children[nbase] = f
delete(omd.children, obase)
return nil
}
// Stat returns file info for the given name.
func (l *LocalBackendMem) Stat(name string) (os.FileInfo, error) {
fh, err := l.Open(name)
if err != nil {
return nil, err
}
return fh.Stat()
}
//
// memFile
//
type memFile struct {
name string
buf []byte
}
func newMemFile(name string) *memFile {
return &memFile{
name: name,
}
}
func (mf *memFile) String() string { return fmt.Sprintf("File[%q]", mf.name) }
func (mf *memFile) Name() string { return mf.name }
func (mf *memFile) Close() error { return nil }
func (mf *memFile) ReadAt(p []byte, off int64) (int, error) {
if off >= int64(len(mf.buf)) {
return 0, io.EOF
}
return copy(p, mf.buf[off:]), nil
}
func (mf *memFile) Write(p []byte) (int, error) {
mf.buf = append(mf.buf, p...)
return len(p), nil
}
func (mf *memFile) Stat() (os.FileInfo, error) {
return (*memFileInfo)(mf), nil
}
func (mf *memFile) Readdir(n int) ([]os.FileInfo, error) {
return nil, errs.New("readdir on regular file")
}
type memFileInfo memFile
var _ os.FileInfo = (*memFileInfo)(nil)
func (mfi *memFileInfo) Name() string {
return filepath.Base((*memFile)(mfi).name)
}
func (mfi *memFileInfo) Size() int64 { return int64(len((*memFile)(mfi).buf)) }
func (mfi *memFileInfo) Mode() fs.FileMode { return 0777 }
func (mfi *memFileInfo) ModTime() time.Time { return time.Time{} }
func (mfi *memFileInfo) IsDir() bool { return false }
func (mfi *memFileInfo) Sys() interface{} { return nil }
//
// memDir
//
type memDir struct {
name string
children map[string]LocalBackendFile
}
func newMemDir(name string) *memDir {
return &memDir{
name: name,
children: make(map[string]LocalBackendFile),
}
}
var _ LocalBackendFile = (*memDir)(nil)
func (md *memDir) String() string { return fmt.Sprintf("Dir[%q, %v]", md.name, md.children) }
func (md *memDir) Name() string { return md.name }
func (md *memDir) Close() error { return nil }
func (md *memDir) ReadAt(p []byte, off int64) (int, error) {
return 0, errs.New("readat on directory")
}
func (md *memDir) Write(p []byte) (int, error) {
return 0, errs.New("writeat on directory")
}
func (md *memDir) Stat() (os.FileInfo, error) {
return (*memDirInfo)(md), nil
}
func (md *memDir) Readdir(n int) ([]os.FileInfo, error) {
if n != -1 {
return nil, errs.New("can only read all entries")
}
out := make([]os.FileInfo, 0, len(md.children))
for _, child := range md.children {
info, _ := child.Stat()
out = append(out, info)
}
return out, nil
}
type memDirInfo memDir
var _ os.FileInfo = (*memDirInfo)(nil)
func (dfi *memDirInfo) Name() string {
return filepath.Base((*memDir)(dfi).name)
}
func (dfi *memDirInfo) Size() int64 { return 0 }
func (dfi *memDirInfo) Mode() fs.FileMode { return 0777 }
func (dfi *memDirInfo) ModTime() time.Time { return time.Time{} }
func (dfi *memDirInfo) IsDir() bool { return true }
func (dfi *memDirInfo) Sys() interface{} { return nil }
//
// helpers
//
func iterateComponents(name string, cb func(name, ent string) error) error {
i := 0
for i < len(name) {
part := strings.IndexByte(name[i:], filepath.Separator)
if part == -1 {
return cb(name, name[i:])
}
if err := cb(name[:i+part], name[i:i+part]); err != nil {
return err
}
i += part + 1
}
return nil
}