backends: make NewObject return fs.ErrorIsDir if possible

This changes the interface to NewObject so that if NewObject is called
on a directory then it should return fs.ErrorIsDir if possible without
doing any extra work, otherwise fs.ErrorObjectNotFound.

Tested on integration test server with:

go run integration-test.go -tests backend -run TestIntegration/FsMkdir/FsPutFiles/FsNewObjectDir -branch fix-stat -maxtries 1
This commit is contained in:
Nick Craig-Wood 2021-09-06 13:54:08 +01:00
parent af732c5431
commit 3fbaa4c0b0
19 changed files with 42 additions and 23 deletions

View file

@ -1184,6 +1184,9 @@ func (o *Object) Size() int64 {
// setMetaData sets the metadata from info // setMetaData sets the metadata from info
func (o *Object) setMetaData(info *api.Item) (err error) { func (o *Object) setMetaData(info *api.Item) (err error) {
if info.Type == api.ItemTypeFolder {
return fs.ErrorIsDir
}
if info.Type != api.ItemTypeFile { if info.Type != api.ItemTypeFile {
return errors.Wrapf(fs.ErrorNotAFile, "%q is %q", o.remote, info.Type) return errors.Wrapf(fs.ErrorNotAFile, "%q is %q", o.remote, info.Type)
} }

View file

@ -1374,7 +1374,7 @@ func (f *Fs) newObjectWithExportInfo(
} }
switch { switch {
case info.MimeType == driveFolderType: case info.MimeType == driveFolderType:
return nil, fs.ErrorNotAFile return nil, fs.ErrorIsDir
case info.MimeType == shortcutMimeType: case info.MimeType == shortcutMimeType:
// We can only get here if f.opt.SkipShortcuts is set // We can only get here if f.opt.SkipShortcuts is set
// and not from a listing. This is unlikely. // and not from a listing. This is unlikely.
@ -2922,7 +2922,7 @@ func (f *Fs) makeShortcut(ctx context.Context, srcPath string, dstFs *Fs, dstPat
} }
isDir = true isDir = true
} else if srcObj, err := srcFs.NewObject(ctx, srcPath); err != nil { } else if srcObj, err := srcFs.NewObject(ctx, srcPath); err != nil {
if err != fs.ErrorNotAFile { if err != fs.ErrorIsDir {
return nil, errors.Wrap(err, "can't find source") return nil, errors.Wrap(err, "can't find source")
} }
// source was a directory // source was a directory
@ -2942,7 +2942,7 @@ func (f *Fs) makeShortcut(ctx context.Context, srcPath string, dstFs *Fs, dstPat
if err != fs.ErrorObjectNotFound { if err != fs.ErrorObjectNotFound {
if err == nil { if err == nil {
err = errors.New("existing file") err = errors.New("existing file")
} else if err == fs.ErrorNotAFile { } else if err == fs.ErrorIsDir {
err = errors.New("existing directory") err = errors.New("existing directory")
} }
return nil, errors.Wrap(err, "not overwriting shortcut target") return nil, errors.Wrap(err, "not overwriting shortcut target")

View file

@ -624,6 +624,9 @@ func (f *Fs) getFileMetadata(ctx context.Context, filePath string) (fileInfo *fi
} }
fileInfo, ok := entry.(*files.FileMetadata) fileInfo, ok := entry.(*files.FileMetadata)
if !ok { if !ok {
if _, ok = entry.(*files.FolderMetadata); ok {
return nil, fs.ErrorIsDir
}
return nil, fs.ErrorNotAFile return nil, fs.ErrorNotAFile
} }
return fileInfo, nil return fileInfo, nil

View file

@ -1089,7 +1089,7 @@ func (o *Object) Size() int64 {
// setMetaData sets the metadata from info // setMetaData sets the metadata from info
func (o *Object) setMetaData(info *api.Item) (err error) { func (o *Object) setMetaData(info *api.Item) (err error) {
if info.Type != api.ItemTypeFile { if info.Type != api.ItemTypeFile {
return errors.Wrapf(fs.ErrorNotAFile, "%q is %q", o.remote, info.Type) return fs.ErrorIsDir
} }
o.hasMetaData = true o.hasMetaData = true
o.size = info.Size o.size = info.Size

View file

@ -599,7 +599,9 @@ func (f *Fs) readMetaDataForPath(ctx context.Context, path string) (info *api.Jo
if err != nil { if err != nil {
return nil, errors.Wrap(err, "read metadata failed") return nil, errors.Wrap(err, "read metadata failed")
} }
if result.XMLName.Local != "file" { if result.XMLName.Local == "folder" {
return nil, fs.ErrorIsDir
} else if result.XMLName.Local != "file" {
return nil, fs.ErrorNotAFile return nil, fs.ErrorNotAFile
} }
return &result, nil return &result, nil
@ -762,7 +764,7 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
// Renew the token in the background // Renew the token in the background
f.tokenRenewer = oauthutil.NewRenew(f.String(), ts, func() error { f.tokenRenewer = oauthutil.NewRenew(f.String(), ts, func() error {
_, err := f.readMetaDataForPath(ctx, "") _, err := f.readMetaDataForPath(ctx, "")
if err == fs.ErrorNotAFile { if err == fs.ErrorNotAFile || err == fs.ErrorIsDir {
err = nil err = nil
} }
return err return err
@ -784,7 +786,7 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
} }
_, err := f.NewObject(context.TODO(), remote) _, err := f.NewObject(context.TODO(), remote)
if err != nil { if err != nil {
if errors.Cause(err) == fs.ErrorObjectNotFound || errors.Cause(err) == fs.ErrorNotAFile { if uErr := errors.Cause(err); uErr == fs.ErrorObjectNotFound || uErr == fs.ErrorNotAFile || uErr == fs.ErrorIsDir {
// File doesn't exist so return old f // File doesn't exist so return old f
f.root = root f.root = root
return f, nil return f, nil

View file

@ -344,7 +344,7 @@ func (f *Fs) NewObject(ctx context.Context, remote string) (obj fs.Object, err e
return nil, translateErrorsObject(err) return nil, translateErrorsObject(err)
} }
if info.Type == "dir" { if info.Type == "dir" {
return nil, fs.ErrorNotAFile return nil, fs.ErrorIsDir
} }
return &Object{ return &Object{
fs: f, fs: f,

View file

@ -401,7 +401,7 @@ func (f *Fs) newObjectWithInfo(remote string, info os.FileInfo) (fs.Object, erro
} }
if o.mode.IsDir() { if o.mode.IsDir() {
return nil, errors.Wrapf(fs.ErrorNotAFile, "%q", remote) return nil, fs.ErrorIsDir
} }
return o, nil return o, nil
} }

View file

@ -1958,7 +1958,7 @@ func (o *Object) readMetaData(ctx context.Context, force bool) error {
} }
newObj, ok := entry.(*Object) newObj, ok := entry.(*Object)
if !ok || dirSize >= 0 { if !ok || dirSize >= 0 {
return fs.ErrorNotAFile return fs.ErrorIsDir
} }
if newObj.remote != o.remote { if newObj.remote != o.remote {
return fmt.Errorf("File %q path has changed to %q", o.remote, newObj.remote) return fmt.Errorf("File %q path has changed to %q", o.remote, newObj.remote)

View file

@ -303,7 +303,7 @@ func (f *Fs) findObject(rootNode *mega.Node, file string) (node *mega.Node, err
if err == mega.ENOENT { if err == mega.ENOENT {
return nil, fs.ErrorObjectNotFound return nil, fs.ErrorObjectNotFound
} else if err == nil && node.GetType() != mega.FILE { } else if err == nil && node.GetType() != mega.FILE {
return nil, fs.ErrorNotAFile return nil, fs.ErrorIsDir // all other node types are directories
} }
return node, err return node, err
} }
@ -958,7 +958,7 @@ func (o *Object) Size() int64 {
// setMetaData sets the metadata from info // setMetaData sets the metadata from info
func (o *Object) setMetaData(info *mega.Node) (err error) { func (o *Object) setMetaData(info *mega.Node) (err error) {
if info.GetType() != mega.FILE { if info.GetType() != mega.FILE {
return fs.ErrorNotAFile return fs.ErrorIsDir // all other node types are directories
} }
o.info = info o.info = info
return nil return nil

View file

@ -1715,7 +1715,7 @@ func (o *Object) Size() int64 {
// setMetaData sets the metadata from info // setMetaData sets the metadata from info
func (o *Object) setMetaData(info *api.Item) (err error) { func (o *Object) setMetaData(info *api.Item) (err error) {
if info.GetFolder() != nil { if info.GetFolder() != nil {
return errors.Wrapf(fs.ErrorNotAFile, "%q", o.remote) return fs.ErrorIsDir
} }
o.hasMetaData = true o.hasMetaData = true
o.size = info.GetSize() o.size = info.GetSize()

View file

@ -151,7 +151,7 @@ func (o *Object) readEntry(ctx context.Context) (f *putio.File, err error) {
return nil, err return nil, err
} }
if resp.File.IsDir() { if resp.File.IsDir() {
return nil, fs.ErrorNotAFile return nil, fs.ErrorIsDir
} }
return &resp.File, err return &resp.File, err
} }

View file

@ -1383,7 +1383,7 @@ func (o *Object) stat(ctx context.Context) error {
return errors.Wrap(err, "stat failed") return errors.Wrap(err, "stat failed")
} }
if info.IsDir() { if info.IsDir() {
return errors.Wrapf(fs.ErrorNotAFile, "%q", o.remote) return fs.ErrorIsDir
} }
o.setMetadata(info) o.setMetadata(info)
return nil return nil

View file

@ -337,8 +337,12 @@ func (f *Fs) readMetaDataForIDPath(ctx context.Context, id, path string, directo
if directoriesOnly && item.Type != api.ItemTypeFolder { if directoriesOnly && item.Type != api.ItemTypeFolder {
return nil, fs.ErrorIsFile return nil, fs.ErrorIsFile
} }
if filesOnly && item.Type != api.ItemTypeFile { if filesOnly {
return nil, fs.ErrorNotAFile if item.Type == api.ItemTypeFolder {
return nil, fs.ErrorIsDir
} else if item.Type != api.ItemTypeFile {
return nil, fs.ErrorNotAFile
}
} }
return &item, nil return &item, nil
} }

View file

@ -317,7 +317,7 @@ func (f *Fs) readMetaDataForPath(ctx context.Context, path string, depth string)
return nil, fs.ErrorObjectNotFound return nil, fs.ErrorObjectNotFound
} }
if itemIsDir(&item) { if itemIsDir(&item) {
return nil, fs.ErrorNotAFile return nil, fs.ErrorIsDir
} }
return &item.Props, nil return &item.Props, nil
} }
@ -469,7 +469,7 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
} }
_, err := f.NewObject(ctx, remote) _, err := f.NewObject(ctx, remote)
if err != nil { if err != nil {
if errors.Cause(err) == fs.ErrorObjectNotFound || errors.Cause(err) == fs.ErrorNotAFile { if errors.Cause(err) == fs.ErrorObjectNotFound || errors.Cause(err) == fs.ErrorIsDir {
// File doesn't exist so return old f // File doesn't exist so return old f
f.root = root f.root = root
return f, nil return f, nil

View file

@ -949,7 +949,9 @@ func (o *Object) readMetaData(ctx context.Context) (err error) {
if err != nil { if err != nil {
return err return err
} }
if info.ResourceType != "file" { if info.ResourceType == "dir" {
return fs.ErrorIsDir
} else if info.ResourceType != "file" {
return fs.ErrorNotAFile return fs.ErrorNotAFile
} }
return o.setMetaData(info) return o.setMetaData(info)

View file

@ -1121,7 +1121,7 @@ func (o *Object) Size() int64 {
// setMetaData sets the metadata from info // setMetaData sets the metadata from info
func (o *Object) setMetaData(info *api.Item) (err error) { func (o *Object) setMetaData(info *api.Item) (err error) {
if info.Attributes.IsFolder { if info.Attributes.IsFolder {
return fs.ErrorNotAFile return fs.ErrorIsDir
} }
o.hasMetaData = true o.hasMetaData = true
o.size = info.Attributes.StorageInfo.Size o.size = info.Attributes.StorageInfo.Size

View file

@ -37,6 +37,7 @@ var (
ErrorListAborted = errors.New("list aborted") ErrorListAborted = errors.New("list aborted")
ErrorListBucketRequired = errors.New("bucket or container name is needed in remote") ErrorListBucketRequired = errors.New("bucket or container name is needed in remote")
ErrorIsFile = errors.New("is a file not a directory") ErrorIsFile = errors.New("is a file not a directory")
ErrorIsDir = errors.New("is a directory not a file")
ErrorNotAFile = errors.New("is not a regular file") ErrorNotAFile = errors.New("is not a regular file")
ErrorNotDeleting = errors.New("not deleting files as there were IO errors") ErrorNotDeleting = errors.New("not deleting files as there were IO errors")
ErrorNotDeletingDirs = errors.New("not deleting directories as there were IO errors") ErrorNotDeletingDirs = errors.New("not deleting directories as there were IO errors")

View file

@ -28,6 +28,10 @@ type Fs interface {
// NewObject finds the Object at remote. If it can't be found // NewObject finds the Object at remote. If it can't be found
// it returns the error ErrorObjectNotFound. // it returns the error ErrorObjectNotFound.
//
// If remote points to a directory then it should return
// ErrorIsDir if possible without doing any extra work,
// otherwise ErrorObjectNotFound.
NewObject(ctx context.Context, remote string) (Object, error) NewObject(ctx context.Context, remote string) (Object, error)
// Put in to the remote path with the modTime given of the given size // Put in to the remote path with the modTime given of the given size

View file

@ -1044,13 +1044,13 @@ func Run(t *testing.T, opt *Opt) {
fstest.CheckListing(t, f, []fstest.Item{file1, file2}) fstest.CheckListing(t, f, []fstest.Item{file1, file2})
}) })
// TestFsNewObjectDir tests NewObject on a directory which should produce an error // TestFsNewObjectDir tests NewObject on a directory which should produce fs.ErrorIsDir if possible or fs.ErrorObjectNotFound if not
t.Run("FsNewObjectDir", func(t *testing.T) { t.Run("FsNewObjectDir", func(t *testing.T) {
skipIfNotOk(t) skipIfNotOk(t)
dir := path.Dir(file2.Path) dir := path.Dir(file2.Path)
obj, err := f.NewObject(ctx, dir) obj, err := f.NewObject(ctx, dir)
assert.Nil(t, obj) assert.Nil(t, obj)
assert.NotNil(t, err) assert.True(t, err == fs.ErrorIsDir || err == fs.ErrorObjectNotFound, fmt.Sprintf("Wrong error: expecting fs.ErrorIsDir or fs.ErrorObjectNotFound but got: %#v", err))
}) })
// TestFsPurge tests Purge // TestFsPurge tests Purge