From 0e9351e4ade8f9a2ead91fc50c977602baa4542d Mon Sep 17 00:00:00 2001 From: Nicola Murino Date: Thu, 24 Jun 2021 19:36:01 +0200 Subject: [PATCH] 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 --- vfs/gcsfs.go | 73 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 48 insertions(+), 25 deletions(-) diff --git a/vfs/gcsfs.go b/vfs/gcsfs.go index c89a0f17..05d91a6d 100644 --- a/vfs/gcsfs.go +++ b/vfs/gcsfs.go @@ -5,7 +5,6 @@ package vfs import ( "context" "encoding/json" - "errors" "fmt" "io" "mime" @@ -112,37 +111,18 @@ func (fs *GCSFs) ConnectionID() string { // Stat returns a FileInfo describing the named file func (fs *GCSFs) Stat(name string) (os.FileInfo, error) { - var result *FileInfo - var err error if name == "" || name == "." { err := fs.checkIfBucketExists() if err != nil { - return result, err + return nil, err } return NewFileInfo(name, true, 0, time.Now(), false), nil } if fs.config.KeyPrefix == name+"/" { return NewFileInfo(name, true, 0, time.Now(), false), nil } - attrs, err := fs.headObject(name) - if err == nil { - 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") + _, info, err := fs.getObjectStat(name) + return info, err } // Lstat returns a FileInfo describing the named file @@ -229,7 +209,7 @@ func (fs *GCSFs) Rename(source, target string) error { if source == target { return nil } - fi, err := fs.Stat(source) + realSourceName, fi, err := fs.getObjectStat(source) if err != nil { return err } @@ -241,8 +221,11 @@ func (fs *GCSFs) Rename(source, target string) error { if hasContents { 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) ctx, cancelFn := context.WithDeadline(context.Background(), time.Now().Add(fs.ctxTimeout)) defer cancelFn() @@ -277,11 +260,18 @@ func (fs *GCSFs) Remove(name string, isDir bool) error { if hasContents { 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)) defer cancelFn() 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) return err } @@ -292,6 +282,9 @@ func (fs *GCSFs) Mkdir(name string) error { if !fs.IsNotExist(err) { return err } + if !strings.HasSuffix(name, "/") { + name += "/" + } _, w, _, err := fs.Create(name, -1) if err != nil { return err @@ -613,6 +606,36 @@ func (fs *GCSFs) resolve(name string, prefix string) (string, bool) { 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 { ctx, cancelFn := context.WithDeadline(context.Background(), time.Now().Add(fs.ctxTimeout)) defer cancelFn()