Skip to content
This repository has been archived by the owner on Oct 22, 2021. It is now read-only.

Implement basic operations #13

Merged
merged 12 commits into from
Jul 27, 2021
3 changes: 1 addition & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,9 @@ github.com/Xuanwo/templateutils v0.1.0 h1:WpkWOqQtIQ2vAIpJLa727DdN8WtxhUkkbDGa6U
github.com/Xuanwo/templateutils v0.1.0/go.mod h1:OdE0DJ+CJxDBq6psX5DPV+gOZi8bhuHuVUpPCG++Wb8=
github.com/beyondstorage/go-integration-test/v4 v4.1.1 h1:9bSXKbr6hLb4+ZsmAhWE32fvqhyrpub4U4qgBGeth4A=
github.com/beyondstorage/go-integration-test/v4 v4.1.1/go.mod h1:ihtCaOJvaHGE0v+IhY6ZUF5NU1IND6xmdrJI9Lq/jhc=
github.com/beyondstorage/go-storage/v4 v4.2.0 h1:J0xqqy4qEQRtIS2zUWMA5wRXVHx/cxX5fHsU2ezA3+I=
github.com/beyondstorage/go-storage/v4 v4.2.0/go.mod h1:rUNzOXcikYk5w0ewvNsKbztg7ndQDyDvjDuP0bznSLU=
github.com/beyondstorage/go-storage/v4 v4.2.1-0.20210709064026-793dd83d71d1 h1:5rloKVOavOAOgE7pfBMk8kxVjyjaedyw3opXyXg4Vqc=
github.com/beyondstorage/go-storage/v4 v4.2.1-0.20210709064026-793dd83d71d1/go.mod h1:0fdcRCzLKMQe7Ve4zPlyTGgoPYwuINiV79Gx9tCt9tQ=
github.com/beyondstorage/specs/go v0.0.0-20210623065218-d1c2d7d81259 h1:mW9XpHLc6pdXBRnsha1VlqF0rNsB/Oc+8l+5UYngmRA=
github.com/beyondstorage/specs/go v0.0.0-20210623065218-d1c2d7d81259/go.mod h1:vF/Q0P1tCvhVAUrxg7i6NvrARRMQVTAuQdDNqpSzR1w=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
Expand Down Expand Up @@ -90,6 +88,7 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.5.0 h1:jlYHihg//f7RRwuPfptm04yp4s7O6Kw8EZiVYIGcH0g=
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
Expand Down
201 changes: 194 additions & 7 deletions storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,222 @@ package gdrive

import (
"context"
"fmt"
"io"
"strings"

"github.com/beyondstorage/go-storage/v4/pkg/iowrap"

. "github.com/beyondstorage/go-storage/v4/types"
"google.golang.org/api/drive/v3"
)

func (s *Storage) create(path string, opt pairStorageCreate) (o *Object) {
panic("not implemented")
o = s.newObject(false)
o.ID = s.getAbsPath(path)
o.Path = path
return o
}

func (s *Storage) createDir(ctx context.Context, name string, parents string) (fileId string, err error) {
Xuanwo marked this conversation as resolved.
Show resolved Hide resolved
dir := &drive.File{
Name: name,
Parents: []string{parents},
MimeType: "application/vnd.google-apps.folder",
Xuanwo marked this conversation as resolved.
Show resolved Hide resolved
}
f, err := s.service.Files.Create(dir).Context(ctx).Do()
if err != nil {
return "", err
}
return f.Id, nil
}

func (s *Storage) delete(ctx context.Context, path string, opt pairStorageDelete) (err error) {
panic("not implemented")
var fileId string
fileId, err = s.pathToId(ctx, path)
Xuanwo marked this conversation as resolved.
Show resolved Hide resolved
err = s.service.Files.Delete(fileId).Do()
if err != nil {
return err
}
return nil
}

// Get FileID via it's name and parents' fileID
func (s *Storage) getFile(ctx context.Context, name string, parents string) ([]*drive.File, error) {
searchArg := fmt.Sprintf("name='%s' and parents='%s'", name, parents)
return s.rawListFiles(ctx, searchArg)
}

func (s *Storage) list(ctx context.Context, path string, opt pairStorageList) (oi *ObjectIterator, err error) {
panic("not implemented")
nextFn := func(ctx context.Context, page *ObjectPage) error {
Xuanwo marked this conversation as resolved.
Show resolved Hide resolved
fs, err := s.listFiles(ctx, path)
Xuanwo marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return err
}
for _, f := range fs {
o := NewObject(s, true)
Xuanwo marked this conversation as resolved.
Show resolved Hide resolved
o.Path = f.Name
Xuanwo marked this conversation as resolved.
Show resolved Hide resolved

switch f.MimeType {
case "application/vnd.google-apps.folder":
Xuanwo marked this conversation as resolved.
Show resolved Hide resolved
o.Mode |= ModeDir
default:
o.Mode |= ModeRead
}
o.SetContentLength(f.Size)
page.Data = append(page.Data, o)
}
return IterateDone
}
oi = NewObjectIterator(ctx, nextFn, nil)
return
}

func (s *Storage) listFiles(ctx context.Context, path string) (fs []*drive.File, err error) {
var pathId string
pathId, err = s.pathToId(ctx, path)
if err != nil {
return fs, err
}
return s.listFilesInDir(ctx, pathId)
}

// List files in a folder by passing the folder's FileID
func (s *Storage) listFilesInDir(ctx context.Context, fileId string) ([]*drive.File, error) {
searchArg := fmt.Sprintf("parents='%s'", fileId)
return s.rawListFiles(ctx, searchArg)
Xuanwo marked this conversation as resolved.
Show resolved Hide resolved
}

