Skip to content

Commit

Permalink
Merge pull request #579 from rsanger/master
Browse files Browse the repository at this point in the history
Add support for Shared Google Drives
  • Loading branch information
gilbertchen authored Apr 7, 2020
2 parents 57dd5ba + aa07fee commit e827662
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 11 deletions.
34 changes: 25 additions & 9 deletions src/duplicacy_gcdstorage.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
var (
GCDFileMimeType = "application/octet-stream"
GCDDirectoryMimeType = "application/vnd.google-apps.folder"
GCDUserDrive = "root"
)

type GCDStorage struct {
Expand All @@ -37,6 +38,7 @@ type GCDStorage struct {
idCacheLock sync.Mutex
backoffs []int // desired backoff time in seconds for each thread
attempts []int // number of failed attempts since last success for each thread
driveID string // the ID of the shared drive or 'root' (GCDUserDrive) if the user's drive

createDirectoryLock sync.Mutex
isConnected bool
Expand Down Expand Up @@ -191,7 +193,11 @@ func (storage *GCDStorage) listFiles(threadIndex int, parentID string, listFiles
var err error

for {
fileList, err = storage.service.Files.List().Q(query).Fields("nextPageToken", "files(name, mimeType, id, size)").PageToken(startToken).PageSize(maxCount).Do()
q := storage.service.Files.List().Q(query).Fields("nextPageToken", "files(name, mimeType, id, size)").PageToken(startToken).PageSize(maxCount)
if storage.driveID != GCDUserDrive {
q = q.DriveId(storage.driveID).IncludeItemsFromAllDrives(true).Corpora("drive").SupportsAllDrives(true)
}
fileList, err = q.Do()
if retry, e := storage.shouldRetry(threadIndex, err); e == nil && !retry {
break
} else if retry {
Expand Down Expand Up @@ -219,7 +225,11 @@ func (storage *GCDStorage) listByName(threadIndex int, parentID string, name str

for {
query := "name = '" + name + "' and '" + parentID + "' in parents and trashed = false "
fileList, err = storage.service.Files.List().Q(query).Fields("files(name, mimeType, id, size)").Do()
q := storage.service.Files.List().Q(query).Fields("files(name, mimeType, id, size)")
if storage.driveID != GCDUserDrive {
q = q.DriveId(storage.driveID).IncludeItemsFromAllDrives(true).Corpora("drive").SupportsAllDrives(true)
}
fileList, err = q.Do()

if retry, e := storage.shouldRetry(threadIndex, err); e == nil && !retry {
break
Expand Down Expand Up @@ -248,7 +258,7 @@ func (storage *GCDStorage) getIDFromPath(threadIndex int, filePath string, creat
return fileID, nil
}

fileID := "root"
fileID := storage.driveID

if rootID, ok := storage.findPathID(""); ok {
fileID = rootID
Expand Down Expand Up @@ -303,7 +313,7 @@ func (storage *GCDStorage) getIDFromPath(threadIndex int, filePath string, creat
}

// CreateGCDStorage creates a GCD storage object.
func CreateGCDStorage(tokenFile string, storagePath string, threads int) (storage *GCDStorage, err error) {
func CreateGCDStorage(tokenFile string, driveID string, storagePath string, threads int) (storage *GCDStorage, err error) {

description, err := ioutil.ReadFile(tokenFile)
if err != nil {
Expand All @@ -328,19 +338,25 @@ func CreateGCDStorage(tokenFile string, storagePath string, threads int) (storag
return nil, err
}

if len(driveID) == 0 {
driveID = GCDUserDrive
}

storage = &GCDStorage{
service: service,
numberOfThreads: threads,
idCache: make(map[string]string),
backoffs: make([]int, threads),
attempts: make([]int, threads),
driveID: driveID,
}

for i := range storage.backoffs {
storage.backoffs[i] = 1
storage.attempts[i] = 0
}

storage.savePathID("", driveID)
storagePathID, err := storage.getIDFromPath(0, storagePath, true)
if err != nil {
return nil, err
Expand Down Expand Up @@ -462,7 +478,7 @@ func (storage *GCDStorage) DeleteFile(threadIndex int, filePath string) (err err
}

for {
err = storage.service.Files.Delete(fileID).Fields("id").Do()
err = storage.service.Files.Delete(fileID).SupportsAllDrives(true).Fields("id").Do()
if retry, err := storage.shouldRetry(threadIndex, err); err == nil && !retry {
storage.deletePathID(filePath)
return nil
Expand Down Expand Up @@ -508,7 +524,7 @@ func (storage *GCDStorage) MoveFile(threadIndex int, from string, to string) (er
}

for {
_, err = storage.service.Files.Update(fileID, nil).AddParents(toParentID).RemoveParents(fromParentID).Do()
_, err = storage.service.Files.Update(fileID, nil).SupportsAllDrives(true).AddParents(toParentID).RemoveParents(fromParentID).Do()
if retry, err := storage.shouldRetry(threadIndex, err); err == nil && !retry {
break
} else if retry {
Expand Down Expand Up @@ -559,7 +575,7 @@ func (storage *GCDStorage) CreateDirectory(threadIndex int, dir string) (err err
Parents: []string{parentID},
}

file, err = storage.service.Files.Create(file).Fields("id").Do()
file, err = storage.service.Files.Create(file).SupportsAllDrives(true).Fields("id").Do()
if retry, err := storage.shouldRetry(threadIndex, err); err == nil && !retry {
break
} else {
Expand Down Expand Up @@ -630,7 +646,7 @@ func (storage *GCDStorage) DownloadFile(threadIndex int, filePath string, chunk
for {
// AcknowledgeAbuse(true) lets the download proceed even if GCD thinks that it contains malware.
// TODO: Should this prompt the user or log a warning?
req := storage.service.Files.Get(fileID)
req := storage.service.Files.Get(fileID).SupportsAllDrives(true)
if e, ok := err.(*googleapi.Error); ok {
if strings.Contains(err.Error(), "cannotDownloadAbusiveFile") || len(e.Errors) > 0 && e.Errors[0].Reason == "cannotDownloadAbusiveFile" {
LOG_WARN("GCD_STORAGE", "%s is marked as abusive, will download anyway.", filePath)
Expand Down Expand Up @@ -676,7 +692,7 @@ func (storage *GCDStorage) UploadFile(threadIndex int, filePath string, content

for {
reader := CreateRateLimitedReader(content, storage.UploadRateLimit/storage.numberOfThreads)
_, err = storage.service.Files.Create(file).Media(reader).Fields("id").Do()
_, err = storage.service.Files.Create(file).SupportsAllDrives(true).Media(reader).Fields("id").Do()
if retry, err := storage.shouldRetry(threadIndex, err); err == nil && !retry {
break
} else if retry {
Expand Down
11 changes: 10 additions & 1 deletion src/duplicacy_storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -624,10 +624,19 @@ func CreateStorage(preference Preference, resetPassword bool, threads int) (stor
SavePassword(preference, "gcs_token", tokenFile)
return gcsStorage
} else if matched[1] == "gcd" {
// Handle writing directly to the root of the drive
// For gcd://driveid@/, driveid@ is match[3] not match[2]
if matched[2] == "" && strings.HasSuffix(matched[3], "@") {
matched[2], matched[3] = matched[3], matched[2]
}
driveID := matched[2]
if driveID != "" {
driveID = driveID[:len(driveID)-1]
}
storagePath := matched[3] + matched[4]
prompt := fmt.Sprintf("Enter the path of the Google Drive token file (downloadable from https://duplicacy.com/gcd_start):")
tokenFile := GetPassword(preference, "gcd_token", prompt, true, resetPassword)
gcdStorage, err := CreateGCDStorage(tokenFile, storagePath, threads)
gcdStorage, err := CreateGCDStorage(tokenFile, driveID, storagePath, threads)
if err != nil {
LOG_ERROR("STORAGE_CREATE", "Failed to load the Google Drive storage at %s: %v", storageURL, err)
return nil
Expand Down
2 changes: 1 addition & 1 deletion src/duplicacy_storage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ func loadStorage(localStoragePath string, threads int) (Storage, error) {
storage.SetDefaultNestingLevels([]int{2, 3}, 2)
return storage, err
} else if testStorageName == "gcd" {
storage, err := CreateGCDStorage(config["token_file"], config["storage_path"], threads)
storage, err := CreateGCDStorage(config["token_file"], "", config["storage_path"], threads)
storage.SetDefaultNestingLevels([]int{2, 3}, 2)
return storage, err
} else if testStorageName == "one" {
Expand Down

0 comments on commit e827662

Please sign in to comment.