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
64 changes: 57 additions & 7 deletions generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

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
11 changes: 11 additions & 0 deletions iterator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package gdrive

type objectPageStatus struct {
limit uint32
path string
pageToken string
}

func (i *objectPageStatus) ContinuationToken() string {
return i.pageToken
}
1 change: 1 addition & 0 deletions service.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name = "gdrive"

[namespace.storage]
implement = ["direr"]

[namespace.storage.new]
required = ["name","credential"]
Expand Down
212 changes: 205 additions & 7 deletions storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,233 @@ package gdrive

import (
"context"
"fmt"
"github.com/beyondstorage/go-storage/v4/services"
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's format the imports.

  • stdlib
  • external libs
  • self libs

"io"
"strings"

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

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

const DirectoryMimeType = "application/vnd.google-apps.folder"
Copy link
Contributor

Choose a reason for hiding this comment

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

Please don't export unless we really need it.


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, path string, opt pairStorageCreateDir) (o *Object, err error) {
path = s.getAbsPath(path)
pathUnits := strings.Split(path, "/")
parentsId := "root"
for i := 0; i < len(pathUnits); i++ {
parentsId, err = s.mkDir(ctx, parentsId, pathUnits[i])
if err != nil {
return nil, err
}
}

o = s.newObject(true)
o.ID = path
o.Path = path
o.Mode = ModeDir

return o, 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
if err != nil {
return err
}
err = s.service.Files.Delete(fileId).Do()
if err != nil {
return err
}
return nil
}

func (s *Storage) list(ctx context.Context, path string, opt pairStorageList) (oi *ObjectIterator, err error) {
panic("not implemented")
input := &objectPageStatus{
limit: 200,
path: s.getAbsPath(path),
}

if !opt.HasListMode || opt.ListMode.IsDir() {
return NewObjectIterator(ctx, s.nextObjectPage, input), nil
} else {
return nil, services.ListModeInvalidError{Actual: opt.ListMode}
}
}

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

// Create a directory by passing it's name and the parents' fileId.
// It will return the fileId of the directory whether it exist or not.
// If error occurs, it will return an empty string and error.
func (s *Storage) mkDir(ctx context.Context, parents string, dirName string) (string, error) {
id, err := s.searchContentInDir(ctx, parents, dirName)
if err != nil {
return "", err
}
// Simply return the fileId if the directory already exist
if id != "" {
return id, nil
}

// create a directory if not exist
dir := &drive.File{
Name: dirName,
Parents: []string{parents},
MimeType: DirectoryMimeType,
}
f, err := s.service.Files.Create(dir).Context(ctx).Do()
if err != nil {
return "", err
}
return f.Id, nil
}

func (s *Storage) nextObjectPage(ctx context.Context, page *ObjectPage) error {
input := page.Status.(*objectPageStatus)
dirId, err := s.pathToId(ctx, input.path)
if err != nil {
return err
}
q := s.service.Files.List().Q(fmt.Sprintf("parents=%s", dirId))

if input.pageToken != "" {
q = q.PageToken(input.pageToken)
}
r, err := q.Do()

if err != nil {
return err
}
for _, f := range r.Files {
o := s.newObject(true)
// There is no way to get the path of the file directly, we have to do this
o.Path = input.path + "/" + f.Name
switch f.MimeType {
case DirectoryMimeType:
o.Mode = ModeDir
default:
o.Mode = ModeRead
}
page.Data = append(page.Data, o)
}

input.pageToken = r.NextPageToken
return nil
}

// TODO: add cache support
// pathToId converts path to fileId, as we talked in RFC-14.
// Ref: https://github.com/beyondstorage/go-service-gdrive/blob/master/docs/rfcs/14-gdrive-for-go-storage-design.md
// If success, we will return the fileId of the path we passing, and nil for sure.
// But if the path is not exist, we will return an empty string and error.
Copy link
Contributor

Choose a reason for hiding this comment

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

we will send API in pathToId, how will we handle the API error here?

How about adding a rule that:

  • err represents the error handled in pathToId
  • fileId represents the results: fileId empty means the path is not exist, otherwise it's the fileId of input path.

func (s *Storage) pathToId(ctx context.Context, path string) (fileId string, err error) {
absPath := s.getAbsPath(path)
pathUnits := strings.Split(absPath, "/")
fileId = "root"
// Traverse the whole path, break the loop if we fails at one search
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?

fileId, err = s.searchContentInDir(ctx, fileId, 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.

Please check error here, we need to break the for loop ASAP.

if fileId == "" {
break
}
}

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

return fileId, 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)
}

// Search something in directory by passing it's name and the fileId of the folder.
// It will return the fileId of the content we want, and nil for sure.
// If nothing is found, we will return an empty string and nil.
// We will only return non nil if error occurs.
func (s *Storage) searchContentInDir(ctx context.Context, dirId string, contentName string) (fileId string, err error) {
searchArg := fmt.Sprintf("name = %s and parents = %s", contentName, dirId)
fileList, err := s.service.Files.List().Context(ctx).Q(searchArg).Do()
if err != nil {
return "", err
}
// Because we assume that the path is unique, so there would be only two results: One file matches or none
if len(fileList.Files) == 0 {
return "", nil
}
return fileList.Files[0].Id, 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, or we will overwrite it.
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)
}
fileId, err := s.pathToId(ctx, path)
if err != nil {
// upload
file := &drive.File{Name: s.getFileName(path)}
_, err = s.service.Files.Create(file).Context(ctx).Media(r).Do()
if err != nil {
return 0, err
}
} else {
// update
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
}
Loading