func (s *Storage) metadata(opt pairStorageMetadata) (meta *StorageMeta) {
panic("not implemented")
meta = NewStorageMeta()
meta.Name = s.name
meta.WorkDir = s.workDir
return meta
}

func (s *Storage) pathToId(ctx context.Context, path string) (fileId string, err error) {
Xuanwo marked this conversation as resolved.
Show resolved Hide resolved
absPath := s.getAbsPath(path)
pathUnits := strings.Split(absPath, "/")

for i := 0; i < len(pathUnits); i++ {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about using for _, v := range pathUnits here?

if i == 0 {
fileId, err = s.searchContentInDir(ctx, "root", pathUnits[i])
}
fileId, err = s.searchContentInDir(ctx, fileId, pathUnits[i])
}

if err != nil {
return "", err
}
return fileId, nil
}

func (s *Storage) rawListFiles(ctx context.Context, searchArg string) ([]*drive.File, error) {
var fs []*drive.File
pageToken := ""
for {
Xuanwo marked this conversation as resolved.
Show resolved Hide resolved
q := s.service.Files.List().Context(ctx).Q(searchArg).Fields("id", "name", "mimeType")
// If we have a pageToken set, apply it to the query
if pageToken != "" {
q = q.PageToken(pageToken)
}
r, err := q.Do()
if err != nil {
return fs, err
}
fs = append(fs, r.Files...)
pageToken = r.NextPageToken
if pageToken == "" {
break
}
}
return fs, nil
}

func (s *Storage) read(ctx context.Context, path string, w io.Writer, opt pairStorageRead) (n int64, err error) {
panic("not implemented")
fileId, err := s.pathToId(ctx, path)
if err != nil {
return 0, err
}
f, err := s.service.Files.Get(fileId).Context(ctx).Download()
if err != nil {
return 0, err
}

if opt.HasIoCallback {
iowrap.CallbackReadCloser(f.Body, opt.IoCallback)
}

return io.Copy(w, f.Body)
}

// We pass the fileId of the directory, and it returns the fileId of the content in the directory
func (s *Storage) searchContentInDir(ctx context.Context, dirId string, contentName string) (fileId string, err error) {
fileList, err := s.listFilesInDir(ctx, dirId)

if err != nil {
return " ", err
}

for _, file := range fileList {
// What if the file does not exist???
if file.Name == contentName {
fileId = file.Id
}
}

if len(fileId) == 0 {
return "", err
}
return fileId, nil
}

func (s *Storage) stat(ctx context.Context, path string, opt pairStorageStat) (o *Object, err error) {
panic("not implemented")
_, err = s.pathToId(ctx, path)
if err != nil {
return nil, err
}
rp := s.getAbsPath(path)
o = s.newObject(true)
o.ID = rp
o.Path = path
return o, nil
}

// First we need make sure this file is not exist
// If it is, then we upload it
// If the path is like `foo.txt`, then we upload it directly.
// But if the path is like `foo/bar.txt`, we need create the directory one by one
func (s *Storage) write(ctx context.Context, path string, r io.Reader, size int64, opt pairStorageWrite) (n int64, err error) {
panic("not implemented")
r = io.LimitReader(r, size)

if opt.HasIoCallback {
r = iowrap.CallbackReader(r, opt.IoCallback)
}
_, err = s.pathToId(ctx, path)
var fileId string
if err != nil {
// upload
tmp := strings.Split(path, "/")
for i := 0; i < len(tmp); i++ {
if i == 0 {
fileId, err = s.createDir(ctx, tmp[0], "root")
} else if i != len(tmp)-1 {
fileId, err = s.createDir(ctx, tmp[i], fileId)
} else {
file := &drive.File{Name: tmp[i]}
_, err = s.service.Files.Create(file).Context(ctx).Media(r).Do()
}
Xuanwo marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member Author

@junaire junaire Jul 26, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess I have misunderstood something. If user calls Write("a/b/c.txt"), but directory a/b is not exist, should we create them for user? If not, these lines could be simply deleted. Ping @Xuanwo to have a look

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If user calls Write("a/b/c.txt"), but directory a/b is not exist, should we create them for user?

Yes, we need to create dirs for users.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I will do a quick fix for this.

}
} else {
// update
fileId, err = s.pathToId(ctx, path)
newFile := &drive.File{Name: s.getFileName(path)}
_, err = s.service.Files.Update(fileId, newFile).Context(ctx).Media(r).Do()
}

if err != nil {
return 0, err
}
return size, nil
}
27 changes: 27 additions & 0 deletions utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package gdrive
import (
"context"
"fmt"
"strings"

ps "github.com/beyondstorage/go-storage/v4/pairs"
"github.com/beyondstorage/go-storage/v4/pkg/credential"
Expand Down Expand Up @@ -118,3 +119,29 @@ func (s *Storage) formatError(op string, err error, path ...string) error {
Path: path,
}
}

func (s *Storage) newObject(done bool) *types.Object {
return types.NewObject(s, done)
}

// getAbsPath will calculate object storage's abs path
func (s *Storage) getAbsPath(path string) string {
prefix := strings.TrimPrefix(s.workDir, "/")
return prefix + path
}

// getRelPath will get object storage's rel path.
func (s *Storage) getRelPath(path string) string {
prefix := strings.TrimPrefix(s.workDir, "/")
return strings.TrimPrefix(path, prefix)
}

// getFileName will get a file's name without path
func (s *Storage) getFileName(path string) string {
if strings.Contains(path, "/") {
tmp := strings.Split(path, "/")
return tmp[len(tmp)-1]
} else {
return path
}
}