GCS: add a trailing / to "directories"

This way SFTPGo should be compatible with Google Cloud console.

This change should be backward compatibile, testing is welcome

Fixes #464
This commit is contained in:
Nicola Murino
2021-06-24 19:36:01 +02:00
parent e09bdd43d4
commit 93dfb03eaf

View File

@@ -5,7 +5,6 @@ package vfs
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"mime" "mime"
@@ -112,37 +111,18 @@ func (fs *GCSFs) ConnectionID() string {
// Stat returns a FileInfo describing the named file // Stat returns a FileInfo describing the named file
func (fs *GCSFs) Stat(name string) (os.FileInfo, error) { func (fs *GCSFs) Stat(name string) (os.FileInfo, error) {
var result *FileInfo
var err error
if name == "" || name == "." { if name == "" || name == "." {
err := fs.checkIfBucketExists() err := fs.checkIfBucketExists()
if err != nil { if err != nil {
return result, err return nil, err
} }
return NewFileInfo(name, true, 0, time.Now(), false), nil return NewFileInfo(name, true, 0, time.Now(), false), nil
} }
if fs.config.KeyPrefix == name+"/" { if fs.config.KeyPrefix == name+"/" {
return NewFileInfo(name, true, 0, time.Now(), false), nil return NewFileInfo(name, true, 0, time.Now(), false), nil
} }
attrs, err := fs.headObject(name) _, info, err := fs.getObjectStat(name)
if err == nil { return info, err
objSize := attrs.Size
objectModTime := attrs.Updated
isDir := attrs.ContentType == dirMimeType || strings.HasSuffix(attrs.Name, "/")
return NewFileInfo(name, isDir, objSize, objectModTime, false), nil
}
if !fs.IsNotExist(err) {
return result, err
}
// now check if this is a prefix (virtual directory)
hasContents, err := fs.hasContents(name)
if err != nil {
return nil, err
}
if hasContents {
return NewFileInfo(name, true, 0, time.Now(), false), nil
}
return nil, errors.New("404 no such file or directory")
} }
// Lstat returns a FileInfo describing the named file // Lstat returns a FileInfo describing the named file
@@ -229,7 +209,7 @@ func (fs *GCSFs) Rename(source, target string) error {
if source == target { if source == target {
return nil return nil
} }
fi, err := fs.Stat(source) realSourceName, fi, err := fs.getObjectStat(source)
if err != nil { if err != nil {
return err return err
} }
@@ -241,8 +221,11 @@ func (fs *GCSFs) Rename(source, target string) error {
if hasContents { if hasContents {
return fmt.Errorf("cannot rename non empty directory: %#v", source) return fmt.Errorf("cannot rename non empty directory: %#v", source)
} }
if !strings.HasSuffix(target, "/") {
target += "/"
}
} }
src := fs.svc.Bucket(fs.config.Bucket).Object(source) src := fs.svc.Bucket(fs.config.Bucket).Object(realSourceName)
dst := fs.svc.Bucket(fs.config.Bucket).Object(target) dst := fs.svc.Bucket(fs.config.Bucket).Object(target)
ctx, cancelFn := context.WithDeadline(context.Background(), time.Now().Add(fs.ctxTimeout)) ctx, cancelFn := context.WithDeadline(context.Background(), time.Now().Add(fs.ctxTimeout))
defer cancelFn() defer cancelFn()
@@ -277,11 +260,18 @@ func (fs *GCSFs) Remove(name string, isDir bool) error {
if hasContents { if hasContents {
return fmt.Errorf("cannot remove non empty directory: %#v", name) return fmt.Errorf("cannot remove non empty directory: %#v", name)
} }
if !strings.HasSuffix(name, "/") {
name += "/"
}
} }
ctx, cancelFn := context.WithDeadline(context.Background(), time.Now().Add(fs.ctxTimeout)) ctx, cancelFn := context.WithDeadline(context.Background(), time.Now().Add(fs.ctxTimeout))
defer cancelFn() defer cancelFn()
err := fs.svc.Bucket(fs.config.Bucket).Object(name).Delete(ctx) err := fs.svc.Bucket(fs.config.Bucket).Object(name).Delete(ctx)
if fs.IsNotExist(err) && isDir {
// we can have directories without a trailing "/" (created using v2.1.0 and before)
err = fs.svc.Bucket(fs.config.Bucket).Object(strings.TrimSuffix(name, "/")).Delete(ctx)
}
metrics.GCSDeleteObjectCompleted(err) metrics.GCSDeleteObjectCompleted(err)
return err return err
} }
@@ -292,6 +282,9 @@ func (fs *GCSFs) Mkdir(name string) error {
if !fs.IsNotExist(err) { if !fs.IsNotExist(err) {
return err return err
} }
if !strings.HasSuffix(name, "/") {
name += "/"
}
_, w, _, err := fs.Create(name, -1) _, w, _, err := fs.Create(name, -1)
if err != nil { if err != nil {
return err return err
@@ -613,6 +606,36 @@ func (fs *GCSFs) resolve(name string, prefix string) (string, bool) {
return result, isDir return result, isDir
} }
// getObjectStat returns the stat result and the real object name as first value
func (fs *GCSFs) getObjectStat(name string) (string, os.FileInfo, error) {
attrs, err := fs.headObject(name)
if err == nil {
objSize := attrs.Size
objectModTime := attrs.Updated
isDir := attrs.ContentType == dirMimeType || strings.HasSuffix(attrs.Name, "/")
return name, NewFileInfo(name, isDir, objSize, objectModTime, false), nil
}
if !fs.IsNotExist(err) {
return "", nil, err
}
// now check if this is a prefix (virtual directory)
hasContents, err := fs.hasContents(name)
if err != nil {
return "", nil, err
}
if hasContents {
return name, NewFileInfo(name, true, 0, time.Now(), false), nil
}
// finally check if this is an object with a trailing /
attrs, err = fs.headObject(name + "/")
if err != nil {
return "", nil, err
}
objSize := attrs.Size
objectModTime := attrs.Updated
return name + "/", NewFileInfo(name, true, objSize, objectModTime, false), nil
}
func (fs *GCSFs) checkIfBucketExists() error { func (fs *GCSFs) checkIfBucketExists() error {
ctx, cancelFn := context.WithDeadline(context.Background(), time.Now().Add(fs.ctxTimeout)) ctx, cancelFn := context.WithDeadline(context.Background(), time.Now().Add(fs.ctxTimeout))
defer cancelFn() defer cancelFn()