mirror of
https://github.com/rclone/rclone.git
synced 2024-11-25 18:04:55 +01:00
local: define OpenWriterAt interface and test and implement it #2252
This will enable multipart downloads in future commits
This commit is contained in:
parent
72721f4c8d
commit
7c4fe3eb75
2
backend/cache/cache_test.go
vendored
2
backend/cache/cache_test.go
vendored
@ -17,7 +17,7 @@ func TestIntegration(t *testing.T) {
|
|||||||
fstests.Run(t, &fstests.Opt{
|
fstests.Run(t, &fstests.Opt{
|
||||||
RemoteName: "TestCache:",
|
RemoteName: "TestCache:",
|
||||||
NilObject: (*cache.Object)(nil),
|
NilObject: (*cache.Object)(nil),
|
||||||
UnimplementableFsMethods: []string{"PublicLink", "MergeDirs"},
|
UnimplementableFsMethods: []string{"PublicLink", "MergeDirs", "OpenWriterAt"},
|
||||||
UnimplementableObjectMethods: []string{"MimeType", "ID", "GetTier", "SetTier"},
|
UnimplementableObjectMethods: []string{"MimeType", "ID", "GetTier", "SetTier"},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ func TestIntegration(t *testing.T) {
|
|||||||
fstests.Run(t, &fstests.Opt{
|
fstests.Run(t, &fstests.Opt{
|
||||||
RemoteName: *fstest.RemoteName,
|
RemoteName: *fstest.RemoteName,
|
||||||
NilObject: (*crypt.Object)(nil),
|
NilObject: (*crypt.Object)(nil),
|
||||||
|
UnimplementableFsMethods: []string{"OpenWriterAt"},
|
||||||
UnimplementableObjectMethods: []string{"MimeType"},
|
UnimplementableObjectMethods: []string{"MimeType"},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -43,6 +44,7 @@ func TestStandard(t *testing.T) {
|
|||||||
{Name: name, Key: "password", Value: obscure.MustObscure("potato")},
|
{Name: name, Key: "password", Value: obscure.MustObscure("potato")},
|
||||||
{Name: name, Key: "filename_encryption", Value: "standard"},
|
{Name: name, Key: "filename_encryption", Value: "standard"},
|
||||||
},
|
},
|
||||||
|
UnimplementableFsMethods: []string{"OpenWriterAt"},
|
||||||
UnimplementableObjectMethods: []string{"MimeType"},
|
UnimplementableObjectMethods: []string{"MimeType"},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -63,6 +65,7 @@ func TestOff(t *testing.T) {
|
|||||||
{Name: name, Key: "password", Value: obscure.MustObscure("potato2")},
|
{Name: name, Key: "password", Value: obscure.MustObscure("potato2")},
|
||||||
{Name: name, Key: "filename_encryption", Value: "off"},
|
{Name: name, Key: "filename_encryption", Value: "off"},
|
||||||
},
|
},
|
||||||
|
UnimplementableFsMethods: []string{"OpenWriterAt"},
|
||||||
UnimplementableObjectMethods: []string{"MimeType"},
|
UnimplementableObjectMethods: []string{"MimeType"},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -84,6 +87,7 @@ func TestObfuscate(t *testing.T) {
|
|||||||
{Name: name, Key: "filename_encryption", Value: "obfuscate"},
|
{Name: name, Key: "filename_encryption", Value: "obfuscate"},
|
||||||
},
|
},
|
||||||
SkipBadWindowsCharacters: true,
|
SkipBadWindowsCharacters: true,
|
||||||
|
UnimplementableFsMethods: []string{"OpenWriterAt"},
|
||||||
UnimplementableObjectMethods: []string{"MimeType"},
|
UnimplementableObjectMethods: []string{"MimeType"},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -998,6 +998,36 @@ func (o *Object) Update(in io.Reader, src fs.ObjectInfo, options ...fs.OpenOptio
|
|||||||
return o.lstat()
|
return o.lstat()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OpenWriterAt opens with a handle for random access writes
|
||||||
|
//
|
||||||
|
// Pass in the remote desired and the size if known.
|
||||||
|
//
|
||||||
|
// It truncates any existing object
|
||||||
|
func (f *Fs) OpenWriterAt(remote string, size int64) (fs.WriterAtCloser, error) {
|
||||||
|
// Temporary Object under construction
|
||||||
|
o := f.newObject(remote, "")
|
||||||
|
|
||||||
|
err := o.mkdirAll()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if o.translatedLink {
|
||||||
|
return nil, errors.New("can't open a symlink for random writing")
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := file.OpenFile(o.path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Pre-allocate the file for performance reasons
|
||||||
|
err = preAllocate(size, out)
|
||||||
|
if err != nil {
|
||||||
|
fs.Debugf(o, "Failed to pre-allocate: %v", err)
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
// setMetadata sets the file info from the os.FileInfo passed in
|
// setMetadata sets the file info from the os.FileInfo passed in
|
||||||
func (o *Object) setMetadata(info os.FileInfo) {
|
func (o *Object) setMetadata(info os.FileInfo) {
|
||||||
// Don't overwrite the info if we don't need to
|
// Don't overwrite the info if we don't need to
|
||||||
@ -1139,10 +1169,11 @@ func cleanWindowsName(f *Fs, name string) string {
|
|||||||
|
|
||||||
// Check the interfaces are satisfied
|
// Check the interfaces are satisfied
|
||||||
var (
|
var (
|
||||||
_ fs.Fs = &Fs{}
|
_ fs.Fs = &Fs{}
|
||||||
_ fs.Purger = &Fs{}
|
_ fs.Purger = &Fs{}
|
||||||
_ fs.PutStreamer = &Fs{}
|
_ fs.PutStreamer = &Fs{}
|
||||||
_ fs.Mover = &Fs{}
|
_ fs.Mover = &Fs{}
|
||||||
_ fs.DirMover = &Fs{}
|
_ fs.DirMover = &Fs{}
|
||||||
_ fs.Object = &Object{}
|
_ fs.OpenWriterAter = &Fs{}
|
||||||
|
_ fs.Object = &Object{}
|
||||||
)
|
)
|
||||||
|
29
fs/fs.go
29
fs/fs.go
@ -427,6 +427,12 @@ type Usage struct {
|
|||||||
Objects *int64 `json:"objects,omitempty"` // objects in the storage system
|
Objects *int64 `json:"objects,omitempty"` // objects in the storage system
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WriterAtCloser wraps io.WriterAt and io.Closer
|
||||||
|
type WriterAtCloser interface {
|
||||||
|
io.WriterAt
|
||||||
|
io.Closer
|
||||||
|
}
|
||||||
|
|
||||||
// Features describe the optional features of the Fs
|
// Features describe the optional features of the Fs
|
||||||
type Features struct {
|
type Features struct {
|
||||||
// Feature flags, whether Fs
|
// Feature flags, whether Fs
|
||||||
@ -548,6 +554,13 @@ type Features struct {
|
|||||||
|
|
||||||
// About gets quota information from the Fs
|
// About gets quota information from the Fs
|
||||||
About func() (*Usage, error)
|
About func() (*Usage, error)
|
||||||
|
|
||||||
|
// OpenWriterAt opens with a handle for random access writes
|
||||||
|
//
|
||||||
|
// Pass in the remote desired and the size if known.
|
||||||
|
//
|
||||||
|
// It truncates any existing object
|
||||||
|
OpenWriterAt func(remote string, size int64) (WriterAtCloser, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable nil's out the named feature. If it isn't found then it
|
// Disable nil's out the named feature. If it isn't found then it
|
||||||
@ -640,6 +653,9 @@ func (ft *Features) Fill(f Fs) *Features {
|
|||||||
if do, ok := f.(Abouter); ok {
|
if do, ok := f.(Abouter); ok {
|
||||||
ft.About = do.About
|
ft.About = do.About
|
||||||
}
|
}
|
||||||
|
if do, ok := f.(OpenWriterAter); ok {
|
||||||
|
ft.OpenWriterAt = do.OpenWriterAt
|
||||||
|
}
|
||||||
return ft.DisableList(Config.DisableFeatures)
|
return ft.DisableList(Config.DisableFeatures)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -705,6 +721,9 @@ func (ft *Features) Mask(f Fs) *Features {
|
|||||||
if mask.About == nil {
|
if mask.About == nil {
|
||||||
ft.About = nil
|
ft.About = nil
|
||||||
}
|
}
|
||||||
|
if mask.OpenWriterAt == nil {
|
||||||
|
ft.OpenWriterAt = nil
|
||||||
|
}
|
||||||
return ft.DisableList(Config.DisableFeatures)
|
return ft.DisableList(Config.DisableFeatures)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -904,6 +923,16 @@ type Abouter interface {
|
|||||||
About() (*Usage, error)
|
About() (*Usage, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OpenWriterAter is an optional interface for Fs
|
||||||
|
type OpenWriterAter interface {
|
||||||
|
// OpenWriterAt opens with a handle for random access writes
|
||||||
|
//
|
||||||
|
// Pass in the remote desired and the size if known.
|
||||||
|
//
|
||||||
|
// It truncates any existing object
|
||||||
|
OpenWriterAt(remote string, size int64) (WriterAtCloser, error)
|
||||||
|
}
|
||||||
|
|
||||||
// ObjectsChan is a channel of Objects
|
// ObjectsChan is a channel of Objects
|
||||||
type ObjectsChan chan Object
|
type ObjectsChan chan Object
|
||||||
|
|
||||||
|
@ -674,6 +674,36 @@ func Run(t *testing.T, opt *Opt) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("FsOpenWriterAt", func(t *testing.T) {
|
||||||
|
skipIfNotOk(t)
|
||||||
|
openWriterAt := remote.Features().OpenWriterAt
|
||||||
|
if openWriterAt == nil {
|
||||||
|
t.Skip("FS has no OpenWriterAt interface")
|
||||||
|
}
|
||||||
|
path := "writer-at-subdir/writer-at-file"
|
||||||
|
out, err := openWriterAt(path, -1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var n int
|
||||||
|
n, err = out.WriteAt([]byte("def"), 3)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 3, n)
|
||||||
|
n, err = out.WriteAt([]byte("ghi"), 6)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 3, n)
|
||||||
|
n, err = out.WriteAt([]byte("abc"), 0)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 3, n)
|
||||||
|
|
||||||
|
assert.NoError(t, out.Close())
|
||||||
|
|
||||||
|
obj := findObject(t, remote, path)
|
||||||
|
assert.Equal(t, "abcdefghi", readObject(t, obj, -1), "contents of file differ")
|
||||||
|
|
||||||
|
assert.NoError(t, obj.Remove())
|
||||||
|
assert.NoError(t, remote.Rmdir("writer-at-subdir"))
|
||||||
|
})
|
||||||
|
|
||||||
// TestFsChangeNotify tests that changes are properly
|
// TestFsChangeNotify tests that changes are properly
|
||||||
// propagated
|
// propagated
|
||||||
//
|
//
|
||||||
|
Loading…
Reference in New Issue
Block a user