c1aaff220d
This is an OS style file system abstraction with directory caching used in mount, cmount, serve webdav and serve http.
203 lines
5.4 KiB
Go
203 lines
5.4 KiB
Go
// Package vfs provides a virtual filing system layer over rclone's
|
|
// native objects.
|
|
//
|
|
// It attempts to behave in a similar way to Go's filing system
|
|
// manipulation code in the os package. The same named function
|
|
// should behave in an identical fashion. The objects also obey Go's
|
|
// standard interfaces.
|
|
//
|
|
// It also includes directory caching
|
|
package vfs
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/ncw/rclone/fs"
|
|
"github.com/spf13/pflag"
|
|
)
|
|
|
|
// Options set by command line flags
|
|
var (
|
|
NoModTime = false
|
|
NoChecksum = false
|
|
NoSeek = false
|
|
DirCacheTime = 5 * 60 * time.Second
|
|
PollInterval = time.Minute
|
|
// mount options
|
|
ReadOnly = false
|
|
Umask = 0
|
|
UID = ^uint32(0) // these values instruct WinFSP-FUSE to use the current user
|
|
GID = ^uint32(0) // overriden for non windows in mount_unix.go
|
|
// foreground = false
|
|
// default permissions for directories - modified by umask in New
|
|
DirPerms = os.FileMode(0777)
|
|
FilePerms = os.FileMode(0666)
|
|
)
|
|
|
|
// Node represents either a directory (*Dir) or a file (*File)
|
|
type Node interface {
|
|
os.FileInfo
|
|
IsFile() bool
|
|
Inode() uint64
|
|
SetModTime(modTime time.Time) error
|
|
Fsync() error
|
|
Remove() error
|
|
RemoveAll() error
|
|
DirEntry() fs.DirEntry
|
|
}
|
|
|
|
// Check interfaces
|
|
var (
|
|
_ Node = (*File)(nil)
|
|
_ Node = (*Dir)(nil)
|
|
)
|
|
|
|
// Nodes is a slice of Node
|
|
type Nodes []Node
|
|
|
|
// Sort functions
|
|
func (ns Nodes) Len() int { return len(ns) }
|
|
func (ns Nodes) Swap(i, j int) { ns[i], ns[j] = ns[j], ns[i] }
|
|
func (ns Nodes) Less(i, j int) bool { return ns[i].DirEntry().Remote() < ns[j].DirEntry().Remote() }
|
|
|
|
// Noder represents something which can return a node
|
|
type Noder interface {
|
|
fmt.Stringer
|
|
Node() Node
|
|
}
|
|
|
|
// Check interfaces
|
|
var (
|
|
_ Noder = (*File)(nil)
|
|
_ Noder = (*Dir)(nil)
|
|
_ Noder = (*ReadFileHandle)(nil)
|
|
_ Noder = (*WriteFileHandle)(nil)
|
|
)
|
|
|
|
// VFS represents the top level filing system
|
|
type VFS struct {
|
|
f fs.Fs
|
|
root *Dir
|
|
noSeek bool // don't allow seeking if set
|
|
noChecksum bool // don't check checksums if set
|
|
readOnly bool // if set VFS is read only
|
|
noModTime bool // don't read mod times for files
|
|
dirCacheTime time.Duration // how long to consider directory listing cache valid
|
|
}
|
|
|
|
// New creates a new VFS and root directory
|
|
func New(f fs.Fs) *VFS {
|
|
fsDir := fs.NewDir("", time.Now())
|
|
vfs := &VFS{
|
|
f: f,
|
|
}
|
|
|
|
// Mask permissions
|
|
DirPerms = 0777 &^ os.FileMode(Umask)
|
|
FilePerms = 0666 &^ os.FileMode(Umask)
|
|
|
|
if NoSeek {
|
|
vfs.noSeek = true
|
|
}
|
|
if NoChecksum {
|
|
vfs.noChecksum = true
|
|
}
|
|
if ReadOnly {
|
|
vfs.readOnly = true
|
|
}
|
|
if NoModTime {
|
|
vfs.noModTime = true
|
|
}
|
|
vfs.dirCacheTime = DirCacheTime
|
|
|
|
vfs.root = newDir(vfs, f, nil, fsDir)
|
|
|
|
if PollInterval > 0 {
|
|
vfs.PollChanges(PollInterval)
|
|
}
|
|
return vfs
|
|
}
|
|
|
|
// PollChanges will poll the remote every pollInterval for changes if the remote
|
|
// supports it. If a non-polling option is used, the given time interval can be
|
|
// ignored
|
|
func (vfs *VFS) PollChanges(pollInterval time.Duration) *VFS {
|
|
doDirChangeNotify := vfs.f.Features().DirChangeNotify
|
|
if doDirChangeNotify != nil {
|
|
doDirChangeNotify(vfs.root.ForgetPath, pollInterval)
|
|
}
|
|
return vfs
|
|
}
|
|
|
|
// Root returns the root node
|
|
func (vfs *VFS) Root() (*Dir, error) {
|
|
// fs.Debugf(vfs.f, "Root()")
|
|
return vfs.root, nil
|
|
}
|
|
|
|
var inodeCount uint64
|
|
|
|
// NewInode creates a new unique inode number
|
|
func NewInode() (inode uint64) {
|
|
return atomic.AddUint64(&inodeCount, 1)
|
|
}
|
|
|
|
// Lookup finds the Node by path starting from the root
|
|
func (vfs *VFS) Lookup(path string) (node Node, err error) {
|
|
node = vfs.root
|
|
for path != "" {
|
|
i := strings.IndexRune(path, '/')
|
|
var name string
|
|
if i < 0 {
|
|
name, path = path, ""
|
|
} else {
|
|
name, path = path[:i], path[i+1:]
|
|
}
|
|
if name == "" {
|
|
continue
|
|
}
|
|
dir, ok := node.(*Dir)
|
|
if !ok {
|
|
// We need to look in a directory, but found a file
|
|
return nil, ENOENT
|
|
}
|
|
node, err = dir.Lookup(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// Statfs is called to obtain file system metadata.
|
|
// It should write that data to resp.
|
|
func (vfs *VFS) Statfs() error {
|
|
/* FIXME
|
|
const blockSize = 4096
|
|
const fsBlocks = (1 << 50) / blockSize
|
|
resp.Blocks = fsBlocks // Total data blocks in file system.
|
|
resp.Bfree = fsBlocks // Free blocks in file system.
|
|
resp.Bavail = fsBlocks // Free blocks in file system if you're not root.
|
|
resp.Files = 1E9 // Total files in file system.
|
|
resp.Ffree = 1E9 // Free files in file system.
|
|
resp.Bsize = blockSize // Block size
|
|
resp.Namelen = 255 // Maximum file name length?
|
|
resp.Frsize = blockSize // Fragment size, smallest addressable data size in the file system.
|
|
*/
|
|
return nil
|
|
}
|
|
|
|
// AddFlags adds the non filing system specific flags to the command
|
|
func AddFlags(flags *pflag.FlagSet) {
|
|
flags.BoolVarP(&NoModTime, "no-modtime", "", NoModTime, "Don't read/write the modification time (can speed things up).")
|
|
flags.BoolVarP(&NoChecksum, "no-checksum", "", NoChecksum, "Don't compare checksums on up/download.")
|
|
flags.BoolVarP(&NoSeek, "no-seek", "", NoSeek, "Don't allow seeking in files.")
|
|
flags.DurationVarP(&DirCacheTime, "dir-cache-time", "", DirCacheTime, "Time to cache directory entries for.")
|
|
flags.DurationVarP(&PollInterval, "poll-interval", "", PollInterval, "Time to wait between polling for changes. Must be smaller than dir-cache-time. Only on supported remotes. Set to 0 to disable.")
|
|
flags.BoolVarP(&ReadOnly, "read-only", "", ReadOnly, "Mount read-only.")
|
|
platformFlags(flags)
|
|
}
|