diff --git a/vfs/dir_handle.go b/vfs/dir_handle.go new file mode 100644 index 000000000..2a1e84286 --- /dev/null +++ b/vfs/dir_handle.go @@ -0,0 +1,91 @@ +package vfs + +import ( + "io" + "os" +) + +// DirHandle represents an open directory +type DirHandle struct { + baseHandle + d *Dir + fis []os.FileInfo // where Readdir got to +} + +// newDirHandle opens a directory for read +func newDirHandle(d *Dir) *DirHandle { + return &DirHandle{ + d: d, + } +} + +// Stat returns info about the current directory +func (fh *DirHandle) Stat() (fi os.FileInfo, err error) { + return fh.d, nil +} + +// Readdir reads the contents of the directory associated with file and returns +// a slice of up to n FileInfo values, as would be returned by Lstat, in +// directory order. Subsequent calls on the same file will yield further +// FileInfos. +// +// If n > 0, Readdir returns at most n FileInfo structures. In this case, if +// Readdir returns an empty slice, it will return a non-nil error explaining +// why. At the end of a directory, the error is io.EOF. +// +// If n <= 0, Readdir returns all the FileInfo from the directory in a single +// slice. In this case, if Readdir succeeds (reads all the way to the end of +// the directory), it returns the slice and a nil error. If it encounters an +// error before the end of the directory, Readdir returns the FileInfo read +// until that point and a non-nil error. +func (fh *DirHandle) Readdir(n int) (fis []os.FileInfo, err error) { + if fh.fis == nil { + nodes, err := fh.d.ReadDirAll() + if err != nil { + return nil, err + } + fh.fis = []os.FileInfo{} + for _, node := range nodes { + fh.fis = append(fh.fis, node) + } + } + nn := len(fh.fis) + if n > 0 { + if nn == 0 { + return nil, io.EOF + } + if nn > n { + nn = n + } + } + fis, fh.fis = fh.fis[:nn], fh.fis[nn:] + return fis, nil +} + +// Readdirnames reads and returns a slice of names from the directory f. +// +// If n > 0, Readdirnames returns at most n names. In this case, if +// Readdirnames returns an empty slice, it will return a non-nil error +// explaining why. At the end of a directory, the error is io.EOF. +// +// If n <= 0, Readdirnames returns all the names from the directory in a single +// slice. In this case, if Readdirnames succeeds (reads all the way to the end +// of the directory), it returns the slice and a nil error. If it encounters an +// error before the end of the directory, Readdirnames returns the names read +// until that point and a non-nil error. +func (fh *DirHandle) Readdirnames(n int) (names []string, err error) { + nodes, err := fh.Readdir(n) + if err != nil { + return nil, err + } + for _, node := range nodes { + names = append(names, node.Name()) + } + return names, nil +} + +// Close closes the handle +func (fh *DirHandle) Close() (err error) { + fh.fis = nil + return nil +} diff --git a/vfs/errors.go b/vfs/errors.go index 09a3d74f8..3d0813ebe 100644 --- a/vfs/errors.go +++ b/vfs/errors.go @@ -19,6 +19,7 @@ const ( ESPIPE EBADF EROFS + ENOSYS ) // Errors which have exact counterparts in os @@ -33,6 +34,7 @@ var errorNames = []string{ ESPIPE: "Illegal seek", EBADF: "Bad file descriptor", EROFS: "Read only file system", + ENOSYS: "Function not implemented", } // Error renders the error as a string diff --git a/vfs/read.go b/vfs/read.go index d0aab4a5b..c8f99377b 100644 --- a/vfs/read.go +++ b/vfs/read.go @@ -2,6 +2,7 @@ package vfs import ( "io" + "os" "sync" "github.com/ncw/rclone/fs" @@ -10,6 +11,7 @@ import ( // ReadFileHandle is an open for read file handle on a File type ReadFileHandle struct { + baseHandle mu sync.Mutex closed bool // set if handle has been closed r *fs.Account @@ -353,6 +355,11 @@ func (fh *ReadFileHandle) Size() int64 { return fh.o.Size() } +// Stat returns info about the file +func (fh *ReadFileHandle) Stat() (os.FileInfo, error) { + return fh.file, nil +} + // Close closes the file calling Flush then Release func (fh *ReadFileHandle) Close() error { err := fh.Flush() diff --git a/vfs/vfs.go b/vfs/vfs.go index c5d2828a2..5329eeac9 100644 --- a/vfs/vfs.go +++ b/vfs/vfs.go @@ -6,12 +6,17 @@ // should behave in an identical fashion. The objects also obey Go's // standard interfaces. // +// Note that paths don't start or end with /, so the root directory +// may be referred to as "". However Stat strips slashes so you can +// use paths with slashes in. +// // It also includes directory caching package vfs import ( "fmt" "os" + "path" "strings" "sync/atomic" "time" @@ -75,6 +80,58 @@ var ( _ Noder = (*WriteFileHandle)(nil) ) +// Handle is the interface statisified by open files or directories. +// It is the methods on *os.File. Not all of them are supported. +type Handle interface { + Chdir() error + Chmod(mode os.FileMode) error + Chown(uid, gid int) error + Close() error + Fd() uintptr + Name() string + Read(b []byte) (n int, err error) + ReadAt(b []byte, off int64) (n int, err error) + Readdir(n int) ([]os.FileInfo, error) + Readdirnames(n int) (names []string, err error) + Seek(offset int64, whence int) (ret int64, err error) + Stat() (os.FileInfo, error) + Sync() error + Truncate(size int64) error + Write(b []byte) (n int, err error) + WriteAt(b []byte, off int64) (n int, err error) + WriteString(s string) (n int, err error) +} + +// baseHandle implements all the missing methods +type baseHandle struct{} + +func (h baseHandle) Chdir() error { return ENOSYS } +func (h baseHandle) Chmod(mode os.FileMode) error { return ENOSYS } +func (h baseHandle) Chown(uid, gid int) error { return ENOSYS } +func (h baseHandle) Close() error { return ENOSYS } +func (h baseHandle) Fd() uintptr { return 0 } +func (h baseHandle) Name() string { return "" } +func (h baseHandle) Read(b []byte) (n int, err error) { return 0, ENOSYS } +func (h baseHandle) ReadAt(b []byte, off int64) (n int, err error) { return 0, ENOSYS } +func (h baseHandle) Readdir(n int) ([]os.FileInfo, error) { return nil, ENOSYS } +func (h baseHandle) Readdirnames(n int) (names []string, err error) { return nil, ENOSYS } +func (h baseHandle) Seek(offset int64, whence int) (ret int64, err error) { return 0, ENOSYS } +func (h baseHandle) Stat() (os.FileInfo, error) { return nil, ENOSYS } +func (h baseHandle) Sync() error { return nil } +func (h baseHandle) Truncate(size int64) error { return ENOSYS } +func (h baseHandle) Write(b []byte) (n int, err error) { return 0, ENOSYS } +func (h baseHandle) WriteAt(b []byte, off int64) (n int, err error) { return 0, ENOSYS } +func (h baseHandle) WriteString(s string) (n int, err error) { return 0, ENOSYS } + +// Check interfaces +var ( + _ Handle = (*baseHandle)(nil) + _ Handle = (*ReadFileHandle)(nil) + _ Handle = (*WriteFileHandle)(nil) + _ Handle = (*DirHandle)(nil) + _ Handle = (*os.File)(nil) +) + // VFS represents the top level filing system type VFS struct { f fs.Fs @@ -146,6 +203,7 @@ func newInode() (inode uint64) { // It is the equivalent of os.Stat - Node contains the os.FileInfo // interface. func (vfs *VFS) Stat(path string) (node Node, err error) { + path = strings.Trim(path, "/") node = vfs.root for path != "" { i := strings.IndexRune(path, '/') @@ -170,3 +228,81 @@ func (vfs *VFS) Stat(path string) (node Node, err error) { } return } + +// StatParent finds the parent directory and the leaf name of a path +func (vfs *VFS) StatParent(name string) (dir *Dir, leaf string, err error) { + name = strings.Trim(name, "/") + parent, leaf := path.Split(name) + node, err := vfs.Stat(parent) + if err != nil { + return nil, "", err + } + if node.IsFile() { + return nil, "", os.ErrExist + } + dir = node.(*Dir) + return dir, leaf, nil +} + +// OpenFile a file according to the flags and perm provided +func (vfs *VFS) OpenFile(name string, flags int, perm os.FileMode) (fd Handle, err error) { + rdwrMode := flags & (os.O_RDONLY | os.O_WRONLY | os.O_RDWR) + var read bool + switch { + case rdwrMode == os.O_RDONLY: + read = true + case rdwrMode == os.O_WRONLY || (rdwrMode == os.O_RDWR && (flags&os.O_TRUNC) != 0): + read = false + case rdwrMode == os.O_RDWR: + fs.Errorf(name, "Can't open for Read and Write") + return nil, os.ErrPermission + default: + fs.Errorf(name, "Can't figure out how to open with flags: 0x%X", flags) + return nil, os.ErrPermission + } + node, err := vfs.Stat(name) + if err != nil { + if err == os.ErrNotExist && !read { + return vfs.createFile(name, flags, perm) + } + return nil, err + } + if node.IsFile() { + file := node.(*File) + if read { + fd, err = file.OpenRead() + } else { + fd, err = file.OpenWrite() + } + } else { + fd, err = newDirHandle(node.(*Dir)), nil + } + return fd, err +} + +func (vfs *VFS) createFile(name string, flags int, perm os.FileMode) (fd Handle, err error) { + dir, leaf, err := vfs.StatParent(name) + if err != nil { + return nil, err + } + _, fd, err = dir.Create(leaf) + return fd, err +} + +// Rename oldName to newName +func (vfs *VFS) Rename(oldName, newName string) error { + // find the parent directories + oldDir, oldLeaf, err := vfs.StatParent(oldName) + if err != nil { + return err + } + newDir, newLeaf, err := vfs.StatParent(newName) + if err != nil { + return err + } + err = oldDir.Rename(oldLeaf, newLeaf, newDir) + if err != nil { + return err + } + return nil +} diff --git a/vfs/write.go b/vfs/write.go index 54a842e9a..3e90d103b 100644 --- a/vfs/write.go +++ b/vfs/write.go @@ -2,6 +2,7 @@ package vfs import ( "io" + "os" "sync" "time" @@ -10,6 +11,7 @@ import ( // WriteFileHandle is an open for write handle on a File type WriteFileHandle struct { + baseHandle mu sync.Mutex closed bool // set if handle has been closed remote string @@ -195,6 +197,11 @@ func (fh *WriteFileHandle) Release() error { return err } +// Stat returns info about the file +func (fh *WriteFileHandle) Stat() (os.FileInfo, error) { + return fh.file, nil +} + // Close closes the file calling Flush then Release func (fh *WriteFileHandle) Close() error { err := fh.Flush()