|
@@ -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)
|
|
|
|
- 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
|
|
// 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()
|