mirror of
https://github.com/rclone/rclone.git
synced 2024-11-25 09:41:44 +08:00
cache,chunker,combine,compress,crypt,hasher,union: implement MkdirMetadata and related Features
This commit is contained in:
parent
0297542f6b
commit
39db8caff1
11
backend/cache/cache_test.go
vendored
11
backend/cache/cache_test.go
vendored
|
@ -16,10 +16,11 @@ import (
|
||||||
// TestIntegration runs integration tests against the remote
|
// TestIntegration runs integration tests against the remote
|
||||||
func TestIntegration(t *testing.T) {
|
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", "OpenWriterAt", "OpenChunkWriter"},
|
UnimplementableFsMethods: []string{"PublicLink", "OpenWriterAt", "OpenChunkWriter", "DirSetModTime", "MkdirMetadata"},
|
||||||
UnimplementableObjectMethods: []string{"MimeType", "ID", "GetTier", "SetTier", "Metadata"},
|
UnimplementableObjectMethods: []string{"MimeType", "ID", "GetTier", "SetTier", "Metadata"},
|
||||||
SkipInvalidUTF8: true, // invalid UTF-8 confuses the cache
|
UnimplementableDirectoryMethods: []string{"Metadata", "SetMetadata", "SetModTime"},
|
||||||
|
SkipInvalidUTF8: true, // invalid UTF-8 confuses the cache
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -338,13 +338,18 @@ func NewFs(ctx context.Context, name, rpath string, m configmap.Mapper) (fs.Fs,
|
||||||
// Note 2: features.Fill() points features.PutStream to our PutStream,
|
// Note 2: features.Fill() points features.PutStream to our PutStream,
|
||||||
// but features.Mask() will nullify it if wrappedFs does not have it.
|
// but features.Mask() will nullify it if wrappedFs does not have it.
|
||||||
f.features = (&fs.Features{
|
f.features = (&fs.Features{
|
||||||
CaseInsensitive: true,
|
CaseInsensitive: true,
|
||||||
DuplicateFiles: true,
|
DuplicateFiles: true,
|
||||||
ReadMimeType: false, // Object.MimeType not supported
|
ReadMimeType: false, // Object.MimeType not supported
|
||||||
WriteMimeType: true,
|
WriteMimeType: true,
|
||||||
BucketBased: true,
|
BucketBased: true,
|
||||||
CanHaveEmptyDirectories: true,
|
CanHaveEmptyDirectories: true,
|
||||||
ServerSideAcrossConfigs: true,
|
ServerSideAcrossConfigs: true,
|
||||||
|
ReadDirMetadata: true,
|
||||||
|
WriteDirMetadata: true,
|
||||||
|
WriteDirSetModTime: true,
|
||||||
|
UserDirMetadata: true,
|
||||||
|
DirModTimeUpdatesOnWrite: true,
|
||||||
}).Fill(ctx, f).Mask(ctx, baseFs).WrapsFs(f, baseFs)
|
}).Fill(ctx, f).Mask(ctx, baseFs).WrapsFs(f, baseFs)
|
||||||
|
|
||||||
f.features.Disable("ListR") // Recursive listing may cause chunker skip files
|
f.features.Disable("ListR") // Recursive listing may cause chunker skip files
|
||||||
|
@ -821,8 +826,7 @@ func (f *Fs) processEntries(ctx context.Context, origEntries fs.DirEntries, dirP
|
||||||
}
|
}
|
||||||
case fs.Directory:
|
case fs.Directory:
|
||||||
isSubdir[entry.Remote()] = true
|
isSubdir[entry.Remote()] = true
|
||||||
wrapDir := fs.NewDirCopy(ctx, entry)
|
wrapDir := fs.NewDirWrapper(entry.Remote(), entry)
|
||||||
wrapDir.SetRemote(entry.Remote())
|
|
||||||
tempEntries = append(tempEntries, wrapDir)
|
tempEntries = append(tempEntries, wrapDir)
|
||||||
default:
|
default:
|
||||||
if f.opt.FailHard {
|
if f.opt.FailHard {
|
||||||
|
@ -1571,6 +1575,14 @@ func (f *Fs) Mkdir(ctx context.Context, dir string) error {
|
||||||
return f.base.Mkdir(ctx, dir)
|
return f.base.Mkdir(ctx, dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MkdirMetadata makes the root directory of the Fs object
|
||||||
|
func (f *Fs) MkdirMetadata(ctx context.Context, dir string, metadata fs.Metadata) (fs.Directory, error) {
|
||||||
|
if do := f.base.Features().MkdirMetadata; do != nil {
|
||||||
|
return do(ctx, dir, metadata)
|
||||||
|
}
|
||||||
|
return nil, fs.ErrorNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
// Rmdir removes the directory (container, bucket) if empty
|
// Rmdir removes the directory (container, bucket) if empty
|
||||||
//
|
//
|
||||||
// Return an error if it doesn't exist or isn't empty
|
// Return an error if it doesn't exist or isn't empty
|
||||||
|
@ -2557,6 +2569,7 @@ var (
|
||||||
_ fs.Mover = (*Fs)(nil)
|
_ fs.Mover = (*Fs)(nil)
|
||||||
_ fs.DirMover = (*Fs)(nil)
|
_ fs.DirMover = (*Fs)(nil)
|
||||||
_ fs.DirSetModTimer = (*Fs)(nil)
|
_ fs.DirSetModTimer = (*Fs)(nil)
|
||||||
|
_ fs.MkdirMetadataer = (*Fs)(nil)
|
||||||
_ fs.PutUncheckeder = (*Fs)(nil)
|
_ fs.PutUncheckeder = (*Fs)(nil)
|
||||||
_ fs.PutStreamer = (*Fs)(nil)
|
_ fs.PutStreamer = (*Fs)(nil)
|
||||||
_ fs.CleanUpper = (*Fs)(nil)
|
_ fs.CleanUpper = (*Fs)(nil)
|
||||||
|
|
|
@ -222,18 +222,23 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (outFs fs
|
||||||
}
|
}
|
||||||
// check features
|
// check features
|
||||||
var features = (&fs.Features{
|
var features = (&fs.Features{
|
||||||
CaseInsensitive: true,
|
CaseInsensitive: true,
|
||||||
DuplicateFiles: false,
|
DuplicateFiles: false,
|
||||||
ReadMimeType: true,
|
ReadMimeType: true,
|
||||||
WriteMimeType: true,
|
WriteMimeType: true,
|
||||||
CanHaveEmptyDirectories: true,
|
CanHaveEmptyDirectories: true,
|
||||||
BucketBased: true,
|
BucketBased: true,
|
||||||
SetTier: true,
|
SetTier: true,
|
||||||
GetTier: true,
|
GetTier: true,
|
||||||
ReadMetadata: true,
|
ReadMetadata: true,
|
||||||
WriteMetadata: true,
|
WriteMetadata: true,
|
||||||
UserMetadata: true,
|
UserMetadata: true,
|
||||||
PartialUploads: true,
|
ReadDirMetadata: true,
|
||||||
|
WriteDirMetadata: true,
|
||||||
|
WriteDirSetModTime: true,
|
||||||
|
UserDirMetadata: true,
|
||||||
|
DirModTimeUpdatesOnWrite: true,
|
||||||
|
PartialUploads: true,
|
||||||
}).Fill(ctx, f)
|
}).Fill(ctx, f)
|
||||||
canMove := true
|
canMove := true
|
||||||
for _, u := range f.upstreams {
|
for _, u := range f.upstreams {
|
||||||
|
@ -440,6 +445,32 @@ func (f *Fs) Mkdir(ctx context.Context, dir string) error {
|
||||||
return u.f.Mkdir(ctx, uRemote)
|
return u.f.Mkdir(ctx, uRemote)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MkdirMetadata makes the root directory of the Fs object
|
||||||
|
func (f *Fs) MkdirMetadata(ctx context.Context, dir string, metadata fs.Metadata) (fs.Directory, error) {
|
||||||
|
u, uRemote, err := f.findUpstream(dir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
do := u.f.Features().MkdirMetadata
|
||||||
|
if do == nil {
|
||||||
|
return nil, fs.ErrorNotImplemented
|
||||||
|
}
|
||||||
|
newDir, err := do(ctx, uRemote, metadata)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
entries := fs.DirEntries{newDir}
|
||||||
|
entries, err = u.wrapEntries(ctx, entries)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
newDir, ok := entries[0].(fs.Directory)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("internal error: expecting %T to be fs.Directory", entries[0])
|
||||||
|
}
|
||||||
|
return newDir, nil
|
||||||
|
}
|
||||||
|
|
||||||
// purge the upstream or fallback to a slow way
|
// purge the upstream or fallback to a slow way
|
||||||
func (u *upstream) purge(ctx context.Context, dir string) (err error) {
|
func (u *upstream) purge(ctx context.Context, dir string) (err error) {
|
||||||
if do := u.f.Features().Purge; do != nil {
|
if do := u.f.Features().Purge; do != nil {
|
||||||
|
@ -755,12 +786,11 @@ func (u *upstream) wrapEntries(ctx context.Context, entries fs.DirEntries) (fs.D
|
||||||
case fs.Object:
|
case fs.Object:
|
||||||
entries[i] = u.newObject(x)
|
entries[i] = u.newObject(x)
|
||||||
case fs.Directory:
|
case fs.Directory:
|
||||||
newDir := fs.NewDirCopy(ctx, x)
|
newPath, err := u.pathAdjustment.do(x.Remote())
|
||||||
newPath, err := u.pathAdjustment.do(newDir.Remote())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
newDir.SetRemote(newPath)
|
newDir := fs.NewDirWrapper(newPath, x)
|
||||||
entries[i] = newDir
|
entries[i] = newDir
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unknown entry type %T", entry)
|
return nil, fmt.Errorf("unknown entry type %T", entry)
|
||||||
|
@ -1116,6 +1146,7 @@ var (
|
||||||
_ fs.PutUncheckeder = (*Fs)(nil)
|
_ fs.PutUncheckeder = (*Fs)(nil)
|
||||||
_ fs.MergeDirser = (*Fs)(nil)
|
_ fs.MergeDirser = (*Fs)(nil)
|
||||||
_ fs.DirSetModTimer = (*Fs)(nil)
|
_ fs.DirSetModTimer = (*Fs)(nil)
|
||||||
|
_ fs.MkdirMetadataer = (*Fs)(nil)
|
||||||
_ fs.CleanUpper = (*Fs)(nil)
|
_ fs.CleanUpper = (*Fs)(nil)
|
||||||
_ fs.OpenWriterAter = (*Fs)(nil)
|
_ fs.OpenWriterAter = (*Fs)(nil)
|
||||||
_ fs.FullObject = (*Object)(nil)
|
_ fs.FullObject = (*Object)(nil)
|
||||||
|
|
|
@ -183,18 +183,23 @@ func NewFs(ctx context.Context, name, rpath string, m configmap.Mapper) (fs.Fs,
|
||||||
// the features here are ones we could support, and they are
|
// the features here are ones we could support, and they are
|
||||||
// ANDed with the ones from wrappedFs
|
// ANDed with the ones from wrappedFs
|
||||||
f.features = (&fs.Features{
|
f.features = (&fs.Features{
|
||||||
CaseInsensitive: true,
|
CaseInsensitive: true,
|
||||||
DuplicateFiles: false,
|
DuplicateFiles: false,
|
||||||
ReadMimeType: false,
|
ReadMimeType: false,
|
||||||
WriteMimeType: false,
|
WriteMimeType: false,
|
||||||
GetTier: true,
|
GetTier: true,
|
||||||
SetTier: true,
|
SetTier: true,
|
||||||
BucketBased: true,
|
BucketBased: true,
|
||||||
CanHaveEmptyDirectories: true,
|
CanHaveEmptyDirectories: true,
|
||||||
ReadMetadata: true,
|
ReadMetadata: true,
|
||||||
WriteMetadata: true,
|
WriteMetadata: true,
|
||||||
UserMetadata: true,
|
UserMetadata: true,
|
||||||
PartialUploads: true,
|
ReadDirMetadata: true,
|
||||||
|
WriteDirMetadata: true,
|
||||||
|
WriteDirSetModTime: true,
|
||||||
|
UserDirMetadata: true,
|
||||||
|
DirModTimeUpdatesOnWrite: true,
|
||||||
|
PartialUploads: true,
|
||||||
}).Fill(ctx, f).Mask(ctx, wrappedFs).WrapsFs(f, wrappedFs)
|
}).Fill(ctx, f).Mask(ctx, wrappedFs).WrapsFs(f, wrappedFs)
|
||||||
// We support reading MIME types no matter the wrapped fs
|
// We support reading MIME types no matter the wrapped fs
|
||||||
f.features.ReadMimeType = true
|
f.features.ReadMimeType = true
|
||||||
|
@ -784,6 +789,14 @@ func (f *Fs) Mkdir(ctx context.Context, dir string) error {
|
||||||
return f.Fs.Mkdir(ctx, dir)
|
return f.Fs.Mkdir(ctx, dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MkdirMetadata makes the root directory of the Fs object
|
||||||
|
func (f *Fs) MkdirMetadata(ctx context.Context, dir string, metadata fs.Metadata) (fs.Directory, error) {
|
||||||
|
if do := f.Fs.Features().MkdirMetadata; do != nil {
|
||||||
|
return do(ctx, dir, metadata)
|
||||||
|
}
|
||||||
|
return nil, fs.ErrorNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
// Rmdir removes the directory (container, bucket) if empty
|
// Rmdir removes the directory (container, bucket) if empty
|
||||||
//
|
//
|
||||||
// Return an error if it doesn't exist or isn't empty
|
// Return an error if it doesn't exist or isn't empty
|
||||||
|
@ -1506,6 +1519,7 @@ var (
|
||||||
_ fs.Mover = (*Fs)(nil)
|
_ fs.Mover = (*Fs)(nil)
|
||||||
_ fs.DirMover = (*Fs)(nil)
|
_ fs.DirMover = (*Fs)(nil)
|
||||||
_ fs.DirSetModTimer = (*Fs)(nil)
|
_ fs.DirSetModTimer = (*Fs)(nil)
|
||||||
|
_ fs.MkdirMetadataer = (*Fs)(nil)
|
||||||
_ fs.PutStreamer = (*Fs)(nil)
|
_ fs.PutStreamer = (*Fs)(nil)
|
||||||
_ fs.CleanUpper = (*Fs)(nil)
|
_ fs.CleanUpper = (*Fs)(nil)
|
||||||
_ fs.UnWrapper = (*Fs)(nil)
|
_ fs.UnWrapper = (*Fs)(nil)
|
||||||
|
|
|
@ -263,19 +263,24 @@ func NewFs(ctx context.Context, name, rpath string, m configmap.Mapper) (fs.Fs,
|
||||||
// the features here are ones we could support, and they are
|
// the features here are ones we could support, and they are
|
||||||
// ANDed with the ones from wrappedFs
|
// ANDed with the ones from wrappedFs
|
||||||
f.features = (&fs.Features{
|
f.features = (&fs.Features{
|
||||||
CaseInsensitive: !cipher.dirNameEncrypt || cipher.NameEncryptionMode() == NameEncryptionOff,
|
CaseInsensitive: !cipher.dirNameEncrypt || cipher.NameEncryptionMode() == NameEncryptionOff,
|
||||||
DuplicateFiles: true,
|
DuplicateFiles: true,
|
||||||
ReadMimeType: false, // MimeTypes not supported with crypt
|
ReadMimeType: false, // MimeTypes not supported with crypt
|
||||||
WriteMimeType: false,
|
WriteMimeType: false,
|
||||||
BucketBased: true,
|
BucketBased: true,
|
||||||
CanHaveEmptyDirectories: true,
|
CanHaveEmptyDirectories: true,
|
||||||
SetTier: true,
|
SetTier: true,
|
||||||
GetTier: true,
|
GetTier: true,
|
||||||
ServerSideAcrossConfigs: opt.ServerSideAcrossConfigs,
|
ServerSideAcrossConfigs: opt.ServerSideAcrossConfigs,
|
||||||
ReadMetadata: true,
|
ReadMetadata: true,
|
||||||
WriteMetadata: true,
|
WriteMetadata: true,
|
||||||
UserMetadata: true,
|
UserMetadata: true,
|
||||||
PartialUploads: true,
|
ReadDirMetadata: true,
|
||||||
|
WriteDirMetadata: true,
|
||||||
|
WriteDirSetModTime: true,
|
||||||
|
UserDirMetadata: true,
|
||||||
|
DirModTimeUpdatesOnWrite: true,
|
||||||
|
PartialUploads: true,
|
||||||
}).Fill(ctx, f).Mask(ctx, wrappedFs).WrapsFs(f, wrappedFs)
|
}).Fill(ctx, f).Mask(ctx, wrappedFs).WrapsFs(f, wrappedFs)
|
||||||
|
|
||||||
return f, err
|
return f, err
|
||||||
|
@ -520,6 +525,25 @@ func (f *Fs) Mkdir(ctx context.Context, dir string) error {
|
||||||
return f.Fs.Mkdir(ctx, f.cipher.EncryptDirName(dir))
|
return f.Fs.Mkdir(ctx, f.cipher.EncryptDirName(dir))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MkdirMetadata makes the root directory of the Fs object
|
||||||
|
func (f *Fs) MkdirMetadata(ctx context.Context, dir string, metadata fs.Metadata) (fs.Directory, error) {
|
||||||
|
do := f.Fs.Features().MkdirMetadata
|
||||||
|
if do == nil {
|
||||||
|
return nil, fs.ErrorNotImplemented
|
||||||
|
}
|
||||||
|
newDir, err := do(ctx, f.cipher.EncryptDirName(dir), metadata)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var entries = make(fs.DirEntries, 0, 1)
|
||||||
|
f.addDir(ctx, &entries, newDir)
|
||||||
|
newDir, ok := entries[0].(fs.Directory)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("internal error: expecting %T to be fs.Directory", entries[0])
|
||||||
|
}
|
||||||
|
return newDir, nil
|
||||||
|
}
|
||||||
|
|
||||||
// DirSetModTime sets the directory modtime for dir
|
// DirSetModTime sets the directory modtime for dir
|
||||||
func (f *Fs) DirSetModTime(ctx context.Context, dir string, modTime time.Time) error {
|
func (f *Fs) DirSetModTime(ctx context.Context, dir string, modTime time.Time) error {
|
||||||
do := f.Fs.Features().DirSetModTime
|
do := f.Fs.Features().DirSetModTime
|
||||||
|
@ -770,7 +794,7 @@ func (f *Fs) MergeDirs(ctx context.Context, dirs []fs.Directory) error {
|
||||||
}
|
}
|
||||||
out := make([]fs.Directory, len(dirs))
|
out := make([]fs.Directory, len(dirs))
|
||||||
for i, dir := range dirs {
|
for i, dir := range dirs {
|
||||||
out[i] = fs.NewDirCopy(ctx, dir).SetRemote(f.cipher.EncryptDirName(dir.Remote()))
|
out[i] = fs.NewDirWrapper(f.cipher.EncryptDirName(dir.Remote()), dir)
|
||||||
}
|
}
|
||||||
return do(ctx, out)
|
return do(ctx, out)
|
||||||
}
|
}
|
||||||
|
@ -1006,14 +1030,14 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op
|
||||||
|
|
||||||
// newDir returns a dir with the Name decrypted
|
// newDir returns a dir with the Name decrypted
|
||||||
func (f *Fs) newDir(ctx context.Context, dir fs.Directory) fs.Directory {
|
func (f *Fs) newDir(ctx context.Context, dir fs.Directory) fs.Directory {
|
||||||
newDir := fs.NewDirCopy(ctx, dir)
|
|
||||||
remote := dir.Remote()
|
remote := dir.Remote()
|
||||||
decryptedRemote, err := f.cipher.DecryptDirName(remote)
|
decryptedRemote, err := f.cipher.DecryptDirName(remote)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fs.Debugf(remote, "Undecryptable dir name: %v", err)
|
fs.Debugf(remote, "Undecryptable dir name: %v", err)
|
||||||
} else {
|
} else {
|
||||||
newDir.SetRemote(decryptedRemote)
|
remote = decryptedRemote
|
||||||
}
|
}
|
||||||
|
newDir := fs.NewDirWrapper(remote, dir)
|
||||||
return newDir
|
return newDir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1217,6 +1241,7 @@ var (
|
||||||
_ fs.Wrapper = (*Fs)(nil)
|
_ fs.Wrapper = (*Fs)(nil)
|
||||||
_ fs.MergeDirser = (*Fs)(nil)
|
_ fs.MergeDirser = (*Fs)(nil)
|
||||||
_ fs.DirSetModTimer = (*Fs)(nil)
|
_ fs.DirSetModTimer = (*Fs)(nil)
|
||||||
|
_ fs.MkdirMetadataer = (*Fs)(nil)
|
||||||
_ fs.DirCacheFlusher = (*Fs)(nil)
|
_ fs.DirCacheFlusher = (*Fs)(nil)
|
||||||
_ fs.ChangeNotifier = (*Fs)(nil)
|
_ fs.ChangeNotifier = (*Fs)(nil)
|
||||||
_ fs.PublicLinker = (*Fs)(nil)
|
_ fs.PublicLinker = (*Fs)(nil)
|
||||||
|
|
|
@ -164,16 +164,21 @@ func NewFs(ctx context.Context, fsname, rpath string, cmap configmap.Mapper) (fs
|
||||||
}
|
}
|
||||||
|
|
||||||
stubFeatures := &fs.Features{
|
stubFeatures := &fs.Features{
|
||||||
CanHaveEmptyDirectories: true,
|
CanHaveEmptyDirectories: true,
|
||||||
IsLocal: true,
|
IsLocal: true,
|
||||||
ReadMimeType: true,
|
ReadMimeType: true,
|
||||||
WriteMimeType: true,
|
WriteMimeType: true,
|
||||||
SetTier: true,
|
SetTier: true,
|
||||||
GetTier: true,
|
GetTier: true,
|
||||||
ReadMetadata: true,
|
ReadMetadata: true,
|
||||||
WriteMetadata: true,
|
WriteMetadata: true,
|
||||||
UserMetadata: true,
|
UserMetadata: true,
|
||||||
PartialUploads: true,
|
ReadDirMetadata: true,
|
||||||
|
WriteDirMetadata: true,
|
||||||
|
WriteDirSetModTime: true,
|
||||||
|
UserDirMetadata: true,
|
||||||
|
DirModTimeUpdatesOnWrite: true,
|
||||||
|
PartialUploads: true,
|
||||||
}
|
}
|
||||||
f.features = stubFeatures.Fill(ctx, f).Mask(ctx, f.Fs).WrapsFs(f, f.Fs)
|
f.features = stubFeatures.Fill(ctx, f).Mask(ctx, f.Fs).WrapsFs(f, f.Fs)
|
||||||
|
|
||||||
|
@ -349,6 +354,14 @@ func (f *Fs) DirSetModTime(ctx context.Context, dir string, modTime time.Time) e
|
||||||
return fs.ErrorNotImplemented
|
return fs.ErrorNotImplemented
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MkdirMetadata makes the root directory of the Fs object
|
||||||
|
func (f *Fs) MkdirMetadata(ctx context.Context, dir string, metadata fs.Metadata) (fs.Directory, error) {
|
||||||
|
if do := f.Fs.Features().MkdirMetadata; do != nil {
|
||||||
|
return do(ctx, dir, metadata)
|
||||||
|
}
|
||||||
|
return nil, fs.ErrorNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
// DirCacheFlush resets the directory cache - used in testing
|
// DirCacheFlush resets the directory cache - used in testing
|
||||||
// as an optional interface
|
// as an optional interface
|
||||||
func (f *Fs) DirCacheFlush() {
|
func (f *Fs) DirCacheFlush() {
|
||||||
|
@ -539,6 +552,7 @@ var (
|
||||||
_ fs.Wrapper = (*Fs)(nil)
|
_ fs.Wrapper = (*Fs)(nil)
|
||||||
_ fs.MergeDirser = (*Fs)(nil)
|
_ fs.MergeDirser = (*Fs)(nil)
|
||||||
_ fs.DirSetModTimer = (*Fs)(nil)
|
_ fs.DirSetModTimer = (*Fs)(nil)
|
||||||
|
_ fs.MkdirMetadataer = (*Fs)(nil)
|
||||||
_ fs.DirCacheFlusher = (*Fs)(nil)
|
_ fs.DirCacheFlusher = (*Fs)(nil)
|
||||||
_ fs.ChangeNotifier = (*Fs)(nil)
|
_ fs.ChangeNotifier = (*Fs)(nil)
|
||||||
_ fs.PublicLinker = (*Fs)(nil)
|
_ fs.PublicLinker = (*Fs)(nil)
|
||||||
|
|
|
@ -27,6 +27,7 @@ type Object struct {
|
||||||
// This is a wrapped object contains all candidates
|
// This is a wrapped object contains all candidates
|
||||||
type Directory struct {
|
type Directory struct {
|
||||||
*upstream.Directory
|
*upstream.Directory
|
||||||
|
fs *Fs // what this directory is part of
|
||||||
cd []upstream.Entry
|
cd []upstream.Entry
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,7 +228,56 @@ func (d *Directory) Size() (s int64) {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetMetadata sets metadata for an DirEntry
|
||||||
|
//
|
||||||
|
// It should return fs.ErrorNotImplemented if it can't set metadata
|
||||||
|
func (d *Directory) SetMetadata(ctx context.Context, metadata fs.Metadata) error {
|
||||||
|
entries, err := d.fs.actionEntries(d.candidates()...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
errs := Errors(make([]error, len(entries)))
|
||||||
|
multithread(len(entries), func(i int) {
|
||||||
|
if d, ok := entries[i].(*upstream.Directory); ok {
|
||||||
|
err := d.SetMetadata(ctx, metadata)
|
||||||
|
if err != nil {
|
||||||
|
errs[i] = fmt.Errorf("%s: %w", d.UpstreamFs().Name(), err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errs[i] = fs.ErrorIsFile
|
||||||
|
}
|
||||||
|
})
|
||||||
|
wg.Wait()
|
||||||
|
return errs.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetModTime sets the metadata on the DirEntry to set the modification date
|
||||||
|
//
|
||||||
|
// If there is any other metadata it does not overwrite it.
|
||||||
|
func (d *Directory) SetModTime(ctx context.Context, t time.Time) error {
|
||||||
|
entries, err := d.fs.actionEntries(d.candidates()...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
errs := Errors(make([]error, len(entries)))
|
||||||
|
multithread(len(entries), func(i int) {
|
||||||
|
if d, ok := entries[i].(*upstream.Directory); ok {
|
||||||
|
err := d.SetModTime(ctx, t)
|
||||||
|
if err != nil {
|
||||||
|
errs[i] = fmt.Errorf("%s: %w", d.UpstreamFs().Name(), err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errs[i] = fs.ErrorIsFile
|
||||||
|
}
|
||||||
|
})
|
||||||
|
wg.Wait()
|
||||||
|
return errs.Err()
|
||||||
|
}
|
||||||
|
|
||||||
// Check the interfaces are satisfied
|
// Check the interfaces are satisfied
|
||||||
var (
|
var (
|
||||||
_ fs.FullObject = (*Object)(nil)
|
_ fs.FullObject = (*Object)(nil)
|
||||||
|
_ fs.FullDirectory = (*Directory)(nil)
|
||||||
)
|
)
|
||||||
|
|
|
@ -95,6 +95,7 @@ func (f *Fs) wrapEntries(entries ...upstream.Entry) (entry, error) {
|
||||||
case *upstream.Directory:
|
case *upstream.Directory:
|
||||||
return &Directory{
|
return &Directory{
|
||||||
Directory: e,
|
Directory: e,
|
||||||
|
fs: f,
|
||||||
cd: entries,
|
cd: entries,
|
||||||
}, nil
|
}, nil
|
||||||
default:
|
default:
|
||||||
|
@ -182,6 +183,51 @@ func (f *Fs) Mkdir(ctx context.Context, dir string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MkdirMetadata makes the root directory of the Fs object
|
||||||
|
func (f *Fs) MkdirMetadata(ctx context.Context, dir string, metadata fs.Metadata) (fs.Directory, error) {
|
||||||
|
upstreams, err := f.create(ctx, dir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
errs := Errors(make([]error, len(upstreams)))
|
||||||
|
entries := make([]upstream.Entry, len(upstreams))
|
||||||
|
multithread(len(upstreams), func(i int) {
|
||||||
|
u := upstreams[i]
|
||||||
|
if do := u.Features().MkdirMetadata; do != nil {
|
||||||
|
newDir, err := do(ctx, dir, metadata)
|
||||||
|
if err != nil {
|
||||||
|
errs[i] = fmt.Errorf("%s: %w", upstreams[i].Name(), err)
|
||||||
|
} else {
|
||||||
|
entries[i], err = u.WrapEntry(newDir)
|
||||||
|
if err != nil {
|
||||||
|
errs[i] = fmt.Errorf("%s: %w", upstreams[i].Name(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Just do Mkdir on upstreams which don't support MkdirMetadata
|
||||||
|
err := u.Mkdir(ctx, dir)
|
||||||
|
if err != nil {
|
||||||
|
errs[i] = fmt.Errorf("%s: %w", upstreams[i].Name(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
err = errs.Err()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
entry, err := f.wrapEntries(entries...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
newDir, ok := entry.(fs.Directory)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("internal error: expecting %T to be an fs.Directory", entry)
|
||||||
|
}
|
||||||
|
return newDir, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Purge all files in the directory
|
// Purge all files in the directory
|
||||||
//
|
//
|
||||||
// Implement this if you have a way of deleting all the files
|
// Implement this if you have a way of deleting all the files
|
||||||
|
@ -922,18 +968,23 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
|
||||||
}
|
}
|
||||||
fs.Debugf(f, "actionPolicy = %T, createPolicy = %T, searchPolicy = %T", f.actionPolicy, f.createPolicy, f.searchPolicy)
|
fs.Debugf(f, "actionPolicy = %T, createPolicy = %T, searchPolicy = %T", f.actionPolicy, f.createPolicy, f.searchPolicy)
|
||||||
var features = (&fs.Features{
|
var features = (&fs.Features{
|
||||||
CaseInsensitive: true,
|
CaseInsensitive: true,
|
||||||
DuplicateFiles: false,
|
DuplicateFiles: false,
|
||||||
ReadMimeType: true,
|
ReadMimeType: true,
|
||||||
WriteMimeType: true,
|
WriteMimeType: true,
|
||||||
CanHaveEmptyDirectories: true,
|
CanHaveEmptyDirectories: true,
|
||||||
BucketBased: true,
|
BucketBased: true,
|
||||||
SetTier: true,
|
SetTier: true,
|
||||||
GetTier: true,
|
GetTier: true,
|
||||||
ReadMetadata: true,
|
ReadMetadata: true,
|
||||||
WriteMetadata: true,
|
WriteMetadata: true,
|
||||||
UserMetadata: true,
|
UserMetadata: true,
|
||||||
PartialUploads: true,
|
ReadDirMetadata: true,
|
||||||
|
WriteDirMetadata: true,
|
||||||
|
WriteDirSetModTime: true,
|
||||||
|
UserDirMetadata: true,
|
||||||
|
DirModTimeUpdatesOnWrite: true,
|
||||||
|
PartialUploads: true,
|
||||||
}).Fill(ctx, f)
|
}).Fill(ctx, f)
|
||||||
canMove, slowHash := true, false
|
canMove, slowHash := true, false
|
||||||
for _, f := range upstreams {
|
for _, f := range upstreams {
|
||||||
|
@ -1009,6 +1060,7 @@ var (
|
||||||
_ fs.Mover = (*Fs)(nil)
|
_ fs.Mover = (*Fs)(nil)
|
||||||
_ fs.DirMover = (*Fs)(nil)
|
_ fs.DirMover = (*Fs)(nil)
|
||||||
_ fs.DirSetModTimer = (*Fs)(nil)
|
_ fs.DirSetModTimer = (*Fs)(nil)
|
||||||
|
_ fs.MkdirMetadataer = (*Fs)(nil)
|
||||||
_ fs.DirCacheFlusher = (*Fs)(nil)
|
_ fs.DirCacheFlusher = (*Fs)(nil)
|
||||||
_ fs.ChangeNotifier = (*Fs)(nil)
|
_ fs.ChangeNotifier = (*Fs)(nil)
|
||||||
_ fs.Abouter = (*Fs)(nil)
|
_ fs.Abouter = (*Fs)(nil)
|
||||||
|
|
|
@ -322,6 +322,39 @@ func (o *Object) Metadata(ctx context.Context) (fs.Metadata, error) {
|
||||||
return do.Metadata(ctx)
|
return do.Metadata(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Metadata returns metadata for an DirEntry
|
||||||
|
//
|
||||||
|
// It should return nil if there is no Metadata
|
||||||
|
func (e *Directory) Metadata(ctx context.Context) (fs.Metadata, error) {
|
||||||
|
do, ok := e.Directory.(fs.Metadataer)
|
||||||
|
if !ok {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return do.Metadata(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMetadata sets metadata for an DirEntry
|
||||||
|
//
|
||||||
|
// It should return fs.ErrorNotImplemented if it can't set metadata
|
||||||
|
func (e *Directory) SetMetadata(ctx context.Context, metadata fs.Metadata) error {
|
||||||
|
do, ok := e.Directory.(fs.SetMetadataer)
|
||||||
|
if !ok {
|
||||||
|
return fs.ErrorNotImplemented
|
||||||
|
}
|
||||||
|
return do.SetMetadata(ctx, metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetModTime sets the metadata on the DirEntry to set the modification date
|
||||||
|
//
|
||||||
|
// If there is any other metadata it does not overwrite it.
|
||||||
|
func (e *Directory) SetModTime(ctx context.Context, t time.Time) error {
|
||||||
|
do, ok := e.Directory.(fs.SetModTimer)
|
||||||
|
if !ok {
|
||||||
|
return fs.ErrorNotImplemented
|
||||||
|
}
|
||||||
|
return do.SetModTime(ctx, t)
|
||||||
|
}
|
||||||
|
|
||||||
// Writeback writes the object back and returns a new object
|
// Writeback writes the object back and returns a new object
|
||||||
//
|
//
|
||||||
// If it returns nil, nil then the original object is OK
|
// If it returns nil, nil then the original object is OK
|
||||||
|
@ -457,5 +490,6 @@ func (f *Fs) updateUsageCore(lock bool) error {
|
||||||
|
|
||||||
// Check the interfaces are satisfied
|
// Check the interfaces are satisfied
|
||||||
var (
|
var (
|
||||||
_ fs.FullObject = (*Object)(nil)
|
_ fs.FullObject = (*Object)(nil)
|
||||||
|
_ fs.FullDirectory = (*Directory)(nil)
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user