mirror of
https://github.com/rclone/rclone.git
synced 2024-11-07 09:04:52 +01:00
vfs: Make file handles compatible with OS
* Implement directory handles * Unify OpenFile * Add all the methods to match *os.File * Add StatParent and Rename methods to VFS
This commit is contained in:
parent
3e0c91ba4b
commit
a5dc62f6c1
91
vfs/dir_handle.go
Normal file
91
vfs/dir_handle.go
Normal file
@ -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
|
||||
}
|
@ -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
|
||||
|
@ -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()
|
||||
|
136
vfs/vfs.go
136
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
|
||||
}
|
||||
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user