mirror of
https://github.com/rclone/rclone.git
synced 2024-11-26 10:13:52 +08:00
c1aaff220d
This is an OS style file system abstraction with directory caching used in mount, cmount, serve webdav and serve http.
497 lines
12 KiB
Go
497 lines
12 KiB
Go
package vfs
|
|
|
|
import (
|
|
"os"
|
|
"path"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/ncw/rclone/fs"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// Dir represents a directory entry
|
|
type Dir struct {
|
|
vfs *VFS
|
|
inode uint64 // inode number
|
|
f fs.Fs
|
|
parent *Dir // parent, nil for root
|
|
path string
|
|
modTime time.Time
|
|
entry fs.Directory
|
|
mu sync.Mutex // protects the following
|
|
read time.Time // time directory entry last read
|
|
items map[string]Node // NB can be nil when directory not read yet
|
|
}
|
|
|
|
func newDir(vfs *VFS, f fs.Fs, parent *Dir, fsDir fs.Directory) *Dir {
|
|
return &Dir{
|
|
vfs: vfs,
|
|
f: f,
|
|
parent: parent,
|
|
entry: fsDir,
|
|
path: fsDir.Remote(),
|
|
modTime: fsDir.ModTime(),
|
|
inode: NewInode(),
|
|
}
|
|
}
|
|
|
|
// String converts it to printablee
|
|
func (d *Dir) String() string {
|
|
if d == nil {
|
|
return "<nil *Dir>"
|
|
}
|
|
return d.path + "/"
|
|
}
|
|
|
|
// IsFile returns false for Dir - satisfies Node interface
|
|
func (d *Dir) IsFile() bool {
|
|
return false
|
|
}
|
|
|
|
// IsDir returns true for Dir - satisfies Node interface
|
|
func (d *Dir) IsDir() bool {
|
|
return true
|
|
}
|
|
|
|
// Mode bits of the directory - satisfies Node interface
|
|
func (d *Dir) Mode() (mode os.FileMode) {
|
|
return os.ModeDir | 0777
|
|
}
|
|
|
|
// Name (base) of the directory - satisfies Node interface
|
|
func (d *Dir) Name() (name string) {
|
|
name = path.Base(d.path)
|
|
if name == "." {
|
|
name = "/"
|
|
}
|
|
return name
|
|
}
|
|
|
|
// Sys returns underlying data source (can be nil) - satisfies Node interface
|
|
func (d *Dir) Sys() interface{} {
|
|
return nil
|
|
}
|
|
|
|
// Inode returns the inode number - satisfies Node interface
|
|
func (d *Dir) Inode() uint64 {
|
|
return d.inode
|
|
}
|
|
|
|
// Node returns the Node assocuated with this - satisfies Noder interface
|
|
func (d *Dir) Node() Node {
|
|
return d
|
|
}
|
|
|
|
// ForgetAll ensures the directory and all its children are purged
|
|
// from the cache.
|
|
func (d *Dir) ForgetAll() {
|
|
d.ForgetPath("")
|
|
}
|
|
|
|
// ForgetPath clears the cache for itself and all subdirectories if
|
|
// they match the given path. The path is specified relative from the
|
|
// directory it is called from.
|
|
// It is not possible to traverse the directory tree upwards, i.e.
|
|
// you cannot clear the cache for the Dir's ancestors or siblings.
|
|
func (d *Dir) ForgetPath(relativePath string) {
|
|
absPath := path.Join(d.path, relativePath)
|
|
if absPath == "." {
|
|
absPath = ""
|
|
}
|
|
|
|
d.walk(absPath, func(dir *Dir) {
|
|
fs.Debugf(dir.path, "forgetting directory cache")
|
|
dir.read = time.Time{}
|
|
dir.items = nil
|
|
})
|
|
}
|
|
|
|
// walk runs a function on all directories whose path matches
|
|
// the given absolute one. It will be called on a directory's
|
|
// children first. It will not apply the function to parent
|
|
// nodes, regardless of the given path.
|
|
func (d *Dir) walk(absPath string, fun func(*Dir)) {
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
if d.items != nil {
|
|
for _, node := range d.items {
|
|
if dir, ok := node.(*Dir); ok {
|
|
dir.walk(absPath, fun)
|
|
}
|
|
}
|
|
}
|
|
|
|
if d.path == absPath || absPath == "" || strings.HasPrefix(d.path, absPath+"/") {
|
|
fun(d)
|
|
}
|
|
}
|
|
|
|
// rename should be called after the directory is renamed
|
|
//
|
|
// Reset the directory to new state, discarding all the objects and
|
|
// reading everything again
|
|
func (d *Dir) rename(newParent *Dir, fsDir fs.Directory) {
|
|
d.ForgetAll()
|
|
d.parent = newParent
|
|
d.entry = fsDir
|
|
d.path = fsDir.Remote()
|
|
d.modTime = fsDir.ModTime()
|
|
d.read = time.Time{}
|
|
}
|
|
|
|
// addObject adds a new object or directory to the directory
|
|
//
|
|
// note that we add new objects rather than updating old ones
|
|
func (d *Dir) addObject(node Node) {
|
|
d.mu.Lock()
|
|
if d.items != nil {
|
|
d.items[node.Name()] = node
|
|
}
|
|
d.mu.Unlock()
|
|
}
|
|
|
|
// delObject removes an object from the directory
|
|
func (d *Dir) delObject(leaf string) {
|
|
d.mu.Lock()
|
|
if d.items != nil {
|
|
delete(d.items, leaf)
|
|
}
|
|
d.mu.Unlock()
|
|
}
|
|
|
|
// read the directory and sets d.items - must be called with the lock held
|
|
func (d *Dir) _readDir() error {
|
|
when := time.Now()
|
|
if d.read.IsZero() || d.items == nil {
|
|
// fs.Debugf(d.path, "Reading directory")
|
|
} else {
|
|
age := when.Sub(d.read)
|
|
if age < d.vfs.dirCacheTime {
|
|
return nil
|
|
}
|
|
fs.Debugf(d.path, "Re-reading directory (%v old)", age)
|
|
}
|
|
entries, err := fs.ListDirSorted(d.f, false, d.path)
|
|
if err == fs.ErrorDirNotFound {
|
|
// We treat directory not found as empty because we
|
|
// create directories on the fly
|
|
} else if err != nil {
|
|
return err
|
|
}
|
|
// NB when we re-read a directory after its cache has expired
|
|
// we drop the old files which should lead to correct
|
|
// behaviour but may not be very efficient.
|
|
|
|
// Keep a note of the previous contents of the directory
|
|
oldItems := d.items
|
|
|
|
// Cache the items by name
|
|
d.items = make(map[string]Node, len(entries))
|
|
for _, entry := range entries {
|
|
switch item := entry.(type) {
|
|
case fs.Object:
|
|
obj := item
|
|
name := path.Base(obj.Remote())
|
|
d.items[name] = newFile(d, obj, name)
|
|
case fs.Directory:
|
|
dir := item
|
|
name := path.Base(dir.Remote())
|
|
// Use old dir value if it exists
|
|
if oldItems != nil {
|
|
if oldNode, ok := oldItems[name]; ok {
|
|
if oldNode.IsDir() {
|
|
d.items[name] = oldNode
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
d.items[name] = newDir(d.vfs, d.f, d, dir)
|
|
default:
|
|
err = errors.Errorf("unknown type %T", item)
|
|
fs.Errorf(d.path, "readDir error: %v", err)
|
|
return err
|
|
}
|
|
}
|
|
d.read = when
|
|
return nil
|
|
}
|
|
|
|
// lookup a single item in the directory
|
|
//
|
|
// returns ENOENT if not found.
|
|
func (d *Dir) lookup(leaf string) (Node, error) {
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
err := d._readDir()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
item, ok := d.items[leaf]
|
|
if !ok {
|
|
return nil, ENOENT
|
|
}
|
|
return item, nil
|
|
}
|
|
|
|
// Check to see if a directory is empty
|
|
func (d *Dir) isEmpty() (bool, error) {
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
err := d._readDir()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return len(d.items) == 0, nil
|
|
}
|
|
|
|
// ModTime returns the modification time of the directory
|
|
func (d *Dir) ModTime() time.Time {
|
|
// fs.Debugf(d.path, "Dir.ModTime %v", d.modTime)
|
|
return d.modTime
|
|
}
|
|
|
|
// Size of the directory
|
|
func (d *Dir) Size() int64 {
|
|
return 0
|
|
}
|
|
|
|
// SetModTime sets the modTime for this dir
|
|
func (d *Dir) SetModTime(modTime time.Time) error {
|
|
if d.vfs.readOnly {
|
|
return EROFS
|
|
}
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
d.modTime = modTime
|
|
return nil
|
|
}
|
|
|
|
// Lookup looks up a specific entry in the receiver.
|
|
//
|
|
// Lookup should return a Node corresponding to the entry. If the
|
|
// name does not exist in the directory, Lookup should return ENOENT.
|
|
//
|
|
// Lookup need not to handle the names "." and "..".
|
|
func (d *Dir) Lookup(name string) (node Node, err error) {
|
|
path := path.Join(d.path, name)
|
|
// fs.Debugf(path, "Dir.Lookup")
|
|
node, err = d.lookup(name)
|
|
if err != nil {
|
|
if err != ENOENT {
|
|
fs.Errorf(path, "Dir.Lookup error: %v", err)
|
|
}
|
|
return nil, err
|
|
}
|
|
// fs.Debugf(path, "Dir.Lookup OK")
|
|
return node, nil
|
|
}
|
|
|
|
// ReadDirAll reads the contents of the directory sorted
|
|
func (d *Dir) ReadDirAll() (items Nodes, err error) {
|
|
// fs.Debugf(d.path, "Dir.ReadDirAll")
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
err = d._readDir()
|
|
if err != nil {
|
|
fs.Debugf(d.path, "Dir.ReadDirAll error: %v", err)
|
|
return nil, err
|
|
}
|
|
for _, item := range d.items {
|
|
items = append(items, item)
|
|
}
|
|
sort.Sort(items)
|
|
// fs.Debugf(d.path, "Dir.ReadDirAll OK with %d entries", len(items))
|
|
return items, nil
|
|
}
|
|
|
|
// Create makes a new file
|
|
func (d *Dir) Create(name string) (*File, *WriteFileHandle, error) {
|
|
if d.vfs.readOnly {
|
|
return nil, nil, EROFS
|
|
}
|
|
path := path.Join(d.path, name)
|
|
// fs.Debugf(path, "Dir.Create")
|
|
src := newCreateInfo(d.f, path)
|
|
// This gets added to the directory when the file is written
|
|
file := newFile(d, nil, name)
|
|
fh, err := newWriteFileHandle(d, file, src)
|
|
if err != nil {
|
|
fs.Errorf(path, "Dir.Create error: %v", err)
|
|
return nil, nil, err
|
|
}
|
|
// fs.Debugf(path, "Dir.Create OK")
|
|
return file, fh, nil
|
|
}
|
|
|
|
// Mkdir creates a new directory
|
|
func (d *Dir) Mkdir(name string) (*Dir, error) {
|
|
if d.vfs.readOnly {
|
|
return nil, EROFS
|
|
}
|
|
path := path.Join(d.path, name)
|
|
// fs.Debugf(path, "Dir.Mkdir")
|
|
err := d.f.Mkdir(path)
|
|
if err != nil {
|
|
fs.Errorf(path, "Dir.Mkdir failed to create directory: %v", err)
|
|
return nil, err
|
|
}
|
|
fsDir := fs.NewDir(path, time.Now())
|
|
dir := newDir(d.vfs, d.f, d, fsDir)
|
|
d.addObject(dir)
|
|
// fs.Debugf(path, "Dir.Mkdir OK")
|
|
return dir, nil
|
|
}
|
|
|
|
// Remove the directory
|
|
func (d *Dir) Remove() error {
|
|
if d.vfs.readOnly {
|
|
return EROFS
|
|
}
|
|
// Check directory is empty first
|
|
empty, err := d.isEmpty()
|
|
if err != nil {
|
|
fs.Errorf(d.path, "Dir.Remove dir error: %v", err)
|
|
return err
|
|
}
|
|
if !empty {
|
|
fs.Errorf(d.path, "Dir.Remove not empty")
|
|
return ENOTEMPTY
|
|
}
|
|
// remove directory
|
|
err = d.f.Rmdir(d.path)
|
|
if err != nil {
|
|
fs.Errorf(d.path, "Dir.Remove failed to remove directory: %v", err)
|
|
return err
|
|
}
|
|
// Remove the item from the parent directory listing
|
|
if d.parent != nil {
|
|
d.parent.delObject(d.Name())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// RemoveAll removes the directory and any contents recursively
|
|
func (d *Dir) RemoveAll() error {
|
|
if d.vfs.readOnly {
|
|
return EROFS
|
|
}
|
|
// Remove contents of the directory
|
|
nodes, err := d.ReadDirAll()
|
|
if err != nil {
|
|
fs.Errorf(d.path, "Dir.RemoveAll failed to read directory: %v", err)
|
|
return err
|
|
}
|
|
for _, node := range nodes {
|
|
err = node.RemoveAll()
|
|
if err != nil {
|
|
fs.Errorf(node.DirEntry(), "Dir.RemoveAll failed to remove: %v", err)
|
|
return err
|
|
}
|
|
}
|
|
return d.Remove()
|
|
}
|
|
|
|
// DirEntry returns the underlying fs.DirEntry
|
|
func (d *Dir) DirEntry() (entry fs.DirEntry) {
|
|
return d.entry
|
|
}
|
|
|
|
// RemoveName removes the entry with the given name from the receiver,
|
|
// which must be a directory. The entry to be removed may correspond
|
|
// to a file (unlink) or to a directory (rmdir).
|
|
func (d *Dir) RemoveName(name string) error {
|
|
if d.vfs.readOnly {
|
|
return EROFS
|
|
}
|
|
path := path.Join(d.path, name)
|
|
// fs.Debugf(path, "Dir.Remove")
|
|
node, err := d.lookup(name)
|
|
if err != nil {
|
|
fs.Errorf(path, "Dir.Remove error: %v", err)
|
|
return err
|
|
}
|
|
return node.Remove()
|
|
}
|
|
|
|
// Rename the file
|
|
func (d *Dir) Rename(oldName, newName string, destDir *Dir) error {
|
|
if d.vfs.readOnly {
|
|
return EROFS
|
|
}
|
|
oldPath := path.Join(d.path, oldName)
|
|
newPath := path.Join(destDir.path, newName)
|
|
// fs.Debugf(oldPath, "Dir.Rename to %q", newPath)
|
|
oldNode, err := d.lookup(oldName)
|
|
if err != nil {
|
|
fs.Errorf(oldPath, "Dir.Rename error: %v", err)
|
|
return err
|
|
}
|
|
switch x := oldNode.DirEntry().(type) {
|
|
case fs.Object:
|
|
oldObject := x
|
|
// FIXME: could Copy then Delete if Move not available
|
|
// - though care needed if case insensitive...
|
|
doMove := d.f.Features().Move
|
|
if doMove == nil {
|
|
err := errors.Errorf("Fs %q can't rename files (no Move)", d.f)
|
|
fs.Errorf(oldPath, "Dir.Rename error: %v", err)
|
|
return err
|
|
}
|
|
newObject, err := doMove(oldObject, newPath)
|
|
if err != nil {
|
|
fs.Errorf(oldPath, "Dir.Rename error: %v", err)
|
|
return err
|
|
}
|
|
// Update the node with the new details
|
|
if oldNode != nil {
|
|
if oldFile, ok := oldNode.(*File); ok {
|
|
fs.Debugf(oldNode.DirEntry(), "Updating file with %v %p", newObject, oldFile)
|
|
oldFile.rename(destDir, newObject)
|
|
}
|
|
}
|
|
case fs.Directory:
|
|
doDirMove := d.f.Features().DirMove
|
|
if doDirMove == nil {
|
|
err := errors.Errorf("Fs %q can't rename directories (no DirMove)", d.f)
|
|
fs.Errorf(oldPath, "Dir.Rename error: %v", err)
|
|
return err
|
|
}
|
|
srcRemote := x.Remote()
|
|
dstRemote := newPath
|
|
err = doDirMove(d.f, srcRemote, dstRemote)
|
|
if err != nil {
|
|
fs.Errorf(oldPath, "Dir.Rename error: %v", err)
|
|
return err
|
|
}
|
|
newDir := fs.NewDirCopy(x).SetRemote(newPath)
|
|
// Update the node with the new details
|
|
if oldNode != nil {
|
|
if oldDir, ok := oldNode.(*Dir); ok {
|
|
fs.Debugf(oldNode.DirEntry(), "Updating dir with %v %p", newDir, oldDir)
|
|
oldDir.rename(destDir, newDir)
|
|
}
|
|
}
|
|
default:
|
|
err = errors.Errorf("unknown type %T", oldNode)
|
|
fs.Errorf(d.path, "Dir.ReadDirAll error: %v", err)
|
|
return err
|
|
}
|
|
|
|
// Show moved - delete from old dir and add to new
|
|
d.delObject(oldName)
|
|
destDir.addObject(oldNode)
|
|
|
|
// fs.Debugf(newPath, "Dir.Rename renamed from %q", oldPath)
|
|
return nil
|
|
}
|
|
|
|
// Fsync the directory
|
|
//
|
|
// Note that we don't do anything except return OK
|
|
func (d *Dir) Fsync() error {
|
|
return nil
|
|
}
|