aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--README.md82
-rw-r--r--compare.go2
-rw-r--r--drive/download.go6
-rw-r--r--drive/errors.go20
-rw-r--r--drive/revision_download.go7
-rw-r--r--drive/sync_download.go9
-rw-r--r--drive/sync_upload.go17
-rw-r--r--drive/timeout_reader.go44
-rw-r--r--drive/update.go6
-rw-r--r--drive/upload.go16
-rw-r--r--gdrive.go47
-rw-r--r--handlers_drive.go14
-rw-r--r--handlers_meta.go2
14 files changed, 199 insertions, 74 deletions
diff --git a/.gitignore b/.gitignore
index ee3fac5..62df61a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,4 @@ _release/bin
Session.vim
.netrwhist
gdrive
+gdrive.sh
diff --git a/README.md b/README.md
index 88d87fc..d908450 100644
--- a/README.md
+++ b/README.md
@@ -8,6 +8,7 @@ gdrive is a command line utility for interacting with Google Drive.
## Prerequisites
None, binaries are statically linked.
If you want to compile from source you need the [go toolchain](http://golang.org/doc/install).
+Version 1.5 or higher.
## Installation
Download `gdrive` from one of the links below. On unix systems
@@ -26,33 +27,33 @@ You will be prompted for a new verification code if the folder does not exist.
### Downloads
| Filename | Version | Description | Shasum |
|:-----------------------|:--------|:-------------------|:-----------------------------------------|
-| [gdrive-osx-x64](https://docs.google.com/uc?id=0B3X9GlR6EmbnZUM0bXRBeUpYQ00&export=download) | 2.0.1 | OS X 64-bit | 180bc98408c7ec6deac6a66bbd9c307c4348ae6f |
-| [gdrive-osx-386](https://docs.google.com/uc?id=0B3X9GlR6EmbnM05hQXhvWm9EOHM&export=download) | 2.0.1 | OS X 32-bit | 366ee217d4327a1855245d8c4a1204f4831eb979 |
-| [gdrive-osx-arm](https://docs.google.com/uc?id=0B3X9GlR6EmbnelYxRU5LbEVfVzQ&export=download) | 2.0.1 | OS X arm | cdc31f83e50560a7f8fbf8a25b9c87c945d1407f |
-| [gdrive-linux-x64](https://docs.google.com/uc?id=0B3X9GlR6EmbnWksyTEtCM0VfaFE&export=download) | 2.0.1 | Linux 64-bit | c636778c4a2c76e47ac731c142f4219a19c30263 |
-| [gdrive-linux-386](https://docs.google.com/uc?id=0B3X9GlR6EmbnbndOUW50ZVllZ3M&export=download) | 2.0.1 | Linux 32-bit | 0968993e4a70a594e0f315034640fd811977e4f1 |
-| [gdrive-linux-rpi](https://docs.google.com/uc?id=0B3X9GlR6EmbnbVRIelM5SG5zcmM&export=download) | 2.0.1 | Linux Raspberry Pi | 7865a1e96e70791aa0f33cb758e6cda7886be240 |
-| [gdrive-linux-arm64](https://docs.google.com/uc?id=0B3X9GlR6EmbnNkxrQ3VzOGNMNWc&export=download) | 2.0.1 | Linux arm 64-bit | 00b293e4501da64e956d32b7c1589a014b541abe |
-| [gdrive-linux-arm](https://docs.google.com/uc?id=0B3X9GlR6EmbnLW51cVJrazVLOG8&export=download) | 2.0.1 | Linux arm 32-bit | 3577a6462dafc47823d5ed053a978af84a99c5af |
-| [gdrive-linux-mips64](https://docs.google.com/uc?id=0B3X9GlR6EmbnTS1fejFtaDMyLU0&export=download) | 2.0.1 | Linux mips 64-bit | e1e5992a5635467b84f149435544d980e99f30c6 |
-| [gdrive-linux-mips64le](https://docs.google.com/uc?id=0B3X9GlR6EmbnV3pJU0ZBTEg0a2M&export=download) | 2.0.1 | Linux mips 64-bit le | 8273d53fc21b6028de781958c6f224f41c0a92db |
-| [gdrive-linux-ppc64](https://docs.google.com/uc?id=0B3X9GlR6EmbnOXNPM01ITHl3NUk&export=download) | 2.0.1 | Linux PPC 64-bit | fc1409b9960ae4a209331e40a4cd9b10a789f8e6 |
-| [gdrive-linux-ppc64le](https://docs.google.com/uc?id=0B3X9GlR6EmbnRVBCRlpXNl95bW8&export=download) | 2.0.1 | Linux PPC 64-bit le | 7a09ed4b43c31198efdf4e4a7da1e196cd3cd54f |
-| [gdrive-windows-386.exe](https://docs.google.com/uc?id=0B3X9GlR6EmbncnpncGpZX0pULUU&export=download) | 2.0.1 | Window 32-bit | 134480ca113d03b91dbfa43326704b57f07ca547 |
-| [gdrive-windows-x64.exe](https://docs.google.com/uc?id=0B3X9GlR6EmbncWNLOS1KYWhLVFE&export=download) | 2.0.1 | Windows 64-bit | c68df6e77aa7fa412bfe318ab270e1245a24966b |
-| [gdrive-dragonfly-x64](https://docs.google.com/uc?id=0B3X9GlR6EmbnX1FiZzFaN1hRekk&export=download) | 2.0.1 | DragonFly BSD 64-bit | 0116d291a859152a4af842254eb88102c18f9ad6 |
-| [gdrive-freebsd-x64](https://docs.google.com/uc?id=0B3X9GlR6Embnck8wR0ozV3J2WHM&export=download) | 2.0.1 | FreeBSD 64-bit | 0ab7d8509efcbbf41a4c684f9942c749706ea8ab |
-| [gdrive-freebsd-386](https://docs.google.com/uc?id=0B3X9GlR6EmbnVUtwX010YUlENWs&export=download) | 2.0.1 | FreeBSD 32-bit | 4e1ac2aeeed59df1d8636641b796aad54ade42d2 |
-| [gdrive-freebsd-arm](https://docs.google.com/uc?id=0B3X9GlR6EmbncXV3SU9NZDc3X3M&export=download) | 2.0.1 | FreeBSD arm | 1220f7f75579b28205d302f1e8ad0faeefc5d188 |
-| [gdrive-netbsd-x64](https://docs.google.com/uc?id=0B3X9GlR6EmbnUk5JMVFiS0pKZW8&export=download) | 2.0.1 | NetBSD 64-bit | dba9e9f57b6ed3c370961e95efaea1ed0ea4429f |
-| [gdrive-netbsd-386](https://docs.google.com/uc?id=0B3X9GlR6EmbnLWZRTy1LX1kxUkE&export=download) | 2.0.1 | NetBSD 32-bit | 8303ec4f0f7ba2acecc4509e0e73f8bf1eb2cd68 |
-| [gdrive-netbsd-arm](https://docs.google.com/uc?id=0B3X9GlR6EmbnQUNFOWxBYmdjUEk&export=download) | 2.0.1 | NetBSD arm | 84aeb602e0cfbb09a35fff97f2af5990673f9bee |
-| [gdrive-openbsd-x64](https://docs.google.com/uc?id=0B3X9GlR6EmbnYk50VlVuNUhuaVE&export=download) | 2.0.1 | OpenBSD 64-bit | 729794ef7cfc320cab84fd78e3113f5fdd476ac6 |
-| [gdrive-openbsd-386](https://docs.google.com/uc?id=0B3X9GlR6EmbnOExCLVNYakJiSUk&export=download) | 2.0.1 | OpenBSD 32-bit | a1f45252d86ae238ce17b49c226b218aec805a02 |
-| [gdrive-openbsd-arm](https://docs.google.com/uc?id=0B3X9GlR6EmbnN1htLTBSSElRUDQ&export=download) | 2.0.1 | OpenBSD arm | 5a732b15a4d36e61768388e323807e9a2fccb4bc |
-| [gdrive-solaris-x64](https://docs.google.com/uc?id=0B3X9GlR6EmbnSEpJSDBidHNDNkE&export=download) | 2.0.1 | Solaris 64-bit | 82dc342873693b37302fc8f3cb97a667bae6e41c |
-| [gdrive-plan9-x64](https://docs.google.com/uc?id=0B3X9GlR6EmbnZWMzR3pyZlZ0cFU&export=download) | 2.0.1 | Plan9 64-bit | e92e4a25517116c4fd466142baab03e0b9d11772 |
-| [gdrive-plan9-386](https://docs.google.com/uc?id=0B3X9GlR6EmbnOXV4d0dNeGZCR00&export=download) | 2.0.1 | Plan9 32-bit | efdfced751ca43995ad28dc1aa96b29a8c237433 |
+| [gdrive-osx-x64](https://docs.google.com/uc?id=0B3X9GlR6Embnb010SnpUV0s2ZkU&export=download) | 2.1.0 | OS X 64-bit | 297ccf3c945b364b5d306cef335ba44b0900e927 |
+| [gdrive-osx-386](https://docs.google.com/uc?id=0B3X9GlR6EmbnTjByNlNvZVNRTjQ&export=download) | 2.1.0 | OS X 32-bit | c64714676a5b028aeeaf09e5f3b84d363e0ec7ed |
+| [gdrive-osx-arm](https://docs.google.com/uc?id=0B3X9GlR6EmbnbURvYnVyVmNNX2M&export=download) | 2.1.0 | OS X arm | eb23b7bb5a072497372bd253e8fc8353bec8a64c |
+| [gdrive-linux-x64](https://docs.google.com/uc?id=0B3X9GlR6EmbnQ0FtZmJJUXEyRTA&export=download) | 2.1.0 | Linux 64-bit | 4fd8391b300cac45963e53da44dcfe68da08d843 |
+| [gdrive-linux-386](https://docs.google.com/uc?id=0B3X9GlR6EmbnLV92dHBpTkFhTEU&export=download) | 2.1.0 | Linux 32-bit | de9f49565fc62552fe862f08f84694ab4653adc2 |
+| [gdrive-linux-rpi](https://docs.google.com/uc?id=0B3X9GlR6EmbnVXNLanp4ZFRRbzg&export=download) | 2.1.0 | Linux Raspberry Pi | e26e9ca3df3d08f970a276782ac5e92731c85467 |
+| [gdrive-linux-arm64](https://docs.google.com/uc?id=0B3X9GlR6EmbnamliN0Rld01oRVk&export=download) | 2.1.0 | Linux arm 64-bit | 3d670905e13edf96d43c9f97293bdba62c740926 |
+| [gdrive-linux-arm](https://docs.google.com/uc?id=0B3X9GlR6EmbnRjBaMVVLalN4cTA&export=download) | 2.1.0 | Linux arm 32-bit | 5b1036e0ef479ce228f7c32d1adfdc3840d71d10 |
+| [gdrive-linux-mips64](https://docs.google.com/uc?id=0B3X9GlR6Embna2lzdEJ6blFzSzQ&export=download) | 2.1.0 | Linux mips 64-bit | 334bbd74b87fd1d05550e366724fe8e3c9e61ca4 |
+| [gdrive-linux-mips64le](https://docs.google.com/uc?id=0B3X9GlR6EmbnWFk4Q3ZVZ1g3ZHM&export=download) | 2.1.0 | Linux mips 64-bit le | bb6961a2c03c074e6d34a1ec280cc69f5d5002f5 |
+| [gdrive-linux-ppc64](https://docs.google.com/uc?id=0B3X9GlR6EmbnS09XMzhfRXBnUzA&export=download) | 2.1.0 | Linux PPC 64-bit | 70a1ac5be9ba819da5cf7a8dbd513805a26509ac |
+| [gdrive-linux-ppc64le](https://docs.google.com/uc?id=0B3X9GlR6EmbneDJ2b3hqbVlNZnc&export=download) | 2.1.0 | Linux PPC 64-bit le | f426817ee4824b83b978f82f8e72eac6db92f2d1 |
+| [gdrive-windows-386.exe](https://docs.google.com/uc?id=0B3X9GlR6EmbnV3RNeFVUQjZvS2c&export=download) | 2.1.0 | Window 32-bit | 1429200631b598543eddc3df3487117cad95adbb |
+| [gdrive-windows-x64.exe](https://docs.google.com/uc?id=0B3X9GlR6EmbnNFRSSW1GaFBSRk0&export=download) | 2.1.0 | Windows 64-bit | 16ccab7c66b144e5806daeb2ba50d567b51504ca |
+| [gdrive-dragonfly-x64](https://docs.google.com/uc?id=0B3X9GlR6EmbnelNIdmRMMGpVa2s&export=download) | 2.1.0 | DragonFly BSD 64-bit | dc214a24e59f68d99ca62757d99099051f83804a |
+| [gdrive-freebsd-x64](https://docs.google.com/uc?id=0B3X9GlR6EmbnQkN0NnUwZ0tKLXM&export=download) | 2.1.0 | FreeBSD 64-bit | 93a5581652f9c01c47fb6c16e8ae655182f265da |
+| [gdrive-freebsd-386](https://docs.google.com/uc?id=0B3X9GlR6EmbnNU5rbXBzeEhhOTA&export=download) | 2.1.0 | FreeBSD 32-bit | b9a3ee1e0fdbb5fa970942ab89b354ee863a5758 |
+| [gdrive-freebsd-arm](https://docs.google.com/uc?id=0B3X9GlR6EmbnVHpUbVFzZzNqeW8&export=download) | 2.1.0 | FreeBSD arm | 7f5d1dedaa98501932ea368f2baba240da0b00d8 |
+| [gdrive-netbsd-x64](https://docs.google.com/uc?id=0B3X9GlR6EmbnbGJobnBQR0dtV2c&export=download) | 2.1.0 | NetBSD 64-bit | 2a088dbd1e149204eb71a47ade109816983fe53f |
+| [gdrive-netbsd-386](https://docs.google.com/uc?id=0B3X9GlR6EmbneWszMnl5RGZnYWs&export=download) | 2.1.0 | NetBSD 32-bit | a2c231b91839171a58da780657c445d4a1430537 |
+| [gdrive-netbsd-arm](https://docs.google.com/uc?id=0B3X9GlR6EmbnVVhOWG9UUUhWNVU&export=download) | 2.1.0 | NetBSD arm | ac8a6354f8a8346c2bf84585e14f4a2cc69451db |
+| [gdrive-openbsd-x64](https://docs.google.com/uc?id=0B3X9GlR6EmbnSy1JdFlHdUYyaGs&export=download) | 2.1.0 | OpenBSD 64-bit | 54be1d38b9014c6a8de5d71233cd6f208c27ac1c |
+| [gdrive-openbsd-386](https://docs.google.com/uc?id=0B3X9GlR6EmbnRWhIZFRCNE1OdWc&export=download) | 2.1.0 | OpenBSD 32-bit | c2e08a9c7242de6d6ffa01598425fea0550076b8 |
+| [gdrive-openbsd-arm](https://docs.google.com/uc?id=0B3X9GlR6EmbnWnAzMTNZanp2UEE&export=download) | 2.1.0 | OpenBSD arm | 22cd413c2705012b2ac78e64cc9f2b5bfa96dbea |
+| [gdrive-solaris-x64](https://docs.google.com/uc?id=0B3X9GlR6EmbnWEtENmQ5dDJtTHc&export=download) | 2.1.0 | Solaris 64-bit | 2da03dfcc818a0bd3588ad850349a5a2554913fb |
+| [gdrive-plan9-x64](https://docs.google.com/uc?id=0B3X9GlR6EmbnRmVyelhxLUUySjA&export=download) | 2.1.0 | Plan9 64-bit | 7b498ce0f416a3e8c1e17f603d21a3e84c1a9283 |
+| [gdrive-plan9-386](https://docs.google.com/uc?id=0B3X9GlR6EmbnckdHZVdRZ0dZTU0&export=download) | 2.1.0 | Plan9 32-bit | cccd9ba86774bc6bd70f092158e2fcafa94601c0 |
## Compile from source
```bash
@@ -147,12 +148,13 @@ global:
--access-token <accessToken> Oauth access token, only recommended for short-lived requests because of short lifetime (for advanced users)
options:
- -f, --force Overwrite existing file
- -r, --recursive Download directory recursively, documents will be skipped
- --path <path> Download path
- --delete Delete remote file when download is successful
- --no-progress Hide progress
- --stdout Write file content to stdout
+ -f, --force Overwrite existing file
+ -r, --recursive Download directory recursively, documents will be skipped
+ --path <path> Download path
+ --delete Delete remote file when download is successful
+ --no-progress Hide progress
+ --stdout Write file content to stdout
+ --timeout <timeout> Set timeout in seconds, use 0 for no timeout. Timeout is reached when no data is transferred in set amount of seconds, default: 300
```
#### Download all files and directories matching query
@@ -188,6 +190,7 @@ options:
--mime <mime> Force mime type
--share Share file
--delete Delete local file when upload is successful
+ --timeout <timeout> Set timeout in seconds, use 0 for no timeout. Timeout is reached when no data is transferred in set amount of seconds, default: 300
--chunksize <chunksize> Set chunk size in bytes, default: 8388608
```
@@ -205,6 +208,7 @@ options:
--chunksize <chunksize> Set chunk size in bytes, default: 8388608
--mime <mime> Force mime type
--share Share file
+ --timeout <timeout> Set timeout in seconds, use 0 for no timeout. Timeout is reached when no data is transferred in set amount of seconds, default: 300
--no-progress Hide progress
```
@@ -222,6 +226,7 @@ options:
--name <name> Filename
--no-progress Hide progress
--mime <mime> Force mime type
+ --timeout <timeout> Set timeout in seconds, use 0 for no timeout. Timeout is reached when no data is transferred in set amount of seconds, default: 300
--chunksize <chunksize> Set chunk size in bytes, default: 8388608
```
@@ -346,6 +351,7 @@ options:
--delete-extraneous Delete extraneous local files
--dry-run Show what would have been transferred
--no-progress Hide progress
+ --timeout <timeout> Set timeout in seconds, use 0 for no timeout. Timeout is reached when no data is transferred in set amount of seconds, default: 300
```
#### Sync local directory to drive
@@ -364,6 +370,7 @@ options:
--delete-extraneous Delete extraneous remote files
--dry-run Show what would have been transferred
--no-progress Hide progress
+ --timeout <timeout> Set timeout in seconds, use 0 for no timeout. Timeout is reached when no data is transferred in set amount of seconds, default: 300
--chunksize <chunksize> Set chunk size in bytes, default: 8388608
```
@@ -409,10 +416,11 @@ global:
--access-token <accessToken> Oauth access token, only recommended for short-lived requests because of short lifetime (for advanced users)
options:
- -f, --force Overwrite existing file
- --no-progress Hide progress
- --stdout Write file content to stdout
- --path <path> Download path
+ -f, --force Overwrite existing file
+ --no-progress Hide progress
+ --stdout Write file content to stdout
+ --path <path> Download path
+ --timeout <timeout> Set timeout in seconds, use 0 for no timeout. Timeout is reached when no data is transferred in set amount of seconds, default: 300
```
#### Delete file revision
diff --git a/compare.go b/compare.go
index ac68284..35c0369 100644
--- a/compare.go
+++ b/compare.go
@@ -1,8 +1,8 @@
package main
import (
- "github.com/prasmussen/gdrive/drive"
"encoding/json"
+ "github.com/prasmussen/gdrive/drive"
"os"
)
diff --git a/drive/download.go b/drive/download.go
index 0856258..ec1af8a 100644
--- a/drive/download.go
+++ b/drive/download.go
@@ -19,6 +19,7 @@ type DownloadArgs struct {
Recursive bool
Delete bool
Stdout bool
+ Timeout time.Duration
}
func (self *Drive) Download(args DownloadArgs) error {
@@ -120,10 +121,13 @@ func (self *Drive) downloadRecursive(args DownloadArgs) error {
func (self *Drive) downloadBinary(f *drive.File, args DownloadArgs) (int64, int64, error) {
// Get timeout reader wrapper and context
- timeoutReaderWrapper, ctx := getTimeoutReaderWrapperContext()
+ timeoutReaderWrapper, ctx := getTimeoutReaderWrapperContext(args.Timeout)
res, err := self.service.Files.Get(f.Id).Context(ctx).Download()
if err != nil {
+ if isTimeoutError(err) {
+ return 0, 0, fmt.Errorf("Failed to download file: timeout, no data was transferred for %v", args.Timeout)
+ }
return 0, 0, fmt.Errorf("Failed to download file: %s", err)
}
diff --git a/drive/errors.go b/drive/errors.go
index e7631f7..f70f0f0 100644
--- a/drive/errors.go
+++ b/drive/errors.go
@@ -1,11 +1,16 @@
package drive
import (
+ "golang.org/x/net/context"
"google.golang.org/api/googleapi"
"time"
)
-const MaxBackendErrorRetries = 5
+const MaxErrorRetries = 5
+
+func isBackendOrRateLimitError(err error) bool {
+ return isBackendError(err) || isRateLimitError(err)
+}
func isBackendError(err error) bool {
if err == nil {
@@ -16,6 +21,19 @@ func isBackendError(err error) bool {
return ok && ae.Code >= 500 && ae.Code <= 599
}
+func isRateLimitError(err error) bool {
+ if err == nil {
+ return false
+ }
+
+ ae, ok := err.(*googleapi.Error)
+ return ok && ae.Code == 403
+}
+
+func isTimeoutError(err error) bool {
+ return err == context.Canceled
+}
+
func exponentialBackoffSleep(try int) {
seconds := pow(2, try)
time.Sleep(time.Duration(seconds) * time.Second)
diff --git a/drive/revision_download.go b/drive/revision_download.go
index 04055fa..6bed886 100644
--- a/drive/revision_download.go
+++ b/drive/revision_download.go
@@ -5,6 +5,7 @@ import (
"io"
"io/ioutil"
"path/filepath"
+ "time"
)
type DownloadRevisionArgs struct {
@@ -15,6 +16,7 @@ type DownloadRevisionArgs struct {
Path string
Force bool
Stdout bool
+ Timeout time.Duration
}
func (self *Drive) DownloadRevision(args DownloadRevisionArgs) (err error) {
@@ -30,10 +32,13 @@ func (self *Drive) DownloadRevision(args DownloadRevisionArgs) (err error) {
}
// Get timeout reader wrapper and context
- timeoutReaderWrapper, ctx := getTimeoutReaderWrapperContext()
+ timeoutReaderWrapper, ctx := getTimeoutReaderWrapperContext(args.Timeout)
res, err := getRev.Context(ctx).Download()
if err != nil {
+ if isTimeoutError(err) {
+ return fmt.Errorf("Failed to download file: timeout, no data was transferred for %v", args.Timeout)
+ }
return fmt.Errorf("Failed to download file: %s", err)
}
diff --git a/drive/sync_download.go b/drive/sync_download.go
index 04b50b9..58cd49c 100644
--- a/drive/sync_download.go
+++ b/drive/sync_download.go
@@ -19,6 +19,7 @@ type DownloadSyncArgs struct {
Path string
DryRun bool
DeleteExtraneous bool
+ Timeout time.Duration
Resolution ConflictResolution
Comparer FileComparer
}
@@ -188,14 +189,16 @@ func (self *Drive) downloadRemoteFile(id, fpath string, args DownloadSyncArgs, t
}
// Get timeout reader wrapper and context
- timeoutReaderWrapper, ctx := getTimeoutReaderWrapperContext()
+ timeoutReaderWrapper, ctx := getTimeoutReaderWrapperContext(args.Timeout)
res, err := self.service.Files.Get(id).Context(ctx).Download()
if err != nil {
- if isBackendError(err) && try < MaxBackendErrorRetries {
+ if isBackendOrRateLimitError(err) && try < MaxErrorRetries {
exponentialBackoffSleep(try)
try++
return self.downloadRemoteFile(id, fpath, args, try)
+ } else if isTimeoutError(err) {
+ return fmt.Errorf("Failed to download file: timeout, no data was transferred for %v", args.Timeout)
} else {
return fmt.Errorf("Failed to download file: %s", err)
}
@@ -228,7 +231,7 @@ func (self *Drive) downloadRemoteFile(id, fpath string, args DownloadSyncArgs, t
_, err = io.Copy(outFile, reader)
if err != nil {
outFile.Close()
- if try < MaxBackendErrorRetries {
+ if try < MaxErrorRetries {
exponentialBackoffSleep(try)
try++
return self.downloadRemoteFile(id, fpath, args, try)
diff --git a/drive/sync_upload.go b/drive/sync_upload.go
index 0d5c208..261497d 100644
--- a/drive/sync_upload.go
+++ b/drive/sync_upload.go
@@ -20,6 +20,7 @@ type UploadSyncArgs struct {
DryRun bool
DeleteExtraneous bool
ChunkSize int64
+ Timeout time.Duration
Resolution ConflictResolution
Comparer FileComparer
}
@@ -269,7 +270,7 @@ func (self *Drive) createMissingRemoteDir(args createMissingRemoteDirArgs) (*dri
f, err := self.service.Files.Create(dstFile).Do()
if err != nil {
- if isBackendError(err) && args.try < MaxBackendErrorRetries {
+ if isBackendOrRateLimitError(err) && args.try < MaxErrorRetries {
exponentialBackoffSleep(args.try)
args.try++
return self.createMissingRemoteDir(args)
@@ -308,14 +309,16 @@ func (self *Drive) uploadMissingFile(parentId string, lf *LocalFile, args Upload
progressReader := getProgressReader(srcFile, args.Progress, lf.info.Size())
// Wrap reader in timeout reader
- reader, ctx := getTimeoutReaderContext(progressReader)
+ reader, ctx := getTimeoutReaderContext(progressReader, args.Timeout)
_, err = self.service.Files.Create(dstFile).Fields("id", "name", "size", "md5Checksum").Context(ctx).Media(reader, chunkSize).Do()
if err != nil {
- if isBackendError(err) && try < MaxBackendErrorRetries {
+ if isBackendOrRateLimitError(err) && try < MaxErrorRetries {
exponentialBackoffSleep(try)
try++
return self.uploadMissingFile(parentId, lf, args, try)
+ } else if isTimeoutError(err) {
+ return fmt.Errorf("Failed to upload file: timeout, no data was transferred for %v", args.Timeout)
} else {
return fmt.Errorf("Failed to upload file: %s", err)
}
@@ -347,14 +350,16 @@ func (self *Drive) updateChangedFile(cf *changedFile, args UploadSyncArgs, try i
progressReader := getProgressReader(srcFile, args.Progress, cf.local.info.Size())
// Wrap reader in timeout reader
- reader, ctx := getTimeoutReaderContext(progressReader)
+ reader, ctx := getTimeoutReaderContext(progressReader, args.Timeout)
_, err = self.service.Files.Update(cf.remote.file.Id, dstFile).Context(ctx).Media(reader, chunkSize).Do()
if err != nil {
- if isBackendError(err) && try < MaxBackendErrorRetries {
+ if isBackendOrRateLimitError(err) && try < MaxErrorRetries {
exponentialBackoffSleep(try)
try++
return self.updateChangedFile(cf, args, try)
+ } else if isTimeoutError(err) {
+ return fmt.Errorf("Failed to upload file: timeout, no data was transferred for %v", args.Timeout)
} else {
return fmt.Errorf("Failed to update file: %s", err)
}
@@ -370,7 +375,7 @@ func (self *Drive) deleteRemoteFile(rf *RemoteFile, args UploadSyncArgs, try int
err := self.service.Files.Delete(rf.file.Id).Do()
if err != nil {
- if isBackendError(err) && try < MaxBackendErrorRetries {
+ if isBackendOrRateLimitError(err) && try < MaxErrorRetries {
exponentialBackoffSleep(try)
try++
return self.deleteRemoteFile(rf, args, try)
diff --git a/drive/timeout_reader.go b/drive/timeout_reader.go
index 9930c12..67fd5b0 100644
--- a/drive/timeout_reader.go
+++ b/drive/timeout_reader.go
@@ -7,39 +7,51 @@ import (
"time"
)
-const MaxIdleTimeout = time.Second * 120
const TimeoutTimerInterval = time.Second * 10
type timeoutReaderWrapper func(io.Reader) io.Reader
-func getTimeoutReaderWrapperContext() (timeoutReaderWrapper, context.Context) {
+func getTimeoutReaderWrapperContext(timeout time.Duration) (timeoutReaderWrapper, context.Context) {
ctx, cancel := context.WithCancel(context.TODO())
wrapper := func(r io.Reader) io.Reader {
- return getTimeoutReader(r, cancel)
+ // Return untouched reader if timeout is 0
+ if timeout == 0 {
+ return r
+ }
+
+ return getTimeoutReader(r, cancel, timeout)
}
return wrapper, ctx
}
-func getTimeoutReaderContext(r io.Reader) (io.Reader, context.Context) {
+func getTimeoutReaderContext(r io.Reader, timeout time.Duration) (io.Reader, context.Context) {
ctx, cancel := context.WithCancel(context.TODO())
- return getTimeoutReader(r, cancel), ctx
+
+ // Return untouched reader if timeout is 0
+ if timeout == 0 {
+ return r, ctx
+ }
+
+ return getTimeoutReader(r, cancel, timeout), ctx
}
-func getTimeoutReader(r io.Reader, cancel context.CancelFunc) io.Reader {
+func getTimeoutReader(r io.Reader, cancel context.CancelFunc, timeout time.Duration) io.Reader {
return &TimeoutReader{
- reader: r,
- cancel: cancel,
- mutex: &sync.Mutex{},
+ reader: r,
+ cancel: cancel,
+ mutex: &sync.Mutex{},
+ maxIdleTimeout: timeout,
}
}
type TimeoutReader struct {
- reader io.Reader
- cancel context.CancelFunc
- lastActivity time.Time
- timer *time.Timer
- mutex *sync.Mutex
- done bool
+ reader io.Reader
+ cancel context.CancelFunc
+ lastActivity time.Time
+ timer *time.Timer
+ mutex *sync.Mutex
+ maxIdleTimeout time.Duration
+ done bool
}
func (self *TimeoutReader) Read(p []byte) (int, error) {
@@ -90,7 +102,7 @@ func (self *TimeoutReader) timeout() {
return
}
- if time.Since(self.lastActivity) > MaxIdleTimeout {
+ if time.Since(self.lastActivity) > self.maxIdleTimeout {
self.cancel()
self.mutex.Unlock()
return
diff --git a/drive/update.go b/drive/update.go
index 156eb2f..2ab684e 100644
--- a/drive/update.go
+++ b/drive/update.go
@@ -20,6 +20,7 @@ type UpdateArgs struct {
Mime string
Recursive bool
ChunkSize int64
+ Timeout time.Duration
}
func (self *Drive) Update(args UpdateArgs) error {
@@ -57,13 +58,16 @@ func (self *Drive) Update(args UpdateArgs) error {
progressReader := getProgressReader(srcFile, args.Progress, srcFileInfo.Size())
// Wrap reader in timeout reader
- reader, ctx := getTimeoutReaderContext(progressReader)
+ reader, ctx := getTimeoutReaderContext(progressReader, args.Timeout)
fmt.Fprintf(args.Out, "Uploading %s\n", args.Path)
started := time.Now()
f, err := self.service.Files.Update(args.Id, dstFile).Fields("id", "name", "size").Context(ctx).Media(reader, chunkSize).Do()
if err != nil {
+ if isTimeoutError(err) {
+ return fmt.Errorf("Failed to upload file: timeout, no data was transferred for %v", args.Timeout)
+ }
return fmt.Errorf("Failed to upload file: %s", err)
}
diff --git a/drive/upload.go b/drive/upload.go
index c42bebd..dbf068c 100644
--- a/drive/upload.go
+++ b/drive/upload.go
@@ -22,6 +22,7 @@ type UploadArgs struct {
Share bool
Delete bool
ChunkSize int64
+ Timeout time.Duration
}
func (self *Drive) Upload(args UploadArgs) error {
@@ -89,10 +90,12 @@ func (self *Drive) uploadRecursive(args UploadArgs) error {
if info.IsDir() {
args.Name = ""
return self.uploadDirectory(args)
- } else {
+ } else if info.Mode().IsRegular() {
_, _, err := self.uploadFile(args)
return err
}
+
+ return nil
}
func (self *Drive) uploadDirectory(args UploadArgs) error {
@@ -173,13 +176,16 @@ func (self *Drive) uploadFile(args UploadArgs) (*drive.File, int64, error) {
progressReader := getProgressReader(srcFile, args.Progress, srcFileInfo.Size())
// Wrap reader in timeout reader
- reader, ctx := getTimeoutReaderContext(progressReader)
+ reader, ctx := getTimeoutReaderContext(progressReader, args.Timeout)
fmt.Fprintf(args.Out, "Uploading %s\n", args.Path)
started := time.Now()
f, err := self.service.Files.Create(dstFile).Fields("id", "name", "size", "md5Checksum", "webContentLink").Context(ctx).Media(reader, chunkSize).Do()
if err != nil {
+ if isTimeoutError(err) {
+ return nil, 0, fmt.Errorf("Failed to upload file: timeout, no data was transferred for %v", args.Timeout)
+ }
return nil, 0, fmt.Errorf("Failed to upload file: %s", err)
}
@@ -198,6 +204,7 @@ type UploadStreamArgs struct {
Share bool
ChunkSize int64
Progress io.Writer
+ Timeout time.Duration
}
func (self *Drive) UploadStream(args UploadStreamArgs) error {
@@ -223,13 +230,16 @@ func (self *Drive) UploadStream(args UploadStreamArgs) error {
progressReader := getProgressReader(args.In, args.Progress, 0)
// Wrap reader in timeout reader
- reader, ctx := getTimeoutReaderContext(progressReader)
+ reader, ctx := getTimeoutReaderContext(progressReader, args.Timeout)
fmt.Fprintf(args.Out, "Uploading %s\n", dstFile.Name)
started := time.Now()
f, err := self.service.Files.Create(dstFile).Fields("id", "name", "size", "webContentLink").Context(ctx).Media(reader, chunkSize).Do()
if err != nil {
+ if isTimeoutError(err) {
+ return fmt.Errorf("Failed to upload file: timeout, no data was transferred for %v", args.Timeout)
+ }
return fmt.Errorf("Failed to upload file: %s", err)
}
diff --git a/gdrive.go b/gdrive.go
index c68d43d..ecb6950 100644
--- a/gdrive.go
+++ b/gdrive.go
@@ -1,19 +1,20 @@
package main
import (
- "github.com/prasmussen/gdrive/cli"
"fmt"
+ "github.com/prasmussen/gdrive/cli"
"os"
)
const Name = "gdrive"
-const Version = "2.0.1"
+const Version = "2.1.0"
const DefaultMaxFiles = 30
const DefaultMaxChanges = 100
const DefaultNameWidth = 40
const DefaultPathWidth = 60
const DefaultUploadChunkSize = 8 * 1024 * 1024
+const DefaultTimeout = 5 * 60
const DefaultQuery = "trashed = false and 'me' in owners"
const DefaultShareRole = "reader"
const DefaultShareType = "anyone"
@@ -134,6 +135,12 @@ func main() {
Description: "Write file content to stdout",
OmitValue: true,
},
+ cli.IntFlag{
+ Name: "timeout",
+ Patterns: []string{"--timeout"},
+ Description: fmt.Sprintf("Set timeout in seconds, use 0 for no timeout. Timeout is reached when no data is transferred in set amount of seconds, default: %d", DefaultTimeout),
+ DefaultValue: DefaultTimeout,
+ },
),
},
},
@@ -217,6 +224,12 @@ func main() {
OmitValue: true,
},
cli.IntFlag{
+ Name: "timeout",
+ Patterns: []string{"--timeout"},
+ Description: fmt.Sprintf("Set timeout in seconds, use 0 for no timeout. Timeout is reached when no data is transferred in set amount of seconds, default: %d", DefaultTimeout),
+ DefaultValue: DefaultTimeout,
+ },
+ cli.IntFlag{
Name: "chunksize",
Patterns: []string{"--chunksize"},
Description: fmt.Sprintf("Set chunk size in bytes, default: %d", DefaultUploadChunkSize),
@@ -254,6 +267,12 @@ func main() {
Description: "Share file",
OmitValue: true,
},
+ cli.IntFlag{
+ Name: "timeout",
+ Patterns: []string{"--timeout"},
+ Description: fmt.Sprintf("Set timeout in seconds, use 0 for no timeout. Timeout is reached when no data is transferred in set amount of seconds, default: %d", DefaultTimeout),
+ DefaultValue: DefaultTimeout,
+ },
cli.BoolFlag{
Name: "noProgress",
Patterns: []string{"--no-progress"},
@@ -292,6 +311,12 @@ func main() {
Description: "Force mime type",
},
cli.IntFlag{
+ Name: "timeout",
+ Patterns: []string{"--timeout"},
+ Description: fmt.Sprintf("Set timeout in seconds, use 0 for no timeout. Timeout is reached when no data is transferred in set amount of seconds, default: %d", DefaultTimeout),
+ DefaultValue: DefaultTimeout,
+ },
+ cli.IntFlag{
Name: "chunksize",
Patterns: []string{"--chunksize"},
Description: fmt.Sprintf("Set chunk size in bytes, default: %d", DefaultUploadChunkSize),
@@ -494,6 +519,12 @@ func main() {
Description: "Hide progress",
OmitValue: true,
},
+ cli.IntFlag{
+ Name: "timeout",
+ Patterns: []string{"--timeout"},
+ Description: fmt.Sprintf("Set timeout in seconds, use 0 for no timeout. Timeout is reached when no data is transferred in set amount of seconds, default: %d", DefaultTimeout),
+ DefaultValue: DefaultTimeout,
+ },
),
},
},
@@ -541,6 +572,12 @@ func main() {
OmitValue: true,
},
cli.IntFlag{
+ Name: "timeout",
+ Patterns: []string{"--timeout"},
+ Description: fmt.Sprintf("Set timeout in seconds, use 0 for no timeout. Timeout is reached when no data is transferred in set amount of seconds, default: %d", DefaultTimeout),
+ DefaultValue: DefaultTimeout,
+ },
+ cli.IntFlag{
Name: "chunksize",
Patterns: []string{"--chunksize"},
Description: fmt.Sprintf("Set chunk size in bytes, default: %d", DefaultUploadChunkSize),
@@ -647,6 +684,12 @@ func main() {
Patterns: []string{"--path"},
Description: "Download path",
},
+ cli.IntFlag{
+ Name: "timeout",
+ Patterns: []string{"--timeout"},
+ Description: fmt.Sprintf("Set timeout in seconds, use 0 for no timeout. Timeout is reached when no data is transferred in set amount of seconds, default: %d", DefaultTimeout),
+ DefaultValue: DefaultTimeout,
+ },
),
},
},
diff --git a/handlers_drive.go b/handlers_drive.go
index f0386ce..8db7329 100644
--- a/handlers_drive.go
+++ b/handlers_drive.go
@@ -1,15 +1,16 @@
package main
import (
+ "fmt"
"github.com/prasmussen/gdrive/auth"
"github.com/prasmussen/gdrive/cli"
"github.com/prasmussen/gdrive/drive"
- "fmt"
"io"
"io/ioutil"
"net/http"
"os"
"path/filepath"
+ "time"
)
const ClientId = "367116221053-7n0vf5akeru7on6o2fjinrecpdoe99eg.apps.googleusercontent.com"
@@ -57,6 +58,7 @@ func downloadHandler(ctx cli.Context) {
Recursive: args.Bool("recursive"),
Stdout: args.Bool("stdout"),
Progress: progressWriter(args.Bool("noProgress")),
+ Timeout: durationInSeconds(args.Int64("timeout")),
})
checkErr(err)
}
@@ -84,6 +86,7 @@ func downloadSyncHandler(ctx cli.Context) {
RootId: args.String("fileId"),
DryRun: args.Bool("dryRun"),
DeleteExtraneous: args.Bool("deleteExtraneous"),
+ Timeout: durationInSeconds(args.Int64("timeout")),
Resolution: conflictResolution(args),
Comparer: NewCachedMd5Comparer(cachePath),
})
@@ -100,6 +103,7 @@ func downloadRevisionHandler(ctx cli.Context) {
Stdout: args.Bool("stdout"),
Path: args.String("path"),
Progress: progressWriter(args.Bool("noProgress")),
+ Timeout: durationInSeconds(args.Int64("timeout")),
})
checkErr(err)
}
@@ -118,6 +122,7 @@ func uploadHandler(ctx cli.Context) {
Share: args.Bool("share"),
Delete: args.Bool("delete"),
ChunkSize: args.Int64("chunksize"),
+ Timeout: durationInSeconds(args.Int64("timeout")),
})
checkErr(err)
}
@@ -132,6 +137,7 @@ func uploadStdinHandler(ctx cli.Context) {
Mime: args.String("mime"),
Share: args.Bool("share"),
ChunkSize: args.Int64("chunksize"),
+ Timeout: durationInSeconds(args.Int64("timeout")),
Progress: progressWriter(args.Bool("noProgress")),
})
checkErr(err)
@@ -148,6 +154,7 @@ func uploadSyncHandler(ctx cli.Context) {
DryRun: args.Bool("dryRun"),
DeleteExtraneous: args.Bool("deleteExtraneous"),
ChunkSize: args.Int64("chunksize"),
+ Timeout: durationInSeconds(args.Int64("timeout")),
Resolution: conflictResolution(args),
Comparer: NewCachedMd5Comparer(cachePath),
})
@@ -165,6 +172,7 @@ func updateHandler(ctx cli.Context) {
Mime: args.String("mime"),
Progress: progressWriter(args.Bool("noProgress")),
ChunkSize: args.Int64("chunksize"),
+ Timeout: durationInSeconds(args.Int64("timeout")),
})
checkErr(err)
}
@@ -385,6 +393,10 @@ func progressWriter(discard bool) io.Writer {
return os.Stderr
}
+func durationInSeconds(seconds int64) time.Duration {
+ return time.Second * time.Duration(seconds)
+}
+
func conflictResolution(args cli.Arguments) drive.ConflictResolution {
keepLocal := args.Bool("keepLocal")
keepRemote := args.Bool("keepRemote")
diff --git a/handlers_meta.go b/handlers_meta.go
index bfd5b8f..2e1c95c 100644
--- a/handlers_meta.go
+++ b/handlers_meta.go
@@ -1,8 +1,8 @@
package main
import (
- "github.com/prasmussen/gdrive/cli"
"fmt"
+ "github.com/prasmussen/gdrive/cli"
"os"
"runtime"
"strings"