Showing preview only (804K chars total). Download the full file or copy to clipboard to get everything.
Repository: prasmussen/gdrive
Branch: master
Commit: ab270856c83a
Files: 111
Total size: 766.5 KB
Directory structure:
gitextract_75b66kff/
├── .github/
│ └── workflows/
│ └── release.yaml
├── .gitignore
├── Godeps/
│ ├── Godeps.json
│ └── Readme
├── LICENSE
├── README.md
├── _release/
│ ├── build-all.sh
│ ├── print_usage_markdown.sh
│ └── upload.sh
├── auth/
│ ├── file_source.go
│ ├── oauth.go
│ └── util.go
├── cli/
│ ├── context.go
│ ├── flags.go
│ ├── handler.go
│ └── parser.go
├── compare.go
├── drive/
│ ├── about.go
│ ├── changes.go
│ ├── delete.go
│ ├── download.go
│ ├── drive.go
│ ├── errors.go
│ ├── export.go
│ ├── import.go
│ ├── info.go
│ ├── list.go
│ ├── mkdir.go
│ ├── path.go
│ ├── progress.go
│ ├── revision_delete.go
│ ├── revision_download.go
│ ├── revision_list.go
│ ├── share.go
│ ├── sync.go
│ ├── sync_download.go
│ ├── sync_list.go
│ ├── sync_upload.go
│ ├── timeout_reader.go
│ ├── update.go
│ ├── upload.go
│ └── util.go
├── gdrive.go
├── handlers_drive.go
├── handlers_meta.go
├── util.go
└── vendor/
├── github.com/
│ ├── sabhiram/
│ │ └── go-git-ignore/
│ │ ├── .gitignore
│ │ ├── .travis.yml
│ │ ├── LICENSE
│ │ ├── README.md
│ │ └── ignore.go
│ └── soniakeys/
│ └── graph/
│ ├── .gitignore
│ ├── .travis.yml
│ ├── adj.go
│ ├── adj_RO.go
│ ├── adj_cg.go
│ ├── bits.go
│ ├── bits32.go
│ ├── bits64.go
│ ├── dir.go
│ ├── dir_RO.go
│ ├── dir_cg.go
│ ├── doc.go
│ ├── fromlist.go
│ ├── graph.go
│ ├── hacking.md
│ ├── mst.go
│ ├── random.go
│ ├── readme.md
│ ├── sssp.go
│ ├── travis.sh
│ ├── undir.go
│ ├── undir_RO.go
│ └── undir_cg.go
├── golang.org/
│ └── x/
│ ├── net/
│ │ ├── LICENSE
│ │ ├── PATENTS
│ │ └── context/
│ │ ├── context.go
│ │ ├── ctxhttp/
│ │ │ ├── cancelreq.go
│ │ │ ├── cancelreq_go14.go
│ │ │ └── ctxhttp.go
│ │ ├── go17.go
│ │ └── pre_go17.go
│ └── oauth2/
│ ├── .travis.yml
│ ├── AUTHORS
│ ├── CONTRIBUTING.md
│ ├── CONTRIBUTORS
│ ├── LICENSE
│ ├── README.md
│ ├── client_appengine.go
│ ├── internal/
│ │ ├── oauth2.go
│ │ ├── token.go
│ │ └── transport.go
│ ├── oauth2.go
│ ├── token.go
│ └── transport.go
└── google.golang.org/
└── api/
├── LICENSE
├── drive/
│ └── v3/
│ ├── drive-api.json
│ └── drive-gen.go
├── gensupport/
│ ├── backoff.go
│ ├── buffer.go
│ ├── doc.go
│ ├── json.go
│ ├── media.go
│ ├── params.go
│ ├── resumable.go
│ └── retry.go
└── googleapi/
├── googleapi.go
├── internal/
│ └── uritemplates/
│ ├── LICENSE
│ ├── uritemplates.go
│ └── utils.go
└── types.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/release.yaml
================================================
# .github/workflows/release.yaml
on: release
name: Build Release
jobs:
release-linux-386:
name: release linux/386
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: compile and release
uses: ngs/go-release.action@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GOARCH: "386"
GOOS: linux
EXTRA_FILES: "LICENSE"
release-linux-amd64:
name: release linux/amd64
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: compile and release
uses: ngs/go-release.action@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GOARCH: amd64
GOOS: linux
EXTRA_FILES: "LICENSE"
release-linux-arm:
name: release linux/386
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: compile and release
uses: ngs/go-release.action@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GOARCH: "arm"
GOOS: linux
EXTRA_FILES: "LICENSE"
release-linux-arm64:
name: release linux/amd64
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: compile and release
uses: ngs/go-release.action@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GOARCH: arm64
GOOS: linux
EXTRA_FILES: "LICENSE"
release-darwin-amd64:
name: release darwin/amd64
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: compile and release
uses: ngs/go-release.action@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GOARCH: amd64
GOOS: darwin
EXTRA_FILES: "LICENSE"
release-windows-386:
name: release windows/386
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: compile and release
uses: ngs/go-release.action@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GOARCH: "386"
GOOS: windows
EXTRA_FILES: "LICENSE"
release-windows-amd64:
name: release windows/amd64
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: compile and release
uses: ngs/go-release.action@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GOARCH: amd64
GOOS: windows
EXTRA_FILES: "LICENSE"
================================================
FILE: .gitignore
================================================
# Ignore bin folder and drive binary
_release/bin
# vim files
.*.sw[a-z]
*.un~
Session.vim
.netrwhist
gdrive
gdrive.sh
================================================
FILE: Godeps/Godeps.json
================================================
{
"ImportPath": "github.com/prasmussen/gdrive",
"GoVersion": "go1.6",
"GodepVersion": "v61",
"Deps": [
{
"ImportPath": "github.com/sabhiram/go-git-ignore",
"Rev": "228fcfa2a06e870a3ef238d54c45ea847f492a37"
},
{
"ImportPath": "github.com/soniakeys/graph",
"Comment": "svg-v0-58-gc265d96",
"Rev": "c265d9676750b13b9520ba4ad4f8359fa1aed9fd"
},
{
"ImportPath": "golang.org/x/net/context",
"Rev": "fb93926129b8ec0056f2f458b1f519654814edf0"
},
{
"ImportPath": "golang.org/x/net/context/ctxhttp",
"Rev": "fb93926129b8ec0056f2f458b1f519654814edf0"
},
{
"ImportPath": "golang.org/x/oauth2",
"Rev": "7e9cd5d59563851383f8f81a7fbb01213709387c"
},
{
"ImportPath": "golang.org/x/oauth2/internal",
"Rev": "7e9cd5d59563851383f8f81a7fbb01213709387c"
},
{
"ImportPath": "google.golang.org/api/drive/v3",
"Rev": "9737cc9e103c00d06a8f3993361dec083df3d252"
},
{
"ImportPath": "google.golang.org/api/gensupport",
"Rev": "9737cc9e103c00d06a8f3993361dec083df3d252"
},
{
"ImportPath": "google.golang.org/api/googleapi",
"Rev": "9737cc9e103c00d06a8f3993361dec083df3d252"
},
{
"ImportPath": "google.golang.org/api/googleapi/internal/uritemplates",
"Rev": "9737cc9e103c00d06a8f3993361dec083df3d252"
}
]
}
================================================
FILE: Godeps/Readme
================================================
This directory tree is generated automatically by godep.
Please do not edit.
See https://github.com/tools/godep for more information.
================================================
FILE: LICENSE
================================================
The MIT License
Copyright (c) 2013 Petter Rasmussen
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================
FILE: README.md
================================================
## IMPORTANT
This repository is not maintained anymore. [Gdrive 3](https://github.com/glotlabs/gdrive) is its successor.
```
```
## 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
### With [Homebrew](http://brew.sh) on Mac
```
brew install gdrive
```
### Other
Download `gdrive` from one of the [links in the latest release](https://github.com/prasmussen/gdrive/releases).
The first time gdrive is launched (i.e. run `gdrive about` in your
terminal not just `gdrive`), you will be prompted for a verification code.
The code is obtained by following the printed url and authenticating with the
google account for the drive you want access to. This will create a token file
inside the .gdrive folder in your home directory. Note that anyone with access
to this file will also have access to your google drive.
If you want to manage multiple drives you can use the global `--config` flag
or set the environment variable `GDRIVE_CONFIG_DIR`.
Example: `GDRIVE_CONFIG_DIR="/home/user/.gdrive-secondary" gdrive list`
You will be prompted for a new verification code if the folder does not exist.
## Compile from source
```bash
go get github.com/prasmussen/gdrive
```
The gdrive binary should now be available at `$GOPATH/bin/gdrive`
### Syncing
Gdrive supports basic syncing. It only syncs one way at the time and works
more like rsync than e.g. dropbox. Files that are synced to google drive
are tagged with an appProperty so that the files on drive can be traversed
faster. This means that you can't upload files with `gdrive upload` into
a sync directory as the files would be missing the sync tag, and would be
ignored by the sync commands.
The current implementation is slow and uses a lot of memory if you are
syncing many files. Currently only one file is uploaded at the time,
the speed can be improved in the future by uploading several files concurrently.
To learn more see usage and the examples below.
### Service Account
For server to server communication, where user interaction is not a viable option,
is it possible to use a service account, as described in this [Google document](https://developers.google.com/identity/protocols/OAuth2ServiceAccount).
If you want to use a service account, instead of being interactively prompted for
authentication, you need to use the `--service-account <serviceAccountCredentials>`
global option, where `serviceAccountCredentials` is a file in JSON format obtained
through the Google API Console, and its location is relative to the config dir.
#### .gdriveignore
Placing a .gdriveignore in the root of your sync directory can be used to
skip certain files from being synced. .gdriveignore follows the same
rules as [.gitignore](https://git-scm.com/docs/gitignore), except that gdrive only reads the .gdriveignore file in the root of the sync directory, not ones in any subdirectories.
## Usage
```
gdrive [global] list [options] List files
gdrive [global] download [options] <fileId> Download file or directory
gdrive [global] download query [options] <query> Download all files and directories matching query
gdrive [global] upload [options] <path> Upload file or directory
gdrive [global] upload - [options] <name> Upload file from stdin
gdrive [global] update [options] <fileId> <path> Update file, this creates a new revision of the file
gdrive [global] info [options] <fileId> Show file info
gdrive [global] mkdir [options] <name> Create directory
gdrive [global] share [options] <fileId> Share file or directory
gdrive [global] share list <fileId> List files permissions
gdrive [global] share revoke <fileId> <permissionId> Revoke permission
gdrive [global] delete [options] <fileId> Delete file or directory
gdrive [global] sync list [options] List all syncable directories on drive
gdrive [global] sync content [options] <fileId> List content of syncable directory
gdrive [global] sync download [options] <fileId> <path> Sync drive directory to local directory
gdrive [global] sync upload [options] <path> <fileId> Sync local directory to drive
gdrive [global] changes [options] List file changes
gdrive [global] revision list [options] <fileId> List file revisions
gdrive [global] revision download [options] <fileId> <revId> Download revision
gdrive [global] revision delete <fileId> <revId> Delete file revision
gdrive [global] import [options] <path> Upload and convert file to a google document, see 'about import' for available conversions
gdrive [global] export [options] <fileId> Export a google document
gdrive [global] about [options] Google drive metadata, quota usage
gdrive [global] about import Show supported import formats
gdrive [global] about export Show supported export formats
gdrive version Print application version
gdrive help Print help
gdrive help <command> Print command help
gdrive help <command> <subcommand> Print subcommand help
```
#### List files
```
gdrive [global] list [options]
global:
-c, --config <configDir> Application path, default: /Users/<user>/.gdrive
--refresh-token <refreshToken> Oauth refresh token used to get access token (for advanced users)
--access-token <accessToken> Oauth access token, only recommended for short-lived requests because of short lifetime (for advanced users)
--service-account <accountFile> Oauth service account filename, used for server to server communication without user interaction (file is relative to config dir)
options:
-m, --max <maxFiles> Max files to list, default: 30
-q, --query <query> Default query: "trashed = false and 'me' in owners". See https://developers.google.com/drive/search-parameters
--order <sortOrder> Sort order. See https://godoc.org/google.golang.org/api/drive/v3#FilesListCall.OrderBy
--name-width <nameWidth> Width of name column, default: 40, minimum: 9, use 0 for full width
--absolute Show absolute path to file (will only show path from first parent)
--no-header Dont print the header
--bytes Size in bytes
```
List file in subdirectory
```
./gdrive list --query " 'IdOfTheParentFolder' in parents"
```
#### Download file or directory
```
gdrive [global] download [options] <fileId>
global:
-c, --config <configDir> Application path, default: /Users/<user>/.gdrive
--refresh-token <refreshToken> Oauth refresh token used to get access token (for advanced users)
--access-token <accessToken> Oauth access token, only recommended for short-lived requests because of short lifetime (for advanced users)
--service-account <accountFile> Oauth service account filename, used for server to server communication without user interaction (file is relative to config dir)
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
--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
```
gdrive [global] download query [options] <query>
global:
-c, --config <configDir> Application path, default: /Users/<user>/.gdrive
--refresh-token <refreshToken> Oauth refresh token used to get access token (for advanced users)
--access-token <accessToken> Oauth access token, only recommended for short-lived requests because of short lifetime (for advanced users)
--service-account <accountFile> Oauth service account filename, used for server to server communication without user interaction (file is relative to config dir)
options:
-f, --force Overwrite existing file
-r, --recursive Download directories recursively, documents will be skipped
--path <path> Download path
--no-progress Hide progress
```
#### Upload file or directory
```
gdrive [global] upload [options] <path>
global:
-c, --config <configDir> Application path, default: /Users/<user>/.gdrive
--refresh-token <refreshToken> Oauth refresh token used to get access token (for advanced users)
--access-token <accessToken> Oauth access token, only recommended for short-lived requests because of short lifetime (for advanced users)
--service-account <accountFile> Oauth service account filename, used for server to server communication without user interaction (file is relative to config dir)
options:
-r, --recursive Upload directory recursively
-p, --parent <parent> Parent id, used to upload file to a specific directory, can be specified multiple times to give many parents
--name <name> Filename
--description <description> File description
--no-progress Hide progress
--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
```
#### Upload file from stdin
```
gdrive [global] upload - [options] <name>
global:
-c, --config <configDir> Application path, default: /Users/<user>/.gdrive
--refresh-token <refreshToken> Oauth refresh token used to get access token (for advanced users)
--access-token <accessToken> Oauth access token, only recommended for short-lived requests because of short lifetime (for advanced users)
--service-account <accountFile> Oauth service account filename, used for server to server communication without user interaction (file is relative to config dir)
options:
-p, --parent <parent> Parent id, used to upload file to a specific directory, can be specified multiple times to give many parents
--chunksize <chunksize> Set chunk size in bytes, default: 8388608
--description <description> File description
--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
```
#### Update file, this creates a new revision of the file
```
gdrive [global] update [options] <fileId> <path>
global:
-c, --config <configDir> Application path, default: /Users/<user>/.gdrive
--refresh-token <refreshToken> Oauth refresh token used to get access token (for advanced users)
--access-token <accessToken> Oauth access token, only recommended for short-lived requests because of short lifetime (for advanced users)
--service-account <accountFile> Oauth service account filename, used for server to server communication without user interaction (file is relative to config dir)
options:
-p, --parent <parent> Parent id, used to upload file to a specific directory, can be specified multiple times to give many parents
--name <name> Filename
--description <description> File description
--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
```
#### Show file info
```
gdrive [global] info [options] <fileId>
global:
-c, --config <configDir> Application path, default: /Users/<user>/.gdrive
--refresh-token <refreshToken> Oauth refresh token used to get access token (for advanced users)
--access-token <accessToken> Oauth access token, only recommended for short-lived requests because of short lifetime (for advanced users)
--service-account <accountFile> Oauth service account filename, used for server to server communication without user interaction (file is relative to config dir)
options:
--bytes Show size in bytes
```
#### Create directory
```
gdrive [global] mkdir [options] <name>
global:
-c, --config <configDir> Application path, default: /Users/<user>/.gdrive
--refresh-token <refreshToken> Oauth refresh token used to get access token (for advanced users)
--access-token <accessToken> Oauth access token, only recommended for short-lived requests because of short lifetime (for advanced users)
--service-account <accountFile> Oauth service account filename, used for server to server communication without user interaction (file is relative to config dir)
options:
-p, --parent <parent> Parent id of created directory, can be specified multiple times to give many parents
--description <description> Directory description
```
#### Share file or directory
```
gdrive [global] share [options] <fileId>
global:
-c, --config <configDir> Application path, default: /Users/<user>/.gdrive
--refresh-token <refreshToken> Oauth refresh token used to get access token (for advanced users)
--access-token <accessToken> Oauth access token, only recommended for short-lived requests because of short lifetime (for advanced users)
--service-account <accountFile> Oauth service account filename, used for server to server communication without user interaction (file is relative to config dir)
options:
--role <role> Share role: owner/writer/commenter/reader, default: reader
--type <type> Share type: user/group/domain/anyone, default: anyone
--email <email> The email address of the user or group to share the file with. Requires 'user' or 'group' as type
--discoverable Make file discoverable by search engines
--revoke Delete all sharing permissions (owner roles will be skipped)
```
#### List files permissions
```
gdrive [global] share list <fileId>
global:
-c, --config <configDir> Application path, default: /Users/<user>/.gdrive
--refresh-token <refreshToken> Oauth refresh token used to get access token (for advanced users)
--access-token <accessToken> Oauth access token, only recommended for short-lived requests because of short lifetime (for advanced users)
--service-account <accountFile> Oauth service account filename, used for server to server communication without user interaction (file is relative to config dir)
```
#### Revoke permission
```
gdrive [global] share revoke <fileId> <permissionId>
global:
-c, --config <configDir> Application path, default: /Users/<user>/.gdrive
--refresh-token <refreshToken> Oauth refresh token used to get access token (for advanced users)
--access-token <accessToken> Oauth access token, only recommended for short-lived requests because of short lifetime (for advanced users)
--service-account <accountFile> Oauth service account filename, used for server to server communication without user interaction (file is relative to config dir)
```
#### Delete file or directory
```
gdrive [global] delete [options] <fileId>
global:
-c, --config <configDir> Application path, default: /Users/<user>/.gdrive
--refresh-token <refreshToken> Oauth refresh token used to get access token (for advanced users)
--access-token <accessToken> Oauth access token, only recommended for short-lived requests because of short lifetime (for advanced users)
--service-account <accountFile> Oauth service account filename, used for server to server communication without user interaction (file is relative to config dir)
options:
-r, --recursive Delete directory and all it's content
```
#### List all syncable directories on drive
```
gdrive [global] sync list [options]
global:
-c, --config <configDir> Application path, default: /Users/<user>/.gdrive
--refresh-token <refreshToken> Oauth refresh token used to get access token (for advanced users)
--access-token <accessToken> Oauth access token, only recommended for short-lived requests because of short lifetime (for advanced users)
--service-account <accountFile> Oauth service account filename, used for server to server communication without user interaction (file is relative to config dir)
options:
--no-header Dont print the header
```
#### List content of syncable directory
```
gdrive [global] sync content [options] <fileId>
global:
-c, --config <configDir> Application path, default: /Users/<user>/.gdrive
--refresh-token <refreshToken> Oauth refresh token used to get access token (for advanced users)
--access-token <accessToken> Oauth access token, only recommended for short-lived requests because of short lifetime (for advanced users)
--service-account <accountFile> Oauth service account filename, used for server to server communication without user interaction (file is relative to config dir)
options:
--order <sortOrder> Sort order. See https://godoc.org/google.golang.org/api/drive/v3#FilesListCall.OrderBy
--path-width <pathWidth> Width of path column, default: 60, minimum: 9, use 0 for full width
--no-header Dont print the header
--bytes Size in bytes
```
#### Sync drive directory to local directory
```
gdrive [global] sync download [options] <fileId> <path>
global:
-c, --config <configDir> Application path, default: /Users/<user>/.gdrive
--refresh-token <refreshToken> Oauth refresh token used to get access token (for advanced users)
--access-token <accessToken> Oauth access token, only recommended for short-lived requests because of short lifetime (for advanced users)
--service-account <accountFile> Oauth service account filename, used for server to server communication without user interaction (file is relative to config dir)
options:
--keep-remote Keep remote file when a conflict is encountered
--keep-local Keep local file when a conflict is encountered
--keep-largest Keep largest file when a conflict is encountered
--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
```
gdrive [global] sync upload [options] <path> <fileId>
global:
-c, --config <configDir> Application path, default: /Users/<user>/.gdrive
--refresh-token <refreshToken> Oauth refresh token used to get access token (for advanced users)
--access-token <accessToken> Oauth access token, only recommended for short-lived requests because of short lifetime (for advanced users)
--service-account <accountFile> Oauth service account filename, used for server to server communication without user interaction (file is relative to config dir)
options:
--keep-remote Keep remote file when a conflict is encountered
--keep-local Keep local file when a conflict is encountered
--keep-largest Keep largest file when a conflict is encountered
--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
```
#### List file changes
```
gdrive [global] changes [options]
global:
-c, --config <configDir> Application path, default: /Users/<user>/.gdrive
--refresh-token <refreshToken> Oauth refresh token used to get access token (for advanced users)
--access-token <accessToken> Oauth access token, only recommended for short-lived requests because of short lifetime (for advanced users)
--service-account <accountFile> Oauth service account filename, used for server to server communication without user interaction (file is relative to config dir)
options:
-m, --max <maxChanges> Max changes to list, default: 100
--since <pageToken> Page token to start listing changes from
--now Get latest page token
--name-width <nameWidth> Width of name column, default: 40, minimum: 9, use 0 for full width
--no-header Dont print the header
```
#### List file revisions
```
gdrive [global] revision list [options] <fileId>
global:
-c, --config <configDir> Application path, default: /Users/<user>/.gdrive
--refresh-token <refreshToken> Oauth refresh token used to get access token (for advanced users)
--access-token <accessToken> Oauth access token, only recommended for short-lived requests because of short lifetime (for advanced users)
--service-account <accountFile> Oauth service account filename, used for server to server communication without user interaction (file is relative to config dir)
options:
--name-width <nameWidth> Width of name column, default: 40, minimum: 9, use 0 for full width
--no-header Dont print the header
--bytes Size in bytes
```
#### Download revision
```
gdrive [global] revision download [options] <fileId> <revId>
global:
-c, --config <configDir> Application path, default: /Users/<user>/.gdrive
--refresh-token <refreshToken> Oauth refresh token used to get access token (for advanced users)
--access-token <accessToken> Oauth access token, only recommended for short-lived requests because of short lifetime (for advanced users)
--service-account <accountFile> Oauth service account filename, used for server to server communication without user interaction (file is relative to config dir)
options:
-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
```
gdrive [global] revision delete <fileId> <revId>
global:
-c, --config <configDir> Application path, default: /Users/<user>/.gdrive
--refresh-token <refreshToken> Oauth refresh token used to get access token (for advanced users)
--access-token <accessToken> Oauth access token, only recommended for short-lived requests because of short lifetime (for advanced users)
--service-account <accountFile> Oauth service account filename, used for server to server communication without user interaction (file is relative to config dir)
```
#### Upload and convert file to a google document, see 'about import' for available conversions
```
gdrive [global] import [options] <path>
global:
-c, --config <configDir> Application path, default: /Users/<user>/.gdrive
--refresh-token <refreshToken> Oauth refresh token used to get access token (for advanced users)
--access-token <accessToken> Oauth access token, only recommended for short-lived requests because of short lifetime (for advanced users)
--service-account <accountFile> Oauth service account filename, used for server to server communication without user interaction (file is relative to config dir)
options:
-p, --parent <parent> Parent id, used to upload file to a specific directory, can be specified multiple times to give many parents
--no-progress Hide progress
```
#### Export a google document
```
gdrive [global] export [options] <fileId>
global:
-c, --config <configDir> Application path, default: /Users/<user>/.gdrive
--refresh-token <refreshToken> Oauth refresh token used to get access token (for advanced users)
--access-token <accessToken> Oauth access token, only recommended for short-lived requests because of short lifetime (for advanced users)
--service-account <accountFile> Oauth service account filename, used for server to server communication without user interaction (file is relative to config dir)
options:
-f, --force Overwrite existing file
--mime <mime> Mime type of exported file
--print-mimes Print available mime types for given file
```
#### Google drive metadata, quota usage
```
gdrive [global] about [options]
global:
-c, --config <configDir> Application path, default: /Users/<user>/.gdrive
--refresh-token <refreshToken> Oauth refresh token used to get access token (for advanced users)
--access-token <accessToken> Oauth access token, only recommended for short-lived requests because of short lifetime (for advanced users)
--service-account <accountFile> Oauth service account filename, used for server to server communication without user interaction (file is relative to config dir)
options:
--bytes Show size in bytes
```
#### Show supported import formats
```
gdrive [global] about import
global:
-c, --config <configDir> Application path, default: /Users/<user>/.gdrive
--refresh-token <refreshToken> Oauth refresh token used to get access token (for advanced users)
--access-token <accessToken> Oauth access token, only recommended for short-lived requests because of short lifetime (for advanced users)
--service-account <accountFile> Oauth service account filename, used for server to server communication without user interaction (file is relative to config dir)
```
#### Show supported export formats
```
gdrive [global] about export
global:
-c, --config <configDir> Application path, default: /Users/<user>/.gdrive
--refresh-token <refreshToken> Oauth refresh token used to get access token (for advanced users)
--access-token <accessToken> Oauth access token, only recommended for short-lived requests because of short lifetime (for advanced users)
--service-account <accountFile> Oauth service account filename, used for server to server communication without user interaction (file is relative to config dir)
```
## Examples
#### List files
```
$ gdrive list
Id Name Type Size Created
0B3X9GlR6EmbnZ3gyeGw4d3ozbUk drive-windows-x64.exe bin 6.6 MB 2015-07-18 16:43:58
0B3X9GlR6EmbnTXlSc1FqV1dvSTQ drive-windows-386.exe bin 5.2 MB 2015-07-18 16:43:53
0B3X9GlR6EmbnVjIzMDRqck1aekE drive-osx-x64 bin 6.5 MB 2015-07-18 16:43:50
0B3X9GlR6EmbnbEpXdlhza25zT1U drive-osx-386 bin 5.2 MB 2015-07-18 16:43:41
0B3X9GlR6Embnb095MGxEYmJhY2c drive-linux-x64 bin 6.5 MB 2015-07-18 16:43:38
```
#### List largest files
```
$ gdrive list --query "name contains 'gdrive'" --order "quotaBytesUsed desc" -m 3
Id Name Type Size Created
0B3X9GlR6EmbnZXpDRG1xblM2LTg gdrive-linux-mips64 bin 8.5 MB 2016-02-22 21:07:04
0B3X9GlR6EmbnNW5CTV8xdFkxTjg gdrive-linux-mips64le bin 8.5 MB 2016-02-22 21:07:07
0B3X9GlR6EmbnZ1NGS25FdEVlWEk gdrive-osx-x64 bin 8.3 MB 2016-02-21 20:22:13
```
#### Upload file
```
$ gdrive upload gdrive-osx-x64
Uploading gdrive-osx-x64
Uploaded 0B3X9GlR6EmbnZ1NGS25FdEVlWEk at 3.8 MB/s, total 8.3 MB
```
#### Make directory
```
$ gdrive mkdir gdrive-bin
Directory 0B3X9GlR6EmbnY1RLVTk5VUtOVkk created
```
#### Upload file to directory
```
$ gdrive upload --parent 0B3X9GlR6EmbnY1RLVTk5VUtOVkk gdrive-osx-x64
Uploading gdrive-osx-x64
Uploaded 0B3X9GlR6EmbnNTk0SkV0bm5Hd0E at 2.5 MB/s, total 8.3 MB
```
#### Download file
```
$ gdrive download 0B3X9GlR6EmbnZ1NGS25FdEVlWEk
Downloading gdrive-osx-x64 -> gdrive-osx-x64
Downloaded 0B3X9GlR6EmbnZ1NGS25FdEVlWEk at 8.3 MB/s, total 8.3 MB
```
#### Share a file
```
$ gdrive share 0B3X9GlR6EmbnNTk0SkV0bm5Hd0E
Granted reader permission to anyone
```
#### Pipe content directly to google drive
```
$ echo "Hello World" | gdrive upload - hello.txt
Uploading hello.txt
Uploaded 0B3X9GlR6EmbnaXVrOUpIcWlUS0E at 8.0 B/s, total 12.0 B
```
#### Print file to stdout
```
$ gdrive download --stdout 0B3X9GlR6EmbnaXVrOUpIcWlUS0E
Hello World
```
#### Get file info
```
$ gdrive info 0B3X9GlR6EmbnNTk0SkV0bm5Hd0E
Id: 0B3X9GlR6EmbnNTk0SkV0bm5Hd0E
Name: gdrive-osx-x64
Path: gdrive-bin/gdrive-osx-x64
Mime: application/octet-stream
Size: 8.3 MB
Created: 2016-02-21 20:47:04
Modified: 2016-02-21 20:47:04
Md5sum: b607f29231a3b2d16098c4212516470f
Shared: True
Parents: 0B3X9GlR6EmbnY1RLVTk5VUtOVkk
ViewUrl: https://drive.google.com/file/d/0B3X9GlR6EmbnNTk0SkV0bm5Hd0E/view?usp=drivesdk
DownloadUrl: https://docs.google.com/uc?id=0B3X9GlR6EmbnNTk0SkV0bm5Hd0E&export=download
```
#### Update file (create new revision)
```
$ gdrive update 0B3X9GlR6EmbnNTk0SkV0bm5Hd0E gdrive-osx-x64
Uploading gdrive-osx-x64
Updated 0B3X9GlR6EmbnNTk0SkV0bm5Hd0E at 2.0 MB/s, total 8.3 MB
```
#### List file revisions
```
$ gdrive revision list 0B3X9GlR6EmbnNTk0SkV0bm5Hd0E
Id Name Size Modified KeepForever
0B3X9GlR6EmbnOFlHSTZQNWJWMGN2ckZucC9VaEUwczV1cUNrPQ gdrive-osx-x64 8.3 MB 2016-02-21 20:47:04 False
0B3X9GlR6EmbndVEwMlZCUldGWUlPb2lTS25rOFo1L2t6c2ZVPQ gdrive-osx-x64 8.3 MB 2016-02-21 21:12:09 False
```
#### Download revision
```
$ gdrive revision download 0B3X9GlR6EmbnNTk0SkV0bm5Hd0E 0B3X9GlR6EmbnOFlHSTZQNWJWMGN2ckZucC9VaEUwczV1cUNrPQ
Downloading gdrive-osx-x64 -> gdrive-osx-x64
Download complete, rate: 8.3 MB/s, total size: 8.3 MB
```
#### Export google doc as docx
```
$ gdrive export --mime application/vnd.openxmlformats-officedocument.wordprocessingml.document 1Kt5A8X7X2RQrEi5t6Y9W1LayRc4hyrFiG63y2dIJEvk
Exported 'foo.docx' with mime type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
```
#### Import csv as google spreadsheet
```
$ gdrive import foo.csv
Imported 1mTl3DjIvap4tpTX_oMkDcbDT8ShtiGJRlozTfkXpeko with mime type: 'application/vnd.google-apps.spreadsheet'
```
#### Syncing directory to drive
```
# Create directory on drive
$ gdrive mkdir drive-bin
Directory 0B3X9GlR6EmbnOEd6cEh6bU9XZWM created
# Sync to drive
$ gdrive sync upload _release/bin 0B3X9GlR6EmbnOEd6cEh6bU9XZWM
Starting sync...
Collecting local and remote file information...
Found 32 local files and 0 remote files
6 remote directories are missing
[0001/0006] Creating directory drive-bin/bsd
[0002/0006] Creating directory drive-bin/linux
[0003/0006] Creating directory drive-bin/osx
[0004/0006] Creating directory drive-bin/plan9
[0005/0006] Creating directory drive-bin/solaris
[0006/0006] Creating directory drive-bin/windows
26 remote files are missing
[0001/0026] Uploading bsd/gdrive-dragonfly-x64 -> drive-bin/bsd/gdrive-dragonfly-x64
[0002/0026] Uploading bsd/gdrive-freebsd-386 -> drive-bin/bsd/gdrive-freebsd-386
[0003/0026] Uploading bsd/gdrive-freebsd-arm -> drive-bin/bsd/gdrive-freebsd-arm
[0004/0026] Uploading bsd/gdrive-freebsd-x64 -> drive-bin/bsd/gdrive-freebsd-x64
[0005/0026] Uploading bsd/gdrive-netbsd-386 -> drive-bin/bsd/gdrive-netbsd-386
[0006/0026] Uploading bsd/gdrive-netbsd-arm -> drive-bin/bsd/gdrive-netbsd-arm
[0007/0026] Uploading bsd/gdrive-netbsd-x64 -> drive-bin/bsd/gdrive-netbsd-x64
[0008/0026] Uploading bsd/gdrive-openbsd-386 -> drive-bin/bsd/gdrive-openbsd-386
[0009/0026] Uploading bsd/gdrive-openbsd-arm -> drive-bin/bsd/gdrive-openbsd-arm
[0010/0026] Uploading bsd/gdrive-openbsd-x64 -> drive-bin/bsd/gdrive-openbsd-x64
[0011/0026] Uploading linux/gdrive-linux-386 -> drive-bin/linux/gdrive-linux-386
[0012/0026] Uploading linux/gdrive-linux-arm -> drive-bin/linux/gdrive-linux-arm
[0013/0026] Uploading linux/gdrive-linux-arm64 -> drive-bin/linux/gdrive-linux-arm64
[0014/0026] Uploading linux/gdrive-linux-mips64 -> drive-bin/linux/gdrive-linux-mips64
[0015/0026] Uploading linux/gdrive-linux-mips64le -> drive-bin/linux/gdrive-linux-mips64le
[0016/0026] Uploading linux/gdrive-linux-ppc64 -> drive-bin/linux/gdrive-linux-ppc64
[0017/0026] Uploading linux/gdrive-linux-ppc64le -> drive-bin/linux/gdrive-linux-ppc64le
[0018/0026] Uploading linux/gdrive-linux-x64 -> drive-bin/linux/gdrive-linux-x64
[0019/0026] Uploading osx/gdrive-osx-386 -> drive-bin/osx/gdrive-osx-386
[0020/0026] Uploading osx/gdrive-osx-arm -> drive-bin/osx/gdrive-osx-arm
[0021/0026] Uploading osx/gdrive-osx-x64 -> drive-bin/osx/gdrive-osx-x64
[0022/0026] Uploading plan9/gdrive-plan9-386 -> drive-bin/plan9/gdrive-plan9-386
[0023/0026] Uploading plan9/gdrive-plan9-x64 -> drive-bin/plan9/gdrive-plan9-x64
[0024/0026] Uploading solaris/gdrive-solaris-x64 -> drive-bin/solaris/gdrive-solaris-x64
[0025/0026] Uploading windows/gdrive-windows-386.exe -> drive-bin/windows/gdrive-windows-386.exe
[0026/0026] Uploading windows/gdrive-windows-x64.exe -> drive-bin/windows/gdrive-windows-x64.exe
Sync finished in 1m18.891946279s
# Add new local file
$ echo "google drive binaries" > _release/bin/readme.txt
# Sync again
$ gdrive sync upload _release/bin 0B3X9GlR6EmbnOEd6cEh6bU9XZWM
Starting sync...
Collecting local and remote file information...
Found 33 local files and 32 remote files
1 remote files are missing
[0001/0001] Uploading readme.txt -> drive-bin/readme.txt
Sync finished in 2.201339535s
# Modify local file
$ echo "for all platforms" >> _release/bin/readme.txt
# Sync again
$ gdrive sync upload _release/bin 0B3X9GlR6EmbnOEd6cEh6bU9XZWM
Starting sync...
Collecting local and remote file information...
Found 33 local files and 33 remote files
1 local files has changed
[0001/0001] Updating readme.txt -> drive-bin/readme.txt
Sync finished in 1.890244258s
```
#### List content of sync directory
```
$ gdrive sync content 0B3X9GlR6EmbnOEd6cEh6bU9XZWM
Id Path Type Size Modified
0B3X9GlR6EmbnMldxMFV1UGVMTlE bsd dir 2016-02-21 22:54:00
0B3X9GlR6EmbnM05sQ3hVUnJnOXc bsd/gdrive-dragonfly-x64 bin 7.8 MB 2016-02-21 22:54:14
0B3X9GlR6EmbnVy1KXzA4dlU5RVE bsd/gdrive-freebsd-386 bin 6.1 MB 2016-02-21 22:54:18
0B3X9GlR6Embnb29QQkFtSlRiZnc bsd/gdrive-freebsd-arm bin 6.1 MB 2016-02-21 22:54:20
0B3X9GlR6EmbnMkFQYVpSaHhHTXM bsd/gdrive-freebsd-x64 bin 7.8 MB 2016-02-21 22:54:23
0B3X9GlR6EmbnVmJRMl9hUDloVU0 bsd/gdrive-netbsd-386 bin 6.1 MB 2016-02-21 22:54:25
0B3X9GlR6EmbnLVlTZWpxOEF4Q2s bsd/gdrive-netbsd-arm bin 6.1 MB 2016-02-21 22:54:28
0B3X9GlR6EmbnOENUZmh3anJmNG8 bsd/gdrive-netbsd-x64 bin 7.8 MB 2016-02-21 22:54:30
0B3X9GlR6EmbnWTRoQ2ZVQXRfQlU bsd/gdrive-openbsd-386 bin 6.1 MB 2016-02-21 22:54:32
0B3X9GlR6EmbncEtlN3ZuQ0VUWms bsd/gdrive-openbsd-arm bin 6.1 MB 2016-02-21 22:54:35
0B3X9GlR6EmbnMlFLY1ptNEFyZWc bsd/gdrive-openbsd-x64 bin 7.8 MB 2016-02-21 22:54:38
0B3X9GlR6EmbncGtSajQyNzloVEE linux dir 2016-02-21 22:54:01
0B3X9GlR6EmbnMWVudkJmb1NZdmM linux/gdrive-linux-386 bin 6.1 MB 2016-02-21 22:54:40
0B3X9GlR6Embnbnpla1R2VHV5T2M linux/gdrive-linux-arm bin 6.1 MB 2016-02-21 22:54:42
0B3X9GlR6EmbnM0s2cU1YWkNJSjA linux/gdrive-linux-arm64 bin 7.7 MB 2016-02-21 22:54:45
0B3X9GlR6EmbnNU9NNi1TdDc4S2c linux/gdrive-linux-mips64 bin 8.5 MB 2016-02-21 22:54:47
0B3X9GlR6EmbnSmdQNjRKZ2dWV1U linux/gdrive-linux-mips64le bin 8.5 MB 2016-02-21 22:54:50
0B3X9GlR6EmbnS0g0OVgxMHY5Z3c linux/gdrive-linux-ppc64 bin 7.8 MB 2016-02-21 22:54:52
0B3X9GlR6EmbneVp6ZXRpR3FhWlU linux/gdrive-linux-ppc64le bin 7.8 MB 2016-02-21 22:54:54
0B3X9GlR6EmbnczdJT195dFVxdU0 linux/gdrive-linux-x64 bin 7.8 MB 2016-02-21 22:54:57
0B3X9GlR6EmbnTXZXeDRnSDdVS1E osx dir 2016-02-21 22:54:02
0B3X9GlR6EmbnWnRheXJNR0pUMU0 osx/gdrive-osx-386 bin 6.6 MB 2016-02-21 22:54:59
0B3X9GlR6EmbnRzNqMWFXdDR1Rms osx/gdrive-osx-arm bin 6.6 MB 2016-02-21 22:55:01
0B3X9GlR6EmbnaDlVWTZDd0JIeEU osx/gdrive-osx-x64 bin 8.3 MB 2016-02-21 22:55:04
0B3X9GlR6EmbnWW84UFBvbHlURXM plan9 dir 2016-02-21 22:54:02
0B3X9GlR6EmbnTmc0a2RNdDZDRUU plan9/gdrive-plan9-386 bin 5.8 MB 2016-02-21 22:55:07
0B3X9GlR6EmbnT1pYZ2p4Sk9FVFk plan9/gdrive-plan9-x64 bin 7.4 MB 2016-02-21 22:55:10
0B3X9GlR6EmbnbnZnXzlYVHoxdk0 readme.txt bin 40.0 B 2016-02-21 22:59:56
0B3X9GlR6EmbnSWF1QUlta3RnaGc solaris dir 2016-02-21 22:54:03
0B3X9GlR6EmbnaWFOV0YxSGs5Znc solaris/gdrive-solaris-x64 bin 7.7 MB 2016-02-21 22:55:13
0B3X9GlR6EmbnNE5ySkEzbWQ4Qms windows dir 2016-02-21 22:54:03
0B3X9GlR6EmbnX1RIT2w1TWZYWFU windows/gdrive-windows-386.exe bin 6.1 MB 2016-02-21 22:55:15
0B3X9GlR6EmbndmVMU05POGRPS3c windows/gdrive-windows-x64.exe bin 7.8 MB 2016-02-21 22:55:18
```
================================================
FILE: _release/build-all.sh
================================================
#!/bin/bash
APP_NAME="gdrive"
PLATFORMS="darwin/386 darwin/amd64 darwin/arm darwin/arm64 dragonfly/amd64 freebsd/386 freebsd/amd64 freebsd/arm linux/386 linux/amd64 linux/arm linux/arm64 linux/ppc64 linux/ppc64le linux/mips64 linux/mips64le linux/rpi netbsd/386 netbsd/amd64 netbsd/arm openbsd/386 openbsd/amd64 openbsd/arm plan9/386 plan9/amd64 solaris/amd64 windows/386 windows/amd64"
BIN_PATH="_release/bin"
# Initialize bin dir
mkdir -p $BIN_PATH
rm $BIN_PATH/* 2> /dev/null
# Build binary for each platform
for PLATFORM in $PLATFORMS; do
GOOS=${PLATFORM%/*}
GOARCH=${PLATFORM#*/}
BIN_NAME="${APP_NAME}-${GOOS/darwin/osx}-${GOARCH/amd64/x64}"
if [ $GOOS == "windows" ]; then
BIN_NAME="${BIN_NAME}.exe"
fi
# Raspberrypi seems to need arm5 binaries
if [ $GOARCH == "rpi" ]; then
export GOARM=5
GOARCH="arm"
else
unset GOARM
fi
export GOOS=$GOOS
export GOARCH=$GOARCH
echo "Building $BIN_NAME"
go build -ldflags '-w -s' -o ${BIN_PATH}/${BIN_NAME}
done
echo "All done"
================================================
FILE: _release/print_usage_markdown.sh
================================================
#!/bin/bash
echo '## Usage'
echo '```'
gdrive help | tail -n+3
echo '```'
IFS=$'\n'
help=$(gdrive help | grep global | sed -E 's/ \[[^]]+\]//g' | sed -E 's/ <[^>]+>//g' | sed -E 's/ {2,}.+//' | sed -E 's/^gdrive //')
for args in $help; do
cmd="gdrive help $args"
echo
eval $cmd | sed -e '1s/^/#### /' | sed -e $'1s/$/\\\n```/' | sed -e 's/pii/<user>/'
echo '```'
done
================================================
FILE: _release/upload.sh
================================================
#!/usr/local/bin/bash
# Grab application version
VERSION=$(_release/bin/gdrive-osx-x64 version | awk 'NR==1 {print $2}')
declare -a filenames
filenames=(
"gdrive-osx-x64"
"gdrive-osx-386"
"gdrive-osx-arm"
"gdrive-linux-x64"
"gdrive-linux-386"
"gdrive-linux-rpi"
"gdrive-linux-arm64"
"gdrive-linux-arm"
"gdrive-linux-mips64"
"gdrive-linux-mips64le"
"gdrive-linux-ppc64"
"gdrive-linux-ppc64le"
"gdrive-windows-386.exe"
"gdrive-windows-x64.exe"
"gdrive-dragonfly-x64"
"gdrive-freebsd-x64"
"gdrive-freebsd-386"
"gdrive-freebsd-arm"
"gdrive-netbsd-x64"
"gdrive-netbsd-386"
"gdrive-netbsd-arm"
"gdrive-openbsd-x64"
"gdrive-openbsd-386"
"gdrive-openbsd-arm"
"gdrive-solaris-x64"
"gdrive-plan9-x64"
"gdrive-plan9-386"
)
# Note: associative array requires bash 4+
declare -A descriptions
descriptions=(
["gdrive-osx-x64"]="OS X 64-bit"
["gdrive-osx-386"]="OS X 32-bit"
["gdrive-osx-arm"]="OS X arm"
["gdrive-linux-x64"]="Linux 64-bit"
["gdrive-linux-386"]="Linux 32-bit"
["gdrive-linux-rpi"]="Linux Raspberry Pi"
["gdrive-linux-arm64"]="Linux arm 64-bit"
["gdrive-linux-arm"]="Linux arm 32-bit"
["gdrive-linux-mips64"]="Linux mips 64-bit"
["gdrive-linux-mips64le"]="Linux mips 64-bit le"
["gdrive-linux-ppc64"]="Linux PPC 64-bit"
["gdrive-linux-ppc64le"]="Linux PPC 64-bit le"
["gdrive-windows-386.exe"]="Window 32-bit"
["gdrive-windows-x64.exe"]="Windows 64-bit"
["gdrive-dragonfly-x64"]="DragonFly BSD 64-bit"
["gdrive-freebsd-x64"]="FreeBSD 64-bit"
["gdrive-freebsd-386"]="FreeBSD 32-bit"
["gdrive-freebsd-arm"]="FreeBSD arm"
["gdrive-netbsd-x64"]="NetBSD 64-bit"
["gdrive-netbsd-386"]="NetBSD 32-bit"
["gdrive-netbsd-arm"]="NetBSD arm"
["gdrive-openbsd-x64"]="OpenBSD 64-bit"
["gdrive-openbsd-386"]="OpenBSD 32-bit"
["gdrive-openbsd-arm"]="OpenBSD arm"
["gdrive-solaris-x64"]="Solaris 64-bit"
["gdrive-plan9-x64"]="Plan9 64-bit"
["gdrive-plan9-386"]="Plan9 32-bit"
)
# Markdown helpers
HEADER='### Downloads
| Filename | Version | Description | Shasum |
|:-----------------------|:--------|:-------------------|:-----------------------------------------|'
ROW_TEMPLATE="| [{{name}}]({{url}}) | $VERSION | {{description}} | {{sha}} |"
# Print header
echo "$HEADER"
for name in ${filenames[@]}; do
bin_path="_release/bin/$name"
# Upload file
url=$(gdrive upload --share $bin_path | awk '/https/ {print $7}')
# Shasum
sha="$(shasum -b $bin_path | awk '{print $1}')"
# Filename
name="$(basename $bin_path)"
# Render markdown row
row=${ROW_TEMPLATE//"{{name}}"/$name}
row=${row//"{{url}}"/$url}
row=${row//"{{description}}"/${descriptions[$name]}}
row=${row//"{{sha}}"/$sha}
# Print row
echo "$row"
done
================================================
FILE: auth/file_source.go
================================================
package auth
import (
"encoding/json"
"golang.org/x/oauth2"
"io/ioutil"
"os"
)
func FileSource(path string, token *oauth2.Token, conf *oauth2.Config) oauth2.TokenSource {
return &fileSource{
tokenPath: path,
tokenSource: conf.TokenSource(oauth2.NoContext, token),
}
}
type fileSource struct {
tokenPath string
tokenSource oauth2.TokenSource
}
func (self *fileSource) Token() (*oauth2.Token, error) {
token, err := self.tokenSource.Token()
if err != nil {
return token, err
}
// Save token to file
SaveToken(self.tokenPath, token)
return token, nil
}
func ReadFile(path string) ([]byte, bool, error) {
if !fileExists(path) {
return nil, false, nil
}
content, err := ioutil.ReadFile(path)
if err != nil {
return nil, true, err
}
return content, true, nil
}
func ReadToken(path string) (*oauth2.Token, bool, error) {
content, exists, err := ReadFile(path)
if err != nil || exists == false {
return nil, exists, err
}
token := &oauth2.Token{}
return token, exists, json.Unmarshal(content, token)
}
func SaveToken(path string, token *oauth2.Token) error {
data, err := json.MarshalIndent(token, "", " ")
if err != nil {
return err
}
if err = mkdir(path); err != nil {
return err
}
// Write to temp file first
tmpFile := path + ".tmp"
err = ioutil.WriteFile(tmpFile, data, 0600)
if err != nil {
os.Remove(tmpFile)
return err
}
// Move file to correct path
return os.Rename(tmpFile, path)
}
================================================
FILE: auth/oauth.go
================================================
package auth
import (
"fmt"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"net/http"
"time"
)
type authCodeFn func(string) func() string
func NewFileSourceClient(clientId, clientSecret, tokenFile string, authFn authCodeFn) (*http.Client, error) {
conf := getConfig(clientId, clientSecret)
// Read cached token
token, exists, err := ReadToken(tokenFile)
if err != nil {
return nil, fmt.Errorf("Failed to read token: %s", err)
}
// Require auth code if token file does not exist
// or refresh token is missing
if !exists || token.RefreshToken == "" {
authUrl := conf.AuthCodeURL("state", oauth2.AccessTypeOffline)
authCode := authFn(authUrl)()
token, err = conf.Exchange(oauth2.NoContext, authCode)
if err != nil {
return nil, fmt.Errorf("Failed to exchange auth code for token: %s", err)
}
}
return oauth2.NewClient(
oauth2.NoContext,
FileSource(tokenFile, token, conf),
), nil
}
func NewRefreshTokenClient(clientId, clientSecret, refreshToken string) *http.Client {
conf := getConfig(clientId, clientSecret)
token := &oauth2.Token{
TokenType: "Bearer",
RefreshToken: refreshToken,
Expiry: time.Now(),
}
return oauth2.NewClient(
oauth2.NoContext,
conf.TokenSource(oauth2.NoContext, token),
)
}
func NewAccessTokenClient(clientId, clientSecret, accessToken string) *http.Client {
conf := getConfig(clientId, clientSecret)
token := &oauth2.Token{
TokenType: "Bearer",
AccessToken: accessToken,
}
return oauth2.NewClient(
oauth2.NoContext,
conf.TokenSource(oauth2.NoContext, token),
)
}
func NewServiceAccountClient(serviceAccountFile string) (*http.Client, error) {
content, exists, err := ReadFile(serviceAccountFile)
if(!exists) {
return nil, fmt.Errorf("Service account filename %q not found", serviceAccountFile)
}
if(err != nil) {
return nil, err
}
conf, err := google.JWTConfigFromJSON(content, "https://www.googleapis.com/auth/drive")
if(err != nil) {
return nil, err
}
return conf.Client(oauth2.NoContext), nil
}
func getConfig(clientId, clientSecret string) *oauth2.Config {
return &oauth2.Config{
ClientID: clientId,
ClientSecret: clientSecret,
Scopes: []string{"https://www.googleapis.com/auth/drive"},
RedirectURL: "urn:ietf:wg:oauth:2.0:oob",
Endpoint: oauth2.Endpoint{
AuthURL: "https://accounts.google.com/o/oauth2/auth",
TokenURL: "https://accounts.google.com/o/oauth2/token",
},
}
}
================================================
FILE: auth/util.go
================================================
package auth
import (
"os"
"path/filepath"
)
func mkdir(path string) error {
dir := filepath.Dir(path)
if fileExists(dir) {
return nil
}
return os.Mkdir(dir, 0700)
}
func fileExists(path string) bool {
_, err := os.Stat(path)
if err == nil {
return true
}
return false
}
================================================
FILE: cli/context.go
================================================
package cli
type Context struct {
args Arguments
handlers []*Handler
}
func (self Context) Args() Arguments {
return self.args
}
func (self Context) Handlers() []*Handler {
return self.handlers
}
type Arguments map[string]interface{}
func (self Arguments) String(key string) string {
return self[key].(string)
}
func (self Arguments) Int64(key string) int64 {
return self[key].(int64)
}
func (self Arguments) Bool(key string) bool {
return self[key].(bool)
}
func (self Arguments) StringSlice(key string) []string {
return self[key].([]string)
}
================================================
FILE: cli/flags.go
================================================
package cli
type Flag interface {
GetPatterns() []string
GetName() string
GetDescription() string
GetParser() Parser
}
func getFlagParser(flags []Flag) Parser {
var parsers []Parser
for _, flag := range flags {
parsers = append(parsers, flag.GetParser())
}
return FlagParser{parsers}
}
type BoolFlag struct {
Patterns []string
Name string
Description string
DefaultValue bool
OmitValue bool
}
func (self BoolFlag) GetName() string {
return self.Name
}
func (self BoolFlag) GetPatterns() []string {
return self.Patterns
}
func (self BoolFlag) GetDescription() string {
return self.Description
}
func (self BoolFlag) GetParser() Parser {
var parsers []Parser
for _, p := range self.Patterns {
parsers = append(parsers, BoolFlagParser{
pattern: p,
key: self.Name,
omitValue: self.OmitValue,
defaultValue: self.DefaultValue,
})
}
if len(parsers) == 1 {
return parsers[0]
}
return ShortCircuitParser{parsers}
}
type StringFlag struct {
Patterns []string
Name string
Description string
DefaultValue string
}
func (self StringFlag) GetName() string {
return self.Name
}
func (self StringFlag) GetPatterns() []string {
return self.Patterns
}
func (self StringFlag) GetDescription() string {
return self.Description
}
func (self StringFlag) GetParser() Parser {
var parsers []Parser
for _, p := range self.Patterns {
parsers = append(parsers, StringFlagParser{
pattern: p,
key: self.Name,
defaultValue: self.DefaultValue,
})
}
if len(parsers) == 1 {
return parsers[0]
}
return ShortCircuitParser{parsers}
}
type IntFlag struct {
Patterns []string
Name string
Description string
DefaultValue int64
}
func (self IntFlag) GetName() string {
return self.Name
}
func (self IntFlag) GetPatterns() []string {
return self.Patterns
}
func (self IntFlag) GetDescription() string {
return self.Description
}
func (self IntFlag) GetParser() Parser {
var parsers []Parser
for _, p := range self.Patterns {
parsers = append(parsers, IntFlagParser{
pattern: p,
key: self.Name,
defaultValue: self.DefaultValue,
})
}
if len(parsers) == 1 {
return parsers[0]
}
return ShortCircuitParser{parsers}
}
type StringSliceFlag struct {
Patterns []string
Name string
Description string
DefaultValue []string
}
func (self StringSliceFlag) GetName() string {
return self.Name
}
func (self StringSliceFlag) GetPatterns() []string {
return self.Patterns
}
func (self StringSliceFlag) GetDescription() string {
return self.Description
}
func (self StringSliceFlag) GetParser() Parser {
var parsers []Parser
for _, p := range self.Patterns {
parsers = append(parsers, StringSliceFlagParser{
pattern: p,
key: self.Name,
defaultValue: self.DefaultValue,
})
}
if len(parsers) == 1 {
return parsers[0]
}
return ShortCircuitParser{parsers}
}
================================================
FILE: cli/handler.go
================================================
package cli
import (
"regexp"
"strings"
)
func NewFlagGroup(name string, flags ...Flag) FlagGroup {
return FlagGroup{
Name: name,
Flags: flags,
}
}
type FlagGroup struct {
Name string
Flags []Flag
}
type FlagGroups []FlagGroup
func (groups FlagGroups) getFlags(name string) []Flag {
for _, group := range groups {
if group.Name == name {
return group.Flags
}
}
return nil
}
var handlers []*Handler
type Handler struct {
Pattern string
FlagGroups FlagGroups
Callback func(Context)
Description string
}
func (self *Handler) getParser() Parser {
var parsers []Parser
for _, pattern := range self.SplitPattern() {
if isFlagGroup(pattern) {
groupName := flagGroupName(pattern)
flags := self.FlagGroups.getFlags(groupName)
parsers = append(parsers, getFlagParser(flags))
} else if isCaptureGroup(pattern) {
parsers = append(parsers, CaptureGroupParser{pattern})
} else {
parsers = append(parsers, EqualParser{pattern})
}
}
return CompleteParser{parsers}
}
// Split on spaces but ignore spaces inside <...> and [...]
func (self *Handler) SplitPattern() []string {
re := regexp.MustCompile(`(<[^>]+>|\[[^\]]+]|\S+)`)
matches := []string{}
for _, value := range re.FindAllStringSubmatch(self.Pattern, -1) {
matches = append(matches, value[1])
}
return matches
}
func SetHandlers(h []*Handler) {
handlers = h
}
func AddHandler(pattern string, groups FlagGroups, callback func(Context), desc string) {
handlers = append(handlers, &Handler{
Pattern: pattern,
FlagGroups: groups,
Callback: callback,
Description: desc,
})
}
func findHandler(args []string) *Handler {
for _, h := range handlers {
if _, ok := h.getParser().Match(args); ok {
return h
}
}
return nil
}
func Handle(args []string) bool {
h := findHandler(args)
if h == nil {
return false
}
_, data := h.getParser().Capture(args)
ctx := Context{
args: data,
handlers: handlers,
}
h.Callback(ctx)
return true
}
func isCaptureGroup(arg string) bool {
return strings.HasPrefix(arg, "<") && strings.HasSuffix(arg, ">")
}
func isFlagGroup(arg string) bool {
return strings.HasPrefix(arg, "[") && strings.HasSuffix(arg, "]")
}
func flagGroupName(s string) string {
return s[1 : len(s)-1]
}
================================================
FILE: cli/parser.go
================================================
package cli
import (
"fmt"
"strconv"
)
type Parser interface {
Match([]string) ([]string, bool)
Capture([]string) ([]string, map[string]interface{})
}
type EqualParser struct {
value string
}
func (self EqualParser) Match(values []string) ([]string, bool) {
if len(values) == 0 {
return values, false
}
if self.value == values[0] {
return values[1:], true
}
return values, false
}
func (self EqualParser) Capture(values []string) ([]string, map[string]interface{}) {
remainingValues, _ := self.Match(values)
return remainingValues, nil
}
func (self EqualParser) String() string {
return fmt.Sprintf("EqualParser '%s'", self.value)
}
type CaptureGroupParser struct {
value string
}
func (self CaptureGroupParser) Match(values []string) ([]string, bool) {
if len(values) == 0 {
return values, false
}
return values[1:], true
}
func (self CaptureGroupParser) key() string {
return self.value[1 : len(self.value)-1]
}
func (self CaptureGroupParser) Capture(values []string) ([]string, map[string]interface{}) {
if remainingValues, ok := self.Match(values); ok {
return remainingValues, map[string]interface{}{self.key(): values[0]}
}
return values, nil
}
func (self CaptureGroupParser) String() string {
return fmt.Sprintf("CaptureGroupParser '%s'", self.value)
}
type BoolFlagParser struct {
pattern string
key string
omitValue bool
defaultValue bool
}
func (self BoolFlagParser) Match(values []string) ([]string, bool) {
if self.omitValue {
return flagKeyMatch(self.pattern, values, 0)
}
remaining, value, ok := flagKeyValueMatch(self.pattern, values, 0)
if !ok {
return remaining, false
}
// Check that value is a valid boolean
if _, err := strconv.ParseBool(value); err != nil {
return remaining, false
}
return remaining, true
}
func (self BoolFlagParser) Capture(values []string) ([]string, map[string]interface{}) {
if self.omitValue {
remaining, ok := flagKeyMatch(self.pattern, values, 0)
return remaining, map[string]interface{}{self.key: ok}
}
remaining, value, ok := flagKeyValueMatch(self.pattern, values, 0)
if !ok {
return remaining, map[string]interface{}{self.key: self.defaultValue}
}
b, _ := strconv.ParseBool(value)
return remaining, map[string]interface{}{self.key: b}
}
func (self BoolFlagParser) String() string {
return fmt.Sprintf("BoolFlagParser '%s'", self.pattern)
}
type StringFlagParser struct {
pattern string
key string
defaultValue string
}
func (self StringFlagParser) Match(values []string) ([]string, bool) {
remaining, _, ok := flagKeyValueMatch(self.pattern, values, 0)
return remaining, ok
}
func (self StringFlagParser) Capture(values []string) ([]string, map[string]interface{}) {
remaining, value, ok := flagKeyValueMatch(self.pattern, values, 0)
if !ok {
return remaining, map[string]interface{}{self.key: self.defaultValue}
}
return remaining, map[string]interface{}{self.key: value}
}
func (self StringFlagParser) String() string {
return fmt.Sprintf("StringFlagParser '%s'", self.pattern)
}
type IntFlagParser struct {
pattern string
key string
defaultValue int64
}
func (self IntFlagParser) Match(values []string) ([]string, bool) {
remaining, value, ok := flagKeyValueMatch(self.pattern, values, 0)
if !ok {
return remaining, false
}
// Check that value is a valid integer
if _, err := strconv.ParseInt(value, 10, 64); err != nil {
return remaining, false
}
return remaining, true
}
func (self IntFlagParser) Capture(values []string) ([]string, map[string]interface{}) {
remaining, value, ok := flagKeyValueMatch(self.pattern, values, 0)
if !ok {
return remaining, map[string]interface{}{self.key: self.defaultValue}
}
n, _ := strconv.ParseInt(value, 10, 64)
return remaining, map[string]interface{}{self.key: n}
}
func (self IntFlagParser) String() string {
return fmt.Sprintf("IntFlagParser '%s'", self.pattern)
}
type StringSliceFlagParser struct {
pattern string
key string
defaultValue []string
}
func (self StringSliceFlagParser) Match(values []string) ([]string, bool) {
if len(values) < 2 {
return values, false
}
var remainingValues []string
for i := 0; i < len(values); i++ {
if values[i] == self.pattern && i+1 < len(values) {
i++
continue
}
remainingValues = append(remainingValues, values[i])
}
return remainingValues, len(values) != len(remainingValues)
}
func (self StringSliceFlagParser) Capture(values []string) ([]string, map[string]interface{}) {
remainingValues, ok := self.Match(values)
if !ok {
return values, map[string]interface{}{self.key: self.defaultValue}
}
var captured []string
for i := 0; i < len(values); i++ {
if values[i] == self.pattern && i+1 < len(values) {
captured = append(captured, values[i+1])
}
}
return remainingValues, map[string]interface{}{self.key: captured}
}
func (self StringSliceFlagParser) String() string {
return fmt.Sprintf("StringSliceFlagParser '%s'", self.pattern)
}
type FlagParser struct {
parsers []Parser
}
func (self FlagParser) Match(values []string) ([]string, bool) {
remainingValues := values
for _, parser := range self.parsers {
remainingValues, _ = parser.Match(remainingValues)
}
return remainingValues, true
}
func (self FlagParser) Capture(values []string) ([]string, map[string]interface{}) {
captured := map[string]interface{}{}
remainingValues := values
for _, parser := range self.parsers {
var data map[string]interface{}
remainingValues, data = parser.Capture(remainingValues)
for key, value := range data {
captured[key] = value
}
}
return remainingValues, captured
}
func (self FlagParser) String() string {
return fmt.Sprintf("FlagParser %v", self.parsers)
}
type ShortCircuitParser struct {
parsers []Parser
}
func (self ShortCircuitParser) Match(values []string) ([]string, bool) {
remainingValues := values
for _, parser := range self.parsers {
var ok bool
remainingValues, ok = parser.Match(remainingValues)
if ok {
return remainingValues, true
}
}
return remainingValues, false
}
func (self ShortCircuitParser) Capture(values []string) ([]string, map[string]interface{}) {
if len(self.parsers) == 0 {
return values, nil
}
for _, parser := range self.parsers {
if _, ok := parser.Match(values); ok {
return parser.Capture(values)
}
}
// No parsers matched at this point,
// just return the capture value of the first one
return self.parsers[0].Capture(values)
}
func (self ShortCircuitParser) String() string {
return fmt.Sprintf("ShortCircuitParser %v", self.parsers)
}
type CompleteParser struct {
parsers []Parser
}
func (self CompleteParser) Match(values []string) ([]string, bool) {
remainingValues := copySlice(values)
for _, parser := range self.parsers {
var ok bool
remainingValues, ok = parser.Match(remainingValues)
if !ok {
return remainingValues, false
}
}
return remainingValues, len(remainingValues) == 0
}
func (self CompleteParser) Capture(values []string) ([]string, map[string]interface{}) {
remainingValues := copySlice(values)
data := map[string]interface{}{}
for _, parser := range self.parsers {
var captured map[string]interface{}
remainingValues, captured = parser.Capture(remainingValues)
for key, value := range captured {
data[key] = value
}
}
return remainingValues, data
}
func (self CompleteParser) String() string {
return fmt.Sprintf("CompleteParser %v", self.parsers)
}
func flagKeyValueMatch(key string, values []string, index int) ([]string, string, bool) {
if index > len(values)-2 {
return values, "", false
}
if values[index] == key {
value := values[index+1]
remaining := append(copySlice(values[:index]), values[index+2:]...)
return remaining, value, true
}
return flagKeyValueMatch(key, values, index+1)
}
func flagKeyMatch(key string, values []string, index int) ([]string, bool) {
if index > len(values)-1 {
return values, false
}
if values[index] == key {
remaining := append(copySlice(values[:index]), values[index+1:]...)
return remaining, true
}
return flagKeyMatch(key, values, index+1)
}
func copySlice(a []string) []string {
b := make([]string, len(a))
copy(b, a)
return b
}
================================================
FILE: compare.go
================================================
package main
import (
"encoding/json"
"github.com/prasmussen/gdrive/drive"
"os"
)
const MinCacheFileSize = 5 * 1024 * 1024
type Md5Comparer struct{}
func (self Md5Comparer) Changed(local *drive.LocalFile, remote *drive.RemoteFile) bool {
return remote.Md5() != md5sum(local.AbsPath())
}
type CachedFileInfo struct {
Size int64 `json:"size"`
Modified int64 `json:"modified"`
Md5 string `json:"md5"`
}
func NewCachedMd5Comparer(path string) CachedMd5Comparer {
cache := map[string]*CachedFileInfo{}
f, err := os.Open(path)
if err == nil {
json.NewDecoder(f).Decode(&cache)
}
f.Close()
return CachedMd5Comparer{path, cache}
}
type CachedMd5Comparer struct {
path string
cache map[string]*CachedFileInfo
}
func (self CachedMd5Comparer) Changed(local *drive.LocalFile, remote *drive.RemoteFile) bool {
return remote.Md5() != self.md5(local)
}
func (self CachedMd5Comparer) md5(local *drive.LocalFile) string {
// See if file exist in cache
cached, found := self.cache[local.AbsPath()]
// If found and modification time and size has not changed, return cached md5
if found && local.Modified().UnixNano() == cached.Modified && local.Size() == cached.Size {
return cached.Md5
}
// Calculate new md5 sum
md5 := md5sum(local.AbsPath())
// Cache file info if file meets size criteria
if local.Size() > MinCacheFileSize {
self.cacheAdd(local, md5)
self.persist()
}
return md5
}
func (self CachedMd5Comparer) cacheAdd(lf *drive.LocalFile, md5 string) {
self.cache[lf.AbsPath()] = &CachedFileInfo{
Size: lf.Size(),
Modified: lf.Modified().UnixNano(),
Md5: md5,
}
}
func (self CachedMd5Comparer) persist() {
writeJson(self.path, self.cache)
}
================================================
FILE: drive/about.go
================================================
package drive
import (
"fmt"
"io"
"text/tabwriter"
)
type AboutArgs struct {
Out io.Writer
SizeInBytes bool
}
func (self *Drive) About(args AboutArgs) (err error) {
about, err := self.service.About.Get().Fields("maxImportSizes", "maxUploadSize", "storageQuota", "user").Do()
if err != nil {
return fmt.Errorf("Failed to get about: %s", err)
}
user := about.User
quota := about.StorageQuota
fmt.Fprintf(args.Out, "User: %s, %s\n", user.DisplayName, user.EmailAddress)
fmt.Fprintf(args.Out, "Used: %s\n", formatSize(quota.Usage, args.SizeInBytes))
fmt.Fprintf(args.Out, "Free: %s\n", formatSize(quota.Limit-quota.Usage, args.SizeInBytes))
fmt.Fprintf(args.Out, "Total: %s\n", formatSize(quota.Limit, args.SizeInBytes))
fmt.Fprintf(args.Out, "Max upload size: %s\n", formatSize(about.MaxUploadSize, args.SizeInBytes))
return
}
type AboutImportArgs struct {
Out io.Writer
}
func (self *Drive) AboutImport(args AboutImportArgs) (err error) {
about, err := self.service.About.Get().Fields("importFormats").Do()
if err != nil {
return fmt.Errorf("Failed to get about: %s", err)
}
printAboutFormats(args.Out, about.ImportFormats)
return
}
type AboutExportArgs struct {
Out io.Writer
}
func (self *Drive) AboutExport(args AboutExportArgs) (err error) {
about, err := self.service.About.Get().Fields("exportFormats").Do()
if err != nil {
return fmt.Errorf("Failed to get about: %s", err)
}
printAboutFormats(args.Out, about.ExportFormats)
return
}
func printAboutFormats(out io.Writer, formats map[string][]string) {
w := new(tabwriter.Writer)
w.Init(out, 0, 0, 3, ' ', 0)
fmt.Fprintln(w, "From\tTo")
for from, toFormats := range formats {
fmt.Fprintf(w, "%s\t%s\n", from, formatList(toFormats))
}
w.Flush()
}
================================================
FILE: drive/changes.go
================================================
package drive
import (
"fmt"
"google.golang.org/api/drive/v3"
"io"
"text/tabwriter"
)
type ListChangesArgs struct {
Out io.Writer
PageToken string
MaxChanges int64
Now bool
NameWidth int64
SkipHeader bool
}
func (self *Drive) ListChanges(args ListChangesArgs) error {
if args.Now {
pageToken, err := self.GetChangesStartPageToken()
if err != nil {
return err
}
fmt.Fprintf(args.Out, "Page token: %s\n", pageToken)
return nil
}
changeList, err := self.service.Changes.List(args.PageToken).PageSize(args.MaxChanges).RestrictToMyDrive(true).Fields("newStartPageToken", "nextPageToken", "changes(fileId,removed,time,file(id,name,md5Checksum,mimeType,createdTime,modifiedTime))").Do()
if err != nil {
return fmt.Errorf("Failed listing changes: %s", err)
}
PrintChanges(PrintChangesArgs{
Out: args.Out,
ChangeList: changeList,
NameWidth: int(args.NameWidth),
SkipHeader: args.SkipHeader,
})
return nil
}
func (self *Drive) GetChangesStartPageToken() (string, error) {
res, err := self.service.Changes.GetStartPageToken().Do()
if err != nil {
return "", fmt.Errorf("Failed getting start page token: %s", err)
}
return res.StartPageToken, nil
}
type PrintChangesArgs struct {
Out io.Writer
ChangeList *drive.ChangeList
NameWidth int
SkipHeader bool
}
func PrintChanges(args PrintChangesArgs) {
w := new(tabwriter.Writer)
w.Init(args.Out, 0, 0, 3, ' ', 0)
if !args.SkipHeader {
fmt.Fprintln(w, "Id\tName\tAction\tTime")
}
for _, c := range args.ChangeList.Changes {
var name string
var action string
if c.Removed {
action = "remove"
} else {
name = c.File.Name
action = "update"
}
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n",
c.FileId,
truncateString(name, args.NameWidth),
action,
formatDatetime(c.Time),
)
}
if len(args.ChangeList.Changes) > 0 {
w.Flush()
pageToken, hasMore := nextChangesPageToken(args.ChangeList)
fmt.Fprintf(args.Out, "\nToken: %s, more: %t\n", pageToken, hasMore)
} else {
fmt.Fprintln(args.Out, "No changes")
}
}
func nextChangesPageToken(cl *drive.ChangeList) (string, bool) {
if cl.NextPageToken != "" {
return cl.NextPageToken, true
}
return cl.NewStartPageToken, false
}
================================================
FILE: drive/delete.go
================================================
package drive
import (
"fmt"
"io"
)
type DeleteArgs struct {
Out io.Writer
Id string
Recursive bool
}
func (self *Drive) Delete(args DeleteArgs) error {
f, err := self.service.Files.Get(args.Id).Fields("name", "mimeType").Do()
if err != nil {
return fmt.Errorf("Failed to get file: %s", err)
}
if isDir(f) && !args.Recursive {
return fmt.Errorf("'%s' is a directory, use the 'recursive' flag to delete directories", f.Name)
}
err = self.service.Files.Delete(args.Id).Do()
if err != nil {
return fmt.Errorf("Failed to delete file: %s", err)
}
fmt.Fprintf(args.Out, "Deleted '%s'\n", f.Name)
return nil
}
func (self *Drive) deleteFile(fileId string) error {
err := self.service.Files.Delete(fileId).Do()
if err != nil {
return fmt.Errorf("Failed to delete file: %s", err)
}
return nil
}
================================================
FILE: drive/download.go
================================================
package drive
import (
"fmt"
"io"
"os"
"path/filepath"
"time"
"google.golang.org/api/drive/v3"
"google.golang.org/api/googleapi"
)
type DownloadArgs struct {
Out io.Writer
Progress io.Writer
Id string
Path string
Force bool
Skip bool
Recursive bool
Delete bool
Stdout bool
Timeout time.Duration
}
func (self *Drive) Download(args DownloadArgs) error {
if args.Recursive {
return self.downloadRecursive(args)
}
f, err := self.service.Files.Get(args.Id).Fields("id", "name", "size", "mimeType", "md5Checksum").Do()
if err != nil {
return fmt.Errorf("Failed to get file: %s", err)
}
if isDir(f) {
return fmt.Errorf("'%s' is a directory, use --recursive to download directories", f.Name)
}
if !isBinary(f) {
return fmt.Errorf("'%s' is a google document and must be exported, see the export command", f.Name)
}
bytes, rate, err := self.downloadBinary(f, args)
if err != nil {
return err
}
if !args.Stdout {
fmt.Fprintf(args.Out, "Downloaded %s at %s/s, total %s\n", f.Id, formatSize(rate, false), formatSize(bytes, false))
}
if args.Delete {
err = self.deleteFile(args.Id)
if err != nil {
return fmt.Errorf("Failed to delete file: %s", err)
}
if !args.Stdout {
fmt.Fprintf(args.Out, "Removed %s\n", args.Id)
}
}
return err
}
type DownloadQueryArgs struct {
Out io.Writer
Progress io.Writer
Query string
Path string
Force bool
Skip bool
Recursive bool
}
func (self *Drive) DownloadQuery(args DownloadQueryArgs) error {
listArgs := listAllFilesArgs{
query: args.Query,
fields: []googleapi.Field{"nextPageToken", "files(id,name,mimeType,size,md5Checksum)"},
}
files, err := self.listAllFiles(listArgs)
if err != nil {
return fmt.Errorf("Failed to list files: %s", err)
}
downloadArgs := DownloadArgs{
Out: args.Out,
Progress: args.Progress,
Path: args.Path,
Force: args.Force,
Skip: args.Skip,
}
for _, f := range files {
if isDir(f) && args.Recursive {
err = self.downloadDirectory(f, downloadArgs)
} else if isBinary(f) {
_, _, err = self.downloadBinary(f, downloadArgs)
}
if err != nil {
return err
}
}
return nil
}
func (self *Drive) downloadRecursive(args DownloadArgs) error {
f, err := self.service.Files.Get(args.Id).Fields("id", "name", "size", "mimeType", "md5Checksum").Do()
if err != nil {
return fmt.Errorf("Failed to get file: %s", err)
}
if isDir(f) {
return self.downloadDirectory(f, args)
} else if isBinary(f) {
_, _, err = self.downloadBinary(f, args)
return err
}
return nil
}
func (self *Drive) downloadBinary(f *drive.File, args DownloadArgs) (int64, int64, error) {
// Get timeout reader wrapper and context
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)
}
// Close body on function exit
defer res.Body.Close()
// Path to file
fpath := filepath.Join(args.Path, f.Name)
if !args.Stdout {
fmt.Fprintf(args.Out, "Downloading %s -> %s\n", f.Name, fpath)
}
return self.saveFile(saveFileArgs{
out: args.Out,
body: timeoutReaderWrapper(res.Body),
contentLength: res.ContentLength,
fpath: fpath,
force: args.Force,
skip: args.Skip,
stdout: args.Stdout,
progress: args.Progress,
})
}
type saveFileArgs struct {
out io.Writer
body io.Reader
contentLength int64
fpath string
force bool
skip bool
stdout bool
progress io.Writer
}
func (self *Drive) saveFile(args saveFileArgs) (int64, int64, error) {
// Wrap response body in progress reader
srcReader := getProgressReader(args.body, args.progress, args.contentLength)
if args.stdout {
// Write file content to stdout
_, err := io.Copy(args.out, srcReader)
return 0, 0, err
}
// Check if file exists to force
if !args.skip && !args.force && fileExists(args.fpath) {
return 0, 0, fmt.Errorf("File '%s' already exists, use --force to overwrite or --skip to skip", args.fpath)
}
//Check if file exists to skip
if args.skip && fileExists(args.fpath) {
fmt.Printf("File '%s' already exists, skipping\n", args.fpath)
return 0, 0, nil
}
// Ensure any parent directories exists
if err := mkdir(args.fpath); err != nil {
return 0, 0, err
}
// Download to tmp file
tmpPath := args.fpath + ".incomplete"
// Create new file
outFile, err := os.Create(tmpPath)
if err != nil {
return 0, 0, fmt.Errorf("Unable to create new file: %s", err)
}
started := time.Now()
// Save file to disk
bytes, err := io.Copy(outFile, srcReader)
if err != nil {
outFile.Close()
os.Remove(tmpPath)
return 0, 0, fmt.Errorf("Failed saving file: %s", err)
}
// Calculate average download rate
rate := calcRate(bytes, started, time.Now())
// Close File
outFile.Close()
// Rename tmp file to proper filename
return bytes, rate, os.Rename(tmpPath, args.fpath)
}
func (self *Drive) downloadDirectory(parent *drive.File, args DownloadArgs) error {
listArgs := listAllFilesArgs{
query: fmt.Sprintf("'%s' in parents", parent.Id),
fields: []googleapi.Field{"nextPageToken", "files(id,name)"},
}
files, err := self.listAllFiles(listArgs)
if err != nil {
return fmt.Errorf("Failed listing files: %s", err)
}
newPath := filepath.Join(args.Path, parent.Name)
for _, f := range files {
// Copy args and update changed fields
newArgs := args
newArgs.Path = newPath
newArgs.Id = f.Id
newArgs.Stdout = false
err = self.downloadRecursive(newArgs)
if err != nil {
return err
}
}
return nil
}
func isDir(f *drive.File) bool {
return f.MimeType == DirectoryMimeType
}
func isBinary(f *drive.File) bool {
return f.Md5Checksum != ""
}
================================================
FILE: drive/drive.go
================================================
package drive
import (
"google.golang.org/api/drive/v3"
"net/http"
)
type Drive struct {
service *drive.Service
}
func New(client *http.Client) (*Drive, error) {
service, err := drive.New(client)
if err != nil {
return nil, err
}
return &Drive{service}, nil
}
================================================
FILE: drive/errors.go
================================================
package drive
import (
"golang.org/x/net/context"
"google.golang.org/api/googleapi"
"time"
)
const MaxErrorRetries = 5
func isBackendOrRateLimitError(err error) bool {
return isBackendError(err) || isRateLimitError(err)
}
func isBackendError(err error) bool {
if err == nil {
return false
}
ae, ok := err.(*googleapi.Error)
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)
}
================================================
FILE: drive/export.go
================================================
package drive
import (
"fmt"
"io"
"mime"
"os"
)
var DefaultExportMime = map[string]string{
"application/vnd.google-apps.form": "application/zip",
"application/vnd.google-apps.document": "application/pdf",
"application/vnd.google-apps.drawing": "image/svg+xml",
"application/vnd.google-apps.spreadsheet": "text/csv",
"application/vnd.google-apps.script": "application/vnd.google-apps.script+json",
"application/vnd.google-apps.presentation": "application/pdf",
}
type ExportArgs struct {
Out io.Writer
Id string
PrintMimes bool
Mime string
Force bool
}
func (self *Drive) Export(args ExportArgs) error {
f, err := self.service.Files.Get(args.Id).Fields("name", "mimeType").Do()
if err != nil {
return fmt.Errorf("Failed to get file: %s", err)
}
if args.PrintMimes {
return self.printMimes(args.Out, f.MimeType)
}
exportMime, err := getExportMime(args.Mime, f.MimeType)
if err != nil {
return err
}
filename := getExportFilename(f.Name, exportMime)
res, err := self.service.Files.Export(args.Id, exportMime).Download()
if err != nil {
return fmt.Errorf("Failed to download file: %s", err)
}
// Close body on function exit
defer res.Body.Close()
// Check if file exists
if !args.Force && fileExists(filename) {
return fmt.Errorf("File '%s' already exists, use --force to overwrite", filename)
}
// Create new file
outFile, err := os.Create(filename)
if err != nil {
return fmt.Errorf("Unable to create new file '%s': %s", filename, err)
}
// Close file on function exit
defer outFile.Close()
// Save file to disk
_, err = io.Copy(outFile, res.Body)
if err != nil {
return fmt.Errorf("Failed saving file: %s", err)
}
fmt.Fprintf(args.Out, "Exported '%s' with mime type: '%s'\n", filename, exportMime)
return nil
}
func (self *Drive) printMimes(out io.Writer, mimeType string) error {
about, err := self.service.About.Get().Fields("exportFormats").Do()
if err != nil {
return fmt.Errorf("Failed to get about: %s", err)
}
mimes, ok := about.ExportFormats[mimeType]
if !ok {
return fmt.Errorf("File with type '%s' cannot be exported", mimeType)
}
fmt.Fprintf(out, "Available mime types: %s\n", formatList(mimes))
return nil
}
func getExportMime(userMime, fileMime string) (string, error) {
if userMime != "" {
return userMime, nil
}
defaultMime, ok := DefaultExportMime[fileMime]
if !ok {
return "", fmt.Errorf("File with type '%s' does not have a default export mime, and can probably not be exported", fileMime)
}
return defaultMime, nil
}
func getExportFilename(name, mimeType string) string {
extensions, err := mime.ExtensionsByType(mimeType)
if err != nil || len(extensions) == 0 {
return name
}
return name + extensions[0]
}
================================================
FILE: drive/import.go
================================================
package drive
import (
"fmt"
"io"
"io/ioutil"
"mime"
"path/filepath"
"strings"
)
type ImportArgs struct {
Out io.Writer
Mime string
Progress io.Writer
Path string
Parents []string
}
func (self *Drive) Import(args ImportArgs) error {
fromMime := args.Mime
if fromMime == "" {
fromMime = getMimeType(args.Path)
}
if fromMime == "" {
return fmt.Errorf("Could not determine mime type of file, use --mime")
}
about, err := self.service.About.Get().Fields("importFormats").Do()
if err != nil {
return fmt.Errorf("Failed to get about: %s", err)
}
toMimes, ok := about.ImportFormats[fromMime]
if !ok || len(toMimes) == 0 {
return fmt.Errorf("Mime type '%s' is not supported for import", fromMime)
}
f, _, err := self.uploadFile(UploadArgs{
Out: ioutil.Discard,
Progress: args.Progress,
Path: args.Path,
Parents: args.Parents,
Mime: toMimes[0],
})
if err != nil {
return err
}
fmt.Fprintf(args.Out, "Imported %s with mime type: '%s'\n", f.Id, toMimes[0])
return nil
}
func getMimeType(path string) string {
t := mime.TypeByExtension(filepath.Ext(path))
return strings.Split(t, ";")[0]
}
================================================
FILE: drive/info.go
================================================
package drive
import (
"fmt"
"google.golang.org/api/drive/v3"
"io"
)
type FileInfoArgs struct {
Out io.Writer
Id string
SizeInBytes bool
}
func (self *Drive) Info(args FileInfoArgs) error {
f, err := self.service.Files.Get(args.Id).Fields("id", "name", "size", "createdTime", "modifiedTime", "md5Checksum", "mimeType", "parents", "shared", "description", "webContentLink", "webViewLink").Do()
if err != nil {
return fmt.Errorf("Failed to get file: %s", err)
}
pathfinder := self.newPathfinder()
absPath, err := pathfinder.absPath(f)
if err != nil {
return err
}
PrintFileInfo(PrintFileInfoArgs{
Out: args.Out,
File: f,
Path: absPath,
SizeInBytes: args.SizeInBytes,
})
return nil
}
type PrintFileInfoArgs struct {
Out io.Writer
File *drive.File
Path string
SizeInBytes bool
}
func PrintFileInfo(args PrintFileInfoArgs) {
f := args.File
items := []kv{
kv{"Id", f.Id},
kv{"Name", f.Name},
kv{"Path", args.Path},
kv{"Description", f.Description},
kv{"Mime", f.MimeType},
kv{"Size", formatSize(f.Size, args.SizeInBytes)},
kv{"Created", formatDatetime(f.CreatedTime)},
kv{"Modified", formatDatetime(f.ModifiedTime)},
kv{"Md5sum", f.Md5Checksum},
kv{"Shared", formatBool(f.Shared)},
kv{"Parents", formatList(f.Parents)},
kv{"ViewUrl", f.WebViewLink},
kv{"DownloadUrl", f.WebContentLink},
}
for _, item := range items {
if item.value != "" {
fmt.Fprintf(args.Out, "%s: %s\n", item.key, item.value)
}
}
}
================================================
FILE: drive/list.go
================================================
package drive
import (
"fmt"
"golang.org/x/net/context"
"google.golang.org/api/drive/v3"
"google.golang.org/api/googleapi"
"io"
"text/tabwriter"
)
type ListFilesArgs struct {
Out io.Writer
MaxFiles int64
NameWidth int64
Query string
SortOrder string
SkipHeader bool
SizeInBytes bool
AbsPath bool
}
func (self *Drive) List(args ListFilesArgs) (err error) {
listArgs := listAllFilesArgs{
query: args.Query,
fields: []googleapi.Field{"nextPageToken", "files(id,name,md5Checksum,mimeType,size,createdTime,parents)"},
sortOrder: args.SortOrder,
maxFiles: args.MaxFiles,
}
files, err := self.listAllFiles(listArgs)
if err != nil {
return fmt.Errorf("Failed to list files: %s", err)
}
pathfinder := self.newPathfinder()
if args.AbsPath {
// Replace name with absolute path
for _, f := range files {
f.Name, err = pathfinder.absPath(f)
if err != nil {
return err
}
}
}
PrintFileList(PrintFileListArgs{
Out: args.Out,
Files: files,
NameWidth: int(args.NameWidth),
SkipHeader: args.SkipHeader,
SizeInBytes: args.SizeInBytes,
})
return
}
type listAllFilesArgs struct {
query string
fields []googleapi.Field
sortOrder string
maxFiles int64
}
func (self *Drive) listAllFiles(args listAllFilesArgs) ([]*drive.File, error) {
var files []*drive.File
var pageSize int64
if args.maxFiles > 0 && args.maxFiles < 1000 {
pageSize = args.maxFiles
} else {
pageSize = 1000
}
controlledStop := fmt.Errorf("Controlled stop")
err := self.service.Files.List().Q(args.query).Fields(args.fields...).OrderBy(args.sortOrder).PageSize(pageSize).Pages(context.TODO(), func(fl *drive.FileList) error {
files = append(files, fl.Files...)
// Stop when we have all the files we need
if args.maxFiles > 0 && len(files) >= int(args.maxFiles) {
return controlledStop
}
return nil
})
if err != nil && err != controlledStop {
return nil, err
}
if args.maxFiles > 0 {
n := min(len(files), int(args.maxFiles))
return files[:n], nil
}
return files, nil
}
type PrintFileListArgs struct {
Out io.Writer
Files []*drive.File
NameWidth int
SkipHeader bool
SizeInBytes bool
}
func PrintFileList(args PrintFileListArgs) {
w := new(tabwriter.Writer)
w.Init(args.Out, 0, 0, 3, ' ', 0)
if !args.SkipHeader {
fmt.Fprintln(w, "Id\tName\tType\tSize\tCreated")
}
for _, f := range args.Files {
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n",
f.Id,
truncateString(f.Name, args.NameWidth),
filetype(f),
formatSize(f.Size, args.SizeInBytes),
formatDatetime(f.CreatedTime),
)
}
w.Flush()
}
func filetype(f *drive.File) string {
if isDir(f) {
return "dir"
} else if isBinary(f) {
return "bin"
}
return "doc"
}
================================================
FILE: drive/mkdir.go
================================================
package drive
import (
"fmt"
"google.golang.org/api/drive/v3"
"io"
)
const DirectoryMimeType = "application/vnd.google-apps.folder"
type MkdirArgs struct {
Out io.Writer
Name string
Description string
Parents []string
}
func (self *Drive) Mkdir(args MkdirArgs) error {
f, err := self.mkdir(args)
if err != nil {
return err
}
fmt.Fprintf(args.Out, "Directory %s created\n", f.Id)
return nil
}
func (self *Drive) mkdir(args MkdirArgs) (*drive.File, error) {
dstFile := &drive.File{
Name: args.Name,
Description: args.Description,
MimeType: DirectoryMimeType,
}
// Set parent folders
dstFile.Parents = args.Parents
// Create directory
f, err := self.service.Files.Create(dstFile).Do()
if err != nil {
return nil, fmt.Errorf("Failed to create directory: %s", err)
}
return f, nil
}
================================================
FILE: drive/path.go
================================================
package drive
import (
"fmt"
"google.golang.org/api/drive/v3"
"path/filepath"
)
func (self *Drive) newPathfinder() *remotePathfinder {
return &remotePathfinder{
service: self.service.Files,
files: make(map[string]*drive.File),
}
}
type remotePathfinder struct {
service *drive.FilesService
files map[string]*drive.File
}
func (self *remotePathfinder) absPath(f *drive.File) (string, error) {
name := f.Name
if len(f.Parents) == 0 {
return name, nil
}
var path []string
for {
parent, err := self.getParent(f.Parents[0])
if err != nil {
return "", err
}
// Stop when we find the root dir
if len(parent.Parents) == 0 {
break
}
path = append([]string{parent.Name}, path...)
f = parent
}
path = append(path, name)
return filepath.Join(path...), nil
}
func (self *remotePathfinder) getParent(id string) (*drive.File, error) {
// Check cache
if f, ok := self.files[id]; ok {
return f, nil
}
// Fetch file from drive
f, err := self.service.Get(id).Fields("id", "name", "parents").Do()
if err != nil {
return nil, fmt.Errorf("Failed to get file: %s", err)
}
// Save in cache
self.files[f.Id] = f
return f, nil
}
================================================
FILE: drive/progress.go
================================================
package drive
import (
"fmt"
"io"
"io/ioutil"
"time"
)
const MaxDrawInterval = time.Second * 1
const MaxRateInterval = time.Second * 3
func getProgressReader(r io.Reader, w io.Writer, size int64) io.Reader {
// Don't wrap reader if output is discarded or size is too small
if w == ioutil.Discard || (size > 0 && size < 1024*1024) {
return r
}
return &Progress{
Reader: r,
Writer: w,
Size: size,
}
}
type Progress struct {
Writer io.Writer
Reader io.Reader
Size int64
progress int64
rate int64
rateProgress int64
rateUpdated time.Time
updated time.Time
done bool
}
func (self *Progress) Read(p []byte) (int, error) {
// Read
n, err := self.Reader.Read(p)
now := time.Now()
isLast := err != nil
// Increment progress
newProgress := self.progress + int64(n)
self.progress = newProgress
// Initialize rate state
if self.rateUpdated.IsZero() {
self.rateUpdated = now
self.rateProgress = newProgress
}
// Update rate every x seconds
if self.rateUpdated.Add(MaxRateInterval).Before(now) {
self.rate = calcRate(newProgress-self.rateProgress, self.rateUpdated, now)
self.rateUpdated = now
self.rateProgress = newProgress
}
// Draw progress every x seconds
if self.updated.Add(MaxDrawInterval).Before(now) || isLast {
self.draw(isLast)
self.updated = now
}
// Mark as done if error occurs
self.done = isLast
return n, err
}
func (self *Progress) draw(isLast bool) {
if self.done {
return
}
self.clear()
// Print progress
fmt.Fprintf(self.Writer, "%s", formatSize(self.progress, false))
// Print total size
if self.Size > 0 {
fmt.Fprintf(self.Writer, "/%s", formatSize(self.Size, false))
}
// Print rate
if self.rate > 0 {
fmt.Fprintf(self.Writer, ", Rate: %s/s", formatSize(self.rate, false))
}
if isLast {
self.clear()
}
}
func (self *Progress) clear() {
fmt.Fprintf(self.Writer, "\r%50s\r", "")
}
================================================
FILE: drive/revision_delete.go
================================================
package drive
import (
"fmt"
"io"
)
type DeleteRevisionArgs struct {
Out io.Writer
FileId string
RevisionId string
}
func (self *Drive) DeleteRevision(args DeleteRevisionArgs) (err error) {
rev, err := self.service.Revisions.Get(args.FileId, args.RevisionId).Fields("originalFilename").Do()
if err != nil {
return fmt.Errorf("Failed to get revision: %s", err)
}
if rev.OriginalFilename == "" {
return fmt.Errorf("Deleting revisions for this file type is not supported")
}
err = self.service.Revisions.Delete(args.FileId, args.RevisionId).Do()
if err != nil {
return fmt.Errorf("Failed to delete revision", err)
}
fmt.Fprintf(args.Out, "Deleted revision '%s'\n", args.RevisionId)
return
}
================================================
FILE: drive/revision_download.go
================================================
package drive
import (
"fmt"
"io"
"io/ioutil"
"path/filepath"
"time"
)
type DownloadRevisionArgs struct {
Out io.Writer
Progress io.Writer
FileId string
RevisionId string
Path string
Force bool
Stdout bool
Timeout time.Duration
}
func (self *Drive) DownloadRevision(args DownloadRevisionArgs) (err error) {
getRev := self.service.Revisions.Get(args.FileId, args.RevisionId)
rev, err := getRev.Fields("originalFilename").Do()
if err != nil {
return fmt.Errorf("Failed to get file: %s", err)
}
if rev.OriginalFilename == "" {
return fmt.Errorf("Download is not supported for this file type")
}
// Get timeout reader wrapper and context
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)
}
// Close body on function exit
defer res.Body.Close()
// Discard other output if file is written to stdout
out := args.Out
if args.Stdout {
out = ioutil.Discard
}
// Path to file
fpath := filepath.Join(args.Path, rev.OriginalFilename)
fmt.Fprintf(out, "Downloading %s -> %s\n", rev.OriginalFilename, fpath)
bytes, rate, err := self.saveFile(saveFileArgs{
out: args.Out,
body: timeoutReaderWrapper(res.Body),
contentLength: res.ContentLength,
fpath: fpath,
force: args.Force,
stdout: args.Stdout,
progress: args.Progress,
})
if err != nil {
return err
}
fmt.Fprintf(out, "Download complete, rate: %s/s, total size: %s\n", formatSize(rate, false), formatSize(bytes, false))
return nil
}
================================================
FILE: drive/revision_list.go
================================================
package drive
import (
"fmt"
"google.golang.org/api/drive/v3"
"io"
"text/tabwriter"
)
type ListRevisionsArgs struct {
Out io.Writer
Id string
NameWidth int64
SkipHeader bool
SizeInBytes bool
}
func (self *Drive) ListRevisions(args ListRevisionsArgs) (err error) {
revList, err := self.service.Revisions.List(args.Id).Fields("revisions(id,keepForever,size,modifiedTime,originalFilename)").Do()
if err != nil {
return fmt.Errorf("Failed listing revisions: %s", err)
}
PrintRevisionList(PrintRevisionListArgs{
Out: args.Out,
Revisions: revList.Revisions,
NameWidth: int(args.NameWidth),
SkipHeader: args.SkipHeader,
SizeInBytes: args.SizeInBytes,
})
return
}
type PrintRevisionListArgs struct {
Out io.Writer
Revisions []*drive.Revision
NameWidth int
SkipHeader bool
SizeInBytes bool
}
func PrintRevisionList(args PrintRevisionListArgs) {
w := new(tabwriter.Writer)
w.Init(args.Out, 0, 0, 3, ' ', 0)
if !args.SkipHeader {
fmt.Fprintln(w, "Id\tName\tSize\tModified\tKeepForever")
}
for _, rev := range args.Revisions {
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n",
rev.Id,
truncateString(rev.OriginalFilename, args.NameWidth),
formatSize(rev.Size, args.SizeInBytes),
formatDatetime(rev.ModifiedTime),
formatBool(rev.KeepForever),
)
}
w.Flush()
}
================================================
FILE: drive/share.go
================================================
package drive
import (
"fmt"
"google.golang.org/api/drive/v3"
"io"
"text/tabwriter"
)
type ShareArgs struct {
Out io.Writer
FileId string
Role string
Type string
Email string
Domain string
Discoverable bool
}
func (self *Drive) Share(args ShareArgs) error {
permission := &drive.Permission{
AllowFileDiscovery: args.Discoverable,
Role: args.Role,
Type: args.Type,
EmailAddress: args.Email,
Domain: args.Domain,
}
_, err := self.service.Permissions.Create(args.FileId, permission).Do()
if err != nil {
return fmt.Errorf("Failed to share file: %s", err)
}
fmt.Fprintf(args.Out, "Granted %s permission to %s\n", args.Role, args.Type)
return nil
}
type RevokePermissionArgs struct {
Out io.Writer
FileId string
PermissionId string
}
func (self *Drive) RevokePermission(args RevokePermissionArgs) error {
err := self.service.Permissions.Delete(args.FileId, args.PermissionId).Do()
if err != nil {
fmt.Errorf("Failed to revoke permission: %s", err)
return err
}
fmt.Fprintf(args.Out, "Permission revoked\n")
return nil
}
type ListPermissionsArgs struct {
Out io.Writer
FileId string
}
func (self *Drive) ListPermissions(args ListPermissionsArgs) error {
permList, err := self.service.Permissions.List(args.FileId).Fields("permissions(id,role,type,domain,emailAddress,allowFileDiscovery)").Do()
if err != nil {
fmt.Errorf("Failed to list permissions: %s", err)
return err
}
printPermissions(printPermissionsArgs{
out: args.Out,
permissions: permList.Permissions,
})
return nil
}
func (self *Drive) shareAnyoneReader(fileId string) error {
permission := &drive.Permission{
Role: "reader",
Type: "anyone",
}
_, err := self.service.Permissions.Create(fileId, permission).Do()
if err != nil {
return fmt.Errorf("Failed to share file: %s", err)
}
return nil
}
type printPermissionsArgs struct {
out io.Writer
permissions []*drive.Permission
}
func printPermissions(args printPermissionsArgs) {
w := new(tabwriter.Writer)
w.Init(args.out, 0, 0, 3, ' ', 0)
fmt.Fprintln(w, "Id\tType\tRole\tEmail\tDomain\tDiscoverable")
for _, p := range args.permissions {
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\n",
p.Id,
p.Type,
p.Role,
p.EmailAddress,
p.Domain,
formatBool(p.AllowFileDiscovery),
)
}
w.Flush()
}
================================================
FILE: drive/sync.go
================================================
package drive
import (
"fmt"
"github.com/sabhiram/go-gitignore"
"github.com/soniakeys/graph"
"google.golang.org/api/drive/v3"
"google.golang.org/api/googleapi"
"io"
"os"
"path/filepath"
"strings"
"text/tabwriter"
"time"
)
const DefaultIgnoreFile = ".gdriveignore"
type ModTime int
const (
LocalLastModified ModTime = iota
RemoteLastModified
EqualModifiedTime
)
type LargestSize int
const (
LocalLargestSize LargestSize = iota
RemoteLargestSize
EqualSize
)
type ConflictResolution int
const (
NoResolution ConflictResolution = iota
KeepLocal
KeepRemote
KeepLargest
)
func (self *Drive) prepareSyncFiles(localPath string, root *drive.File, cmp FileComparer) (*syncFiles, error) {
localCh := make(chan struct {
files []*LocalFile
err error
})
remoteCh := make(chan struct {
files []*RemoteFile
err error
})
go func() {
files, err := prepareLocalFiles(localPath)
localCh <- struct {
files []*LocalFile
err error
}{files, err}
}()
go func() {
files, err := self.prepareRemoteFiles(root, "")
remoteCh <- struct {
files []*RemoteFile
err error
}{files, err}
}()
local := <-localCh
if local.err != nil {
return nil, local.err
}
remote := <-remoteCh
if remote.err != nil {
return nil, remote.err
}
return &syncFiles{
root: &RemoteFile{file: root},
local: local.files,
remote: remote.files,
compare: cmp,
}, nil
}
func (self *Drive) isSyncFile(id string) (bool, error) {
f, err := self.service.Files.Get(id).Fields("appProperties").Do()
if err != nil {
return false, fmt.Errorf("Failed to get file: %s", err)
}
_, ok := f.AppProperties["sync"]
return ok, nil
}
func prepareLocalFiles(root string) ([]*LocalFile, error) {
var files []*LocalFile
// Get absolute root path
absRootPath, err := filepath.Abs(root)
if err != nil {
return nil, err
}
// Prepare ignorer
shouldIgnore, err := prepareIgnorer(filepath.Join(absRootPath, DefaultIgnoreFile))
if err != nil {
return nil, err
}
err = filepath.Walk(absRootPath, func(absPath string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// Skip root directory
if absPath == absRootPath {
return nil
}
// Skip files that are not a directory or regular file
if !info.IsDir() && !info.Mode().IsRegular() {
return nil
}
// Get relative path from root
relPath, err := filepath.Rel(absRootPath, absPath)
if err != nil {
return err
}
// Skip file if it is ignored by ignore file
if shouldIgnore(relPath) {
return nil
}
files = append(files, &LocalFile{
absPath: absPath,
relPath: relPath,
info: info,
})
return nil
})
if err != nil {
return nil, fmt.Errorf("Failed to prepare local files: %s", err)
}
return files, err
}
func (self *Drive) prepareRemoteFiles(rootDir *drive.File, sortOrder string) ([]*RemoteFile, error) {
// Find all files which has rootDir as root
listArgs := listAllFilesArgs{
query: fmt.Sprintf("appProperties has {key='syncRootId' and value='%s'}", rootDir.Id),
fields: []googleapi.Field{"nextPageToken", "files(id,name,parents,md5Checksum,mimeType,size,modifiedTime)"},
sortOrder: sortOrder,
}
files, err := self.listAllFiles(listArgs)
if err != nil {
return nil, fmt.Errorf("Failed listing files: %s", err)
}
if err := checkFiles(files); err != nil {
return nil, err
}
relPaths, err := prepareRemoteRelPaths(rootDir, files)
if err != nil {
return nil, err
}
var remoteFiles []*RemoteFile
for _, f := range files {
relPath, ok := relPaths[f.Id]
if !ok {
return nil, fmt.Errorf("File %s does not have a valid parent", f.Id)
}
remoteFiles = append(remoteFiles, &RemoteFile{
relPath: relPath,
file: f,
})
}
return remoteFiles, nil
}
func prepareRemoteRelPaths(root *drive.File, files []*drive.File) (map[string]string, error) {
// The tree only holds integer values so we use
// maps to lookup file by index and index by file id
indexLookup := map[string]graph.NI{}
fileLookup := map[graph.NI]*drive.File{}
// All files includes root dir
allFiles := append([]*drive.File{root}, files...)
// Prepare lookup maps
for i, f := range allFiles {
indexLookup[f.Id] = graph.NI(i)
fileLookup[graph.NI(i)] = f
}
// This will hold 'parent index' -> 'file index' relationships
pathEnds := make([]graph.PathEnd, len(allFiles))
// Prepare parent -> file relationships
for i, f := range allFiles {
if f == root {
pathEnds[i] = graph.PathEnd{From: -1}
continue
}
// Lookup index of parent
parentIdx, found := indexLookup[f.Parents[0]]
if !found {
return nil, fmt.Errorf("Could not find parent of %s (%s)", f.Id, f.Name)
}
pathEnds[i] = graph.PathEnd{From: parentIdx}
}
// Create parent pointer tree and calculate path lengths
tree := &graph.FromList{Paths: pathEnds}
tree.RecalcLeaves()
tree.RecalcLen()
// This will hold a map of file id => relative path
paths := map[string]string{}
// Find relative path from root for all files
for _, f := range allFiles {
if f == root {
continue
}
// Find nodes between root and file
nodes := tree.PathTo(indexLookup[f.Id], nil)
// This will hold the name of all paths between root and
// file (exluding root and including file itself)
pathNames := []string{}
// Lookup file for each node and grab name
for _, n := range nodes {
file := fileLookup[n]
if file == root {
continue
}
pathNames = append(pathNames, file.Name)
}
// Join path names to form relative path and add to map
paths[f.Id] = filepath.Join(pathNames...)
}
return paths, nil
}
func checkFiles(files []*drive.File) error {
uniq := map[string]string{}
for _, f := range files {
// Ensure all files have exactly one parent
if len(f.Parents) != 1 {
return fmt.Errorf("File %s does not have exacly one parent", f.Id)
}
// Ensure that there are no duplicate files
uniqKey := f.Name + f.Parents[0]
if dupeId, isDupe := uniq[uniqKey]; isDupe {
return fmt.Errorf("Found name collision between %s and %s", f.Id, dupeId)
}
uniq[uniqKey] = f.Id
}
return nil
}
type LocalFile struct {
absPath string
relPath string
info os.FileInfo
}
type RemoteFile struct {
relPath string
file *drive.File
}
type changedFile struct {
local *LocalFile
remote *RemoteFile
}
type syncFiles struct {
root *RemoteFile
local []*LocalFile
remote []*RemoteFile
compare FileComparer
}
type FileComparer interface {
Changed(*LocalFile, *RemoteFile) bool
}
func (self LocalFile) AbsPath() string {
return self.absPath
}
func (self LocalFile) Size() int64 {
return self.info.Size()
}
func (self LocalFile) Modified() time.Time {
return self.info.ModTime()
}
func (self RemoteFile) Md5() string {
return self.file.Md5Checksum
}
func (self RemoteFile) Size() int64 {
return self.file.Size
}
func (self RemoteFile) Modified() time.Time {
t, _ := time.Parse(time.RFC3339, self.file.ModifiedTime)
return t
}
func (self *changedFile) compareModTime() ModTime {
localTime := self.local.Modified()
remoteTime := self.remote.Modified()
if localTime.After(remoteTime) {
return LocalLastModified
}
if remoteTime.After(localTime) {
return RemoteLastModified
}
return EqualModifiedTime
}
func (self *changedFile) compareSize() LargestSize {
localSize := self.local.Size()
remoteSize := self.remote.Size()
if localSize > remoteSize {
return LocalLargestSize
}
if remoteSize > localSize {
return RemoteLargestSize
}
return EqualSize
}
func (self *syncFiles) filterMissingRemoteDirs() []*LocalFile {
var files []*LocalFile
for _, lf := range self.local {
if lf.info.IsDir() && !self.existsRemote(lf) {
files = append(files, lf)
}
}
return files
}
func (self *syncFiles) filterMissingLocalDirs() []*RemoteFile {
var files []*RemoteFile
for _, rf := range self.remote {
if isDir(rf.file) && !self.existsLocal(rf) {
files = append(files, rf)
}
}
return files
}
func (self *syncFiles) filterMissingRemoteFiles() []*LocalFile {
var files []*LocalFile
for _, lf := range self.local {
if !lf.info.IsDir() && !self.existsRemote(lf) {
files = append(files, lf)
}
}
return files
}
func (self *syncFiles) filterMissingLocalFiles() []*RemoteFile {
var files []*RemoteFile
for _, rf := range self.remote {
if !isDir(rf.file) && !self.existsLocal(rf) {
files = append(files, rf)
}
}
return files
}
func (self *syncFiles) filterChangedLocalFiles() []*changedFile {
var files []*changedFile
for _, lf := range self.local {
// Skip directories
if lf.info.IsDir() {
continue
}
// Skip files that don't exist on drive
rf, found := self.findRemoteByPath(lf.relPath)
if !found {
continue
}
// Check if file has changed
if self.compare.Changed(lf, rf) {
files = append(files, &changedFile{
local: lf,
remote: rf,
})
}
}
return files
}
func (self *syncFiles) filterChangedRemoteFiles() []*changedFile {
var files []*changedFile
for _, rf := range self.remote {
// Skip directories
if isDir(rf.file) {
continue
}
// Skip local files that don't exist
lf, found := self.findLocalByPath(rf.relPath)
if !found {
continue
}
// Check if file has changed
if self.compare.Changed(lf, rf) {
files = append(files, &changedFile{
local: lf,
remote: rf,
})
}
}
return files
}
func (self *syncFiles) filterExtraneousRemoteFiles() []*RemoteFile {
var files []*RemoteFile
for _, rf := range self.remote {
if !self.existsLocal(rf) {
files = append(files, rf)
}
}
return files
}
func (self *syncFiles) filterExtraneousLocalFiles() []*LocalFile {
var files []*LocalFile
for _, lf := range self.local {
if !self.existsRemote(lf) {
files = append(files, lf)
}
}
return files
}
func (self *syncFiles) existsRemote(lf *LocalFile) bool {
_, found := self.findRemoteByPath(lf.relPath)
return found
}
func (self *syncFiles) existsLocal(rf *RemoteFile) bool {
_, found := self.findLocalByPath(rf.relPath)
return found
}
func (self *syncFiles) findRemoteByPath(relPath string) (*RemoteFile, bool) {
if relPath == "." {
return self.root, true
}
for _, rf := range self.remote {
if relPath == rf.relPath {
return rf, true
}
}
return nil, false
}
func (self *syncFiles) findLocalByPath(relPath string) (*LocalFile, bool) {
for _, lf := range self.local {
if relPath == lf.relPath {
return lf, true
}
}
return nil, false
}
func findLocalConflicts(files []*changedFile) []*changedFile {
var conflicts []*changedFile
for _, cf := range files {
if cf.compareModTime() == LocalLastModified {
conflicts = append(conflicts, cf)
}
}
return conflicts
}
func findRemoteConflicts(files []*changedFile) []*changedFile {
var conflicts []*changedFile
for _, cf := range files {
if cf.compareModTime() == RemoteLastModified {
conflicts = append(conflicts, cf)
}
}
return conflicts
}
type byLocalPathLength []*LocalFile
func (self byLocalPathLength) Len() int {
return len(self)
}
func (self byLocalPathLength) Swap(i, j int) {
self[i], self[j] = self[j], self[i]
}
func (self byLocalPathLength) Less(i, j int) bool {
return pathLength(self[i].relPath) < pathLength(self[j].relPath)
}
type byRemotePathLength []*RemoteFile
func (self byRemotePathLength) Len() int {
return len(self)
}
func (self byRemotePathLength) Swap(i, j int) {
self[i], self[j] = self[j], self[i]
}
func (self byRemotePathLength) Less(i, j int) bool {
return pathLength(self[i].relPath) < pathLength(self[j].relPath)
}
type byRemotePath []*RemoteFile
func (self byRemotePath) Len() int {
return len(self)
}
func (self byRemotePath) Swap(i, j int) {
self[i], self[j] = self[j], self[i]
}
func (self byRemotePath) Less(i, j int) bool {
return strings.ToLower(self[i].relPath) < strings.ToLower(self[j].relPath)
}
type ignoreFunc func(string) bool
func prepareIgnorer(path string) (ignoreFunc, error) {
acceptAll := func(string) bool {
return false
}
if !fileExists(path) {
return acceptAll, nil
}
ignorer, err := ignore.CompileIgnoreFile(path)
if err != nil {
return acceptAll, fmt.Errorf("Failed to prepare ignorer: %s", err)
}
return ignorer.MatchesPath, nil
}
func formatConflicts(conflicts []*changedFile, out io.Writer) {
w := new(tabwriter.Writer)
w.Init(out, 0, 0, 3, ' ', 0)
fmt.Fprintln(w, "Path\tSize Local\tSize Remote\tModified Local\tModified Remote")
for _, cf := range conflicts {
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n",
truncateString(cf.local.relPath, 60),
formatSize(cf.local.Size(), false),
formatSize(cf.remote.Size(), false),
cf.local.Modified().Local().Format("Jan _2 2006 15:04:05.000"),
cf.remote.Modified().Local().Format("Jan _2 2006 15:04:05.000"),
)
}
w.Flush()
}
================================================
FILE: drive/sync_download.go
================================================
package drive
import (
"bytes"
"fmt"
"google.golang.org/api/drive/v3"
"google.golang.org/api/googleapi"
"io"
"os"
"path/filepath"
"sort"
"time"
)
type DownloadSyncArgs struct {
Out io.Writer
Progress io.Writer
RootId string
Path string
DryRun bool
DeleteExtraneous bool
Timeout time.Duration
Resolution ConflictResolution
Comparer FileComparer
}
func (self *Drive) DownloadSync(args DownloadSyncArgs) error {
fmt.Fprintln(args.Out, "Starting sync...")
started := time.Now()
// Get remote root dir
rootDir, err := self.getSyncRoot(args.RootId)
if err != nil {
return err
}
fmt.Fprintln(args.Out, "Collecting file information...")
files, err := self.prepareSyncFiles(args.Path, rootDir, args.Comparer)
if err != nil {
return err
}
// Find changed files
changedFiles := files.filterChangedRemoteFiles()
fmt.Fprintf(args.Out, "Found %d local files and %d remote files\n", len(files.local), len(files.remote))
// Ensure that we don't overwrite any local changes
if args.Resolution == NoResolution {
err = ensureNoLocalModifications(changedFiles)
if err != nil {
return fmt.Errorf("Conflict detected!\nThe following files have changed and the local file are newer than it's remote counterpart:\n\n%s\nNo conflict resolution was given, aborting...", err)
}
}
// Create missing directories
err = self.createMissingLocalDirs(files, args)
if err != nil {
return err
}
// Download missing files
err = self.downloadMissingFiles(files, args)
if err != nil {
return err
}
// Download files that has changed
err = self.downloadChangedFiles(changedFiles, args)
if err != nil {
return err
}
// Delete extraneous local files
if args.DeleteExtraneous {
err = self.deleteExtraneousLocalFiles(files, args)
if err != nil {
return err
}
}
fmt.Fprintf(args.Out, "Sync finished in %s\n", time.Since(started))
return nil
}
func (self *Drive) getSyncRoot(rootId string) (*drive.File, error) {
fields := []googleapi.Field{"id", "name", "mimeType", "appProperties"}
f, err := self.service.Files.Get(rootId).Fields(fields...).Do()
if err != nil {
return nil, fmt.Errorf("Failed to find root dir: %s", err)
}
// Ensure file is a directory
if !isDir(f) {
return nil, fmt.Errorf("Provided root id is not a directory")
}
// Ensure directory is a proper syncRoot
if _, ok := f.AppProperties["syncRoot"]; !ok {
return nil, fmt.Errorf("Provided id is not a sync root directory")
}
return f, nil
}
func (self *Drive) createMissingLocalDirs(files *syncFiles, args DownloadSyncArgs) error {
missingDirs := files.filterMissingLocalDirs()
missingCount := len(missingDirs)
if missingCount > 0 {
fmt.Fprintf(args.Out, "\n%d local directories are missing\n", missingCount)
}
// Sort directories so that the dirs with the shortest path comes first
sort.Sort(byRemotePathLength(missingDirs))
for i, rf := range missingDirs {
absPath, err := filepath.Abs(filepath.Join(args.Path, rf.relPath))
if err != nil {
return fmt.Errorf("Failed to determine local absolute path: %s", err)
}
fmt.Fprintf(args.Out, "[%04d/%04d] Creating directory %s\n", i+1, missingCount, filepath.Join(filepath.Base(args.Path), rf.relPath))
if args.DryRun {
continue
}
os.MkdirAll(absPath, 0775)
}
return nil
}
func (self *Drive) downloadMissingFiles(files *syncFiles, args DownloadSyncArgs) error {
missingFiles := files.filterMissingLocalFiles()
missingCount := len(missingFiles)
if missingCount > 0 {
fmt.Fprintf(args.Out, "\n%d local files are missing\n", missingCount)
}
for i, rf := range missingFiles {
absPath, err := filepath.Abs(filepath.Join(args.Path, rf.relPath))
if err != nil {
return fmt.Errorf("Failed to determine local absolute path: %s", err)
}
fmt.Fprintf(args.Out, "[%04d/%04d] Downloading %s -> %s\n", i+1, missingCount, rf.relPath, filepath.Join(filepath.Base(args.Path), rf.relPath))
err = self.downloadRemoteFile(rf.file.Id, absPath, args, 0)
if err != nil {
return err
}
}
return nil
}
func (self *Drive) downloadChangedFiles(changedFiles []*changedFile, args DownloadSyncArgs) error {
changedCount := len(changedFiles)
if changedCount > 0 {
fmt.Fprintf(args.Out, "\n%d remote files has changed\n", changedCount)
}
for i, cf := range changedFiles {
if skip, reason := checkLocalConflict(cf, args.Resolution); skip {
fmt.Fprintf(args.Out, "[%04d/%04d] Skipping %s (%s)\n", i+1, changedCount, cf.remote.relPath, reason)
continue
}
absPath, err := filepath.Abs(filepath.Join(args.Path, cf.remote.relPath))
if err != nil {
return fmt.Errorf("Failed to determine local absolute path: %s", err)
}
fmt.Fprintf(args.Out, "[%04d/%04d] Downloading %s -> %s\n", i+1, changedCount, cf.remote.relPath, filepath.Join(filepath.Base(args.Path), cf.remote.relPath))
err = self.downloadRemoteFile(cf.remote.file.Id, absPath, args, 0)
if err != nil {
return err
}
}
return nil
}
func (self *Drive) downloadRemoteFile(id, fpath string, args DownloadSyncArgs, try int) error {
if args.DryRun {
return nil
}
// Get timeout reader wrapper and context
timeoutReaderWrapper, ctx := getTimeoutReaderWrapperContext(args.Timeout)
res, err := self.service.Files.Get(id).Context(ctx).Download()
if err != nil {
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)
}
}
// Close body on function exit
defer res.Body.Close()
// Wrap response body in progress reader
progressReader := getProgressReader(res.Body, args.Progress, res.ContentLength)
// Wrap reader in timeout reader
reader := timeoutReaderWrapper(progressReader)
// Ensure any parent directories exists
if err = mkdir(fpath); err != nil {
return err
}
// Download to tmp file
tmpPath := fpath + ".incomplete"
// Create new file
outFile, err := os.Create(tmpPath)
if err != nil {
return fmt.Errorf("Unable to create local file: %s", err)
}
// Save file to disk
_, err = io.Copy(outFile, reader)
if err != nil {
outFile.Close()
if try < MaxErrorRetries {
exponentialBackoffSleep(try)
try++
return self.downloadRemoteFile(id, fpath, args, try)
} else {
os.Remove(tmpPath)
return fmt.Errorf("Download was interrupted: %s", err)
}
}
// Close file
outFile.Close()
// Rename tmp file to proper filename
return os.Rename(tmpPath, fpath)
}
func (self *Drive) deleteExtraneousLocalFiles(files *syncFiles, args DownloadSyncArgs) error {
extraneousFiles := files.filterExtraneousLocalFiles()
extraneousCount := len(extraneousFiles)
if extraneousCount > 0 {
fmt.Fprintf(args.Out, "\n%d local files are extraneous\n", extraneousCount)
}
// Sort files so that the files with the longest path comes first
sort.Sort(sort.Reverse(byLocalPathLength(extraneousFiles)))
for i, lf := range extraneousFiles {
fmt.Fprintf(args.Out, "[%04d/%04d] Deleting %s\n", i+1, extraneousCount, lf.absPath)
if args.DryRun {
continue
}
err := os.Remove(lf.absPath)
if err != nil {
return fmt.Errorf("Failed to delete local file: %s", err)
}
}
return nil
}
func checkLocalConflict(cf *changedFile, resolution ConflictResolution) (bool, string) {
// No conflict unless local file was last modified
if cf.compareModTime() != LocalLastModified {
return false, ""
}
// Don't skip if want to keep the remote file
if resolution == KeepRemote {
return false, ""
}
// Skip if we want to keep the local file
if resolution == KeepLocal {
return true, "conflicting file, keeping local file"
}
if resolution == KeepLargest {
largest := cf.compareSize()
// Skip if the local file is largest
if largest == LocalLargestSize {
return true, "conflicting file, local file is largest, keeping local"
}
// Don't skip if the remote file is largest
if largest == RemoteLargestSize {
return false, ""
}
// Keep local if both files have the same size
if largest == EqualSize {
return true, "conflicting file, file sizes are equal, keeping local"
}
}
// The conditionals above should cover all cases,
// unless the programmer did something wrong,
// in which case we default to being non-destructive and skip the file
return true, "conflicting file, unhandled case"
}
func ensureNoLocalModifications(files []*changedFile) error {
conflicts := findLocalConflicts(files)
if len(conflicts) == 0 {
return nil
}
buffer := bytes.NewBufferString("")
formatConflicts(conflicts, buffer)
return fmt.Errorf(buffer.String())
}
================================================
FILE: drive/sync_list.go
================================================
package drive
import (
"fmt"
"google.golang.org/api/drive/v3"
"google.golang.org/api/googleapi"
"io"
"sort"
"text/tabwriter"
)
type ListSyncArgs struct {
Out io.Writer
SkipHeader bool
}
func (self *Drive) ListSync(args ListSyncArgs) error {
listArgs := listAllFilesArgs{
query: "appProperties has {key='syncRoot' and value='true'}",
fields: []googleapi.Field{"nextPageToken", "files(id,name,mimeType,createdTime)"},
}
files, err := self.listAllFiles(listArgs)
if err != nil {
return err
}
printSyncDirectories(files, args)
return nil
}
type ListRecursiveSyncArgs struct {
Out io.Writer
RootId string
SkipHeader bool
PathWidth int64
SizeInBytes bool
SortOrder string
}
func (self *Drive) ListRecursiveSync(args ListRecursiveSyncArgs) error {
rootDir, err := self.getSyncRoot(args.RootId)
if err != nil {
return err
}
files, err := self.prepareRemoteFiles(rootDir, args.SortOrder)
if err != nil {
return err
}
printSyncDirContent(files, args)
return nil
}
func printSyncDirectories(files []*drive.File, args ListSyncArgs) {
w := new(tabwriter.Writer)
w.Init(args.Out, 0, 0, 3, ' ', 0)
if !args.SkipHeader {
fmt.Fprintln(w, "Id\tName\tCreated")
}
for _, f := range files {
fmt.Fprintf(w, "%s\t%s\t%s\n",
f.Id,
f.Name,
formatDatetime(f.CreatedTime),
)
}
w.Flush()
}
func printSyncDirContent(files []*RemoteFile, args ListRecursiveSyncArgs) {
if args.SortOrder == "" {
// Sort files by path
sort.Sort(byRemotePath(files))
}
w := new(tabwriter.Writer)
w.Init(args.Out, 0, 0, 3, ' ', 0)
if !args.SkipHeader {
fmt.Fprintln(w, "Id\tPath\tType\tSize\tModified")
}
for _, rf := range files {
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n",
rf.file.Id,
truncateString(rf.relPath, int(args.PathWidth)),
filetype(rf.file),
formatSize(rf.file.Size, args.SizeInBytes),
formatDatetime(rf.file.ModifiedTime),
)
}
w.Flush()
}
================================================
FILE: drive/sync_upload.go
================================================
package drive
import (
"bytes"
"fmt"
"google.golang.org/api/drive/v3"
"google.golang.org/api/googleapi"
"io"
"os"
"path/filepath"
"sort"
"time"
)
type UploadSyncArgs struct {
Out io.Writer
Progress io.Writer
Path string
RootId string
DryRun bool
DeleteExtraneous bool
ChunkSize int64
Timeout time.Duration
Resolution ConflictResolution
Comparer FileComparer
}
func (self *Drive) UploadSync(args UploadSyncArgs) error {
if args.ChunkSize > intMax()-1 {
return fmt.Errorf("Chunk size is to big, max chunk size for this computer is %d", intMax()-1)
}
fmt.Fprintln(args.Out, "Starting sync...")
started := time.Now()
// Create root directory if it does not exist
rootDir, err := self.prepareSyncRoot(args)
if err != nil {
return err
}
fmt.Fprintln(args.Out, "Collecting local and remote file information...")
files, err := self.prepareSyncFiles(args.Path, rootDir, args.Comparer)
if err != nil {
return err
}
// Find missing and changed files
changedFiles := files.filterChangedLocalFiles()
missingFiles := files.filterMissingRemoteFiles()
fmt.Fprintf(args.Out, "Found %d local files and %d remote files\n", len(files.local), len(files.remote))
// Ensure that there is enough free space on drive
if ok, msg := self.checkRemoteFreeSpace(missingFiles, changedFiles); !ok {
return fmt.Errorf(msg)
}
// Ensure that we don't overwrite any remote changes
if args.Resolution == NoResolution {
err = ensureNoRemoteModifications(changedFiles)
if err != nil {
return fmt.Errorf("Conflict detected!\nThe following files have changed and the remote file are newer than it's local counterpart:\n\n%s\nNo conflict resolution was given, aborting...", err)
}
}
// Create missing directories
files, err = self.createMissingRemoteDirs(files, args)
if err != nil {
return err
}
// Upload missing files
err = self.uploadMissingFiles(missingFiles, files, args)
if err != nil {
return err
}
// Update modified files
err = self.updateChangedFiles(changedFiles, rootDir, args)
if err != nil {
return err
}
// Delete extraneous files on drive
if args.DeleteExtraneous {
err = self.deleteExtraneousRemoteFiles(files, args)
if err != nil {
return err
}
}
fmt.Fprintf(args.Out, "Sync finished in %s\n", time.Since(started))
return nil
}
func (self *Drive) prepareSyncRoot(args UploadSyncArgs) (*drive.File, error) {
fields := []googleapi.Field{"id", "name", "mimeType", "appProperties"}
f, err := self.service.Files.Get(args.RootId).Fields(fields...).Do()
if err != nil {
return nil, fmt.Errorf("Failed to find root dir: %s", err)
}
// Ensure file is a directory
if !isDir(f) {
return nil, fmt.Errorf("Provided root id is not a directory")
}
// Return directory if syncRoot property is already set
if _, ok := f.AppProperties["syncRoot"]; ok {
return f, nil
}
// This is the first time this directory have been used for sync
// Check if the directory is empty
isEmpty, err := self.dirIsEmpty(f.Id)
if err != nil {
return nil, fmt.Errorf("Failed to check if root dir is empty: %s", err)
}
// Ensure that the directory is empty
if !isEmpty {
return nil, fmt.Errorf("Root directory is not empty, the initial sync requires an empty directory")
}
// Update directory with syncRoot property
dstFile := &drive.File{
AppProperties: map[string]string{"sync": "true", "syncRoot": "true"},
}
f, err = self.service.Files.Update(f.Id, dstFile).Fields(fields...).Do()
if err != nil {
return nil, fmt.Errorf("Failed to update root directory: %s", err)
}
return f, nil
}
func (self *Drive) createMissingRemoteDirs(files *syncFiles, args UploadSyncArgs) (*syncFiles, error) {
missingDirs := files.filterMissingRemoteDirs()
missingCount := len(missingDirs)
if missingCount > 0 {
fmt.Fprintf(args.Out, "\n%d remote directories are missing\n", missingCount)
}
// Sort directories so that the dirs with the shortest path comes first
sort.Sort(byLocalPathLength(missingDirs))
for i, lf := range missingDirs {
parentPath := parentFilePath(lf.relPath)
parent, ok := files.findRemoteByPath(parentPath)
if !ok {
return nil, fmt.Errorf("Could not find remote directory with path '%s'", parentPath)
}
fmt.Fprintf(args.Out, "[%04d/%04d] Creating directory %s\n", i+1, missingCount, filepath.Join(files.root.file.Name, lf.relPath))
f, err := self.createMissingRemoteDir(createMissingRemoteDirArgs{
name: lf.info.Name(),
parentId: parent.file.Id,
rootId: args.RootId,
dryRun: args.DryRun,
try: 0,
})
if err != nil {
return nil, err
}
files.remote = append(files.remote, &RemoteFile{
relPath: lf.relPath,
file: f,
})
}
return files, nil
}
type createMissingRemoteDirArgs struct {
name string
parentId string
rootId string
dryRun bool
try int
}
func (self *Drive) uploadMissingFiles(missingFiles []*LocalFile, files *syncFiles, args UploadSyncArgs) error {
missingCount := len(missingFiles)
if missingCount > 0 {
fmt.Fprintf(args.Out, "\n%d remote files are missing\n", missingCount)
}
for i, lf := range missingFiles {
parentPath := parentFilePath(lf.relPath)
parent, ok := files.findRemoteByPath(parentPath)
if !ok {
return fmt.Errorf("Could not find remote directory with path '%s'", parentPath)
}
fmt.Fprintf(args.Out, "[%04d/%04d] Uploading %s -> %s\n", i+1, missingCount, lf.relPath, filepath.Join(files.root.file.Name, lf.relPath))
err := self.uploadMissingFile(parent.file.Id, lf, args, 0)
if err != nil {
return err
}
}
return nil
}
func (self *Drive) updateChangedFiles(changedFiles []*changedFile, root *drive.File, args UploadSyncArgs) error {
changedCount := len(changedFiles)
if changedCount > 0 {
fmt.Fprintf(args.Out, "\n%d local files has changed\n", changedCount)
}
for i, cf := range changedFiles {
if skip, reason := checkRemoteConflict(cf, args.Resolution); skip {
fmt.Fprintf(args.Out, "[%04d/%04d] Skipping %s (%s)\n", i+1, changedCount, cf.local.relPath, reason)
continue
}
fmt.Fprintf(args.Out, "[%04d/%04d] Updating %s -> %s\n", i+1, changedCount, cf.local.relPath, filepath.Join(root.Name, cf.local.relPath))
err := self.updateChangedFile(cf, args, 0)
if err != nil {
return err
}
}
return nil
}
func (self *Drive) deleteExtraneousRemoteFiles(files *syncFiles, args UploadSyncArgs) error {
extraneousFiles := files.filterExtraneousRemoteFiles()
extraneousCount := len(extraneousFiles)
if extraneousCount > 0 {
fmt.Fprintf(args.Out, "\n%d remote files are extraneous\n", extraneousCount)
}
// Sort files so that the files with the longest path comes first
sort.Sort(sort.Reverse(byRemotePathLength(extraneousFiles)))
for i, rf := range extraneousFiles {
fmt.Fprintf(args.Out, "[%04d/%04d] Deleting %s\n", i+1, extraneousCount, filepath.Join(files.root.file.Name, rf.relPath))
err := self.deleteRemoteFile(rf, args, 0)
if err != nil {
return err
}
}
return nil
}
func (self *Drive) createMissingRemoteDir(args createMissingRemoteDirArgs) (*drive.File, error) {
dstFile := &drive.File{
Name: args.name,
MimeType: DirectoryMimeType,
Parents: []string{args.parentId},
AppProperties: map[string]string{"sync": "true", "syncRootId": args.rootId},
}
if args.dryRun {
return dstFile, nil
}
f, err := self.service.Files.Create(dstFile).Do()
if err != nil {
if isBackendOrRateLimitError(err) && args.try < MaxErrorRetries {
exponentialBackoffSleep(args.try)
args.try++
return self.createMissingRemoteDir(args)
} else {
return nil, fmt.Errorf("Failed to create directory: %s", err)
}
}
return f, nil
}
func (self *Drive) uploadMissingFile(parentId string, lf *LocalFile, args UploadSyncArgs, try int) error {
if args.DryRun {
return nil
}
srcFile, err := os.Open(lf.absPath)
if err != nil {
return fmt.Errorf("Failed to open file: %s", err)
}
// Close file on function exit
defer srcFile.Close()
// Instantiate drive file
dstFile := &drive.File{
Name: lf.info.Name(),
Parents: []string{parentId},
AppProperties: map[string]string{"sync": "true", "syncRootId": args.RootId},
}
// Chunk size option
chunkSize := googleapi.ChunkSize(int(args.ChunkSize))
// Wrap file in progress reader
progressReader := getProgressReader(srcFile, args.Progress, lf.info.Size())
// Wrap reader in timeout reader
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 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)
}
}
return nil
}
func (self *Drive) updateChangedFile(cf *changedFile, args UploadSyncArgs, try int) error {
if args.DryRun {
return nil
}
srcFile, err := os.Open(cf.local.absPath)
if err != nil {
return fmt.Errorf("Failed to open file: %s", err)
}
// Close file on function exit
defer srcFile.Close()
// Instantiate drive file
dstFile := &drive.File{}
// Chunk size option
chunkSize := googleapi.ChunkSize(int(args.ChunkSize))
// Wrap file in progress reader
progressReader := getProgressReader(srcFile, args.Progress, cf.local.info.Size())
// Wrap reader in timeout reader
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 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)
}
}
return nil
}
func (self *Drive) deleteRemoteFile(rf *RemoteFile, args UploadSyncArgs, try int) error {
if args.DryRun {
return nil
}
err := self.service.Files.Delete(rf.file.Id).Do()
if err != nil {
if isBackendOrRateLimitError(err) && try < MaxErrorRetries {
exponentialBackoffSleep(try)
try++
return self.deleteRemoteFile(rf, args, try)
} else {
return fmt.Errorf("Failed to delete file: %s", err)
}
}
return nil
}
func (self *Drive) dirIsEmpty(id string) (bool, error) {
query := fmt.Sprintf("'%s' in parents", id)
fileList, err := self.service.Files.List().Q(query).Do()
if err != nil {
return false, fmt.Errorf("Empty dir check failed: ", err)
}
return len(fileList.Files) == 0, nil
}
func checkRemoteConflict(cf *changedFile, resolution ConflictResolution) (bool, string) {
// No conflict unless remote file was last modified
if cf.compareModTime() != RemoteLastModified {
return false, ""
}
// Don't skip if want to keep the local file
if resolution == KeepLocal {
return false, ""
}
// Skip if we want to keep the remote file
if resolution == KeepRemote {
return true, "conflicting file, keeping remote file"
}
if resolution == KeepLargest {
largest := cf.compareSize()
// Skip if the remote file is largest
if largest == RemoteLargestSize {
return true, "conflicting file, remote file is largest, keeping remote"
}
// Don't skip if the local file is largest
if largest == LocalLargestSize {
return false, ""
}
// Keep remote if both files have the same size
if largest == EqualSize {
return true, "conflicting file, file sizes are equal, keeping remote"
}
}
// The conditionals above should cover all cases,
// unless the programmer did something wrong,
// in which case we default to being non-destructive and skip the file
return true, "conflicting file, unhandled case"
}
func ensureNoRemoteModifications(files []*changedFile) error {
conflicts := findRemoteConflicts(files)
if len(conflicts) == 0 {
return nil
}
buffer := bytes.NewBufferString("")
formatConflicts(conflicts, buffer)
return fmt.Errorf(buffer.String())
}
func (self *Drive) checkRemoteFreeSpace(missingFiles []*LocalFile, changedFiles []*changedFile) (bool, string) {
about, err := self.service.About.Get().Fields("storageQuota").Do()
if err != nil {
return false, fmt.Sprintf("Failed to determine free space: %s", err)
}
quota := about.StorageQuota
if quota.Limit == 0 {
return true, ""
}
freeSpace := quota.Limit - quota.Usage
var totalSize int64
for _, lf := range missingFiles {
totalSize += lf.Size()
}
for _, cf := range changedFiles {
totalSize += cf.local.Size()
}
if totalSize > freeSpace {
return false, fmt.Sprintf("Not enough free space, have %s need %s", formatSize(freeSpace, false), formatSize(totalSize, false))
}
return true, ""
}
================================================
FILE: drive/timeout_reader.go
================================================
package drive
import (
"golang.org/x/net/context"
"io"
"sync"
"time"
)
const TimeoutTimerInterval = time.Second * 10
type timeoutReaderWrapper func(io.Reader) io.Reader
func getTimeoutReaderWrapperContext(timeout time.Duration) (timeoutReaderWrapper, context.Context) {
ctx, cancel := context.WithCancel(context.TODO())
wrapper := func(r io.Reader) io.Reader {
// 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, timeout time.Duration) (io.Reader, context.Context) {
ctx, cancel := context.WithCancel(context.TODO())
// 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, timeout time.Duration) io.Reader {
return &TimeoutReader{
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
maxIdleTimeout time.Duration
done bool
}
func (self *TimeoutReader) Read(p []byte) (int, error) {
if self.timer == nil {
self.startTimer()
}
self.mutex.Lock()
// Read
n, err := self.reader.Read(p)
self.lastActivity = time.Now()
self.done = (err != nil)
self.mutex.Unlock()
if self.done {
self.stopTimer()
}
return n, err
}
func (self *TimeoutReader) startTimer() {
self.mutex.Lock()
defer self.mutex.Unlock()
if !self.done {
self.timer = time.AfterFunc(TimeoutTimerInterval, self.timeout)
}
}
func (self *TimeoutReader) stopTimer() {
self.mutex.Lock()
defer self.mutex.Unlock()
if self.timer != nil {
self.timer.Stop()
}
}
func (self *TimeoutReader) timeout() {
self.mutex.Lock()
if self.done {
self.mutex.Unlock()
return
}
if time.Since(self.lastActivity) > self.maxIdleTimeout {
self.cancel()
self.mutex.Unlock()
return
}
self.mutex.Unlock()
self.startTimer()
}
================================================
FILE: drive/update.go
================================================
package drive
import (
"fmt"
"google.golang.org/api/drive/v3"
"google.golang.org/api/googleapi"
"io"
"mime"
"path/filepath"
"time"
)
type UpdateArgs struct {
Out io.Writer
Progress io.Writer
Id string
Path string
Name string
Description string
Parents []string
Mime string
Recursive bool
ChunkSize int64
Timeout time.Duration
}
func (self *Drive) Update(args UpdateArgs) error {
srcFile, srcFileInfo, err := openFile(args.Path)
if err != nil {
return fmt.Errorf("Failed to open file: %s", err)
}
defer srcFile.Close()
// Instantiate empty drive file
dstFile := &drive.File{Description: args.Description}
// Use provided file name or use filename
if args.Name == "" {
dstFile.Name = filepath.Base(srcFileInfo.Name())
} else {
dstFile.Name = args.Name
}
// Set provided mime type or get type based on file extension
if args.Mime == "" {
dstFile.MimeType = mime.TypeByExtension(filepath.Ext(dstFile.Name))
} else {
dstFile.MimeType = args.Mime
}
// Set parent folders
dstFile.Parents = args.Parents
// Chunk size option
chunkSize := googleapi.ChunkSize(int(args.ChunkSize))
// Wrap file in progress reader
progressReader := getProgressReader(srcFile, args.Progress, srcFileInfo.Size())
// Wrap reader in timeout reader
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)
}
// Calculate average upload rate
rate := calcRate(f.Size, started, time.Now())
fmt.Fprintf(args.Out, "Updated %s at %s/s, total %s\n", f.Id, formatSize(rate, false), formatSize(f.Size, false))
return nil
}
================================================
FILE: drive/upload.go
================================================
package drive
import (
"fmt"
"google.golang.org/api/drive/v3"
"google.golang.org/api/googleapi"
"io"
"mime"
"os"
"path/filepath"
"time"
)
type UploadArgs struct {
Out io.Writer
Progress io.Writer
Path string
Name string
Description string
Parents []string
Mime string
Recursive bool
Share bool
Delete bool
ChunkSize int64
Timeout time.Duration
}
func (self *Drive) Upload(args UploadArgs) error {
if args.ChunkSize > intMax()-1 {
return fmt.Errorf("Chunk size is to big, max chunk size for this computer is %d", intMax()-1)
}
// Ensure that none of the parents are sync dirs
for _, parent := range args.Parents {
isSyncDir, err := self.isSyncFile(parent)
if err != nil {
return err
}
if isSyncDir {
return fmt.Errorf("%s is a sync directory, use 'sync upload' instead", parent)
}
}
if args.Recursive {
return self.uploadRecursive(args)
}
info, err := os.Stat(args.Path)
if err != nil {
return fmt.Errorf("Failed stat file: %s", err)
}
if info.IsDir() {
return fmt.Errorf("'%s' is a directory, use --recursive to upload directories", info.Name())
}
f, rate, err := self.uploadFile(args)
if err != nil {
return err
}
fmt.Fprintf(args.Out, "Uploaded %s at %s/s, total %s\n", f.Id, formatSize(rate, false), formatSize(f.Size, false))
if args.Share {
err = self.shareAnyoneReader(f.Id)
if err != nil {
return err
}
fmt.Fprintf(args.Out, "File is readable by anyone at %s\n", f.WebContentLink)
}
if args.Delete {
err = os.Remove(args.Path)
if err != nil {
return fmt.Errorf("Failed to delete file: %s", err)
}
fmt.Fprintf(args.Out, "Removed %s\n", args.Path)
}
return nil
}
func (self *Drive) uploadRecursive(args UploadArgs) error {
info, err := os.Stat(args.Path)
if err != nil {
return fmt.Errorf("Failed stat file: %s", err)
}
if info.IsDir() {
args.Name = ""
return self.uploadDirectory(args)
} else if info.Mode().IsRegular() {
_, _, err := self.uploadFile(args)
return err
}
return nil
}
func (self *Drive) uploadDirectory(args UploadArgs) error {
srcFile, srcFileInfo, err := openFile(args.Path)
if err != nil {
return err
}
// Close file on function exit
defer srcFile.Close()
fmt.Fprintf(args.Out, "Creating directory %s\n", srcFileInfo.Name())
// Make directory on drive
f, err := self.mkdir(MkdirArgs{
Out: args.Out,
Name: srcFileInfo.Name(),
Parents: args.Parents,
Description: args.Description,
})
if err != nil {
return err
}
// Read files from directory
names, err := srcFile.Readdirnames(0)
if err != nil && err != io.EOF {
return fmt.Errorf("Failed reading directory: %s", err)
}
for _, name := range names {
// Copy args and set new path and parents
newArgs := args
newArgs.Path = filepath.Join(args.Path, name)
newArgs.Parents = []string{f.Id}
newArgs.Description = ""
// Upload
err = self.uploadRecursive(newArgs)
if err != nil {
return err
}
}
return nil
}
func (self *Drive) uploadFile(args UploadArgs) (*drive.File, int64, error) {
srcFile, srcFileInfo, err := openFile(args.Path)
if err != nil {
return nil, 0, err
}
// Close file on function exit
defer srcFile.Close()
// Instantiate empty drive file
dstFile := &drive.File{Description: args.Description}
// Use provided file name or use filename
if args.Name == "" {
dstFile.Name = filepath.Base(srcFileInfo.Name())
} else {
dstFile.Name = args.Name
}
// Set provided mime type or get type based on file extension
if args.Mime == "" {
dstFile.MimeType = mime.TypeByExtension(filepath.Ext(dstFile.Name))
} else {
dstFile.MimeType = args.Mime
}
// Set parent folders
dstFile.Parents = args.Parents
// Chunk size option
chunkSize := googleapi.ChunkSize(int(args.ChunkSize))
// Wrap file in progress reader
progressReader := getProgressReader(srcFile, args.Progress, srcFileInfo.Size())
// Wrap reader in timeout reader
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)
}
// Calculate average upload rate
rate := calcRate(f.Size, started, time.Now())
return f, rate, nil
}
type UploadStreamArgs struct {
Out io.Writer
In io.Reader
Name string
Description string
Parents []string
Mime string
Share bool
ChunkSize int64
Progress io.Writer
Timeout time.Duration
}
func (self *Drive) UploadStream(args UploadStreamArgs) error {
if args.ChunkSize > intMax()-1 {
return fmt.Errorf("Chunk size is to big, max chunk size for this computer is %d", intMax()-1)
}
// Instantiate empty drive file
dstFile := &drive.File{Name: args.Name, Description: args.Description}
// Set mime type if provided
if args.Mime != "" {
dstFile.MimeType = args.Mime
}
// Set parent folders
dstFile.Parents = args.Parents
// Chunk size option
chunkSize := googleapi.ChunkSize(int(args.ChunkSize))
// Wrap file in progress reader
progressReader := getProgressReader(args.In, args.Progress, 0)
// Wrap reader in timeout reader
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)
}
// Calculate average upload rate
rate := calcRate(f.Size, started, time.Now())
fmt.Fprintf(args.Out, "Uploaded %s at %s/s, total %s\n", f.Id, formatSize(rate, false), formatSize(f.Size, false))
if args.Share {
err = self.shareAnyoneReader(f.Id)
if err != nil {
return err
}
fmt.Fprintf(args.Out, "File is readable by anyone at %s\n", f.WebContentLink)
}
return nil
}
================================================
FILE: drive/util.go
================================================
package drive
import (
"fmt"
"math"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"unicode/utf8"
)
type kv struct {
key string
value string
}
func formatList(a []string) string {
return strings.Join(a, ", ")
}
func formatSize(bytes int64, forceBytes bool) string {
if bytes == 0 {
return ""
}
if forceBytes {
return fmt.Sprintf("%v B", bytes)
}
units := []string{"B", "KB", "MB", "GB", "TB", "PB"}
var i int
value := float64(bytes)
for value > 1000 {
value /= 1000
i++
}
return fmt.Sprintf("%.1f %s", value, units[i])
}
func calcRate(bytes int64, start, end time.Time) int64 {
seconds := float64(end.Sub(start).Seconds())
if seconds < 1.0 {
return bytes
}
return round(float64(bytes) / seconds)
}
func round(n float64) int64 {
if n < 0 {
return int64(math.Ceil(n - 0.5))
}
return int64(math.Floor(n + 0.5))
}
func formatBool(b bool) string {
return strings.Title(strconv.FormatBool(b))
}
func formatDatetime(iso string) string {
t, err := time.Parse(time.RFC3339, iso)
if err != nil {
return iso
}
local := t.Local()
year, month, day := local.Date()
hour, min, sec := local.Clock()
return fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d", year, month, day, hour, min, sec)
}
// Truncates string to given max length, and inserts ellipsis into
// the middle of the string to signify that the string has been truncated
func truncateString(str string, maxRunes int) string {
indicator := "..."
// Number of runes in string
runeCount := utf8.RuneCountInString(str)
// Return input string if length of input string is less than max length
// Input string is also returned if max length is less than 9 which is the minmal supported length
if runeCount <= maxRunes || maxRunes < 9 {
return str
}
// Number of remaining runes to be removed
remaining := (runeCount - maxRunes) + utf8.RuneCountInString(indicator)
var truncated string
var skip bool
for leftOffset, char := range str {
rightOffset := runeCount - (leftOffset + remaining)
// Start skipping chars when the left and right offsets are equal
// Or in the case where we wont be able to do an even split: when the left offset is larger than the right offset
if leftOffset == rightOffset || (leftOffset > rightOffset && !skip) {
skip = true
truncated += indicator
}
if skip && remaining > 0 {
// Skip char and decrement the remaining skip counter
remaining--
continue
}
// Add char to result string
truncated += string(char)
}
// Return truncated string
return truncated
}
func fileExists(path string) bool {
_, err := os.Stat(path)
if err == nil {
return true
}
return false
}
func mkdir(path string) error {
dir := filepath.Dir(path)
if fileExists(dir) {
return nil
}
return os.MkdirAll(dir, 0775)
}
func intMax() int64 {
return 1<<(strconv.IntSize-1) - 1
}
func pathLength(path string) int {
return strings.Count(path, string(os.PathSeparator))
}
func parentFilePath(path string) string {
dir, _ := filepath.Split(path)
return filepath.Dir(dir)
}
func pow(x int, y int) int {
f := math.Pow(float64(x), float64(y))
return int(f)
}
func min(x int, y int) int {
n := math.Min(float64(x), float64(y))
return int(n)
}
func openFile(path string) (*os.File, os.FileInfo, error) {
f, err := os.Open(path)
if err != nil {
return nil, nil, fmt.Errorf("Failed to open file: %s", err)
}
info, err := f.Stat()
if err != nil {
return nil, nil, fmt.Errorf("Failed getting file metadata: %s", err)
}
return f, info, nil
}
================================================
FILE: gdrive.go
================================================
package main
import (
"fmt"
"os"
"github.com/prasmussen/gdrive/cli"
)
const Name = "gdrive"
const Version = "2.1.1"
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"
var DefaultConfigDir = GetDefaultConfigDir()
func main() {
globalFlags := []cli.Flag{
cli.StringFlag{
Name: "configDir",
Patterns: []string{"-c", "--config"},
Description: fmt.Sprintf("Application path, default: %s", DefaultConfigDir),
DefaultValue: DefaultConfigDir,
},
cli.StringFlag{
Name: "refreshToken",
Patterns: []string{"--refresh-token"},
Description: "Oauth refresh token used to get access token (for advanced users)",
},
cli.StringFlag{
Name: "accessToken",
Patterns: []string{"--access-token"},
Description: "Oauth access token, only recommended for short-lived requests because of short lifetime (for advanced users)",
},
cli.StringFlag{
Name: "serviceAccount",
Patterns: []string{"--service-account"},
Description: "Oauth service account filename, used for server to server communication without user interaction (filename path is relative to config dir)",
},
}
handlers := []*cli.Handler{
&cli.Handler{
Pattern: "[global] list [options]",
Description: "List files",
Callback: listHandler,
FlagGroups: cli.FlagGroups{
cli.NewFlagGroup("global", globalFlags...),
cli.NewFlagGroup("options",
cli.IntFlag{
Name: "maxFiles",
Patterns: []string{"-m", "--max"},
Description: fmt.Sprintf("Max files to list, default: %d", DefaultMaxFiles),
DefaultValue: DefaultMaxFiles,
},
cli.StringFlag{
Name: "query",
Patterns: []string{"-q", "--query"},
Description: fmt.Sprintf(`Default query: "%s". See https://developers.google.com/drive/search-parameters`, DefaultQuery),
DefaultValue: DefaultQuery,
},
cli.StringFlag{
Name: "sortOrder",
Patterns: []string{"--order"},
Description: "Sort order. See https://godoc.org/google.golang.org/api/drive/v3#FilesListCall.OrderBy",
},
cli.IntFlag{
Name: "nameWidth",
Patterns: []string{"--name-width"},
Description: fmt.Sprintf("Width of name column, default: %d, minimum: 9, use 0 for full width", DefaultNameWidth),
DefaultValue: DefaultNameWidth,
},
cli.BoolFlag{
Name: "absPath",
Patterns: []string{"--absolute"},
Description: "Show absolute path to file (will only show path from first parent)",
OmitValue: true,
},
cli.BoolFlag{
Name: "skipHeader",
Patterns: []string{"--no-header"},
Description: "Dont print the header",
OmitValue: true,
},
cli.BoolFlag{
Name: "sizeInBytes",
Patterns: []string{"--bytes"},
Description: "Size in bytes",
OmitValue: true,
},
),
},
},
&cli.Handler{
Pattern: "[global] download [options] <fileId>",
Description: "Download file or directory",
Callback: downloadHandler,
FlagGroups: cli.FlagGroups{
cli.NewFlagGroup("global", globalFlags...),
cli.NewFlagGroup("options",
cli.BoolFlag{
Name: "force",
Patterns: []string{"-f", "--force"},
Description: "Overwrite existing file",
OmitValue: true,
},
cli.BoolFlag{
Name: "skip",
Patterns: []string{"-s", "--skip"},
Description: "Skip existing files",
OmitValue: true,
},
cli.BoolFlag{
Name: "recursive",
Patterns: []string{"-r", "--recursive"},
Description: "Download directory recursively, documents will be skipped",
OmitValue: true,
},
cli.StringFlag{
Name: "path",
Patterns: []string{"--path"},
Description: "Download path",
},
cli.BoolFlag{
Name: "delete",
Patterns: []string{"--delete"},
Description: "Delete remote file when download is successful",
OmitValue: true,
},
cli.BoolFlag{
Name: "noProgress",
Patterns: []string{"--no-progress"},
Description: "Hide progress",
OmitValue: true,
},
cli.BoolFlag{
Name: "stdout",
Patterns: []string{"--stdout"},
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,
},
),
},
},
&cli.Handler{
Pattern: "[global] download query [options] <query>",
Description: "Download all files and directories matching query",
Callback: downloadQueryHandler,
FlagGroups: cli.FlagGroups{
cli.NewFlagGroup("global", globalFlags...),
cli.NewFlagGroup("options",
cli.BoolFlag{
Name: "force",
Patterns: []string{"-f", "--force"},
Description: "Overwrite existing file",
OmitValue: true,
},
cli.BoolFlag{
Name: "skip",
Patterns: []string{"-s", "--skip"},
Description: "Skip existing files",
OmitValue: true,
},
cli.BoolFlag{
Name: "recursive",
Patterns: []string{"-r", "--recursive"},
Description: "Download directories recursively, documents will be skipped",
OmitValue: true,
},
cli.StringFlag{
Name: "path",
Patterns: []string{"--path"},
Description: "Download path",
},
cli.BoolFlag{
Name: "noProgress",
Patterns: []string{"--no-progress"},
Description: "Hide progress",
OmitValue: true,
},
),
},
},
&cli.Handler{
Pattern: "[global] upload [options] <path>",
Description: "Upload file or directory",
Callback: uploadHandler,
FlagGroups: cli.FlagGroups{
cli.NewFlagGroup("global", globalFlags...),
cli.NewFlagGroup("options",
cli.BoolFlag{
Name: "recursive",
Patterns: []string{"-r", "--recursive"},
Description: "Upload directory recursively",
OmitValue: true,
},
cli.StringSliceFlag{
Name: "parent",
Patterns: []string{"-p", "--parent"},
Description: "Parent id, used to upload file to a specific directory, can be specified multiple times to give many parents",
},
cli.StringFlag{
Name: "name",
Patterns: []string{"--name"},
Description: "Filename",
},
cli.StringFlag{
Name: "description",
Patterns: []string{"--description"},
Description: "File description",
},
cli.BoolFlag{
Name: "noProgress",
Patterns: []string{"--no-progress"},
Description: "Hide progress",
OmitValue: true,
},
cli.StringFlag{
Name: "mime",
Patterns: []string{"--mime"},
Description: "Force mime type",
},
cli.BoolFlag{
Name: "share",
Patterns: []string{"--share"},
Description: "Share file",
OmitValue: true,
},
cli.BoolFlag{
Name: "delete",
Patterns: []string{"--delete"},
Description: "Delete local file when upload is successful",
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),
DefaultValue: DefaultUploadChunkSize,
},
),
},
},
&cli.Handler{
Pattern: "[global] upload - [options] <name>",
Description: "Upload file from stdin",
Callback: uploadStdinHandler,
FlagGroups: cli.FlagGroups{
cli.NewFlagGroup("global", globalFlags...),
cli.NewFlagGroup("options",
cli.StringSliceFlag{
Name: "parent",
Patterns: []string{"-p", "--parent"},
Description: "Parent id, used to upload file to a specific directory, can be specified multiple times to give many parents",
},
cli.IntFlag{
Name: "chunksize",
Patterns: []string{"--chunksize"},
Description: fmt.Sprintf("Set chunk size in bytes, default: %d", DefaultUploadChunkSize),
DefaultValue: DefaultUploadChunkSize,
},
cli.StringFlag{
Name: "description",
Patterns: []string{"--description"},
Description: "File description",
},
cli.StringFlag{
Name: "mime",
Patterns: []string{"--mime"},
Description: "Force mime type",
},
cli.BoolFlag{
Name: "share",
Patterns: []string{"--share"},
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"},
Description: "Hide progress",
OmitValue: true,
},
),
},
},
&cli.Handler{
Pattern: "[global] update [options] <fileId> <path>",
Description: "Update file, this creates a new revision of the file",
Callback: updateHandler,
FlagGroups: cli.FlagGroups{
cli.NewFlagGroup("global", globalFlags...),
cli.NewFlagGroup("options",
cli.StringSliceFlag{
Name: "parent",
Patterns: []string{"-p", "--parent"},
Description: "Parent id, used to upload file to a specific directory, can be specified multiple times to give many parents",
},
cli.StringFlag{
Name: "name",
Patterns: []string{"--name"},
Description: "Filename",
},
cli.StringFlag{
Name: "description",
Patterns: []string{"--description"},
Description: "File description",
},
cli.BoolFlag{
Name: "noProgress",
Patterns: []string{"--no-progress"},
Description: "Hide progress",
OmitValue: true,
},
cli.StringFlag{
Name: "mime",
Patterns: []string{"--mime"},
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),
DefaultValue: DefaultUploadChunkSize,
},
),
},
},
&cli.Handler{
Pattern: "[global] info [options] <fileId>",
Description: "Show file info",
Callback: infoHandler,
FlagGroups: cli.FlagGroups{
cli.NewFlagGroup("global", globalFlags...),
cli.NewFlagGroup("options",
cli.BoolFlag{
Name: "sizeInBytes",
Patterns: []string{"--bytes"},
Description: "Show size in bytes",
OmitValue: true,
},
),
},
},
&cli.Handler{
Pattern: "[global] mkdir [options] <name>",
Description: "Create directory",
Callback: mkdirHandler,
FlagGroups: cli.FlagGroups{
cli.NewFlagGroup("global", globalFlags...),
cli.NewFlagGroup("options",
cli.StringSliceFlag{
Name: "parent",
Patterns: []string{"-p", "--parent"},
Description: "Parent id of created directory, can be specified multiple times to give many parents",
},
cli.StringFlag{
Name: "description",
Patterns: []string{"--description"},
Description: "Directory description",
},
),
},
},
&cli.Handler{
Pattern: "[global] share [options] <fileId>",
Description: "Share file or directory",
Callback: shareHandler,
FlagGroups: cli.FlagGroups{
cli.NewFlagGroup("global", globalFlags...),
cli.NewFlagGroup("options",
cli.StringFlag{
Name: "role",
Patterns: []string{"--role"},
Description: fmt.Sprintf("Share role: owner/writer/commenter/reader, default: %s", DefaultShareRole),
DefaultValue: DefaultShareRole,
},
cli.StringFlag{
Name: "type",
Patterns: []string{"--type"},
Description: fmt.Sprintf("Share type: user/group/domain/anyone, default: %s", DefaultShareType),
DefaultValue: DefaultShareType,
},
cli.StringFlag{
Name: "email",
Patterns: []string{"--email"},
Description: "The email address of the user or group to share the file with. Requires 'user' or 'group' as type",
},
cli.StringFlag{
Name: "domain",
Patterns: []string{"--domain"},
Description: "The name of Google Apps domain. Requires 'domain' as type",
},
cli.BoolFlag{
Name: "discoverable",
Patterns: []string{"--discoverable"},
Description: "Make file discoverable by search engines",
OmitValue: true,
},
cli.BoolFlag{
Name: "revoke",
Patterns: []string{"--revoke"},
Description: "Delete all sharing permissions (owner roles will be skipped)",
OmitValue: true,
},
),
},
},
&cli.Handler{
Pattern: "[global] share list <fileId>",
Description: "List files permissions",
Callback: shareListHandler,
FlagGroups: cli.FlagGroups{
cli.NewFlagGroup("global", globalFlags...),
},
},
&cli.Handler{
Pattern: "[global] share revoke <fileId> <permissionId>",
Description: "Revoke permission",
Callback: shareRevokeHandler,
FlagGroups: cli.FlagGroups{
cli.NewFlagGroup("global", globalFlags...),
},
},
&cli.Handler{
Pattern: "[global] delete [options] <fileId>",
Description: "Delete file or directory",
Callback: deleteHandler,
FlagGroups: cli.FlagGroups{
cli.NewFlagGroup("global", globalFlags...),
cli.NewFlagGroup("options",
cli.BoolFlag{
Name: "recursive",
Patterns: []string{"-r", "--recursive"},
Description: "Delete directory and all it's content",
OmitValue: true,
},
),
},
},
&cli.Handler{
Pattern: "[global] sync list [options]",
Description: "List all syncable directories on drive",
Callback: listSyncHandler,
FlagGroups: cli.FlagGroups{
cli.NewFlagGroup("global", globalFlags...),
cli.NewFlagGroup("options",
cli.BoolFlag{
Name: "skipHeader",
Patterns: []string{"--no-header"},
Description: "Dont print the header",
OmitValue: true,
},
),
},
},
&cli.Handler{
Pattern: "[global] sync content [options] <fileId>",
Description: "List content of syncable directory",
Callback: listRecursiveSyncHandler,
FlagGroups: cli.FlagGroups{
cli.NewFlagGroup("global", globalFlags...),
cli.NewFlagGroup("options",
cli.StringFlag{
Name: "sortOrder",
Patterns: []string{"--order"},
Description: "Sort order. See https://godoc.org/google.golang.org/api/drive/v3#FilesListCall.OrderBy",
},
cli.IntFlag{
Name: "pathWidth",
Patterns: []string{"--path-width"},
Description: fmt.Sprintf("Width of path column, default: %d, minimum: 9, use 0 for full width", DefaultPathWidth),
DefaultValue: DefaultPathWidth,
},
cli.BoolFlag{
Name: "skipHeader",
Patterns: []string{"--no-header"},
Description: "Dont print the header",
OmitValue: true,
},
cli.BoolFlag{
Name: "sizeInBytes",
Patterns: []string{"--bytes"},
Description: "Size in bytes",
OmitValue: true,
},
),
},
},
&cli.Handler{
Pattern: "[global] sync download [options] <fileId> <path>",
Description: "Sync drive directory to local directory",
Callback: downloadSyncHandler,
FlagGroups: cli.FlagGroups{
cli.NewFlagGroup("global", globalFlags...),
cli.NewFlagGroup("options",
cli.BoolFlag{
Name: "keepRemote",
Patterns: []string{"--keep-remote"},
Description: "Keep remote file when a conflict is encountered",
OmitValue: true,
},
cli.BoolFlag{
Name: "keepLocal",
Patterns: []string{"--keep-local"},
Description: "Keep local file when a conflict is encountered",
OmitValue: true,
},
cli.BoolFlag{
Name: "keepLargest",
Patterns: []string{"--keep-largest"},
Description: "Keep largest file when a conflict is encountered",
OmitValue: true,
},
cli.BoolFlag{
Name: "deleteExtraneous",
Patterns: []string{"--delete-extraneous"},
Description: "Delete extraneous local files",
OmitValue: true,
},
cli.BoolFlag{
Name: "dryRun",
Patterns: []string{"--dry-run"},
Description: "Show what would have been transferred",
OmitValue: true,
},
cli.BoolFlag{
Name: "noProgress",
Patterns: []string{"--no-progress"},
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,
},
),
},
},
&cli.Handler{
Pattern: "[global] sync upload [options] <path> <fileId>",
Description: "Sync local directory to drive",
Callback: uploadSyncHandler,
FlagGroups: cli.FlagGroups{
cli.NewFlagGroup("global", globalFlags...),
cli.NewFlagGroup("options",
cli.BoolFlag{
Name: "keepRemote",
Patterns: []string{"--keep-remote"},
Description: "Keep remote file when a conflict is encountered",
OmitValue: true,
},
cli.BoolFlag{
Name: "keepLocal",
Patterns: []string{"--keep-local"},
Description: "Keep local file when a conflict is encountered",
OmitValue: true,
},
cli.BoolFlag{
Name: "keepLargest",
Patterns: []string{"--keep-largest"},
Description: "Keep largest file when a conflict is encountered",
OmitValue: true,
},
cli.BoolFlag{
Name: "deleteExtraneous",
Patterns: []string{"--delete-extraneous"},
Description: "Delete extraneous remote files",
OmitValue: true,
},
cli.BoolFlag{
Name: "dryRun",
Patterns: []string{"--dry-run"},
Description: "Show what would have been transferred",
OmitValue: true,
},
cli.BoolFlag{
Name: "noProgress",
Patterns: []string{"--no-progress"},
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,
},
cli.IntFlag{
Name: "chunksize",
Patterns: []string{"--chunksize"},
Description: fmt.Sprintf("Set chunk size in bytes, default: %d", DefaultUploadChunkSize),
DefaultValue: DefaultUploadChunkSize,
},
),
},
},
&cli.Handler{
Pattern: "[global] changes [options]",
Description: "List file changes",
Callback: listChangesHandler,
FlagGroups: cli.FlagGroups{
cli.NewFlagGroup("global", globalFlags...),
cli.NewFlagGroup("options",
cli.IntFlag{
Name: "maxChanges",
Patterns: []string{"-m", "--max"},
Description: fmt.Sprintf("Max changes to list, default: %d", DefaultMaxChanges),
DefaultValue: DefaultMaxChanges,
},
cli.StringFlag{
Name: "pageToken",
Patterns: []string{"--since"},
Description: fmt.Sprintf("Page token to start listing changes from"),
DefaultValue: "1",
},
cli.BoolFlag{
Name: "now",
Patterns: []string{"--now"},
Description: fmt.Sprintf("Get latest page token"),
OmitValue: true,
},
cli.IntFlag{
Name: "nameWidth",
Patterns: []string{"--name-width"},
Description: fmt.Sprintf("Width of name column, default: %d, minimum: 9, use 0 for full width", DefaultNameWidth),
DefaultValue: DefaultNameWidth,
},
cli.BoolFlag{
Name: "skipHeader",
Patterns: []string{"--no-header"},
Description: "Dont print the header",
OmitValue: true,
},
),
},
},
&cli.Handler{
Pattern: "[global] revision list [options] <fileId>",
Description: "List file revisions",
Callback: listRevisionsHandler,
FlagGroups: cli.FlagGroups{
cli.NewFlagGroup("global", globalFlags...),
cli.NewFlagGroup("options",
cli.IntFlag{
Name: "nameWidth",
Patterns: []string{"--name-width"},
Description: fmt.Sprintf("Width of name column, default: %d, minimum: 9, use 0 for full width", DefaultNameWidth),
DefaultValue: DefaultNameWidth,
},
cli.BoolFlag{
Name: "skipHeader",
Patterns: []string{"--no-header"},
Description: "Dont print the header",
OmitValue: true,
},
cli.BoolFlag{
Name: "sizeInBytes",
Patterns: []string{"--bytes"},
Description: "Size in bytes",
OmitValue: true,
},
),
},
},
&cli.Handler{
Pattern: "[global] revision download [options] <fileId> <revId>",
Description: "Download revision",
Callback: downloadRevisionHandler,
FlagGroups: cli.FlagGroups{
cli.NewFlagGroup("global", globalFlags...),
cli.NewFlagGroup("options",
cli.BoolFlag{
Name: "force",
Patterns: []string{"-f", "--force"},
Description: "Overwrite existing file",
OmitValue: true,
},
cli.BoolFlag{
Name: "noProgress",
Patterns: []string{"--no-progress"},
Description: "Hide progress",
OmitValue: true,
},
cli.BoolFlag{
Name: "stdout",
Patterns: []string{"--stdout"},
Description: "Write file content to stdout",
OmitValue: true,
},
cli.StringFlag{
Name: "path",
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,
},
),
},
},
&cli.Handler{
Pattern: "[global] revision delete <fileId> <revId>",
Description: "Delete file revision",
Callback: deleteRevisionHandler,
FlagGroups: cli.FlagGroups{
cli.NewFlagGroup("global", globalFlags...),
},
},
&cli.Handler{
Pattern: "[global] import [options] <path>",
Description: "Upload and convert file to a google document, see 'about import' for available conversions",
Callback: importHandler,
FlagGroups: cli.FlagGroups{
cli.NewFlagGroup("global", globalFlags...),
cli.NewFlagGroup("options",
cli.StringSliceFlag{
Name: "parent",
Patterns: []string{"-p", "--parent"},
Description: "Parent id, used to upload file to a specific directory, can be specified multiple times to give many parents",
},
cli.BoolFlag{
Name: "noProgress",
Patterns: []string{"--no-progress"},
Description: "Hide progress",
OmitValue: true,
},
cli.StringFlag{
Name: "mime",
Patterns: []string{"--mime"},
Description: "Mime type of imported file",
},
),
},
},
&cli.Handler{
Pattern: "[global] export [options] <fileId>",
Description: "Export a google document",
Callback: exportHandler,
FlagGroups: cli.FlagGroups{
cli.NewFlagGroup("global", globalFlags...),
cli.NewFlagGroup("options",
cli.BoolFlag{
Name: "force",
Patterns: []string{"-f", "--force"},
Description: "Overwrite existing file",
OmitValue: true,
},
cli.StringFlag{
Name: "mime",
Patterns: []string{"--mime"},
Description: "Mime type of exported file",
},
cli.BoolFlag{
Name: "printMimes",
Patterns: []string{"--print-mimes"},
Description: "Print available mime types for given file",
OmitValue: true,
},
),
},
},
&cli.Handler{
Pattern: "[global] about [options]",
Description: "Google drive metadata, quota usage",
Callback: aboutHandler,
FlagGroups: cli.FlagGroups{
cli.NewFlagGroup("global", globalFlags...),
cli.NewFlagGroup("options",
cli.BoolFlag{
Name: "sizeInBytes",
Patterns: []string{"--bytes"},
Description: "Show size in bytes",
OmitValue: true,
},
),
},
},
&cli.Handler{
Pattern: "[global] about import",
Description: "Show supported import formats",
Callback: aboutImportHandler,
FlagGroups: cli.FlagGroups{
cli.NewFlagGroup("global", globalFlags...),
},
},
&cli.Handler{
Pattern: "[global] about export",
Description: "Show supported export formats",
Callback: aboutExportHandler,
FlagGroups: cli.FlagGroups{
cli.NewFlagGroup("global", globalFlags...),
},
},
&cli.Handler{
Pattern: "version",
Description: "Print application version",
Callback: printVersion,
},
&cli.Handler{
Pattern: "help",
Description: "Print help",
Callback: printHelp,
},
&cli.Handler{
Pattern: "help <command>",
Description: "Print command help",
Callback: printCommandHelp,
},
&cli.Handler{
Pattern: "help <command> <subcommand>",
Description: "Print subcommand help",
Callback: printSubCommandHelp,
},
}
cli.SetHandlers(handlers)
if ok := cli.Handle(os.Args[1:]); !ok {
ExitF("No valid arguments given, use '%s help' to see available commands", Name)
}
}
================================================
FILE: handlers_drive.go
================================================
package main
import (
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"time"
"github.com/prasmussen/gdrive/auth"
"github.com/prasmussen/gdrive/cli"
"github.com/prasmussen/gdrive/drive"
)
const ClientId = "367116221053-7n0vf5akeru7on6o2fjinrecpdoe99eg.apps.googleusercontent.com"
const ClientSecret = "1qsNodXNaWq1mQuBjUjmvhoO"
const TokenFilename = "token_v2.json"
const DefaultCacheFileName = "file_cache.json"
func listHandler(ctx cli.Context) {
args := ctx.Args()
err := newDrive(args).List(drive.ListFilesArgs{
Out: os.Stdout,
MaxFiles: args.Int64("maxFiles"),
NameWidth: args.Int64("nameWidth"),
Query: args.String("query"),
SortOrder: args.String("sortOrder"),
SkipHeader: args.Bool("skipHeader"),
SizeInBytes: args.Bool("sizeInBytes"),
AbsPath: args.Bool("absPath"),
})
checkErr(err)
}
func listChangesHandler(ctx cli.Context) {
args := ctx.Args()
err := newDrive(args).ListChanges(drive.ListChangesArgs{
Out: os.Stdout,
PageToken: args.String("pageToken"),
MaxChanges: args.Int64("maxChanges"),
Now: args.Bool("now"),
NameWidth: args.Int64("nameWidth"),
SkipHeader: args.Bool("skipHeader"),
})
checkErr(err)
}
func downloadHandler(ctx cli.Context) {
args := ctx.Args()
checkDownloadArgs(args)
err := newDrive(args).Download(drive.DownloadArgs{
Out: os.Stdout,
Id: args.String("fileId"),
Force: args.Bool("force"),
Skip: args.Bool("skip"),
Path: args.String("path"),
Delete: args.Bool("delete"),
Recursive: args.Bool("recursive"),
Stdout: args.Bool("stdout"),
Progress: progressWriter(args.Bool("noProgress")),
Timeout: durationInSeconds(args.Int64("timeout")),
})
checkErr(err)
}
func downloadQueryHandler(ctx cli.Context) {
args := ctx.Args()
err := newDrive(args).DownloadQuery(drive.DownloadQueryArgs{
Out: os.Stdout,
Query: args.String("query"),
Force: args.Bool("force"),
Skip: args.Bool("skip"),
Recursive: args.Bool("recursive"),
Path: args.String("path"),
Progress: progressWriter(args.Bool("noProgress")),
})
checkErr(err)
}
func downloadSyncHandler(ctx cli.Context) {
args := ctx.Args()
cachePath := filepath.Join(args.String("configDir"), DefaultCacheFileName)
err := newDrive(args).DownloadSync(drive.DownloadSyncArgs{
Out: os.Stdout,
Progress: progressWriter(args.Bool("noProgress")),
Path: args.String("path"),
RootId: args.String("fileId"),
DryRun: args.Bool("dryRun"),
DeleteExtraneous: args.Bool("deleteExtraneous"),
Timeout: durationInSeconds(args.Int64("timeout")),
Resolution: conflictResolution(args),
Comparer: NewCachedMd5Comparer(cachePath),
})
checkErr(err)
}
func downloadRevisionHandler(ctx cli.Context) {
args := ctx.Args()
err := newDrive(args).DownloadRevision(drive.DownloadRevisionArgs{
Out: os.Stdout,
FileId: args.String("fileId"),
RevisionId: args.String("revId"),
Force: args.Bool("force"),
Stdout: args.Bool("stdout"),
Path: args.String("path"),
Progress: progressWriter(args.Bool("noProgress")),
Timeout: durationInSeconds(args.Int64("timeout")),
})
checkErr(err)
}
func uploadHandler(ctx cli.Context) {
args := ctx.Args()
checkUploadArgs(args)
err := newDrive(args).Upload(drive.UploadArgs{
Out: os.Stdout,
Progress: progressWriter(args.Bool("noProgress")),
Path: args.String("path"),
Name: args.String("name"),
Description: args.String("description"),
Parents: args.StringSlice("parent"),
Mime: args.String("mime"),
Recursive: args.Bool("recursive"),
Share: args.Bool("share"),
Delete: args.Bool("delete"),
ChunkSize: args.Int64("chunksize"),
Timeout: durationInSeconds(args.Int64("timeout")),
})
checkErr(err)
}
func uploadStdinHandler(ctx cli.Context) {
args := ctx.Args()
err := newDrive(args).UploadStream(drive.UploadStreamArgs{
Out: os.Stdout,
In: os.Stdin,
Name: args.String("name"),
Description: args.String("description"),
Parents: args.StringSlice("parent"),
Mime: args.String("mime"),
Share: args.Bool("share"),
ChunkSize: args.Int64("chunksize"),
Timeout: durationInSeconds(args.Int64("timeout")),
Progress: progressWriter(args.Bool("noProgress")),
})
checkErr(err)
}
func uploadSyncHandler(ctx cli.Context) {
args := ctx.Args()
cachePath := filepath.Join(args.String("configDir"), DefaultCacheFileName)
err := newDrive(args).UploadSync(drive.UploadSyncArgs{
Out: os.Stdout,
Progress: progressWriter(args.Bool("noProgress")),
Path: args.String("path"),
RootId: args.String("fileId"),
DryRun: args.Bool("dryRun"),
DeleteExtraneous: args.Bool("deleteExtraneous"),
ChunkSize: args.Int64("chunksize"),
Timeout: durationInSeconds(args.Int64("timeout")),
Resolution: conflictResolution(args),
Comparer: NewCachedMd5Comparer(cachePath),
})
checkErr(err)
}
func updateHandler(ctx cli.Context) {
args := ctx.Args()
err := newDrive(args).Update(drive.UpdateArgs{
Out: os.Stdout,
Id: args.String("fileId"),
Path: args.String("path"),
Name: args.String("name"),
Description: args.String("description"),
Parents: args.StringSlice("parent"),
Mime: args.String("mime"),
Progress: progressWriter(args.Bool("noProgress")),
ChunkSize: args.Int64("chunksize"),
Timeout: durationInSeconds(args.Int64("timeout")),
})
checkErr(err)
}
func infoHandler(ctx cli.Context) {
args := ctx.Args()
err := newDrive(args).Info(drive.FileInfoArgs{
Out: os.Stdout,
Id: args.String("fileId"),
SizeInBytes: args.Bool("sizeInBytes"),
})
checkErr(err)
}
func importHandler(ctx cli.Context) {
args := ctx.Args()
err := newDrive(args).Import(drive.ImportArgs{
Mime: args.String("mime"),
Out: os.Stdout,
Path: args.String("path"),
Parents: args.StringSlice("parent"),
Progress: progressWriter(args.Bool("noProgress")),
})
checkErr(err)
}
func exportHandler(ctx cli.Context) {
args := ctx.Args()
err := newDrive(args).Export(drive.ExportArgs{
Out: os.Stdout,
Id: args.String("fileId"),
Mime: args.String("mime"),
PrintMimes: args.Bool("printMimes"),
Force: args.Bool("force"),
})
checkErr(err)
}
func listRevisionsHandler(ctx cli.Context) {
args := ctx.Args()
err := newDrive(args).ListRevisions(drive.ListRevisionsArgs{
Out: os.Stdout,
Id: args.String("fileId"),
NameWidth: args.Int64("nameWidth"),
SizeInBytes: args.Bool("sizeInBytes"),
SkipHeader: args.Bool("skipHeader"),
})
checkErr(err)
}
func mkdirHandler(ctx cli.Context) {
args := ctx.Args()
err := newDrive(args).Mkdir(drive.MkdirArgs{
Out: os.Stdout,
Name: args.String("name"),
Description: args.String("description"),
Parents: args.StringSlice("parent"),
})
checkErr(err)
}
func shareHandler(ctx cli.Context) {
args := ctx.Args()
err := newDrive(args).Share(drive.ShareArgs{
Out: os.Stdout,
FileId: args.String("fileId"),
Role: args.String("role"),
Type: args.String("type"),
Email: args.String("email"),
Domain: args.String("domain"),
Discoverable: args.Bool("discoverable"),
})
checkErr(err)
}
func shareListHandler(ctx cli.Context) {
args := ctx.Args()
err := newDrive(args).ListPermissions(drive.ListPermissionsArgs{
Out: os.Stdout,
FileId: args.String("fileId"),
})
checkErr(err)
}
func shareRevokeHandler(ctx cli.Context) {
args := ctx.Args()
err := newDrive(args).RevokePermission(drive.RevokePermissionArgs{
Out: os.Stdout,
FileId: args.String("fileId"),
PermissionId: args.String("permissionId"),
})
checkErr(err)
}
func deleteHandler(ctx cli.Context) {
args := ctx.Args()
err := newDrive(args).Delete(drive.DeleteArgs{
Out: os.Stdout,
Id: args.String("fileId"),
Recursive: args.Bool("recursive"),
})
checkErr(err)
}
func listSyncHandler(ctx cli.Context) {
args := ctx.Args()
err := newDrive(args).ListSync(drive.ListSyncArgs{
Out: os.Stdout,
SkipHeader: args.Bool("skipHeader"),
})
checkErr(err)
}
func listRecursiveSyncHandler(ctx cli.Context) {
args := ctx.Args()
err := newDrive(args).ListRecursiveSync(drive.ListRecursiveSyncArgs{
Out: os.Stdout,
RootId: args.String("fileId"),
SkipHeader: args.Bool("skipHeader"),
PathWidth: args.Int64("pathWidth"),
SizeInBytes: args.Bool("sizeInBytes"),
SortOrder: args.String("sortOrder"),
})
checkErr(err)
}
func deleteRevisionHandler(ctx cli.Context) {
args := ctx.Args()
err := newDrive(args).DeleteRevision(drive.DeleteRevisionArgs{
Out: os.Stdout,
FileId: args.String("fileId"),
RevisionId: args.String("revId"),
})
checkErr(err)
}
func aboutHandler(ctx cli.Context) {
args := ctx.Args()
err := newDrive(args).About(drive.AboutArgs{
Out: os.Stdout,
SizeInBytes: args.Bool("sizeInBytes"),
})
checkErr(err)
}
func aboutImportHandler(ctx cli.Context) {
args := ctx.Args()
err := newDrive(args).AboutImport(drive.AboutImportArgs{
Out: os.Stdout,
})
checkErr(err)
}
func aboutExportHandler(ctx cli.Context) {
args := ctx.Args()
err := newDrive(args).AboutExport(drive.AboutExportArgs{
Out: os.Stdout,
})
checkErr(err)
}
func getOauthClient(args cli.Arguments) (*http.Client, error) {
if args.String("refreshToken") != "" && args.String("accessToken") != "" {
ExitF("Access token not needed when refresh token is provided")
}
if args.String("refreshToken") != "" {
return auth.NewRefreshTokenClient(ClientId, ClientSecret, args.String("refreshToken")), nil
}
if args.String("accessToken") != "" {
return auth.NewAccessTokenClient(ClientId, ClientSecret, args.String("accessToken")), nil
}
configDir := getConfigDir(args)
if args.String("serviceAccount") != "" {
serviceAccountPath := ConfigFilePath(configDir, args.String("serviceAccount"))
serviceAccountClient, err := auth.NewServiceAccountClient(serviceAccountPath)
if err != nil {
return nil, err
}
return serviceAccountClient, nil
}
tokenPath := ConfigFilePath(configDir, TokenFilename)
return auth.NewFileSourceClient(ClientId, ClientSecret, tokenPath, authCodePrompt)
}
func getConfigDir(args cli.Arguments) string {
// Use dir from environment var if present
if os.Getenv("GDRIVE_CONFIG_DIR") != "" {
return os.Getenv("GDRIVE_CONFIG_DIR")
}
return args.String("configDir")
}
func newDrive(args cli.Arguments) *drive.Drive {
oauth, err := getOauthClient(args)
if err != nil {
ExitF("Failed getting oauth client: %s", err.Error())
}
client, err := drive.New(oauth)
if err != nil {
ExitF("Failed getting drive: %s", err.Error())
}
return client
}
func authCodePrompt(url string) func() string {
return func() string {
fmt.Println("Authentication needed")
fmt.Println("Go to the following url in your browser:")
fmt.Printf("%s\n\n", url)
fmt.Print("Enter verification code: ")
var code string
if _, err := fmt.Scan(&code); err != nil {
fmt.Printf("Failed reading code: %s", err.Error())
}
return code
}
}
func progressWriter(discard bool) io.Writer {
if discard {
return ioutil.Discard
}
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")
keepLargest := args.Bool("keepLargest")
if (keepLocal && keepRemote) || (keepLocal && keepLargest) || (keepRemote && keepLargest) {
ExitF("Only one conflict resolution flag can be given")
}
if keepLocal {
return drive.KeepLocal
}
if keepRemote {
return drive.KeepRemote
}
if keepLargest {
return drive.KeepLargest
}
return drive.NoResolution
}
func checkUploadArgs(args cli.Arguments) {
if args.Bool("recursive") && args.Bool("delete") {
ExitF("--delete is not allowed for recursive uploads")
}
if args.Bool("recursive") && args.Bool("share") {
ExitF("--share is not allowed for recursive uploads")
}
}
func checkDownloadArgs(args cli.Arguments) {
if args.Bool("recursive") && args.Bool("delete") {
ExitF("--delete is not allowed for recursive downloads")
}
}
================================================
FILE: handlers_meta.go
================================================
package main
import (
"fmt"
"github.com/prasmussen/gdrive/cli"
"os"
"runtime"
"strings"
"text/tabwriter"
)
func printVersion(ctx cli.Context) {
fmt.Printf("%s: %s\n", Name, Version)
fmt.Printf("Golang: %s\n", runtime.Version())
fmt.Printf("OS/Arch: %s/%s\n", runtime.GOOS, runtime.GOARCH)
}
func printHelp(ctx cli.Context) {
w := new(tabwriter.Writer)
w.Init(os.Stdout, 0, 0, 3, ' ', 0)
fmt.Fprintf(w, "%s usage:\n\n", Name)
for _, h := range ctx.Handlers() {
fmt.Fprintf(w, "%s %s\t%s\n", Name, h.Pattern, h.Description)
}
w.Flush()
}
func printCommandHelp(ctx cli.Context) {
args := ctx.Args()
printCommandPrefixHelp(ctx, args.String("command"))
}
func printSubCommandHelp(ctx cli.Context) {
args := ctx.Args()
printCommandPrefixHelp(ctx, args.String("command"), args.String("subcommand"))
}
func printCommandPrefixHelp(ctx cli.Context, prefix ...string) {
handler := getHandler(ctx.Handlers(), prefix)
if handler == nil {
ExitF("Command not found")
}
w := new(tabwriter.Writer)
w.Init(os.Stdout, 0, 0, 3, ' ', 0)
fmt.Fprintf(w, "%s\n", handler.Description)
fmt.Fprintf(w, "%s %s\n", Name, handler.Pattern)
for _, group := range handler.FlagGroups {
fmt.Fprintf(w, "\n%s:\n", group.Name)
for _, flag := range group.Flags {
boolFlag, isBool := flag.(cli.BoolFlag)
if isBool && boolFlag.OmitValue {
fmt.Fprintf(w, " %s\t%s\n", strings.Join(flag.GetPatterns(), ", "), flag.GetDescription())
} else {
fmt.Fprintf(w, " %s <%s>\t%s\n", strings.Join(flag.GetPatterns(), ", "), flag.GetName(), flag.GetDescription())
}
}
}
w.Flush()
}
func getHandler(handlers []*cli.Handler, prefix []string) *cli.Handler {
for _, h := range handlers {
pattern := stripOptionals(h.SplitPattern())
if len(prefix) > len(pattern) {
continue
}
if equal(prefix, pattern[:len(prefix)]) {
return h
}
}
return nil
}
// Strip optional groups (<...>) from pattern
func stripOptionals(pattern []string) []string {
newArgs := []string{}
for _, arg := range pattern {
if strings.HasPrefix(arg, "[") && strings.HasSuffix(arg, "]") {
continue
}
newArgs = append(newArgs, arg)
}
return newArgs
}
================================================
FILE: util.go
================================================
package main
import (
"crypto/md5"
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"runtime"
)
func GetDefaultConfigDir() string {
return filepath.Join(Homedir(), ".gdrive")
}
func ConfigFilePath(basePath, name string) string {
return filepath.Join(basePath, name)
}
func Homedir() string {
if runtime.GOOS == "windows" {
return os.Getenv("APPDATA")
}
return os.Getenv("HOME")
}
func equal(a, b []string) bool {
if a == nil && b == nil {
return true
}
if a == nil || b == nil {
return false
}
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
func ExitF(format string, a ...interface{}) {
fmt.Fprintf(os.Stderr, format, a...)
fmt.Println("")
os.Exit(1)
}
func checkErr(err error) {
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}
func writeJson(path string, data interface{}) error {
tmpFile := path + ".tmp"
f, err := os.Create(tmpFile)
if err != nil {
return err
}
err = json.NewEncoder(f).Encode(data)
f.Close()
if err != nil {
os.Remove(tmpFile)
return err
}
return os.Rename(tmpFile, path)
}
func md5sum(path string) string {
h := md5.New()
f, err := os.Open(path)
if err != nil {
return ""
}
defer f.Close()
io.Copy(h, f)
return fmt.Sprintf("%x", h.Sum(nil))
}
================================================
FILE: vendor/github.com/sabhiram/go-git-ignore/.gitignore
================================================
# Package test fixtures
test_fixtures
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof
================================================
FILE: vendor/github.com/sabhiram/go-git-ignore/.travis.yml
================================================
language: go
go:
- 1.3
- tip
env:
- "PATH=$HOME/gopath/bin:$PATH"
before_install:
- go get github.com/stretchr/testify/assert
- go get github.com/axw/gocov/gocov
- go get github.com/mattn/goveralls
- if ! go get code.google.com/p/go.tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi
script:
- go test -v -covermode=count -coverprofile=coverage.out
- goveralls -coverprofile=coverage.out -service travis-ci -repotoken $COVERALLS_TOKEN
================================================
FILE: vendor/github.com/sabhiram/go-git-ignore/LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2015 Shaba Abhiram
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
gitextract_75b66kff/
├── .github/
│ └── workflows/
│ └── release.yaml
├── .gitignore
├── Godeps/
│ ├── Godeps.json
│ └── Readme
├── LICENSE
├── README.md
├── _release/
│ ├── build-all.sh
│ ├── print_usage_markdown.sh
│ └── upload.sh
├── auth/
│ ├── file_source.go
│ ├── oauth.go
│ └── util.go
├── cli/
│ ├── context.go
│ ├── flags.go
│ ├── handler.go
│ └── parser.go
├── compare.go
├── drive/
│ ├── about.go
│ ├── changes.go
│ ├── delete.go
│ ├── download.go
│ ├── drive.go
│ ├── errors.go
│ ├── export.go
│ ├── import.go
│ ├── info.go
│ ├── list.go
│ ├── mkdir.go
│ ├── path.go
│ ├── progress.go
│ ├── revision_delete.go
│ ├── revision_download.go
│ ├── revision_list.go
│ ├── share.go
│ ├── sync.go
│ ├── sync_download.go
│ ├── sync_list.go
│ ├── sync_upload.go
│ ├── timeout_reader.go
│ ├── update.go
│ ├── upload.go
│ └── util.go
├── gdrive.go
├── handlers_drive.go
├── handlers_meta.go
├── util.go
└── vendor/
├── github.com/
│ ├── sabhiram/
│ │ └── go-git-ignore/
│ │ ├── .gitignore
│ │ ├── .travis.yml
│ │ ├── LICENSE
│ │ ├── README.md
│ │ └── ignore.go
│ └── soniakeys/
│ └── graph/
│ ├── .gitignore
│ ├── .travis.yml
│ ├── adj.go
│ ├── adj_RO.go
│ ├── adj_cg.go
│ ├── bits.go
│ ├── bits32.go
│ ├── bits64.go
│ ├── dir.go
│ ├── dir_RO.go
│ ├── dir_cg.go
│ ├── doc.go
│ ├── fromlist.go
│ ├── graph.go
│ ├── hacking.md
│ ├── mst.go
│ ├── random.go
│ ├── readme.md
│ ├── sssp.go
│ ├── travis.sh
│ ├── undir.go
│ ├── undir_RO.go
│ └── undir_cg.go
├── golang.org/
│ └── x/
│ ├── net/
│ │ ├── LICENSE
│ │ ├── PATENTS
│ │ └── context/
│ │ ├── context.go
│ │ ├── ctxhttp/
│ │ │ ├── cancelreq.go
│ │ │ ├── cancelreq_go14.go
│ │ │ └── ctxhttp.go
│ │ ├── go17.go
│ │ └── pre_go17.go
│ └── oauth2/
│ ├── .travis.yml
│ ├── AUTHORS
│ ├── CONTRIBUTING.md
│ ├── CONTRIBUTORS
│ ├── LICENSE
│ ├── README.md
│ ├── client_appengine.go
│ ├── internal/
│ │ ├── oauth2.go
│ │ ├── token.go
│ │ └── transport.go
│ ├── oauth2.go
│ ├── token.go
│ └── transport.go
└── google.golang.org/
└── api/
├── LICENSE
├── drive/
│ └── v3/
│ ├── drive-api.json
│ └── drive-gen.go
├── gensupport/
│ ├── backoff.go
│ ├── buffer.go
│ ├── doc.go
│ ├── json.go
│ ├── media.go
│ ├── params.go
│ ├── resumable.go
│ └── retry.go
└── googleapi/
├── googleapi.go
├── internal/
│ └── uritemplates/
│ ├── LICENSE
│ ├── uritemplates.go
│ └── utils.go
└── types.go
SYMBOL INDEX (1240 symbols across 80 files)
FILE: auth/file_source.go
function FileSource (line 10) | func FileSource(path string, token *oauth2.Token, conf *oauth2.Config) o...
type fileSource (line 17) | type fileSource struct
method Token (line 22) | func (self *fileSource) Token() (*oauth2.Token, error) {
function ReadFile (line 34) | func ReadFile(path string) ([]byte, bool, error) {
function ReadToken (line 47) | func ReadToken(path string) (*oauth2.Token, bool, error) {
function SaveToken (line 58) | func SaveToken(path string, token *oauth2.Token) error {
FILE: auth/oauth.go
type authCodeFn (line 11) | type authCodeFn
function NewFileSourceClient (line 13) | func NewFileSourceClient(clientId, clientSecret, tokenFile string, authF...
function NewRefreshTokenClient (line 39) | func NewRefreshTokenClient(clientId, clientSecret, refreshToken string) ...
function NewAccessTokenClient (line 54) | func NewAccessTokenClient(clientId, clientSecret, accessToken string) *h...
function NewServiceAccountClient (line 68) | func NewServiceAccountClient(serviceAccountFile string) (*http.Client, e...
function getConfig (line 85) | func getConfig(clientId, clientSecret string) *oauth2.Config {
FILE: auth/util.go
function mkdir (line 8) | func mkdir(path string) error {
function fileExists (line 16) | func fileExists(path string) bool {
FILE: cli/context.go
type Context (line 3) | type Context struct
method Args (line 8) | func (self Context) Args() Arguments {
method Handlers (line 12) | func (self Context) Handlers() []*Handler {
type Arguments (line 16) | type Arguments
method String (line 18) | func (self Arguments) String(key string) string {
method Int64 (line 22) | func (self Arguments) Int64(key string) int64 {
method Bool (line 26) | func (self Arguments) Bool(key string) bool {
method StringSlice (line 30) | func (self Arguments) StringSlice(key string) []string {
FILE: cli/flags.go
type Flag (line 3) | type Flag interface
function getFlagParser (line 10) | func getFlagParser(flags []Flag) Parser {
type BoolFlag (line 20) | type BoolFlag struct
method GetName (line 28) | func (self BoolFlag) GetName() string {
method GetPatterns (line 32) | func (self BoolFlag) GetPatterns() []string {
method GetDescription (line 36) | func (self BoolFlag) GetDescription() string {
method GetParser (line 40) | func (self BoolFlag) GetParser() Parser {
type StringFlag (line 57) | type StringFlag struct
method GetName (line 64) | func (self StringFlag) GetName() string {
method GetPatterns (line 68) | func (self StringFlag) GetPatterns() []string {
method GetDescription (line 72) | func (self StringFlag) GetDescription() string {
method GetParser (line 76) | func (self StringFlag) GetParser() Parser {
type IntFlag (line 92) | type IntFlag struct
method GetName (line 99) | func (self IntFlag) GetName() string {
method GetPatterns (line 103) | func (self IntFlag) GetPatterns() []string {
method GetDescription (line 107) | func (self IntFlag) GetDescription() string {
method GetParser (line 111) | func (self IntFlag) GetParser() Parser {
type StringSliceFlag (line 127) | type StringSliceFlag struct
method GetName (line 134) | func (self StringSliceFlag) GetName() string {
method GetPatterns (line 138) | func (self StringSliceFlag) GetPatterns() []string {
method GetDescription (line 142) | func (self StringSliceFlag) GetDescription() string {
method GetParser (line 146) | func (self StringSliceFlag) GetParser() Parser {
FILE: cli/handler.go
function NewFlagGroup (line 8) | func NewFlagGroup(name string, flags ...Flag) FlagGroup {
type FlagGroup (line 15) | type FlagGroup struct
type FlagGroups (line 20) | type FlagGroups
method getFlags (line 22) | func (groups FlagGroups) getFlags(name string) []Flag {
type Handler (line 34) | type Handler struct
method getParser (line 41) | func (self *Handler) getParser() Parser {
method SplitPattern (line 60) | func (self *Handler) SplitPattern() []string {
function SetHandlers (line 71) | func SetHandlers(h []*Handler) {
function AddHandler (line 75) | func AddHandler(pattern string, groups FlagGroups, callback func(Context...
function findHandler (line 84) | func findHandler(args []string) *Handler {
function Handle (line 93) | func Handle(args []string) bool {
function isCaptureGroup (line 108) | func isCaptureGroup(arg string) bool {
function isFlagGroup (line 112) | func isFlagGroup(arg string) bool {
function flagGroupName (line 116) | func flagGroupName(s string) string {
FILE: cli/parser.go
type Parser (line 8) | type Parser interface
type EqualParser (line 13) | type EqualParser struct
method Match (line 17) | func (self EqualParser) Match(values []string) ([]string, bool) {
method Capture (line 29) | func (self EqualParser) Capture(values []string) ([]string, map[string...
method String (line 34) | func (self EqualParser) String() string {
type CaptureGroupParser (line 38) | type CaptureGroupParser struct
method Match (line 42) | func (self CaptureGroupParser) Match(values []string) ([]string, bool) {
method key (line 50) | func (self CaptureGroupParser) key() string {
method Capture (line 54) | func (self CaptureGroupParser) Capture(values []string) ([]string, map...
method String (line 62) | func (self CaptureGroupParser) String() string {
type BoolFlagParser (line 66) | type BoolFlagParser struct
method Match (line 73) | func (self BoolFlagParser) Match(values []string) ([]string, bool) {
method Capture (line 91) | func (self BoolFlagParser) Capture(values []string) ([]string, map[str...
method String (line 106) | func (self BoolFlagParser) String() string {
type StringFlagParser (line 110) | type StringFlagParser struct
method Match (line 116) | func (self StringFlagParser) Match(values []string) ([]string, bool) {
method Capture (line 121) | func (self StringFlagParser) Capture(values []string) ([]string, map[s...
method String (line 130) | func (self StringFlagParser) String() string {
type IntFlagParser (line 134) | type IntFlagParser struct
method Match (line 140) | func (self IntFlagParser) Match(values []string) ([]string, bool) {
method Capture (line 154) | func (self IntFlagParser) Capture(values []string) ([]string, map[stri...
method String (line 164) | func (self IntFlagParser) String() string {
type StringSliceFlagParser (line 168) | type StringSliceFlagParser struct
method Match (line 174) | func (self StringSliceFlagParser) Match(values []string) ([]string, bo...
method Capture (line 192) | func (self StringSliceFlagParser) Capture(values []string) ([]string, ...
method String (line 209) | func (self StringSliceFlagParser) String() string {
type FlagParser (line 213) | type FlagParser struct
method Match (line 217) | func (self FlagParser) Match(values []string) ([]string, bool) {
method Capture (line 226) | func (self FlagParser) Capture(values []string) ([]string, map[string]...
method String (line 241) | func (self FlagParser) String() string {
type ShortCircuitParser (line 245) | type ShortCircuitParser struct
method Match (line 249) | func (self ShortCircuitParser) Match(values []string) ([]string, bool) {
method Capture (line 263) | func (self ShortCircuitParser) Capture(values []string) ([]string, map...
method String (line 279) | func (self ShortCircuitParser) String() string {
type CompleteParser (line 283) | type CompleteParser struct
method Match (line 287) | func (self CompleteParser) Match(values []string) ([]string, bool) {
method Capture (line 301) | func (self CompleteParser) Capture(values []string) ([]string, map[str...
method String (line 316) | func (self CompleteParser) String() string {
function flagKeyValueMatch (line 320) | func flagKeyValueMatch(key string, values []string, index int) ([]string...
function flagKeyMatch (line 334) | func flagKeyMatch(key string, values []string, index int) ([]string, boo...
function copySlice (line 347) | func copySlice(a []string) []string {
FILE: compare.go
constant MinCacheFileSize (line 9) | MinCacheFileSize = 5 * 1024 * 1024
type Md5Comparer (line 11) | type Md5Comparer struct
method Changed (line 13) | func (self Md5Comparer) Changed(local *drive.LocalFile, remote *drive....
type CachedFileInfo (line 17) | type CachedFileInfo struct
function NewCachedMd5Comparer (line 23) | func NewCachedMd5Comparer(path string) CachedMd5Comparer {
type CachedMd5Comparer (line 34) | type CachedMd5Comparer struct
method Changed (line 39) | func (self CachedMd5Comparer) Changed(local *drive.LocalFile, remote *...
method md5 (line 43) | func (self CachedMd5Comparer) md5(local *drive.LocalFile) string {
method cacheAdd (line 64) | func (self CachedMd5Comparer) cacheAdd(lf *drive.LocalFile, md5 string) {
method persist (line 72) | func (self CachedMd5Comparer) persist() {
FILE: drive/about.go
type AboutArgs (line 9) | type AboutArgs struct
method About (line 14) | func (self *Drive) About(args AboutArgs) (err error) {
type AboutImportArgs (line 31) | type AboutImportArgs struct
method AboutImport (line 35) | func (self *Drive) AboutImport(args AboutImportArgs) (err error) {
type AboutExportArgs (line 44) | type AboutExportArgs struct
method AboutExport (line 48) | func (self *Drive) AboutExport(args AboutExportArgs) (err error) {
function printAboutFormats (line 57) | func printAboutFormats(out io.Writer, formats map[string][]string) {
FILE: drive/changes.go
type ListChangesArgs (line 10) | type ListChangesArgs struct
method ListChanges (line 19) | func (self *Drive) ListChanges(args ListChangesArgs) error {
method GetChangesStartPageToken (line 45) | func (self *Drive) GetChangesStartPageToken() (string, error) {
type PrintChangesArgs (line 54) | type PrintChangesArgs struct
function PrintChanges (line 61) | func PrintChanges(args PrintChangesArgs) {
function nextChangesPageToken (line 97) | func nextChangesPageToken(cl *drive.ChangeList) (string, bool) {
FILE: drive/delete.go
type DeleteArgs (line 8) | type DeleteArgs struct
method Delete (line 14) | func (self *Drive) Delete(args DeleteArgs) error {
method deleteFile (line 33) | func (self *Drive) deleteFile(fileId string) error {
FILE: drive/download.go
type DownloadArgs (line 14) | type DownloadArgs struct
method Download (line 27) | func (self *Drive) Download(args DownloadArgs) error {
type DownloadQueryArgs (line 67) | type DownloadQueryArgs struct
method DownloadQuery (line 77) | func (self *Drive) DownloadQuery(args DownloadQueryArgs) error {
method downloadRecursive (line 110) | func (self *Drive) downloadRecursive(args DownloadArgs) error {
method downloadBinary (line 126) | func (self *Drive) downloadBinary(f *drive.File, args DownloadArgs) (int...
type saveFileArgs (line 160) | type saveFileArgs struct
method saveFile (line 171) | func (self *Drive) saveFile(args saveFileArgs) (int64, int64, error) {
method downloadDirectory (line 226) | func (self *Drive) downloadDirectory(parent *drive.File, args DownloadAr...
function isDir (line 254) | func isDir(f *drive.File) bool {
function isBinary (line 258) | func isBinary(f *drive.File) bool {
FILE: drive/drive.go
type Drive (line 8) | type Drive struct
function New (line 12) | func New(client *http.Client) (*Drive, error) {
FILE: drive/errors.go
constant MaxErrorRetries (line 9) | MaxErrorRetries = 5
function isBackendOrRateLimitError (line 11) | func isBackendOrRateLimitError(err error) bool {
function isBackendError (line 15) | func isBackendError(err error) bool {
function isRateLimitError (line 24) | func isRateLimitError(err error) bool {
function isTimeoutError (line 33) | func isTimeoutError(err error) bool {
function exponentialBackoffSleep (line 37) | func exponentialBackoffSleep(try int) {
FILE: drive/export.go
type ExportArgs (line 19) | type ExportArgs struct
method Export (line 27) | func (self *Drive) Export(args ExportArgs) error {
method printMimes (line 76) | func (self *Drive) printMimes(out io.Writer, mimeType string) error {
function getExportMime (line 91) | func getExportMime(userMime, fileMime string) (string, error) {
function getExportFilename (line 104) | func getExportFilename(name, mimeType string) string {
FILE: drive/import.go
type ImportArgs (line 12) | type ImportArgs struct
method Import (line 20) | func (self *Drive) Import(args ImportArgs) error {
function getMimeType (line 54) | func getMimeType(path string) string {
FILE: drive/info.go
type FileInfoArgs (line 9) | type FileInfoArgs struct
method Info (line 15) | func (self *Drive) Info(args FileInfoArgs) error {
type PrintFileInfoArgs (line 37) | type PrintFileInfoArgs struct
function PrintFileInfo (line 44) | func PrintFileInfo(args PrintFileInfoArgs) {
FILE: drive/list.go
type ListFilesArgs (line 12) | type ListFilesArgs struct
method List (line 23) | func (self *Drive) List(args ListFilesArgs) (err error) {
type listAllFilesArgs (line 58) | type listAllFilesArgs struct
method listAllFiles (line 65) | func (self *Drive) listAllFiles(args listAllFilesArgs) ([]*drive.File, e...
type PrintFileListArgs (line 100) | type PrintFileListArgs struct
function PrintFileList (line 108) | func PrintFileList(args PrintFileListArgs) {
function filetype (line 129) | func filetype(f *drive.File) string {
FILE: drive/mkdir.go
constant DirectoryMimeType (line 9) | DirectoryMimeType = "application/vnd.google-apps.folder"
type MkdirArgs (line 11) | type MkdirArgs struct
method Mkdir (line 18) | func (self *Drive) Mkdir(args MkdirArgs) error {
method mkdir (line 27) | func (self *Drive) mkdir(args MkdirArgs) (*drive.File, error) {
FILE: drive/path.go
method newPathfinder (line 9) | func (self *Drive) newPathfinder() *remotePathfinder {
type remotePathfinder (line 16) | type remotePathfinder struct
method absPath (line 21) | func (self *remotePathfinder) absPath(f *drive.File) (string, error) {
method getParent (line 49) | func (self *remotePathfinder) getParent(id string) (*drive.File, error) {
FILE: drive/progress.go
constant MaxDrawInterval (line 10) | MaxDrawInterval = time.Second * 1
constant MaxRateInterval (line 11) | MaxRateInterval = time.Second * 3
function getProgressReader (line 13) | func getProgressReader(r io.Reader, w io.Writer, size int64) io.Reader {
type Progress (line 26) | type Progress struct
method Read (line 38) | func (self *Progress) Read(p []byte) (int, error) {
method draw (line 74) | func (self *Progress) draw(isLast bool) {
method clear (line 99) | func (self *Progress) clear() {
FILE: drive/revision_delete.go
type DeleteRevisionArgs (line 8) | type DeleteRevisionArgs struct
method DeleteRevision (line 14) | func (self *Drive) DeleteRevision(args DeleteRevisionArgs) (err error) {
FILE: drive/revision_download.go
type DownloadRevisionArgs (line 11) | type DownloadRevisionArgs struct
method DownloadRevision (line 22) | func (self *Drive) DownloadRevision(args DownloadRevisionArgs) (err erro...
FILE: drive/revision_list.go
type ListRevisionsArgs (line 10) | type ListRevisionsArgs struct
method ListRevisions (line 18) | func (self *Drive) ListRevisions(args ListRevisionsArgs) (err error) {
type PrintRevisionListArgs (line 35) | type PrintRevisionListArgs struct
function PrintRevisionList (line 43) | func PrintRevisionList(args PrintRevisionListArgs) {
FILE: drive/share.go
type ShareArgs (line 10) | type ShareArgs struct
method Share (line 20) | func (self *Drive) Share(args ShareArgs) error {
type RevokePermissionArgs (line 38) | type RevokePermissionArgs struct
method RevokePermission (line 44) | func (self *Drive) RevokePermission(args RevokePermissionArgs) error {
type ListPermissionsArgs (line 55) | type ListPermissionsArgs struct
method ListPermissions (line 60) | func (self *Drive) ListPermissions(args ListPermissionsArgs) error {
method shareAnyoneReader (line 74) | func (self *Drive) shareAnyoneReader(fileId string) error {
type printPermissionsArgs (line 88) | type printPermissionsArgs struct
function printPermissions (line 93) | func printPermissions(args printPermissionsArgs) {
FILE: drive/sync.go
constant DefaultIgnoreFile (line 17) | DefaultIgnoreFile = ".gdriveignore"
type ModTime (line 19) | type ModTime
constant LocalLastModified (line 22) | LocalLastModified ModTime = iota
constant RemoteLastModified (line 23) | RemoteLastModified
constant EqualModifiedTime (line 24) | EqualModifiedTime
type LargestSize (line 27) | type LargestSize
constant LocalLargestSize (line 30) | LocalLargestSize LargestSize = iota
constant RemoteLargestSize (line 31) | RemoteLargestSize
constant EqualSize (line 32) | EqualSize
type ConflictResolution (line 35) | type ConflictResolution
constant NoResolution (line 38) | NoResolution ConflictResolution = iota
constant KeepLocal (line 39) | KeepLocal
constant KeepRemote (line 40) | KeepRemote
constant KeepLargest (line 41) | KeepLargest
method prepareSyncFiles (line 44) | func (self *Drive) prepareSyncFiles(localPath string, root *drive.File, ...
method isSyncFile (line 88) | func (self *Drive) isSyncFile(id string) (bool, error) {
function prepareLocalFiles (line 98) | func prepareLocalFiles(root string) ([]*LocalFile, error) {
method prepareRemoteFiles (line 155) | func (self *Drive) prepareRemoteFiles(rootDir *drive.File, sortOrder str...
function prepareRemoteRelPaths (line 191) | func prepareRemoteRelPaths(root *drive.File, files []*drive.File) (map[s...
function checkFiles (line 261) | func checkFiles(files []*drive.File) error {
type LocalFile (line 281) | type LocalFile struct
method AbsPath (line 308) | func (self LocalFile) AbsPath() string {
method Size (line 312) | func (self LocalFile) Size() int64 {
method Modified (line 316) | func (self LocalFile) Modified() time.Time {
type RemoteFile (line 287) | type RemoteFile struct
method Md5 (line 320) | func (self RemoteFile) Md5() string {
method Size (line 324) | func (self RemoteFile) Size() int64 {
method Modified (line 328) | func (self RemoteFile) Modified() time.Time {
type changedFile (line 292) | type changedFile struct
method compareModTime (line 333) | func (self *changedFile) compareModTime() ModTime {
method compareSize (line 348) | func (self *changedFile) compareSize() LargestSize {
type syncFiles (line 297) | type syncFiles struct
method filterMissingRemoteDirs (line 363) | func (self *syncFiles) filterMissingRemoteDirs() []*LocalFile {
method filterMissingLocalDirs (line 375) | func (self *syncFiles) filterMissingLocalDirs() []*RemoteFile {
method filterMissingRemoteFiles (line 387) | func (self *syncFiles) filterMissingRemoteFiles() []*LocalFile {
method filterMissingLocalFiles (line 399) | func (self *syncFiles) filterMissingLocalFiles() []*RemoteFile {
method filterChangedLocalFiles (line 411) | func (self *syncFiles) filterChangedLocalFiles() []*changedFile {
method filterChangedRemoteFiles (line 438) | func (self *syncFiles) filterChangedRemoteFiles() []*changedFile {
method filterExtraneousRemoteFiles (line 465) | func (self *syncFiles) filterExtraneousRemoteFiles() []*RemoteFile {
method filterExtraneousLocalFiles (line 477) | func (self *syncFiles) filterExtraneousLocalFiles() []*LocalFile {
method existsRemote (line 489) | func (self *syncFiles) existsRemote(lf *LocalFile) bool {
method existsLocal (line 494) | func (self *syncFiles) existsLocal(rf *RemoteFile) bool {
method findRemoteByPath (line 499) | func (self *syncFiles) findRemoteByPath(relPath string) (*RemoteFile, ...
method findLocalByPath (line 513) | func (self *syncFiles) findLocalByPath(relPath string) (*LocalFile, bo...
type FileComparer (line 304) | type FileComparer interface
function findLocalConflicts (line 523) | func findLocalConflicts(files []*changedFile) []*changedFile {
function findRemoteConflicts (line 535) | func findRemoteConflicts(files []*changedFile) []*changedFile {
type byLocalPathLength (line 547) | type byLocalPathLength
method Len (line 549) | func (self byLocalPathLength) Len() int {
method Swap (line 553) | func (self byLocalPathLength) Swap(i, j int) {
method Less (line 557) | func (self byLocalPathLength) Less(i, j int) bool {
type byRemotePathLength (line 561) | type byRemotePathLength
method Len (line 563) | func (self byRemotePathLength) Len() int {
method Swap (line 567) | func (self byRemotePathLength) Swap(i, j int) {
method Less (line 571) | func (self byRemotePathLength) Less(i, j int) bool {
type byRemotePath (line 575) | type byRemotePath
method Len (line 577) | func (self byRemotePath) Len() int {
method Swap (line 581) | func (self byRemotePath) Swap(i, j int) {
method Less (line 585) | func (self byRemotePath) Less(i, j int) bool {
type ignoreFunc (line 589) | type ignoreFunc
function prepareIgnorer (line 591) | func prepareIgnorer(path string) (ignoreFunc, error) {
function formatConflicts (line 608) | func formatConflicts(conflicts []*changedFile, out io.Writer) {
FILE: drive/sync_download.go
type DownloadSyncArgs (line 15) | type DownloadSyncArgs struct
method DownloadSync (line 27) | func (self *Drive) DownloadSync(args DownloadSyncArgs) error {
method getSyncRoot (line 86) | func (self *Drive) getSyncRoot(rootId string) (*drive.File, error) {
method createMissingLocalDirs (line 106) | func (self *Drive) createMissingLocalDirs(files *syncFiles, args Downloa...
method downloadMissingFiles (line 134) | func (self *Drive) downloadMissingFiles(files *syncFiles, args DownloadS...
method downloadChangedFiles (line 158) | func (self *Drive) downloadChangedFiles(changedFiles []*changedFile, arg...
method downloadRemoteFile (line 186) | func (self *Drive) downloadRemoteFile(id, fpath string, args DownloadSyn...
method deleteExtraneousLocalFiles (line 251) | func (self *Drive) deleteExtraneousLocalFiles(files *syncFiles, args Dow...
function checkLocalConflict (line 278) | func checkLocalConflict(cf *changedFile, resolution ConflictResolution) ...
function ensureNoLocalModifications (line 319) | func ensureNoLocalModifications(files []*changedFile) error {
FILE: drive/sync_list.go
type ListSyncArgs (line 12) | type ListSyncArgs struct
method ListSync (line 17) | func (self *Drive) ListSync(args ListSyncArgs) error {
type ListRecursiveSyncArgs (line 30) | type ListRecursiveSyncArgs struct
method ListRecursiveSync (line 39) | func (self *Drive) ListRecursiveSync(args ListRecursiveSyncArgs) error {
function printSyncDirectories (line 54) | func printSyncDirectories(files []*drive.File, args ListSyncArgs) {
function printSyncDirContent (line 73) | func printSyncDirContent(files []*RemoteFile, args ListRecursiveSyncArgs) {
FILE: drive/sync_upload.go
type UploadSyncArgs (line 15) | type UploadSyncArgs struct
method UploadSync (line 28) | func (self *Drive) UploadSync(args UploadSyncArgs) error {
method prepareSyncRoot (line 97) | func (self *Drive) prepareSyncRoot(args UploadSyncArgs) (*drive.File, er...
method createMissingRemoteDirs (line 139) | func (self *Drive) createMissingRemoteDirs(files *syncFiles, args Upload...
type createMissingRemoteDirArgs (line 179) | type createMissingRemoteDirArgs struct
method uploadMissingFiles (line 187) | func (self *Drive) uploadMissingFiles(missingFiles []*LocalFile, files *...
method updateChangedFiles (line 212) | func (self *Drive) updateChangedFiles(changedFiles []*changedFile, root ...
method deleteExtraneousRemoteFiles (line 236) | func (self *Drive) deleteExtraneousRemoteFiles(files *syncFiles, args Up...
method createMissingRemoteDir (line 259) | func (self *Drive) createMissingRemoteDir(args createMissingRemoteDirArg...
method uploadMissingFile (line 285) | func (self *Drive) uploadMissingFile(parentId string, lf *LocalFile, arg...
method updateChangedFile (line 330) | func (self *Drive) updateChangedFile(cf *changedFile, args UploadSyncArg...
method deleteRemoteFile (line 371) | func (self *Drive) deleteRemoteFile(rf *RemoteFile, args UploadSyncArgs,...
method dirIsEmpty (line 390) | func (self *Drive) dirIsEmpty(id string) (bool, error) {
function checkRemoteConflict (line 400) | func checkRemoteConflict(cf *changedFile, resolution ConflictResolution)...
function ensureNoRemoteModifications (line 441) | func ensureNoRemoteModifications(files []*changedFile) error {
method checkRemoteFreeSpace (line 452) | func (self *Drive) checkRemoteFreeSpace(missingFiles []*LocalFile, chang...
FILE: drive/timeout_reader.go
constant TimeoutTimerInterval (line 10) | TimeoutTimerInterval = time.Second * 10
type timeoutReaderWrapper (line 12) | type timeoutReaderWrapper
function getTimeoutReaderWrapperContext (line 14) | func getTimeoutReaderWrapperContext(timeout time.Duration) (timeoutReade...
function getTimeoutReaderContext (line 27) | func getTimeoutReaderContext(r io.Reader, timeout time.Duration) (io.Rea...
function getTimeoutReader (line 38) | func getTimeoutReader(r io.Reader, cancel context.CancelFunc, timeout ti...
type TimeoutReader (line 47) | type TimeoutReader struct
method Read (line 57) | func (self *TimeoutReader) Read(p []byte) (int, error) {
method startTimer (line 79) | func (self *TimeoutReader) startTimer() {
method stopTimer (line 88) | func (self *TimeoutReader) stopTimer() {
method timeout (line 97) | func (self *TimeoutReader) timeout() {
FILE: drive/update.go
type UpdateArgs (line 13) | type UpdateArgs struct
method Update (line 27) | func (self *Drive) Update(args UpdateArgs) error {
FILE: drive/upload.go
type UploadArgs (line 14) | type UploadArgs struct
method Upload (line 29) | func (self *Drive) Upload(args UploadArgs) error {
method uploadRecursive (line 85) | func (self *Drive) uploadRecursive(args UploadArgs) error {
method uploadDirectory (line 102) | func (self *Drive) uploadDirectory(args UploadArgs) error {
method uploadFile (line 146) | func (self *Drive) uploadFile(args UploadArgs) (*drive.File, int64, erro...
type UploadStreamArgs (line 201) | type UploadStreamArgs struct
method UploadStream (line 214) | func (self *Drive) UploadStream(args UploadStreamArgs) error {
FILE: drive/util.go
type kv (line 14) | type kv struct
function formatList (line 19) | func formatList(a []string) string {
function formatSize (line 23) | func formatSize(bytes int64, forceBytes bool) string {
function calcRate (line 44) | func calcRate(bytes int64, start, end time.Time) int64 {
function round (line 52) | func round(n float64) int64 {
function formatBool (line 59) | func formatBool(b bool) string {
function formatDatetime (line 63) | func formatDatetime(iso string) string {
function truncateString (line 76) | func truncateString(str string, maxRunes int) string {
function fileExists (line 118) | func fileExists(path string) bool {
function mkdir (line 126) | func mkdir(path string) error {
function intMax (line 134) | func intMax() int64 {
function pathLength (line 138) | func pathLength(path string) int {
function parentFilePath (line 142) | func parentFilePath(path string) string {
function pow (line 147) | func pow(x int, y int) int {
function min (line 152) | func min(x int, y int) int {
function openFile (line 157) | func openFile(path string) (*os.File, os.FileInfo, error) {
FILE: gdrive.go
constant Name (line 10) | Name = "gdrive"
constant Version (line 11) | Version = "2.1.1"
constant DefaultMaxFiles (line 13) | DefaultMaxFiles = 30
constant DefaultMaxChanges (line 14) | DefaultMaxChanges = 100
constant DefaultNameWidth (line 15) | DefaultNameWidth = 40
constant DefaultPathWidth (line 16) | DefaultPathWidth = 60
constant DefaultUploadChunkSize (line 17) | DefaultUploadChunkSize = 8 * 1024 * 1024
constant DefaultTimeout (line 18) | DefaultTimeout = 5 * 60
constant DefaultQuery (line 19) | DefaultQuery = "trashed = false and 'me' in owners"
constant DefaultShareRole (line 20) | DefaultShareRole = "reader"
constant DefaultShareType (line 21) | DefaultShareType = "anyone"
function main (line 25) | func main() {
FILE: handlers_drive.go
constant ClientId (line 17) | ClientId = "367116221053-7n0vf5akeru7on6o2fjinrecpdoe99eg.apps.googleuse...
constant ClientSecret (line 18) | ClientSecret = "1qsNodXNaWq1mQuBjUjmvhoO"
constant TokenFilename (line 19) | TokenFilename = "token_v2.json"
constant DefaultCacheFileName (line 20) | DefaultCacheFileName = "file_cache.json"
function listHandler (line 22) | func listHandler(ctx cli.Context) {
function listChangesHandler (line 37) | func listChangesHandler(ctx cli.Context) {
function downloadHandler (line 50) | func downloadHandler(ctx cli.Context) {
function downloadQueryHandler (line 68) | func downloadQueryHandler(ctx cli.Context) {
function downloadSyncHandler (line 82) | func downloadSyncHandler(ctx cli.Context) {
function downloadRevisionHandler (line 99) | func downloadRevisionHandler(ctx cli.Context) {
function uploadHandler (line 114) | func uploadHandler(ctx cli.Context) {
function uploadStdinHandler (line 134) | func uploadStdinHandler(ctx cli.Context) {
function uploadSyncHandler (line 151) | func uploadSyncHandler(ctx cli.Context) {
function updateHandler (line 169) | func updateHandler(ctx cli.Context) {
function infoHandler (line 186) | func infoHandler(ctx cli.Context) {
function importHandler (line 196) | func importHandler(ctx cli.Context) {
function exportHandler (line 208) | func exportHandler(ctx cli.Context) {
function listRevisionsHandler (line 220) | func listRevisionsHandler(ctx cli.Context) {
function mkdirHandler (line 232) | func mkdirHandler(ctx cli.Context) {
function shareHandler (line 243) | func shareHandler(ctx cli.Context) {
function shareListHandler (line 257) | func shareListHandler(ctx cli.Context) {
function shareRevokeHandler (line 266) | func shareRevokeHandler(ctx cli.Context) {
function deleteHandler (line 276) | func deleteHandler(ctx cli.Context) {
function listSyncHandler (line 286) | func listSyncHandler(ctx cli.Context) {
function listRecursiveSyncHandler (line 295) | func listRecursiveSyncHandler(ctx cli.Context) {
function deleteRevisionHandler (line 308) | func deleteRevisionHandler(ctx cli.Context) {
function aboutHandler (line 318) | func aboutHandler(ctx cli.Context) {
function aboutImportHandler (line 327) | func aboutImportHandler(ctx cli.Context) {
function aboutExportHandler (line 335) | func aboutExportHandler(ctx cli.Context) {
function getOauthClient (line 343) | func getOauthClient(args cli.Arguments) (*http.Client, error) {
function getConfigDir (line 371) | func getConfigDir(args cli.Arguments) string {
function newDrive (line 379) | func newDrive(args cli.Arguments) *drive.Drive {
function authCodePrompt (line 393) | func authCodePrompt(url string) func() string {
function progressWriter (line 408) | func progressWriter(discard bool) io.Writer {
function durationInSeconds (line 415) | func durationInSeconds(seconds int64) time.Duration {
function conflictResolution (line 419) | func conflictResolution(args cli.Arguments) drive.ConflictResolution {
function checkUploadArgs (line 443) | func checkUploadArgs(args cli.Arguments) {
function checkDownloadArgs (line 453) | func checkDownloadArgs(args cli.Arguments) {
FILE: handlers_meta.go
function printVersion (line 12) | func printVersion(ctx cli.Context) {
function printHelp (line 18) | func printHelp(ctx cli.Context) {
function printCommandHelp (line 31) | func printCommandHelp(ctx cli.Context) {
function printSubCommandHelp (line 36) | func printSubCommandHelp(ctx cli.Context) {
function printCommandPrefixHelp (line 41) | func printCommandPrefixHelp(ctx cli.Context, prefix ...string) {
function getHandler (line 68) | func getHandler(handlers []*cli.Handler, prefix []string) *cli.Handler {
function stripOptionals (line 85) | func stripOptionals(pattern []string) []string {
FILE: util.go
function GetDefaultConfigDir (line 13) | func GetDefaultConfigDir() string {
function ConfigFilePath (line 17) | func ConfigFilePath(basePath, name string) string {
function Homedir (line 21) | func Homedir() string {
function equal (line 28) | func equal(a, b []string) bool {
function ExitF (line 50) | func ExitF(format string, a ...interface{}) {
function checkErr (line 56) | func checkErr(err error) {
function writeJson (line 63) | func writeJson(path string, data interface{}) error {
function md5sum (line 80) | func md5sum(path string) string {
FILE: vendor/github.com/sabhiram/go-git-ignore/ignore.go
type IgnoreParser (line 63) | type IgnoreParser interface
type GitIgnore (line 71) | type GitIgnore struct
method MatchesPath (line 183) | func (g GitIgnore) MatchesPath(f string) bool {
function getPatternFromLine (line 78) | func getPatternFromLine(line string) (*regexp.Regexp, bool) {
function CompileIgnoreLines (line 157) | func CompileIgnoreLines(lines ...string) (*GitIgnore, error) {
function CompileIgnoreFile (line 171) | func CompileIgnoreFile(fpath string) (*GitIgnore, error) {
FILE: vendor/github.com/soniakeys/graph/adj.go
method HasParallelSort (line 32) | func (g AdjacencyList) HasParallelSort() (has bool, fr, to NI) {
method IsUndirected (line 56) | func (g AdjacencyList) IsUndirected() (u bool, from, to NI) {
method EdgeList (line 93) | func (g LabeledAdjacencyList) EdgeList() (el []LabeledEdge) {
method FloydWarshall (line 111) | func (g LabeledAdjacencyList) FloydWarshall(w WeightFunc) (d [][]float64) {
function newFWd (line 123) | func newFWd(n int) [][]float64 {
function solveFW (line 138) | func solveFW(d [][]float64) {
method HasArcLabel (line 156) | func (g LabeledAdjacencyList) HasArcLabel(fr, to NI, l LI) (bool, int) {
method HasParallelSort (line 180) | func (g LabeledAdjacencyList) HasParallelSort() (has bool, fr, to NI) {
method IsUndirected (line 211) | func (g LabeledAdjacencyList) IsUndirected() (u bool, from NI, to Half) {
method NegativeArc (line 242) | func (g LabeledAdjacencyList) NegativeArc(w WeightFunc) bool {
method Unlabeled (line 254) | func (g LabeledAdjacencyList) Unlabeled() AdjacencyList {
method WeightedEdgeList (line 271) | func (g LabeledAdjacencyList) WeightedEdgeList(w WeightFunc) *WeightedEd...
method WeightedInDegree (line 288) | func (g LabeledAdjacencyList) WeightedInDegree(w WeightFunc) []float64 {
method WeightedOutDegree (line 311) | func (g LabeledAdjacencyList) WeightedOutDegree(n NI, w WeightFunc) (d f...
FILE: vendor/github.com/soniakeys/graph/adj_RO.go
method ArcSize (line 23) | func (g AdjacencyList) ArcSize() int {
method BoundsOk (line 42) | func (g AdjacencyList) BoundsOk() (ok bool, fr NI, to NI) {
method BreadthFirst (line 79) | func (g AdjacencyList) BreadthFirst(start NI, r *rand.Rand, f *FromList,...
method BreadthFirstPath (line 141) | func (g AdjacencyList) BreadthFirstPath(start, end NI) []NI {
method Copy (line 151) | func (g AdjacencyList) Copy() (c AdjacencyList, ma int) {
method DepthFirst (line 182) | func (g AdjacencyList) DepthFirst(start NI, bm *Bits, v OkNodeVisitor) (...
method DepthFirstRandom (line 217) | func (g AdjacencyList) DepthFirstRandom(start NI, bm *Bits, v OkNodeVisi...
method HasArc (line 253) | func (g AdjacencyList) HasArc(fr, to NI) (bool, int) {
method HasLoop (line 271) | func (g AdjacencyList) HasLoop() (bool, NI) {
method HasParallelMap (line 299) | func (g AdjacencyList) HasParallelMap() (has bool, fr, to NI) {
method IsSimple (line 326) | func (g AdjacencyList) IsSimple() (ok bool, n NI) {
method IsolatedNodes (line 341) | func (g AdjacencyList) IsolatedNodes() (i Bits) {
FILE: vendor/github.com/soniakeys/graph/adj_cg.go
method ArcSize (line 23) | func (g LabeledAdjacencyList) ArcSize() int {
method BoundsOk (line 42) | func (g LabeledAdjacencyList) BoundsOk() (ok bool, fr NI, to Half) {
method BreadthFirst (line 79) | func (g LabeledAdjacencyList) BreadthFirst(start NI, r *rand.Rand, f *Fr...
method BreadthFirstPath (line 141) | func (g LabeledAdjacencyList) BreadthFirstPath(start, end NI) []NI {
method Copy (line 151) | func (g LabeledAdjacencyList) Copy() (c LabeledAdjacencyList, ma int) {
method DepthFirst (line 182) | func (g LabeledAdjacencyList) DepthFirst(start NI, bm *Bits, v OkNodeVis...
method DepthFirstRandom (line 217) | func (g LabeledAdjacencyList) DepthFirstRandom(start NI, bm *Bits, v OkN...
method HasArc (line 253) | func (g LabeledAdjacencyList) HasArc(fr, to NI) (bool, int) {
method HasLoop (line 271) | func (g LabeledAdjacencyList) HasLoop() (bool, NI) {
method HasParallelMap (line 299) | func (g LabeledAdjacencyList) HasParallelMap() (has bool, fr, to NI) {
method IsSimple (line 326) | func (g LabeledAdjacencyList) IsSimple() (ok bool, n NI) {
method IsolatedNodes (line 341) | func (g LabeledAdjacencyList) IsolatedNodes() (i Bits) {
FILE: vendor/github.com/soniakeys/graph/bits.go
type Bits (line 16) | type Bits struct
method AllNot (line 31) | func (z *Bits) AllNot(n int, x Bits) {
method And (line 38) | func (z *Bits) And(x, y Bits) {
method AndNot (line 43) | func (z *Bits) AndNot(x, y Bits) {
method Bit (line 48) | func (b Bits) Bit(n NI) uint {
method Clear (line 53) | func (z *Bits) Clear() {
method Format (line 60) | func (b Bits) Format(s fmt.State, ch rune) {
method From (line 78) | func (b Bits) From(n NI) NI {
method Iterate (line 108) | func (b Bits) Iterate(v OkNodeVisitor) bool {
method Or (line 130) | func (z *Bits) Or(x, y Bits) {
method PopCount (line 135) | func (b Bits) PopCount() (c int) {
method Set (line 147) | func (z *Bits) Set(x Bits) {
method SetAll (line 156) | func (z *Bits) SetAll(n int) {
method SetBit (line 161) | func (z *Bits) SetBit(n NI, b uint) {
method Single (line 166) | func (b Bits) Single() bool {
method Slice (line 182) | func (b Bits) Slice() (s []NI) {
method Xor (line 193) | func (z *Bits) Xor(x, y Bits) {
method Zero (line 198) | func (b Bits) Zero() bool {
function NewBits (line 21) | func NewBits(ns ...NI) (b Bits) {
function trailingZeros (line 205) | func trailingZeros(v big.Word) int {
FILE: vendor/github.com/soniakeys/graph/bits32.go
constant wordSize (line 10) | wordSize = 32
constant wordExp (line 11) | wordExp = 5
constant deBruijnMultiple (line 17) | deBruijnMultiple = 0x077CB531
constant deBruijnShift (line 18) | deBruijnShift = 27
FILE: vendor/github.com/soniakeys/graph/bits64.go
constant wordSize (line 9) | wordSize = 64
constant wordExp (line 10) | wordExp = 6
constant deBruijnMultiple (line 14) | deBruijnMultiple = 0x03f79d71b4ca8b09
constant deBruijnShift (line 15) | deBruijnShift = 58
FILE: vendor/github.com/soniakeys/graph/dir.go
method DAGMaxLenPath (line 16) | func (g Directed) DAGMaxLenPath(ordering []NI) (path []NI) {
method EulerianCycle (line 58) | func (g Directed) EulerianCycle() ([]NI, error) {
method EulerianCycleD (line 77) | func (g Directed) EulerianCycleD(ma int) ([]NI, error) {
method EulerianPath (line 108) | func (g Directed) EulerianPath() ([]NI, error) {
method EulerianPathD (line 135) | func (g Directed) EulerianPathD(ma int, start NI) ([]NI, error) {
type eulerian (line 216) | type eulerian struct
method push (line 162) | func (e *eulerian) push() {
method pushUndir (line 178) | func (e *eulerian) pushUndir() {
method keep (line 204) | func (e *eulerian) keep() {
method top (line 226) | func (e *eulerian) top() NI {
function newEulerian (line 230) | func newEulerian(g AdjacencyList, m int) *eulerian {
method MaximalNonBranchingPaths (line 256) | func (g Directed) MaximalNonBranchingPaths(emit func([]NI) bool) {
method Undirected (line 300) | func (g Directed) Undirected() Undirected {
method Transpose (line 386) | func (g Directed) Transpose() (t Directed, ma int) {
method DAGMaxLenPath (line 405) | func (g LabeledDirected) DAGMaxLenPath(ordering []NI) (n NI, path []Half) {
method FromListLabels (line 448) | func (g LabeledDirected) FromListLabels() (*FromList, []LI, NI) {
method Transpose (line 471) | func (g LabeledDirected) Transpose() (t LabeledDirected, ma int) {
method Undirected (line 484) | func (g LabeledDirected) Undirected() LabeledUndirected {
method Unlabeled (line 516) | func (g LabeledDirected) Unlabeled() Directed {
method UnlabeledTranspose (line 529) | func (g LabeledDirected) UnlabeledTranspose() (t Directed, ma int) {
FILE: vendor/github.com/soniakeys/graph/dir_RO.go
method Balanced (line 15) | func (g Directed) Balanced() bool {
method Copy (line 28) | func (g Directed) Copy() (c Directed, ma int) {
method Cyclic (line 43) | func (g Directed) Cyclic() (cyclic bool, fr NI, to NI) {
method FromList (line 101) | func (g Directed) FromList() (*FromList, NI) {
method InDegree (line 120) | func (g Directed) InDegree() []int {
method IsTree (line 137) | func (g Directed) IsTree(root NI) (isTree, allTree bool) {
method Tarjan (line 170) | func (g Directed) Tarjan(emit func([]NI) bool) {
method TarjanForward (line 233) | func (g Directed) TarjanForward() [][]NI {
method TarjanCondensation (line 251) | func (g Directed) TarjanCondensation() (scc [][]NI, cd AdjacencyList) {
method Topological (line 284) | func (g Directed) Topological() (ordering, cycle []NI) {
method TopologicalKahn (line 347) | func (g Directed) TopologicalKahn(tr Directed) (ordering, cycle []NI) {
FILE: vendor/github.com/soniakeys/graph/dir_cg.go
method Balanced (line 15) | func (g LabeledDirected) Balanced() bool {
method Copy (line 28) | func (g LabeledDirected) Copy() (c LabeledDirected, ma int) {
method Cyclic (line 43) | func (g LabeledDirected) Cyclic() (cyclic bool, fr NI, to Half) {
method FromList (line 101) | func (g LabeledDirected) FromList() (*FromList, NI) {
method InDegree (line 120) | func (g LabeledDirected) InDegree() []int {
method IsTree (line 137) | func (g LabeledDirected) IsTree(root NI) (isTree, allTree bool) {
method Tarjan (line 170) | func (g LabeledDirected) Tarjan(emit func([]NI) bool) {
method TarjanForward (line 233) | func (g LabeledDirected) TarjanForward() [][]NI {
method TarjanCondensation (line 251) | func (g LabeledDirected) TarjanCondensation() (scc [][]NI, cd AdjacencyL...
method Topological (line 284) | func (g LabeledDirected) Topological() (ordering, cycle []NI) {
method TopologicalKahn (line 347) | func (g LabeledDirected) TopologicalKahn(tr Directed) (ordering, cycle [...
FILE: vendor/github.com/soniakeys/graph/fromlist.go
type FromList (line 35) | type FromList struct
method BoundsOk (line 63) | func (f FromList) BoundsOk() (ok bool, n NI) {
method CommonStart (line 78) | func (f FromList) CommonStart(a, b NI) NI {
method Cyclic (line 108) | func (f FromList) Cyclic() (cyclic bool, n NI) {
method IsolatedNodes (line 130) | func (f FromList) IsolatedNodes() (iso Bits) {
method PathTo (line 155) | func (f FromList) PathTo(end NI, p []NI) []NI {
method Preorder (line 204) | func (f FromList) Preorder(v OkNodeVisitor) bool {
method RecalcLeaves (line 224) | func (f *FromList) RecalcLeaves() {
method RecalcLen (line 239) | func (f *FromList) RecalcLen() {
method ReRoot (line 272) | func (f *FromList) ReRoot(n NI) {
method Root (line 291) | func (f FromList) Root(n NI) NI {
method Transpose (line 309) | func (f FromList) Transpose() Directed {
method TransposeLabeled (line 335) | func (f FromList) TransposeLabeled(labels []LI) LabeledDirected {
method TransposeLabeledRoots (line 368) | func (f FromList) TransposeLabeledRoots(labels []LI) (forest LabeledDi...
method TransposeRoots (line 401) | func (f FromList) TransposeRoots() (forest Directed, nRoots int, roots...
type PathEnd (line 44) | type PathEnd struct
function NewFromList (line 53) | func NewFromList(n int) FromList {
function PathTo (line 170) | func PathTo(paths []PathEnd, end NI, p []NI) []NI {
FILE: vendor/github.com/soniakeys/graph/graph.go
type NI (line 48) | type NI
type NodeList (line 51) | type NodeList
method Len (line 53) | func (l NodeList) Len() int { return len(l) }
method Less (line 54) | func (l NodeList) Less(i, j int) bool { return l[i] < l[j] }
method Swap (line 55) | func (l NodeList) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
type AdjacencyList (line 64) | type AdjacencyList
type Directed (line 70) | type Directed struct
type Undirected (line 82) | type Undirected struct
type LI (line 87) | type LI
type Half (line 93) | type Half struct
type LabeledAdjacencyList (line 118) | type LabeledAdjacencyList
type LabeledDirected (line 124) | type LabeledDirected struct
type LabeledUndirected (line 132) | type LabeledUndirected struct
type Edge (line 137) | type Edge struct
type LabeledEdge (line 140) | type LabeledEdge struct
type WeightFunc (line 151) | type WeightFunc
type WeightedEdgeList (line 164) | type WeightedEdgeList struct
method Len (line 171) | func (l WeightedEdgeList) Len() int { return len(l.Edges) }
method Less (line 174) | func (l WeightedEdgeList) Less(i, j int) bool {
method Swap (line 179) | func (l WeightedEdgeList) Swap(i, j int) {
FILE: vendor/github.com/soniakeys/graph/mst.go
type dsElement (line 11) | type dsElement struct
type disjointSet (line 16) | type disjointSet struct
method union (line 30) | func (ds disjointSet) union(x, y NI) bool {
method find (line 48) | func (ds disjointSet) find(n NI) NI {
function newDisjointSet (line 20) | func newDisjointSet(n int) disjointSet {
method Kruskal (line 98) | func (l WeightedEdgeList) Kruskal() (g LabeledUndirected, dist float64) {
method KruskalSorted (line 118) | func (l WeightedEdgeList) KruskalSorted() (g LabeledUndirected, dist flo...
method Prim (line 151) | func (g LabeledUndirected) Prim(start NI, w WeightFunc, f *FromList, lab...
type fromHalf (line 213) | type fromHalf struct
type prNode (line 218) | type prNode struct
type prHeap (line 225) | type prHeap
method Len (line 227) | func (h prHeap) Len() int { return len(h) }
method Less (line 228) | func (h prHeap) Less(i, j int) bool { return h[i].wt < h[j].wt }
method Swap (line 229) | func (h prHeap) Swap(i, j int) {
method Push (line 234) | func (p *prHeap) Push(x interface{}) {
method Pop (line 239) | func (p *prHeap) Pop() interface{} {
FILE: vendor/github.com/soniakeys/graph/random.go
function Euclidean (line 38) | func Euclidean(nNodes, nArcs int, affinity float64, patience int, r *ran...
function LabeledEuclidean (line 96) | func LabeledEuclidean(nNodes, nArcs int, affinity float64, patience int,...
function Geometric (line 163) | func Geometric(nNodes int, radius float64, r *rand.Rand) (g Undirected, ...
function LabeledGeometric (line 196) | func LabeledGeometric(nNodes int, radius float64, r *rand.Rand) (g Label...
function KroneckerDirected (line 232) | func KroneckerDirected(scale uint, arcFactor float64, r *rand.Rand) (g D...
function KroneckerUndirected (line 250) | func KroneckerUndirected(scale uint, edgeFactor float64, r *rand.Rand) (...
function kronecker (line 260) | func kronecker(scale uint, edgeFactor float64, dir bool, r *rand.Rand) (...
FILE: vendor/github.com/soniakeys/graph/sssp.go
type rNode (line 13) | type rNode struct
constant unreached (line 22) | unreached = 0
constant reached (line 23) | reached = 1
constant open (line 24) | open = 1
constant closed (line 25) | closed = 2
type openHeap (line 28) | type openHeap
method Len (line 314) | func (h openHeap) Len() int { return len(h) }
method Less (line 315) | func (h openHeap) Less(i, j int) bool { return h[i].f < h[j].f }
method Swap (line 316) | func (h openHeap) Swap(i, j int) {
method Push (line 321) | func (p *openHeap) Push(x interface{}) {
method Pop (line 329) | func (p *openHeap) Pop() interface{} {
type Heuristic (line 45) | type Heuristic
method Admissible (line 51) | func (h Heuristic) Admissible(g LabeledAdjacencyList, w WeightFunc, en...
method Monotonic (line 81) | func (h Heuristic) Monotonic(g LabeledAdjacencyList, w WeightFunc) (bo...
method AStarA (line 131) | func (g LabeledAdjacencyList) AStarA(w WeightFunc, start, end NI, h Heur...
method AStarAPath (line 207) | func (g LabeledAdjacencyList) AStarAPath(start, end NI, h Heuristic, w W...
method AStarM (line 218) | func (g LabeledAdjacencyList) AStarM(w WeightFunc, start, end NI, h Heur...
method AStarMPath (line 308) | func (g LabeledAdjacencyList) AStarMPath(start, end NI, h Heuristic, w W...
method BellmanFord (line 357) | func (g LabeledDirected) BellmanFord(w WeightFunc, start NI) (f FromList...
method BellmanFordCycle (line 403) | func (f FromList) BellmanFordCycle(end NI) (c []NI) {
method HasNegativeCycle (line 429) | func (g LabeledDirected) HasNegativeCycle(w WeightFunc) bool {
method NegativeCycle (line 467) | func (g LabeledDirected) NegativeCycle(w WeightFunc) (c []NI) {
type NodeVisitor (line 527) | type NodeVisitor
type OkNodeVisitor (line 535) | type OkNodeVisitor
function BreadthFirst2 (line 544) | func BreadthFirst2(g, tr AdjacencyList, ma int, start NI, f *FromList, v...
method DAGMinDistPath (line 671) | func (g LabeledDirected) DAGMinDistPath(start, end NI, w WeightFunc) ([]...
method DAGMaxDistPath (line 682) | func (g LabeledDirected) DAGMaxDistPath(start, end NI, w WeightFunc) ([]...
method dagPath (line 686) | func (g LabeledDirected) dagPath(start, end NI, w WeightFunc, longest bo...
method DAGOptimalPaths (line 717) | func (g LabeledDirected) DAGOptimalPaths(start, end NI, ordering []NI, w...
method Dijkstra (line 784) | func (g LabeledAdjacencyList) Dijkstra(start, end NI, w WeightFunc) (f F...
method DijkstraPath (line 849) | func (g LabeledAdjacencyList) DijkstraPath(start, end NI, w WeightFunc) ...
type tentResult (line 874) | type tentResult struct
type tent (line 881) | type tent
method Len (line 855) | func (t tent) Len() int { return len(t) }
method Less (line 856) | func (t tent) Less(i, j int) bool { return t[i].dist < t[j].dist }
method Swap (line 857) | func (t tent) Swap(i, j int) {
method Push (line 862) | func (s *tent) Push(x interface{}) {
method Pop (line 867) | func (s *tent) Pop() interface{} {
FILE: vendor/github.com/soniakeys/graph/undir.go
method AddEdge (line 21) | func (p *Undirected) AddEdge(n1, n2 NI) {
method EulerianCycleD (line 55) | func (g Undirected) EulerianCycleD(m int) ([]NI, error) {
method TarjanBiconnectedComponents (line 83) | func (g Undirected) TarjanBiconnectedComponents(emit func([]Edge) bool) {
method AddEdge (line 232) | func (p *LabeledUndirected) AddEdge(e Edge, l LI) {
method TarjanBiconnectedComponents (line 264) | func (g LabeledUndirected) TarjanBiconnectedComponents(emit func([]Label...
FILE: vendor/github.com/soniakeys/graph/undir_RO.go
method Bipartite (line 23) | func (g Undirected) Bipartite(n NI) (b bool, c1, c2 Bits, oc []NI) {
method BronKerbosch1 (line 77) | func (g Undirected) BronKerbosch1(emit func([]NI) bool) {
method BKPivotMaxDegree (line 127) | func (g Undirected) BKPivotMaxDegree(P, X *Bits) (p NI) {
method BKPivotMinP (line 156) | func (g Undirected) BKPivotMinP(P, X *Bits) NI {
method BronKerbosch2 (line 182) | func (g Undirected) BronKerbosch2(pivot func(P, X *Bits) NI, emit func([...
method BronKerbosch3 (line 249) | func (g Undirected) BronKerbosch3(pivot func(P, X *Bits) NI, emit func([...
method ConnectedComponentBits (line 327) | func (g Undirected) ConnectedComponentBits() func() (order int, bits Bit...
method ConnectedComponentLists (line 368) | func (g Undirected) ConnectedComponentLists() func() []NI {
method ConnectedComponentReps (line 412) | func (g Undirected) ConnectedComponentReps() (reps []NI, orders []int) {
method Copy (line 442) | func (g Undirected) Copy() (c Undirected, ma int) {
method Degeneracy (line 452) | func (g Undirected) Degeneracy() (k int, ordering []NI, cores []int) {
method Degree (line 523) | func (g Undirected) Degree(n NI) int {
method FromList (line 553) | func (g Undirected) FromList(root NI) (f FromList, cycle NI) {
method IsConnected (line 592) | func (g Undirected) IsConnected() bool {
method IsTree (line 619) | func (g Undirected) IsTree(root NI) (isTree, allTree bool) {
method Size (line 648) | func (g Undirected) Size() int {
FILE: vendor/github.com/soniakeys/graph/undir_cg.go
method Bipartite (line 23) | func (g LabeledUndirected) Bipartite(n NI) (b bool, c1, c2 Bits, oc []NI) {
method BronKerbosch1 (line 77) | func (g LabeledUndirected) BronKerbosch1(emit func([]NI) bool) {
method BKPivotMaxDegree (line 127) | func (g LabeledUndirected) BKPivotMaxDegree(P, X *Bits) (p NI) {
method BKPivotMinP (line 156) | func (g LabeledUndirected) BKPivotMinP(P, X *Bits) NI {
method BronKerbosch2 (line 182) | func (g LabeledUndirected) BronKerbosch2(pivot func(P, X *Bits) NI, emit...
method BronKerbosch3 (line 249) | func (g LabeledUndirected) BronKerbosch3(pivot func(P, X *Bits) NI, emit...
method ConnectedComponentBits (line 327) | func (g LabeledUndirected) ConnectedComponentBits() func() (order int, b...
method ConnectedComponentLists (line 368) | func (g LabeledUndirected) ConnectedComponentLists() func() []NI {
method ConnectedComponentReps (line 412) | func (g LabeledUndirected) ConnectedComponentReps() (reps []NI, orders [...
method Copy (line 442) | func (g LabeledUndirected) Copy() (c LabeledUndirected, ma int) {
method Degeneracy (line 452) | func (g LabeledUndirected) Degeneracy() (k int, ordering []NI, cores []i...
method Degree (line 523) | func (g LabeledUndirected) Degree(n NI) int {
method FromList (line 553) | func (g LabeledUndirected) FromList(root NI) (f FromList, cycle NI) {
method IsConnected (line 592) | func (g LabeledUndirected) IsConnected() bool {
method IsTree (line 619) | func (g LabeledUndirected) IsTree(root NI) (isTree, allTree bool) {
method Size (line 648) | func (g LabeledUndirected) Size() int {
FILE: vendor/golang.org/x/net/context/context.go
type Context (line 45) | type Context interface
function Background (line 140) | func Background() Context {
function TODO (line 149) | func TODO() Context {
type CancelFunc (line 156) | type CancelFunc
FILE: vendor/golang.org/x/net/context/ctxhttp/cancelreq.go
function canceler (line 11) | func canceler(client *http.Client, req *http.Request) func() {
FILE: vendor/golang.org/x/net/context/ctxhttp/cancelreq_go14.go
type requestCanceler (line 11) | type requestCanceler interface
function canceler (line 15) | func canceler(client *http.Client, req *http.Request) func() {
FILE: vendor/golang.org/x/net/context/ctxhttp/ctxhttp.go
function nop (line 17) | func nop() {}
function Do (line 28) | func Do(ctx context.Context, client *http.Client, req *http.Request) (*h...
function Get (line 90) | func Get(ctx context.Context, client *http.Client, url string) (*http.Re...
function Head (line 99) | func Head(ctx context.Context, client *http.Client, url string) (*http.R...
function Post (line 108) | func Post(ctx context.Context, client *http.Client, url string, bodyType...
function PostForm (line 118) | func PostForm(ctx context.Context, client *http.Client, url string, data...
type notifyingReader (line 124) | type notifyingReader struct
method Read (line 129) | func (r *notifyingReader) Read(p []byte) (int, error) {
method Close (line 138) | func (r *notifyingReader) Close() error {
FILE: vendor/golang.org/x/net/context/go17.go
function WithCancel (line 32) | func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
function WithDeadline (line 46) | func WithDeadline(parent Context, deadline time.Time) (Context, CancelFu...
function WithTimeout (line 61) | func WithTimeout(parent Context, timeout time.Duration) (Context, Cancel...
function WithValue (line 70) | func WithValue(parent Context, key interface{}, val interface{}) Context {
FILE: vendor/golang.org/x/net/context/pre_go17.go
type emptyCtx (line 18) | type emptyCtx
method Deadline (line 20) | func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
method Done (line 24) | func (*emptyCtx) Done() <-chan struct{} {
method Err (line 28) | func (*emptyCtx) Err() error {
method Value (line 32) | func (*emptyCtx) Value(key interface{}) interface{} {
method String (line 36) | func (e *emptyCtx) String() string {
function WithCancel (line 64) | func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
function newCancelCtx (line 71) | func newCancelCtx(parent Context) *cancelCtx {
function propagateCancel (line 79) | func propagateCancel(parent Context, child canceler) {
function parentCancelCtx (line 109) | func parentCancelCtx(parent Context) (*cancelCtx, bool) {
function removeChild (line 125) | func removeChild(parent Context, child canceler) {
type canceler (line 139) | type canceler interface
type cancelCtx (line 146) | type cancelCtx struct
method Done (line 156) | func (c *cancelCtx) Done() <-chan struct{} {
method Err (line 160) | func (c *cancelCtx) Err() error {
method String (line 166) | func (c *cancelCtx) String() string {
method cancel (line 172) | func (c *cancelCtx) cancel(removeFromParent bool, err error) {
function WithDeadline (line 204) | func WithDeadline(parent Context, deadline time.Time) (Context, CancelFu...
type timerCtx (line 232) | type timerCtx struct
method Deadline (line 239) | func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {
method String (line 243) | func (c *timerCtx) String() string {
method cancel (line 247) | func (c *timerCtx) cancel(removeFromParent bool, err error) {
function WithTimeout (line 271) | func WithTimeout(parent Context, timeout time.Duration) (Context, Cancel...
function WithValue (line 280) | func WithValue(parent Context, key interface{}, val interface{}) Context {
type valueCtx (line 286) | type valueCtx struct
method String (line 291) | func (c *valueCtx) String() string {
method Value (line 295) | func (c *valueCtx) Value(key interface{}) interface{} {
FILE: vendor/golang.org/x/oauth2/client_appengine.go
function init (line 19) | func init() {
function contextClientAppEngine (line 23) | func contextClientAppEngine(ctx context.Context) (*http.Client, error) {
FILE: vendor/golang.org/x/oauth2/internal/oauth2.go
function ParseKey (line 24) | func ParseKey(key []byte) (*rsa.PrivateKey, error) {
function ParseINI (line 43) | func ParseINI(ini io.Reader) (map[string]map[string]string, error) {
function CondVal (line 71) | func CondVal(v string) []string {
FILE: vendor/golang.org/x/oauth2/internal/token.go
type Token (line 30) | type Token struct
type tokenJSON (line 58) | type tokenJSON struct
method expiry (line 66) | func (e *tokenJSON) expiry() (t time.Time) {
type expirationTime (line 76) | type expirationTime
method UnmarshalJSON (line 78) | func (e *expirationTime) UnmarshalJSON(b []byte) error {
function RegisterBrokenAuthHeaderProvider (line 122) | func RegisterBrokenAuthHeaderProvider(tokenURL string) {
function providerAuthHeaderWorks (line 134) | func providerAuthHeaderWorks(tokenURL string) bool {
function RetrieveToken (line 149) | func RetrieveToken(ctx context.Context, ClientID, ClientSecret, TokenURL...
FILE: vendor/golang.org/x/oauth2/internal/transport.go
type ContextKey (line 21) | type ContextKey struct
type ContextClientFunc (line 27) | type ContextClientFunc
function RegisterContextClientFunc (line 31) | func RegisterContextClientFunc(fn ContextClientFunc) {
function ContextClient (line 35) | func ContextClient(ctx context.Context) (*http.Client, error) {
function ContextTransport (line 53) | func ContextTransport(ctx context.Context) http.RoundTripper {
type ErrorTransport (line 65) | type ErrorTransport struct
method RoundTrip (line 67) | func (t ErrorTransport) RoundTrip(*http.Request) (*http.Response, erro...
FILE: vendor/golang.org/x/oauth2/oauth2.go
function RegisterBrokenAuthHeaderProvider (line 34) | func RegisterBrokenAuthHeaderProvider(tokenURL string) {
type Config (line 40) | type Config struct
method AuthCodeURL (line 122) | func (c *Config) AuthCodeURL(state string, opts ...AuthCodeOption) str...
method PasswordCredentialsToken (line 155) | func (c *Config) PasswordCredentialsToken(ctx context.Context, usernam...
method Exchange (line 174) | func (c *Config) Exchange(ctx context.Context, code string) (*Token, e...
method Client (line 187) | func (c *Config) Client(ctx context.Context, t *Token) *http.Client {
method TokenSource (line 195) | func (c *Config) TokenSource(ctx context.Context, t *Token) TokenSource {
type TokenSource (line 62) | type TokenSource interface
type Endpoint (line 71) | type Endpoint struct
type AuthCodeOption (line 98) | type AuthCodeOption interface
type setParam (line 102) | type setParam struct
method setValue (line 104) | func (p setParam) setValue(m url.Values) { m.Set(p.k, p.v) }
function SetAuthURLParam (line 108) | func SetAuthURLParam(key, value string) AuthCodeOption {
type tokenRefresher (line 211) | type tokenRefresher struct
method Token (line 221) | func (tf *tokenRefresher) Token() (*Token, error) {
type reuseTokenSource (line 244) | type reuseTokenSource struct
method Token (line 254) | func (s *reuseTokenSource) Token() (*Token, error) {
function StaticTokenSource (line 271) | func StaticTokenSource(t *Token) TokenSource {
type staticTokenSource (line 276) | type staticTokenSource struct
method Token (line 280) | func (s staticTokenSource) Token() (*Token, error) {
function NewClient (line 294) | func NewClient(ctx context.Context, src TokenSource) *http.Client {
function ReuseTokenSource (line 322) | func ReuseTokenSource(t *Token, src TokenSource) TokenSource {
FILE: vendor/golang.org/x/oauth2/token.go
constant expiryDelta (line 21) | expiryDelta = 10 * time.Second
type Token (line 30) | type Token struct
method Type (line 57) | func (t *Token) Type() string {
method SetAuthHeader (line 78) | func (t *Token) SetAuthHeader(r *http.Request) {
method WithExtra (line 85) | func (t *Token) WithExtra(extra interface{}) *Token {
method Extra (line 95) | func (t *Token) Extra(key string) interface{} {
method expired (line 122) | func (t *Token) expired() bool {
method Valid (line 130) | func (t *Token) Valid() bool {
function tokenFromInternal (line 136) | func tokenFromInternal(t *internal.Token) *Token {
function retrieveToken (line 152) | func retrieveToken(ctx context.Context, c *Config, v url.Values) (*Token...
FILE: vendor/golang.org/x/oauth2/transport.go
type Transport (line 20) | type Transport struct
method RoundTrip (line 36) | func (t *Transport) RoundTrip(req *http.Request) (*http.Response, erro...
method CancelRequest (line 61) | func (t *Transport) CancelRequest(req *http.Request) {
method base (line 74) | func (t *Transport) base() http.RoundTripper {
method setModReq (line 81) | func (t *Transport) setModReq(orig, mod *http.Request) {
function cloneRequest (line 96) | func cloneRequest(r *http.Request) *http.Request {
type onEOFReader (line 108) | type onEOFReader struct
method Read (line 113) | func (r *onEOFReader) Read(p []byte) (n int, err error) {
method Close (line 121) | func (r *onEOFReader) Close() error {
method runFunc (line 127) | func (r *onEOFReader) runFunc() {
FILE: vendor/google.golang.org/api/drive/v3/drive-gen.go
constant apiId (line 43) | apiId = "drive:v3"
constant apiName (line 44) | apiName = "drive"
constant apiVersion (line 45) | apiVersion = "v3"
constant basePath (line 46) | basePath = "https://www.googleapis.com/drive/v3/"
constant DriveScope (line 51) | DriveScope = "https://www.googleapis.com/auth/drive"
constant DriveAppdataScope (line 54) | DriveAppdataScope = "https://www.googleapis.com/auth/drive.appdata"
constant DriveFileScope (line 58) | DriveFileScope = "https://www.googleapis.com/auth/drive.file"
constant DriveMetadataScope (line 61) | DriveMetadataScope = "https://www.googleapis.com/auth/drive.metadata"
constant DriveMetadataReadonlyScope (line 64) | DriveMetadataReadonlyScope = "https://www.googleapis.com/auth/drive.meta...
constant DrivePhotosReadonlyScope (line 67) | DrivePhotosReadonlyScope = "https://www.googleapis.com/auth/drive.photos...
constant DriveReadonlyScope (line 70) | DriveReadonlyScope = "https://www.googleapis.com/auth/drive.readonly"
constant DriveScriptsScope (line 73) | DriveScriptsScope = "https://www.googleapis.com/auth/drive.scripts"
function New (line 76) | func New(client *http.Client) (*Service, error) {
type Service (line 92) | type Service struct
method userAgent (line 114) | func (s *Service) userAgent() string {
function NewAboutService (line 121) | func NewAboutService(s *Service) *AboutService {
type AboutService (line 126) | type AboutService struct
method Get (line 1367) | func (r *AboutService) Get() *AboutGetCall {
function NewChangesService (line 130) | func NewChangesService(s *Service) *ChangesService {
type ChangesService (line 135) | type ChangesService struct
method GetStartPageToken (line 1483) | func (r *ChangesService) GetStartPageToken() *ChangesGetStartPageToken...
method List (line 1598) | func (r *ChangesService) List(pageToken string) *ChangesListCall {
method Watch (line 1786) | func (r *ChangesService) Watch(pageToken string, channel *Channel) *Ch...
function NewChannelsService (line 139) | func NewChannelsService(s *Service) *ChannelsService {
type ChannelsService (line 144) | type ChannelsService struct
method Stop (line 1972) | func (r *ChannelsService) Stop(channel *Channel) *ChannelsStopCall {
function NewCommentsService (line 148) | func NewCommentsService(s *Service) *CommentsService {
type CommentsService (line 153) | type CommentsService struct
method Create (line 2059) | func (r *CommentsService) Create(fileId string, comment *Comment) *Com...
method Delete (line 2181) | func (r *CommentsService) Delete(fileId string, commentId string) *Com...
method Get (line 2276) | func (r *CommentsService) Get(fileId string, commentId string) *Commen...
method List (line 2425) | func (r *CommentsService) List(fileId string) *CommentsListCall {
method Update (line 2629) | func (r *CommentsService) Update(fileId string, commentId string, comm...
function NewFilesService (line 157) | func NewFilesService(s *Service) *FilesService {
type FilesService (line 162) | type FilesService struct
method Copy (line 2761) | func (r *FilesService) Copy(fileId string, file *File) *FilesCopyCall {
method Create (line 2933) | func (r *FilesService) Create(file *File) *FilesCreateCall {
method Delete (line 3213) | func (r *FilesService) Delete(fileId string) *FilesDeleteCall {
method EmptyTrash (line 3297) | func (r *FilesService) EmptyTrash() *FilesEmptyTrashCall {
method Export (line 3368) | func (r *FilesService) Export(fileId string, mimeType string) *FilesEx...
method GenerateIds (line 3492) | func (r *FilesService) GenerateIds() *FilesGenerateIdsCall {
method Get (line 3636) | func (r *FilesService) Get(fileId string) *FilesGetCall {
method List (line 3799) | func (r *FilesService) List() *FilesListCall {
method Update (line 4043) | func (r *FilesService) Update(fileId string, file *File) *FilesUpdateC...
method Watch (line 4342) | func (r *FilesService) Watch(fileId string, channel *Channel) *FilesWa...
function NewPermissionsService (line 166) | func NewPermissionsService(s *Service) *PermissionsService {
type PermissionsService (line 171) | type PermissionsService struct
method Create (line 4504) | func (r *PermissionsService) Create(fileId string, permission *Permiss...
method Delete (line 4668) | func (r *PermissionsService) Delete(fileId string, permissionId string...
method Get (line 4763) | func (r *PermissionsService) Get(fileId string, permissionId string) *...
method List (line 4901) | func (r *PermissionsService) List(fileId string) *PermissionsListCall {
method Update (line 5031) | func (r *PermissionsService) Update(fileId string, permissionId string...
function NewRepliesService (line 175) | func NewRepliesService(s *Service) *RepliesService {
type RepliesService (line 180) | type RepliesService struct
method Create (line 5178) | func (r *RepliesService) Create(fileId string, commentId string, reply...
method Delete (line 5310) | func (r *RepliesService) Delete(fileId string, commentId string, reply...
method Get (line 5415) | func (r *RepliesService) Get(fileId string, commentId string, replyId ...
method List (line 5574) | func (r *RepliesService) List(fileId string, commentId string) *Replie...
method Update (line 5775) | func (r *RepliesService) Update(fileId string, commentId string, reply...
function NewRevisionsService (line 184) | func NewRevisionsService(s *Service) *RevisionsService {
type RevisionsService (line 189) | type RevisionsService struct
method Delete (line 5916) | func (r *RevisionsService) Delete(fileId string, revisionId string) *R...
method Get (line 6012) | func (r *RevisionsService) Get(fileId string, revisionId string) *Revi...
method List (line 6184) | func (r *RevisionsService) List(fileId string) *RevisionsListCall {
method Update (line 6315) | func (r *RevisionsService) Update(fileId string, revisionId string, re...
type About (line 195) | type About struct
method MarshalJSON (line 240) | func (s *About) MarshalJSON() ([]byte, error) {
type AboutStorageQuota (line 248) | type AboutStorageQuota struct
method MarshalJSON (line 271) | func (s *AboutStorageQuota) MarshalJSON() ([]byte, error) {
type Change (line 278) | type Change struct
method MarshalJSON (line 305) | func (s *Change) MarshalJSON() ([]byte, error) {
type ChangeList (line 312) | type ChangeList struct
method MarshalJSON (line 341) | func (s *ChangeList) MarshalJSON() ([]byte, error) {
type Channel (line 348) | type Channel struct
method MarshalJSON (line 399) | func (s *Channel) MarshalJSON() ([]byte, error) {
type Comment (line 406) | type Comment struct
method MarshalJSON (line 466) | func (s *Comment) MarshalJSON() ([]byte, error) {
type CommentQuotedFileContent (line 475) | type CommentQuotedFileContent struct
method MarshalJSON (line 492) | func (s *CommentQuotedFileContent) MarshalJSON() ([]byte, error) {
type CommentList (line 499) | type CommentList struct
method MarshalJSON (line 523) | func (s *CommentList) MarshalJSON() ([]byte, error) {
type File (line 530) | type File struct
method MarshalJSON (line 730) | func (s *File) MarshalJSON() ([]byte, error) {
type FileCapabilities (line 737) | type FileCapabilities struct
method MarshalJSON (line 764) | func (s *FileCapabilities) MarshalJSON() ([]byte, error) {
type FileContentHints (line 772) | type FileContentHints struct
method MarshalJSON (line 791) | func (s *FileContentHints) MarshalJSON() ([]byte, error) {
type FileContentHintsThumbnail (line 799) | type FileContentHintsThumbnail struct
method MarshalJSON (line 816) | func (s *FileContentHintsThumbnail) MarshalJSON() ([]byte, error) {
type FileImageMediaMetadata (line 824) | type FileImageMediaMetadata struct
method MarshalJSON (line 900) | func (s *FileImageMediaMetadata) MarshalJSON() ([]byte, error) {
type FileImageMediaMetadataLocation (line 908) | type FileImageMediaMetadataLocation struct
method MarshalJSON (line 927) | func (s *FileImageMediaMetadataLocation) MarshalJSON() ([]byte, error) {
type FileVideoMediaMetadata (line 935) | type FileVideoMediaMetadata struct
method MarshalJSON (line 954) | func (s *FileVideoMediaMetadata) MarshalJSON() ([]byte, error) {
type FileList (line 961) | type FileList struct
method MarshalJSON (line 985) | func (s *FileList) MarshalJSON() ([]byte, error) {
type GeneratedIds (line 993) | type GeneratedIds struct
method MarshalJSON (line 1017) | func (s *GeneratedIds) MarshalJSON() ([]byte, error) {
type Permission (line 1025) | type Permission struct
method MarshalJSON (line 1078) | func (s *Permission) MarshalJSON() ([]byte, error) {
type PermissionList (line 1085) | type PermissionList struct
method MarshalJSON (line 1105) | func (s *PermissionList) MarshalJSON() ([]byte, error) {
type Reply (line 1112) | type Reply struct
method MarshalJSON (line 1161) | func (s *Reply) MarshalJSON() ([]byte, error) {
type ReplyList (line 1168) | type ReplyList struct
method MarshalJSON (line 1192) | func (s *ReplyList) MarshalJSON() ([]byte, error) {
type Revision (line 1199) | type Revision struct
method MarshalJSON (line 1260) | func (s *Revision) MarshalJSON() ([]byte, error) {
type RevisionList (line 1267) | type RevisionList struct
method MarshalJSON (line 1287) | func (s *RevisionList) MarshalJSON() ([]byte, error) {
type StartPageToken (line 1293) | type StartPageToken struct
method MarshalJSON (line 1313) | func (s *StartPageToken) MarshalJSON() ([]byte, error) {
type User (line 1320) | type User struct
method MarshalJSON (line 1350) | func (s *User) MarshalJSON() ([]byte, error) {
type AboutGetCall (line 1358) | type AboutGetCall struct
method Fields (line 1375) | func (c *AboutGetCall) Fields(s ...googleapi.Field) *AboutGetCall {
method IfNoneMatch (line 1385) | func (c *AboutGetCall) IfNoneMatch(entityTag string) *AboutGetCall {
method Context (line 1393) | func (c *AboutGetCall) Context(ctx context.Context) *AboutGetCall {
method doRequest (line 1398) | func (c *AboutGetCall) doRequest(alt string) (*http.Response, error) {
method Do (line 1422) | func (c *AboutGetCall) Do(opts ...googleapi.CallOption) (*About, error) {
type ChangesGetStartPageTokenCall (line 1474) | type ChangesGetStartPageTokenCall struct
method Fields (line 1491) | func (c *ChangesGetStartPageTokenCall) Fields(s ...googleapi.Field) *C...
method IfNoneMatch (line 1501) | func (c *ChangesGetStartPageTokenCall) IfNoneMatch(entityTag string) *...
method Context (line 1509) | func (c *ChangesGetStartPageTokenCall) Context(ctx context.Context) *C...
method doRequest (line 1514) | func (c *ChangesGetStartPageTokenCall) doRequest(alt string) (*http.Re...
method Do (line 1538) | func (c *ChangesGetStartPageTokenCall) Do(opts ...googleapi.CallOption...
type ChangesListCall (line 1590) | type ChangesListCall struct
method IncludeRemoved (line 1607) | func (c *ChangesListCall) IncludeRemoved(includeRemoved bool) *Changes...
method PageSize (line 1614) | func (c *ChangesListCall) PageSize(pageSize int64) *ChangesListCall {
method RestrictToMyDrive (line 1624) | func (c *ChangesListCall) RestrictToMyDrive(restrictToMyDrive bool) *C...
method Spaces (line 1632) | func (c *ChangesListCall) Spaces(spaces string) *ChangesListCall {
method Fields (line 1640) | func (c *ChangesListCall) Fields(s ...googleapi.Field) *ChangesListCall {
method IfNoneMatch (line 1650) | func (c *ChangesListCall) IfNoneMatch(entityTag string) *ChangesListCa...
method Context (line 1658) | func (c *ChangesListCall) Context(ctx context.Context) *ChangesListCall {
method doRequest (line 1663) | func (c *ChangesListCall) doRequest(alt string) (*http.Response, error) {
method Do (line 1687) | func (c *ChangesListCall) Do(opts ...googleapi.CallOption) (*ChangeLis...
type ChangesWatchCall (line 1778) | type ChangesWatchCall struct
method IncludeRemoved (line 1796) | func (c *ChangesWatchCall) IncludeRemoved(includeRemoved bool) *Change...
method PageSize (line 1803) | func (c *ChangesWatchCall) PageSize(pageSize int64) *ChangesWatchCall {
method RestrictToMyDrive (line 1813) | func (c *ChangesWatchCall) RestrictToMyDrive(restrictToMyDrive bool) *...
method Spaces (line 1821) | func (c *ChangesWatchCall) Spaces(spaces string) *ChangesWatchCall {
method Fields (line 1829) | func (c *ChangesWatchCall) Fields(s ...googleapi.Field) *ChangesWatchC...
method Context (line 1837) | func (c *ChangesWatchCall) Context(ctx context.Context) *ChangesWatchC...
method doRequest (line 1842) | func (c *ChangesWatchCall) doRequest(alt string) (*http.Response, erro...
method Do (line 1869) | func (c *ChangesWatchCall) Do(opts ...googleapi.CallOption) (*Channel,...
type ChannelsStopCall (line 1964) | type ChannelsStopCall struct
method Fields (line 1981) | func (c *ChannelsStopCall) Fields(s ...googleapi.Field) *ChannelsStopC...
method Context (line 1989) | func (c *ChannelsStopCall) Context(ctx context.Context) *ChannelsStopC...
method doRequest (line 1994) | func (c *ChannelsStopCall) doRequest(alt string) (*http.Response, erro...
method Do (line 2015) | func (c *ChannelsStopCall) Do(opts ...googleapi.CallOption) error {
type CommentsCreateCall (line 2050) | type CommentsCreateCall struct
method Fields (line 2069) | func (c *CommentsCreateCall) Fields(s ...googleapi.Field) *CommentsCre...
method Context (line 2077) | func (c *CommentsCreateCall) Context(ctx context.Context) *CommentsCre...
method doRequest (line 2082) | func (c *CommentsCreateCall) doRequest(alt string) (*http.Response, er...
method Do (line 2111) | func (c *CommentsCreateCall) Do(opts ...googleapi.CallOption) (*Commen...
type CommentsDeleteCall (line 2172) | type CommentsDeleteCall struct
method Fields (line 2191) | func (c *CommentsDeleteCall) Fields(s ...googleapi.Field) *CommentsDel...
method Context (line 2199) | func (c *CommentsDeleteCall) Context(ctx context.Context) *CommentsDel...
method doRequest (line 2204) | func (c *CommentsDeleteCall) doRequest(alt string) (*http.Response, er...
method Do (line 2222) | func (c *CommentsDeleteCall) Do(opts ...googleapi.CallOption) error {
type CommentsGetCall (line 2266) | type CommentsGetCall struct
method IncludeDeleted (line 2286) | func (c *CommentsGetCall) IncludeDeleted(includeDeleted bool) *Comment...
method Fields (line 2294) | func (c *CommentsGetCall) Fields(s ...googleapi.Field) *CommentsGetCall {
method IfNoneMatch (line 2304) | func (c *CommentsGetCall) IfNoneMatch(entityTag string) *CommentsGetCa...
method Context (line 2312) | func (c *CommentsGetCall) Context(ctx context.Context) *CommentsGetCall {
method doRequest (line 2317) | func (c *CommentsGetCall) doRequest(alt string) (*http.Response, error) {
method Do (line 2344) | func (c *CommentsGetCall) Do(opts ...googleapi.CallOption) (*Comment, ...
type CommentsListCall (line 2416) | type CommentsListCall struct
method IncludeDeleted (line 2434) | func (c *CommentsListCall) IncludeDeleted(includeDeleted bool) *Commen...
method PageSize (line 2441) | func (c *CommentsListCall) PageSize(pageSize int64) *CommentsListCall {
method PageToken (line 2449) | func (c *CommentsListCall) PageToken(pageToken string) *CommentsListCa...
method StartModifiedTime (line 2457) | func (c *CommentsListCall) StartModifiedTime(startModifiedTime string)...
method Fields (line 2465) | func (c *CommentsListCall) Fields(s ...googleapi.Field) *CommentsListC...
method IfNoneMatch (line 2475) | func (c *CommentsListCall) IfNoneMatch(entityTag string) *CommentsList...
method Context (line 2483) | func (c *CommentsListCall) Context(ctx context.Context) *CommentsListC...
method doRequest (line 2488) | func (c *CommentsListCall) doRequest(alt string) (*http.Response, erro...
method Do (line 2514) | func (c *CommentsListCall) Do(opts ...googleapi.CallOption) (*CommentL...
method Pages (line 2599) | func (c *CommentsListCall) Pages(ctx context.Context, f func(*CommentL...
type CommentsUpdateCall (line 2619) | type CommentsUpdateCall struct
method Fields (line 2640) | func (c *CommentsUpdateCall) Fields(s ...googleapi.Field) *CommentsUpd...
method Context (line 2648) | func (c *CommentsUpdateCall) Context(ctx context.Context) *CommentsUpd...
method doRequest (line 2653) | func (c *CommentsUpdateCall) doRequest(alt string) (*http.Response, er...
method Do (line 2683) | func (c *CommentsUpdateCall) Do(opts ...googleapi.CallOption) (*Commen...
type FilesCopyCall (line 2751) | type FilesCopyCall struct
method IgnoreDefaultVisibility (line 2774) | func (c *FilesCopyCall) IgnoreDefaultVisibility(ignoreDefaultVisibilit...
method KeepRevisionForever (line 2783) | func (c *FilesCopyCall) KeepRevisionForever(keepRevisionForever bool) ...
method OcrLanguage (line 2790) | func (c *FilesCopyCall) OcrLanguage(ocrLanguage string) *FilesCopyCall {
method Fields (line 2798) | func (c *FilesCopyCall) Fields(s ...googleapi.Field) *FilesCopyCall {
method Context (line 2806) | func (c *FilesCopyCall) Context(ctx context.Context) *FilesCopyCall {
method doRequest (line 2811) | func (c *FilesCopyCall) doRequest(alt string) (*http.Response, error) {
method Do (line 2840) | func (c *FilesCopyCall) Do(opts ...googleapi.CallOption) (*File, error) {
type FilesCreateCall (line 2920) | type FilesCreateCall struct
method IgnoreDefaultVisibility (line 2945) | func (c *FilesCreateCall) IgnoreDefaultVisibility(ignoreDefaultVisibil...
method KeepRevisionForever (line 2954) | func (c *FilesCreateCall) KeepRevisionForever(keepRevisionForever bool...
method OcrLanguage (line 2961) | func (c *FilesCreateCall) OcrLanguage(ocrLanguage string) *FilesCreate...
method UseContentAsIndexableText (line 2969) | func (c *FilesCreateCall) UseContentAsIndexableText(useContentAsIndexa...
method Media (line 2982) | func (c *FilesCreateCall) Media(r io.Reader, options ...googleapi.Medi...
method ResumableMedia (line 3001) | func (c *FilesCreateCall) ResumableMedia(ctx context.Context, r io.Rea...
method ProgressUpdater (line 3015) | func (c *FilesCreateCall) ProgressUpdater(pu googleapi.ProgressUpdater...
method Fields (line 3023) | func (c *FilesCreateCall) Fields(s ...googleapi.Field) *FilesCreateCall {
method Context (line 3033) | func (c *FilesCreateCall) Context(ctx context.Context) *FilesCreateCall {
method doRequest (line 3038) | func (c *FilesCreateCall) doRequest(alt string) (*http.Response, error) {
method Do (line 3082) | func (c *FilesCreateCall) Do(opts ...googleapi.CallOption) (*File, err...
type FilesDeleteCall (line 3203) | type FilesDeleteCall struct
method Fields (line 3222) | func (c *FilesDeleteCall) Fields(s ...googleapi.Field) *FilesDeleteCall {
method Context (line 3230) | func (c *FilesDeleteCall) Context(ctx context.Context) *FilesDeleteCall {
method doRequest (line 3235) | func (c *FilesDeleteCall) doRequest(alt string) (*http.Response, error) {
method Do (line 3252) | func (c *FilesDeleteCall) Do(opts ...googleapi.CallOption) error {
type FilesEmptyTrashCall (line 3290) | type FilesEmptyTrashCall struct
method Fields (line 3305) | func (c *FilesEmptyTrashCall) Fields(s ...googleapi.Field) *FilesEmpty...
method Context (line 3313) | func (c *FilesEmptyTrashCall) Context(ctx context.Context) *FilesEmpty...
method doRequest (line 3318) | func (c *FilesEmptyTrashCall) doRequest(alt string) (*http.Response, e...
method Do (line 3333) | func (c *FilesEmptyTrashCall) Do(opts ...googleapi.CallOption) error {
type FilesExportCall (line 3358) | type FilesExportCall struct
method Fields (line 3378) | func (c *FilesExportCall) Fields(s ...googleapi.Field) *FilesExportCall {
method IfNoneMatch (line 3388) | func (c *FilesExportCall) IfNoneMatch(entityTag string) *FilesExportCa...
method Context (line 3396) | func (c *FilesExportCall) Context(ctx context.Context) *FilesExportCall {
method doRequest (line 3401) | func (c *FilesExportCall) doRequest(alt string) (*http.Response, error) {
method Download (line 3423) | func (c *FilesExportCall) Download(opts ...googleapi.CallOption) (*htt...
method Do (line 3437) | func (c *FilesExportCall) Do(opts ...googleapi.CallOption) error {
type FilesGenerateIdsCall (line 3483) | type FilesGenerateIdsCall struct
method Count (line 3499) | func (c *FilesGenerateIdsCall) Count(count int64) *FilesGenerateIdsCall {
method Space (line 3507) | func (c *FilesGenerateIdsCall) Space(space string) *FilesGenerateIdsCa...
method Fields (line 3515) | func (c *FilesGenerateIdsCall) Fields(s ...googleapi.Field) *FilesGene...
method IfNoneMatch (line 3525) | func (c *FilesGenerateIdsCall) IfNoneMatch(entityTag string) *FilesGen...
method Context (line 3533) | func (c *FilesGenerateIdsCall) Context(ctx context.Context) *FilesGene...
method doRequest (line 3538) | func (c *FilesGenerateIdsCall) doRequest(alt string) (*http.Response, ...
method Do (line 3562) | func (c *FilesGenerateIdsCall) Do(opts ...googleapi.CallOption) (*Gene...
type FilesGetCall (line 3627) | type FilesGetCall struct
method AcknowledgeAbuse (line 3646) | func (c *FilesGetCall) AcknowledgeAbuse(acknowledgeAbuse bool) *FilesG...
method Fields (line 3654) | func (c *FilesGetCall) Fields(s ...googleapi.Field) *FilesGetCall {
method IfNoneMatch (line 3664) | func (c *FilesGetCall) IfNoneMatch(entityTag string) *FilesGetCall {
method Context (line 3672) | func (c *FilesGetCall) Context(ctx context.Context) *FilesGetCall {
method doRequest (line 3677) | func (c *FilesGetCall) doRequest(alt string) (*http.Response, error) {
method Download (line 3699) | func (c *FilesGetCall) Download(opts ...googleapi.CallOption) (*http.R...
method Do (line 3719) | func (c *FilesGetCall) Do(opts ...googleapi.CallOption) (*File, error) {
type FilesListCall (line 3791) | type FilesListCall struct
method Corpus (line 3810) | func (c *FilesListCall) Corpus(corpus string) *FilesListCall {
method OrderBy (line 3824) | func (c *FilesListCall) OrderBy(orderBy string) *FilesListCall {
method PageSize (line 3831) | func (c *FilesListCall) PageSize(pageSize int64) *FilesListCall {
method PageToken (line 3839) | func (c *FilesListCall) PageToken(pageToken string) *FilesListCall {
method Q (line 3846) | func (c *FilesListCall) Q(q string) *FilesListCall {
method Spaces (line 3854) | func (c *FilesListCall) Spaces(spaces string) *FilesListCall {
method Fields (line 3862) | func (c *FilesListCall) Fields(s ...googleapi.Field) *FilesListCall {
method IfNoneMatch (line 3872) | func (c *FilesListCall) IfNoneMatch(entityTag string) *FilesListCall {
method Context (line 3880) | func (c *FilesListCall) Context(ctx context.Context) *FilesListCall {
method doRequest (line 3885) | func (c *FilesListCall) doRequest(alt string) (*http.Response, error) {
method Do (line 3909) | func (c *FilesListCall) Do(opts ...googleapi.CallOption) (*FileList, e...
method Pages (line 4008) | func (c *FilesListCall) Pages(ctx context.Context, f func(*FileList) e...
type FilesUpdateCall (line 4028) | type FilesUpdateCall struct
method AddParents (line 4052) | func (c *FilesUpdateCall) AddParents(addParents string) *FilesUpdateCa...
method KeepRevisionForever (line 4061) | func (c *FilesUpdateCall) KeepRevisionForever(keepRevisionForever bool...
method OcrLanguage (line 4068) | func (c *FilesUpdateCall) OcrLanguage(ocrLanguage string) *FilesUpdate...
method RemoveParents (line 4075) | func (c *FilesUpdateCall) RemoveParents(removeParents string) *FilesUp...
method UseContentAsIndexableText (line 4083) | func (c *FilesUpdateCall) UseContentAsIndexableText(useContentAsIndexa...
method Media (line 4096) | func (c *FilesUpdateCall) Media(r io.Reader, options ...googleapi.Medi...
method ResumableMedia (line 4115) | func (c *FilesUpdateCall) ResumableMedia(ctx context.Context, r io.Rea...
method ProgressUpdater (line 4129) | func (c *FilesUpdateCall) ProgressUpdater(pu googleapi.ProgressUpdater...
method Fields (line 4137) | func (c *FilesUpdateCall) Fields(s ...googleapi.Field) *FilesUpdateCall {
method Context (line 4147) | func (c *FilesUpdateCall) Context(ctx context.Context) *FilesUpdateCall {
method doRequest (line 4152) | func (c *FilesUpdateCall) doRequest(alt string) (*http.Response, error) {
method Do (line 4198) | func (c *FilesUpdateCall) Do(opts ...googleapi.CallOption) (*File, err...
type FilesWatchCall (line 4333) | type FilesWatchCall struct
method AcknowledgeAbuse (line 4353) | func (c *FilesWatchCall) AcknowledgeAbuse(acknowledgeAbuse bool) *File...
method Fields (line 4361) | func (c *FilesWatchCall) Fields(s ...googleapi.Field) *FilesWatchCall {
method Context (line 4369) | func (c *FilesWatchCall) Context(ctx context.Context) *FilesWatchCall {
method doRequest (line 4374) | func (c *FilesWatchCall) doRequest(alt string) (*http.Response, error) {
method Download (line 4399) | func (c *FilesWatchCall) Download(opts ...googleapi.CallOption) (*http...
method Do (line 4419) | func (c *FilesWatchCall) Do(opts ...googleapi.CallOption) (*Channel, e...
type PermissionsCreateCall (line 4495) | type PermissionsCreateCall struct
method EmailMessage (line 4513) | func (c *PermissionsCreateCall) EmailMessage(emailMessage string) *Per...
method SendNotificationEmail (line 4523) | func (c *PermissionsCreateCall) SendNotificationEmail(sendNotification...
method TransferOwnership (line 4532) | func (c *PermissionsCreateCall) TransferOwnership(transferOwnership bo...
method Fields (line 4540) | func (c *PermissionsCreateCall) Fields(s ...googleapi.Field) *Permissi...
method Context (line 4548) | func (c *PermissionsCreateCall) Context(ctx context.Context) *Permissi...
method doRequest (line 4553) | func (c *PermissionsCreateCall) doRequest(alt string) (*http.Response,...
method Do (line 4582) | func (c *PermissionsCreateCall) Do(opts ...googleapi.CallOption) (*Per...
type PermissionsDeleteCall (line 4659) | type PermissionsDeleteCall struct
method Fields (line 4678) | func (c *PermissionsDeleteCall) Fields(s ...googleapi.Field) *Permissi...
method Context (line 4686) | func (c *PermissionsDeleteCall) Context(ctx context.Context) *Permissi...
method doRequest (line 4691) | func (c *PermissionsDeleteCall) doRequest(alt string) (*http.Response,...
method Do (line 4709) | func (c *PermissionsDeleteCall) Do(opts ...googleapi.CallOption) error {
type PermissionsGetCall (line 4753) | type PermissionsGetCall struct
method Fields (line 4773) | func (c *PermissionsGetCall) Fields(s ...googleapi.Field) *Permissions...
method IfNoneMatch (line 4783) | func (c *PermissionsGetCall) IfNoneMatch(entityTag string) *Permission...
method Context (line 4791) | func (c *PermissionsGetCall) Context(ctx context.Context) *Permissions...
method doRequest (line 4796) | func (c *PermissionsGetCall) doRequest(alt string) (*http.Response, er...
method Do (line 4823) | func (c *PermissionsGetCall) Do(opts ...googleapi.CallOption) (*Permis...
type PermissionsListCall (line 4892) | type PermissionsListCall struct
method Fields (line 4910) | func (c *PermissionsListCall) Fields(s ...googleapi.Field) *Permission...
method IfNoneMatch (line 4920) | func (c *PermissionsListCall) IfNoneMatch(entityTag string) *Permissio...
method Context (line 4928) | func (c *PermissionsListCall) Context(ctx context.Context) *Permission...
method doRequest (line 4933) | func (c *PermissionsListCall) doRequest(alt string) (*http.Response, e...
method Do (line 4959) | func (c *PermissionsListCall) Do(opts ...googleapi.CallOption) (*Permi...
type PermissionsUpdateCall (line 5021) | type PermissionsUpdateCall struct
method TransferOwnership (line 5043) | func (c *PermissionsUpdateCall) TransferOwnership(transferOwnership bo...
method Fields (line 5051) | func (c *PermissionsUpdateCall) Fields(s ...googleapi.Field) *Permissi...
method Context (line 5059) | func (c *PermissionsUpdateCall) Context(ctx context.Context) *Permissi...
method doRequest (line 5064) | func (c *PermissionsUpdateCall) doRequest(alt string) (*http.Response,...
method Do (line 5094) | func (c *PermissionsUpdateCall) Do(opts ...googleapi.CallOption) (*Per...
type RepliesCreateCall (line 5168) | type RepliesCreateCall struct
method Fields (line 5189) | func (c *RepliesCreateCall) Fields(s ...googleapi.Field) *RepliesCreat...
method Context (line 5197) | func (c *RepliesCreateCall) Context(ctx context.Context) *RepliesCreat...
method doRequest (line 5202) | func (c *RepliesCreateCall) doRequest(alt string) (*http.Response, err...
method Do (line 5232) | func (c *RepliesCreateCall) Do(opts ...googleapi.CallOption) (*Reply, ...
type RepliesDeleteCall (line 5300) | type RepliesDeleteCall struct
method Fields (line 5321) | func (c *RepliesDeleteCall) Fields(s ...googleapi.Field) *RepliesDelet...
method Context (line 5329) | func (c *RepliesDeleteCall) Context(ctx context.Context) *RepliesDelet...
method doRequest (line 5334) | func (c *RepliesDeleteCall) doRequest(alt string) (*http.Response, err...
method Do (line 5353) | func (c *RepliesDeleteCall) Do(opts ...googleapi.CallOption) error {
type RepliesGetCall (line 5404) | type RepliesGetCall struct
method IncludeDeleted (line 5426) | func (c *RepliesGetCall) IncludeDeleted(includeDeleted bool) *RepliesG...
method Fields (line 5434) | func (c *RepliesGetCall) Fields(s ...googleapi.Field) *RepliesGetCall {
method IfNoneMatch (line 5444) | func (c *RepliesGetCall) IfNoneMatch(entityTag string) *RepliesGetCall {
method Context (line 5452) | func (c *RepliesGetCall) Context(ctx context.Context) *RepliesGetCall {
method doRequest (line 5457) | func (c *RepliesGetCall) doRequest(alt string) (*http.Response, error) {
method Do (line 5485) | func (c *RepliesGetCall) Do(opts ...googleapi.CallOption) (*Reply, err...
type RepliesListCall (line 5564) | type RepliesListCall struct
method IncludeDeleted (line 5584) | func (c *RepliesListCall) IncludeDeleted(includeDeleted bool) *Replies...
method PageSize (line 5591) | func (c *RepliesListCall) PageSize(pageSize int64) *RepliesListCall {
method PageToken (line 5599) | func (c *RepliesListCall) PageToken(pageToken string) *RepliesListCall {
method Fields (line 5607) | func (c *RepliesListCall) Fields(s ...googleapi.Field) *RepliesListCall {
method IfNoneMatch (line 5617) | func (c *RepliesListCall) IfNoneMatch(entityTag string) *RepliesListCa...
method Context (line 5625) | func (c *RepliesListCall) Context(ctx context.Context) *RepliesListCall {
method doRequest (line 5630) | func (c *RepliesListCall) doRequest(alt string) (*http.Response, error) {
method Do (line 5657) | func (c *RepliesListCall) Do(opts ...googleapi.CallOption) (*ReplyList...
method Pages (line 5744) | func (c *RepliesListCall) Pages(ctx context.Context, f func(*ReplyList...
type RepliesUpdateCall (line 5764) | type RepliesUpdateCall struct
method Fields (line 5787) | func (c *RepliesUpdateCall) Fields(s ...googleapi.Field) *RepliesUpdat...
method Context (line 5795) | func (c *RepliesUpdateCall) Context(ctx context.Context) *RepliesUpdat...
method doRequest (line 5800) | func (c *RepliesUpdateCall) doRequest(alt string) (*http.Response, err...
method Do (line 5831) | func (c *RepliesUpdateCall) Do(opts ...googleapi.CallOption) (*Reply, ...
type RevisionsDeleteCall (line 5906) | type RevisionsDeleteCall struct
method Fields (line 5926) | func (c *RevisionsDeleteCall) Fields(s ...googleapi.Field) *RevisionsD...
method Context (line 5934) | func (c *RevisionsDeleteCall) Context(ctx context.Context) *RevisionsD...
method doRequest (line 5939) | func (c *RevisionsDeleteCall) doRequest(alt string) (*http.Response, e...
method Do (line 5957) | func (c *RevisionsDeleteCall) Do(opts ...googleapi.CallOption) error {
type RevisionsGetCall (line 6002) | type RevisionsGetCall struct
method AcknowledgeAbuse (line 6023) | func (c *RevisionsGetCall) AcknowledgeAbuse(acknowledgeAbuse bool) *Re...
method Fields (line 6031) | func (c *RevisionsGetCall) Fields(s ...googleapi.Field) *RevisionsGetC...
method IfNoneMatch (line 6041) | func (c *RevisionsGetCall) IfNoneMatch(entityTag string) *RevisionsGet...
method Context (line 6049) | func (c *RevisionsGetCall) Context(ctx context.Context) *RevisionsGetC...
method doRequest (line 6054) | func (c *RevisionsGetCall) doRequest(alt string) (*http.Response, erro...
method Download (line 6077) | func (c *RevisionsGetCall) Download(opts ...googleapi.CallOption) (*ht...
method Do (line 6097) | func (c *RevisionsGetCall) Do(opts ...googleapi.CallOption) (*Revision...
type RevisionsListCall (line 6175) | type RevisionsListCall struct
method Fields (line 6193) | func (c *RevisionsListCall) Fields(s ...googleapi.Field) *RevisionsLis...
method IfNoneMatch (line 6203) | func (c *RevisionsListCall) IfNoneMatch(entityTag string) *RevisionsLi...
method Context (line 6211) | func (c *RevisionsListCall) Context(ctx context.Context) *RevisionsLis...
method doRequest (line 6216) | func (c *RevisionsListCall) doRequest(alt string) (*http.Response, err...
method Do (line 6242) | func (c *RevisionsListCall) Do(opts ...googleapi.CallOption) (*Revisio...
type RevisionsUpdateCall (line 6305) | type RevisionsUpdateCall struct
method Fields (line 6326) | func (c *RevisionsUpdateCall) Fields(s ...googleapi.Field) *RevisionsU...
method Context (line 6334) | func (c *RevisionsUpdateCall) Context(ctx context.Context) *RevisionsU...
method doRequest (line 6339) | func (c *RevisionsUpdateCall) doRequest(alt string) (*http.Response, e...
method Do (line 6369) | func (c *RevisionsUpdateCall) Do(opts ...googleapi.CallOption) (*Revis...
FILE: vendor/google.golang.org/api/gensupport/backoff.go
type BackoffStrategy (line 12) | type BackoffStrategy interface
type ExponentialBackoff (line 24) | type ExponentialBackoff struct
method Pause (line 31) | func (eb *ExponentialBackoff) Pause() (time.Duration, bool) {
method Reset (line 43) | func (eb *ExponentialBackoff) Reset() {
FILE: vendor/google.golang.org/api/gensupport/buffer.go
type ResumableBuffer (line 15) | type ResumableBuffer struct
method Chunk (line 32) | func (rb *ResumableBuffer) Chunk() (chunk io.Reader, off int64, size i...
method loadChunk (line 41) | func (rb *ResumableBuffer) loadChunk() error {
method Next (line 58) | func (rb *ResumableBuffer) Next() {
function NewResumableBuffer (line 25) | func NewResumableBuffer(media io.Reader, chunkSize int) *ResumableBuffer {
type readerTyper (line 63) | type readerTyper struct
function ReaderAtToReader (line 71) | func ReaderAtToReader(ra io.ReaderAt, size int64) io.Reader {
FILE: vendor/google.golang.org/api/gensupport/json.go
function MarshalJSON (line 20) | func MarshalJSON(schema interface{}, forceSendFields []string) ([]byte, ...
function schemaToMap (line 37) | func schemaToMap(schema interface{}, mustInclude map[string]struct{}) (m...
function formatAsString (line 83) | func formatAsString(v reflect.Value, kind reflect.Kind) string {
type jsonTag (line 93) | type jsonTag struct
function parseJSONTag (line 102) | func parseJSONTag(val string) (jsonTag, error) {
function includeField (line 130) | func includeField(v reflect.Value, f reflect.StructField, mustInclude ma...
function isEmptyValue (line 156) | func isEmptyValue(v reflect.Value) bool {
FILE: vendor/google.golang.org/api/gensupport/media.go
constant sniffBuffSize (line 18) | sniffBuffSize = 512
function newContentSniffer (line 20) | func newContentSniffer(r io.Reader) *contentSniffer {
type contentSniffer (line 25) | type contentSniffer struct
method Read (line 34) | func (cs *contentSniffer) Read(p []byte) (n int, err error) {
method ContentType (line 55) | func (cs *contentSniffer) ContentType() (string, bool) {
function DetermineContentType (line 79) | func DetermineContentType(media io.Reader, ctype string) (io.Reader, str...
type typeReader (line 101) | type typeReader struct
type multipartReader (line 108) | type multipartReader struct
method Read (line 142) | func (mp *multipartReader) Read(data []byte) (n int, err error) {
method Close (line 146) | func (mp *multipartReader) Close() error {
function newMultipartReader (line 114) | func newMultipartReader(parts []typeReader) *multipartReader {
function CombineBodyMedia (line 158) | func CombineBodyMedia(body io.Reader, bodyContentType string, media io.R...
function typeHeader (line 166) | func typeHeader(contentType string) textproto.MIMEHeader {
function PrepareUpload (line 183) | func PrepareUpload(media io.Reader, chunkSize int) (io.Reader,
FILE: vendor/google.golang.org/api/gensupport/params.go
type URLParams (line 15) | type URLParams
method Get (line 18) | func (u URLParams) Get(key string) string {
method Set (line 28) | func (u URLParams) Set(key, value string) {
method SetMulti (line 36) | func (u URLParams) SetMulti(key string, values []string) {
method Encode (line 42) | func (u URLParams) Encode() string {
function SetOptions (line 46) | func SetOptions(u URLParams, opts ...googleapi.CallOption) {
FILE: vendor/google.golang.org/api/gensupport/resumable.go
constant statusResumeIncomplete (line 21) | statusResumeIncomplete = 308
constant statusTooManyRequests (line 27) | statusTooManyRequests = 429
type ResumableUpload (line 32) | type ResumableUpload struct
method Progress (line 53) | func (rx *ResumableUpload) Progress() int64 {
method doUploadRequest (line 63) | func (rx *ResumableUpload) doUploadRequest(ctx context.Context, data i...
method reportProgress (line 89) | func (rx *ResumableUpload) reportProgress(old, updated int64) {
method transferChunk (line 102) | func (rx *ResumableUpload) transferChunk(ctx context.Context) (*http.R...
method Upload (line 140) | func (rx *ResumableUpload) Upload(ctx context.Context) (resp *http.Res...
function contextDone (line 125) | func contextDone(ctx context.Context) bool {
FILE: vendor/google.golang.org/api/gensupport/retry.go
function Retry (line 14) | func Retry(ctx context.Context, f func() (*http.Response, error), backof...
function DefaultBackoffStrategy (line 48) | func DefaultBackoffStrategy() BackoffStrategy {
function shouldRetry (line 57) | func shouldRetry(status int, err error) bool {
FILE: vendor/google.golang.org/api/googleapi/googleapi.go
type ContentTyper (line 26) | type ContentTyper interface
type SizeReaderAt (line 32) | type SizeReaderAt interface
type ServerResponse (line 39) | type ServerResponse struct
constant Version (line 48) | Version = "0.5"
constant UserAgent (line 51) | UserAgent = "google-api-go-client/" + Version
constant DefaultUploadChunkSize (line 54) | DefaultUploadChunkSize = 8 * 1024 * 1024
constant MinUploadChunkSize (line 58) | MinUploadChunkSize = 256 * 1024
type Error (line 62) | type Error struct
method Error (line 85) | func (e *Error) Error() string {
type ErrorItem (line 78) | type ErrorItem struct
type errorReply (line 108) | type errorReply struct
function CheckResponse (line 114) | func CheckResponse(res *http.Response) error {
function IsNotModified (line 141) | func IsNotModified(err error) bool {
function CheckMediaResponse (line 152) | func CheckMediaResponse(res *http.Response) error {
type MarshalStyle (line 164) | type MarshalStyle
method JSONReader (line 169) | func (wrap MarshalStyle) JSONReader(v interface{}) (io.Reader, error) {
type endingWithErrorReader (line 186) | type endingWithErrorReader struct
method Read (line 191) | func (er endingWithErrorReader) Read(p []byte) (n int, err error) {
type countingWriter (line 201) | type countingWriter struct
method Write (line 205) | func (w countingWriter) Write(p []byte) (int, error) {
type ProgressUpdater (line 213) | type ProgressUpdater
type MediaOption (line 215) | type MediaOption interface
type contentTypeOption (line 219) | type contentTypeOption
method setOptions (line 221) | func (ct contentTypeOption) setOptions(o *MediaOptions) {
function ContentType (line 230) | func ContentType(ctype string) MediaOption {
type chunkSizeOption (line 234) | type chunkSizeOption
method setOptions (line 236) | func (cs chunkSizeOption) setOptions(o *MediaOptions) {
function ChunkSize (line 249) | func ChunkSize(size int) MediaOption {
type MediaOptions (line 254) | type MediaOptions struct
function ProcessMediaOptions (line 263) | func ProcessMediaOptions(opts []MediaOption) *MediaOptions {
function ResolveRelative (line 271) | func ResolveRelative(basestr, relstr string) string {
function init (line 286) | func init() {
function SetOpaque (line 300) | func SetOpaque(u *url.URL) {
function Expand (line 311) | func Expand(u *url.URL, expansions map[string]string) {
function CloseBody (line 322) | func CloseBody(res *http.Response) {
function VariantType (line 345) | func VariantType(t map[string]interface{}) string {
function ConvertVariant (line 353) | func ConvertVariant(v map[string]interface{}, dst interface{}) bool {
type Field (line 385) | type Field
function CombineFields (line 388) | func CombineFields(s []Field) string {
type CallOption (line 402) | type CallOption interface
function QuotaUser (line 410) | func QuotaUser(u string) CallOption { return quotaUser(u) }
type quotaUser (line 412) | type quotaUser
method Get (line 414) | func (q quotaUser) Get() (string, string) { return "quotaUser", string...
function UserIP (line 418) | func UserIP(ip string) CallOption { return userIP(ip) }
type userIP (line 420) | type userIP
method Get (line 422) | func (i userIP) Get() (string, string) { return "userIp", string(i) }
function Trace (line 426) | func Trace(traceToken string) CallOption { return traceTok(traceToken) }
type traceTok (line 428) | type traceTok
method Get (line 430) | func (t traceTok) Get() (string, string) { return "trace", "token:" + ...
FILE: vendor/google.golang.org/api/googleapi/internal/uritemplates/uritemplates.go
function pctEncode (line 26) | func pctEncode(src []byte) []byte {
function escape (line 37) | func escape(s string, allowReserved bool) string {
type uriTemplate (line 45) | type uriTemplate struct
method Expand (line 174) | func (t *uriTemplate) Expand(values map[string]string) string {
function parse (line 51) | func parse(rawTemplate string) (*uriTemplate, error) {
type templatePart (line 80) | type templatePart struct
method expand (line 182) | func (tp *templatePart) expand(buf *bytes.Buffer, values map[string]st...
method expandName (line 203) | func (tp *templatePart) expandName(buf *bytes.Buffer, name string, emp...
method expandString (line 214) | func (tp *templatePart) expandString(buf *bytes.Buffer, t templateTerm...
type templateTerm (line 90) | type templateTerm struct
function parseExpression (line 96) | func parseExpression(expression string) (result templatePart, err error) {
function parseTerm (line 146) | func parseTerm(term string) (result templateTerm, err error) {
FILE: vendor/google.golang.org/api/googleapi/internal/uritemplates/utils.go
function Expand (line 7) | func Expand(path string, values map[string]string) (string, error) {
FILE: vendor/google.golang.org/api/googleapi/types.go
type Int64s (line 13) | type Int64s
method UnmarshalJSON (line 15) | func (q *Int64s) UnmarshalJSON(raw []byte) error {
method MarshalJSON (line 122) | func (s Int64s) MarshalJSON() ([]byte, error) {
type Int32s (line 32) | type Int32s
method UnmarshalJSON (line 34) | func (q *Int32s) UnmarshalJSON(raw []byte) error {
method MarshalJSON (line 128) | func (s Int32s) MarshalJSON() ([]byte, error) {
type Uint64s (line 51) | type Uint64s
method UnmarshalJSON (line 53) | func (q *Uint64s) UnmarshalJSON(raw []byte) error {
method MarshalJSON (line 134) | func (s Uint64s) MarshalJSON() ([]byte, error) {
type Uint32s (line 70) | type Uint32s
method UnmarshalJSON (line 72) | func (q *Uint32s) UnmarshalJSON(raw []byte) error {
method MarshalJSON (line 140) | func (s Uint32s) MarshalJSON() ([]byte, error) {
type Float64s (line 89) | type Float64s
method UnmarshalJSON (line 91) | func (q *Float64s) UnmarshalJSON(raw []byte) error {
method MarshalJSON (line 146) | func (s Float64s) MarshalJSON() ([]byte, error) {
function quotedList (line 107) | func quotedList(n int, fn func(dst []byte, i int) []byte) ([]byte, error) {
function Bool (line 158) | func Bool(v bool) *bool { return &v }
function Int32 (line 162) | func Int32(v int32) *int32 { return &v }
function Int64 (line 166) | func Int64(v int64) *int64 { return &v }
function Float64 (line 170) | func Float64(v float64) *float64 { return &v }
function Uint32 (line 174) | func Uint32(v uint32) *uint32 { return &v }
function Uint64 (line 178) | func Uint64(v uint64) *uint64 { return &v }
function String (line 182) | func String(v string) *string { return &v }
Condensed preview — 111 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (858K chars).
[
{
"path": ".github/workflows/release.yaml",
"chars": 2413,
"preview": "# .github/workflows/release.yaml\n\non: release\nname: Build Release\njobs:\n release-linux-386:\n name: release linux/386"
},
{
"path": ".gitignore",
"chars": 120,
"preview": "# Ignore bin folder and drive binary\n_release/bin\n\n# vim files\n.*.sw[a-z]\n*.un~\nSession.vim\n.netrwhist\ngdrive\ngdrive.sh\n"
},
{
"path": "Godeps/Godeps.json",
"chars": 1285,
"preview": "{\n\t\"ImportPath\": \"github.com/prasmussen/gdrive\",\n\t\"GoVersion\": \"go1.6\",\n\t\"GodepVersion\": \"v61\",\n\t\"Deps\": [\n\t\t{\n\t\t\t\"Impor"
},
{
"path": "Godeps/Readme",
"chars": 136,
"preview": "This directory tree is generated automatically by godep.\n\nPlease do not edit.\n\nSee https://github.com/tools/godep for mo"
},
{
"path": "LICENSE",
"chars": 1078,
"preview": "The MIT License\n\nCopyright (c) 2013 Petter Rasmussen\n\nPermission is hereby granted, free of charge, to any person obtain"
},
{
"path": "README.md",
"chars": 38283,
"preview": "## IMPORTANT\nThis repository is not maintained anymore. [Gdrive 3](https://github.com/glotlabs/gdrive) is its successor."
},
{
"path": "_release/build-all.sh",
"chars": 1062,
"preview": "#!/bin/bash\n\nAPP_NAME=\"gdrive\"\nPLATFORMS=\"darwin/386 darwin/amd64 darwin/arm darwin/arm64 dragonfly/amd64 freebsd/386 fr"
},
{
"path": "_release/print_usage_markdown.sh",
"chars": 388,
"preview": "#!/bin/bash\n\necho '## Usage'\necho '```'\ngdrive help | tail -n+3\necho '```'\n\nIFS=$'\\n'\n\nhelp=$(gdrive help | grep global "
},
{
"path": "_release/upload.sh",
"chars": 2934,
"preview": "#!/usr/local/bin/bash\n\n# Grab application version\nVERSION=$(_release/bin/gdrive-osx-x64 version | awk 'NR==1 {print $2}'"
},
{
"path": "auth/file_source.go",
"chars": 1462,
"preview": "package auth\n\nimport (\n\t\"encoding/json\"\n\t\"golang.org/x/oauth2\"\n\t\"io/ioutil\"\n\t\"os\"\n)\n\nfunc FileSource(path string, token "
},
{
"path": "auth/oauth.go",
"chars": 2439,
"preview": "package auth\n\nimport (\n\t\"fmt\"\n\t\"golang.org/x/oauth2\"\n\t\"golang.org/x/oauth2/google\"\n\t\"net/http\"\n\t\"time\"\n)\n\ntype authCodeF"
},
{
"path": "auth/util.go",
"chars": 288,
"preview": "package auth\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n)\n\nfunc mkdir(path string) error {\n\tdir := filepath.Dir(path)\n\tif fileExis"
},
{
"path": "cli/context.go",
"chars": 566,
"preview": "package cli\n\ntype Context struct {\n\targs Arguments\n\thandlers []*Handler\n}\n\nfunc (self Context) Args() Arguments {\n\tr"
},
{
"path": "cli/flags.go",
"chars": 2963,
"preview": "package cli\n\ntype Flag interface {\n\tGetPatterns() []string\n\tGetName() string\n\tGetDescription() string\n\tGetParser() Parse"
},
{
"path": "cli/handler.go",
"chars": 2272,
"preview": "package cli\n\nimport (\n\t\"regexp\"\n\t\"strings\"\n)\n\nfunc NewFlagGroup(name string, flags ...Flag) FlagGroup {\n\treturn FlagGrou"
},
{
"path": "cli/parser.go",
"chars": 8246,
"preview": "package cli\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n)\n\ntype Parser interface {\n\tMatch([]string) ([]string, bool)\n\tCapture([]string) "
},
{
"path": "compare.go",
"chars": 1709,
"preview": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"github.com/prasmussen/gdrive/drive\"\n\t\"os\"\n)\n\nconst MinCacheFileSize = 5 * 1024"
},
{
"path": "drive/about.go",
"chars": 1763,
"preview": "package drive\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"text/tabwriter\"\n)\n\ntype AboutArgs struct {\n\tOut io.Writer\n\tSizeInBytes bo"
},
{
"path": "drive/changes.go",
"chars": 2240,
"preview": "package drive\n\nimport (\n\t\"fmt\"\n\t\"google.golang.org/api/drive/v3\"\n\t\"io\"\n\t\"text/tabwriter\"\n)\n\ntype ListChangesArgs struct "
},
{
"path": "drive/delete.go",
"chars": 832,
"preview": "package drive\n\nimport (\n\t\"fmt\"\n\t\"io\"\n)\n\ntype DeleteArgs struct {\n\tOut io.Writer\n\tId string\n\tRecursive bool\n"
},
{
"path": "drive/download.go",
"chars": 6031,
"preview": "package drive\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"time\"\n\n\t\"google.golang.org/api/drive/v3\"\n\t\"google.golang.o"
},
{
"path": "drive/drive.go",
"chars": 273,
"preview": "package drive\n\nimport (\n\t\"google.golang.org/api/drive/v3\"\n\t\"net/http\"\n)\n\ntype Drive struct {\n\tservice *drive.Service\n}\n\n"
},
{
"path": "drive/errors.go",
"chars": 719,
"preview": "package drive\n\nimport (\n\t\"golang.org/x/net/context\"\n\t\"google.golang.org/api/googleapi\"\n\t\"time\"\n)\n\nconst MaxErrorRetries "
},
{
"path": "drive/export.go",
"chars": 2783,
"preview": "package drive\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"mime\"\n\t\"os\"\n)\n\nvar DefaultExportMime = map[string]string{\n\t\"application/vnd.googl"
},
{
"path": "drive/import.go",
"chars": 1164,
"preview": "package drive\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"mime\"\n\t\"path/filepath\"\n\t\"strings\"\n)\n\ntype ImportArgs struct {\n\tOut "
},
{
"path": "drive/info.go",
"chars": 1537,
"preview": "package drive\n\nimport (\n\t\"fmt\"\n\t\"google.golang.org/api/drive/v3\"\n\t\"io\"\n)\n\ntype FileInfoArgs struct {\n\tOut io.Wri"
},
{
"path": "drive/list.go",
"chars": 2781,
"preview": "package drive\n\nimport (\n\t\"fmt\"\n\t\"golang.org/x/net/context\"\n\t\"google.golang.org/api/drive/v3\"\n\t\"google.golang.org/api/goo"
},
{
"path": "drive/mkdir.go",
"chars": 846,
"preview": "package drive\n\nimport (\n\t\"fmt\"\n\t\"google.golang.org/api/drive/v3\"\n\t\"io\"\n)\n\nconst DirectoryMimeType = \"application/vnd.goo"
},
{
"path": "drive/path.go",
"chars": 1178,
"preview": "package drive\n\nimport (\n\t\"fmt\"\n\t\"google.golang.org/api/drive/v3\"\n\t\"path/filepath\"\n)\n\nfunc (self *Drive) newPathfinder() "
},
{
"path": "drive/progress.go",
"chars": 1938,
"preview": "package drive\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"time\"\n)\n\nconst MaxDrawInterval = time.Second * 1\nconst MaxRateInterv"
},
{
"path": "drive/revision_delete.go",
"chars": 726,
"preview": "package drive\n\nimport (\n\t\"fmt\"\n\t\"io\"\n)\n\ntype DeleteRevisionArgs struct {\n\tOut io.Writer\n\tFileId string\n\tRevis"
},
{
"path": "drive/revision_download.go",
"chars": 1797,
"preview": "package drive\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"path/filepath\"\n\t\"time\"\n)\n\ntype DownloadRevisionArgs struct {\n\tOut "
},
{
"path": "drive/revision_list.go",
"chars": 1351,
"preview": "package drive\n\nimport (\n\t\"fmt\"\n\t\"google.golang.org/api/drive/v3\"\n\t\"io\"\n\t\"text/tabwriter\"\n)\n\ntype ListRevisionsArgs struc"
},
{
"path": "drive/share.go",
"chars": 2427,
"preview": "package drive\n\nimport (\n\t\"fmt\"\n\t\"google.golang.org/api/drive/v3\"\n\t\"io\"\n\t\"text/tabwriter\"\n)\n\ntype ShareArgs struct {\n\tOut"
},
{
"path": "drive/sync.go",
"chars": 12788,
"preview": "package drive\n\nimport (\n\t\"fmt\"\n\t\"github.com/sabhiram/go-gitignore\"\n\t\"github.com/soniakeys/graph\"\n\t\"google.golang.org/api"
},
{
"path": "drive/sync_download.go",
"chars": 8828,
"preview": "package drive\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"google.golang.org/api/drive/v3\"\n\t\"google.golang.org/api/googleapi\"\n\t\"io\"\n\t\"os\""
},
{
"path": "drive/sync_list.go",
"chars": 1935,
"preview": "package drive\n\nimport (\n\t\"fmt\"\n\t\"google.golang.org/api/drive/v3\"\n\t\"google.golang.org/api/googleapi\"\n\t\"io\"\n\t\"sort\"\n\t\"text"
},
{
"path": "drive/sync_upload.go",
"chars": 13096,
"preview": "package drive\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"google.golang.org/api/drive/v3\"\n\t\"google.golang.org/api/googleapi\"\n\t\"io\"\n\t\"os\""
},
{
"path": "drive/timeout_reader.go",
"chars": 2150,
"preview": "package drive\n\nimport (\n\t\"golang.org/x/net/context\"\n\t\"io\"\n\t\"sync\"\n\t\"time\"\n)\n\nconst TimeoutTimerInterval = time.Second * "
},
{
"path": "drive/update.go",
"chars": 2021,
"preview": "package drive\n\nimport (\n\t\"fmt\"\n\t\"google.golang.org/api/drive/v3\"\n\t\"google.golang.org/api/googleapi\"\n\t\"io\"\n\t\"mime\"\n\t\"path"
},
{
"path": "drive/upload.go",
"chars": 6395,
"preview": "package drive\n\nimport (\n\t\"fmt\"\n\t\"google.golang.org/api/drive/v3\"\n\t\"google.golang.org/api/googleapi\"\n\t\"io\"\n\t\"mime\"\n\t\"os\"\n"
},
{
"path": "drive/util.go",
"chars": 3518,
"preview": "package drive\n\nimport (\n\t\"fmt\"\n\t\"math\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\t\"unicode/utf8\"\n)\n\ntype kv s"
},
{
"path": "gdrive.go",
"chars": 27579,
"preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/prasmussen/gdrive/cli\"\n)\n\nconst Name = \"gdrive\"\nconst Version = \"2.1.1"
},
{
"path": "handlers_drive.go",
"chars": 12521,
"preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"time\"\n\n\t\"github.com/prasmussen/gdr"
},
{
"path": "handlers_meta.go",
"chars": 2170,
"preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/prasmussen/gdrive/cli\"\n\t\"os\"\n\t\"runtime\"\n\t\"strings\"\n\t\"text/tabwriter\"\n)\n\nfunc "
},
{
"path": "util.go",
"chars": 1307,
"preview": "package main\n\nimport (\n\t\"crypto/md5\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n)\n\nfunc GetDefaultC"
},
{
"path": "vendor/github.com/sabhiram/go-git-ignore/.gitignore",
"chars": 306,
"preview": "# Package test fixtures\ntest_fixtures\n\n# Compiled Object files, Static and Dynamic libs (Shared Objects)\n*.o\n*.a\n*.so\n\n#"
},
{
"path": "vendor/github.com/sabhiram/go-git-ignore/.travis.yml",
"chars": 468,
"preview": "language: go\n\ngo:\n - 1.3\n - tip\n\nenv:\n - \"PATH=$HOME/gopath/bin:$PATH\"\n\nbefore_install:\n - go get github.com/stretch"
},
{
"path": "vendor/github.com/sabhiram/go-git-ignore/LICENSE",
"chars": 1081,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2015 Shaba Abhiram\n\nPermission is hereby granted, free of charge, to any person obt"
},
{
"path": "vendor/github.com/sabhiram/go-git-ignore/README.md",
"chars": 413,
"preview": "# go-git-ignore\n\n[](https://travis-ci.org/sabhiram/go-g"
},
{
"path": "vendor/github.com/sabhiram/go-git-ignore/ignore.go",
"chars": 7623,
"preview": "/*\nignore is a library which returns a new ignorer object which can\ntest against various paths. This is particularly use"
},
{
"path": "vendor/github.com/soniakeys/graph/.gitignore",
"chars": 7,
"preview": "*.dot\n\n"
},
{
"path": "vendor/github.com/soniakeys/graph/.travis.yml",
"chars": 143,
"preview": "sudo: false\nlanguage: go\n# update travis.sh when changing version number here\ngo:\n - 1.2.1\n - 1.6\ninstall: go get -t ./."
},
{
"path": "vendor/github.com/soniakeys/graph/adj.go",
"chars": 9326,
"preview": "// Copyright 2014 Sonia Keys\n// License MIT: https://opensource.org/licenses/MIT\n\npackage graph\n\n// adj.go contains meth"
},
{
"path": "vendor/github.com/soniakeys/graph/adj_RO.go",
"chars": 10916,
"preview": "// Copyright 2014 Sonia Keys\n// License MIT: http://opensource.org/licenses/MIT\n\npackage graph\n\n// adj_RO.go is code gen"
},
{
"path": "vendor/github.com/soniakeys/graph/adj_cg.go",
"chars": 11069,
"preview": "// Copyright 2014 Sonia Keys\n// License MIT: http://opensource.org/licenses/MIT\n\npackage graph\n\n// adj_RO.go is code gen"
},
{
"path": "vendor/github.com/soniakeys/graph/bits.go",
"chars": 4724,
"preview": "// Copyright 2014 Sonia Keys\n// License MIT: http://opensource.org/licenses/MIT\n\npackage graph\n\nimport (\n\t\"fmt\"\n\t\"math/b"
},
{
"path": "vendor/github.com/soniakeys/graph/bits32.go",
"chars": 528,
"preview": "// Copyright 2014 Sonia Keys\n// License MIT: http://opensource.org/licenses/MIT\n\n// +build 386 arm\n\npackage graph\n\n// \"w"
},
{
"path": "vendor/github.com/soniakeys/graph/bits64.go",
"chars": 583,
"preview": "// Copyright 2014 Sonia Keys\n// License MIT: http://opensource.org/licenses/MIT\n\n// +build !386,!arm\n\npackage graph\n\ncon"
},
{
"path": "vendor/github.com/soniakeys/graph/dir.go",
"chars": 14722,
"preview": "// Copyright 2014 Sonia Keys\n// License MIT: http://opensource.org/licenses/MIT\n\npackage graph\n\n// dir.go has methods sp"
},
{
"path": "vendor/github.com/soniakeys/graph/dir_RO.go",
"chars": 10579,
"preview": "// Copyright 2014 Sonia Keys\n// License MIT: http://opensource.org/licenses/MIT\n\npackage graph\n\n// dir_RO.go is code gen"
},
{
"path": "vendor/github.com/soniakeys/graph/dir_cg.go",
"chars": 10847,
"preview": "// Copyright 2014 Sonia Keys\n// License MIT: http://opensource.org/licenses/MIT\n\npackage graph\n\n// dir_RO.go is code gen"
},
{
"path": "vendor/github.com/soniakeys/graph/doc.go",
"chars": 6724,
"preview": "// Copyright 2014 Sonia Keys\n// License MIT: http://opensource.org/licenses/MIT\n\n// Graph algorithms: Dijkstra, A*, Bell"
},
{
"path": "vendor/github.com/soniakeys/graph/fromlist.go",
"chars": 11916,
"preview": "// Copyright 2014 Sonia Keys\n// License MIT: http://opensource.org/licenses/MIT\n\npackage graph\n\n// FromList represents a"
},
{
"path": "vendor/github.com/soniakeys/graph/graph.go",
"chars": 6236,
"preview": "// Copyright 2014 Sonia Keys\n// License MIT: http://opensource.org/licenses/MIT\n\npackage graph\n\n// graph.go contains typ"
},
{
"path": "vendor/github.com/soniakeys/graph/hacking.md",
"chars": 1869,
"preview": "#Hacking\n\nBasic use of the package is just go get, or git clone; go install. There are\nno dependencies outside the stan"
},
{
"path": "vendor/github.com/soniakeys/graph/mst.go",
"chars": 6667,
"preview": "// Copyright 2014 Sonia Keys\n// License MIT: http://opensource.org/licenses/MIT\n\npackage graph\n\nimport (\n\t\"container/hea"
},
{
"path": "vendor/github.com/soniakeys/graph/random.go",
"chars": 9410,
"preview": "// Copyright 2016 Sonia Keys\n// License MIT: https://opensource.org/licenses/MIT\n\npackage graph\n\nimport (\n\t\"errors\"\n\t\"ma"
},
{
"path": "vendor/github.com/soniakeys/graph/readme.md",
"chars": 1734,
"preview": "#Graph\n\nA graph library with goals of speed and simplicity, Graph implements\ngraph algorithms on graphs of zero-based in"
},
{
"path": "vendor/github.com/soniakeys/graph/sssp.go",
"chars": 25256,
"preview": "// Copyright 2013 Sonia Keys\n// License MIT: http://opensource.org/licenses/MIT\n\npackage graph\n\nimport (\n\t\"container/hea"
},
{
"path": "vendor/github.com/soniakeys/graph/travis.sh",
"chars": 247,
"preview": "#!/bin/bash\nset -ex\ngo test ./...\nif [ \"$TRAVIS_GO_VERSION\" = \"1.6\" ]; then\n GOARCH=386 go test ./...\n go tool vet -exam"
},
{
"path": "vendor/github.com/soniakeys/graph/undir.go",
"chars": 8669,
"preview": "// Copyright 2014 Sonia Keys\n// License MIT: http://opensource.org/licenses/MIT\n\npackage graph\n\n// undir.go has methods "
},
{
"path": "vendor/github.com/soniakeys/graph/undir_RO.go",
"chars": 16624,
"preview": "// Copyright 2014 Sonia Keys\n// License MIT: http://opensource.org/licenses/MIT\n\npackage graph\n\n// undir_RO.go is code g"
},
{
"path": "vendor/github.com/soniakeys/graph/undir_cg.go",
"chars": 16998,
"preview": "// Copyright 2014 Sonia Keys\n// License MIT: http://opensource.org/licenses/MIT\n\npackage graph\n\n// undir_RO.go is code g"
},
{
"path": "vendor/golang.org/x/net/LICENSE",
"chars": 1479,
"preview": "Copyright (c) 2009 The Go Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or with"
},
{
"path": "vendor/golang.org/x/net/PATENTS",
"chars": 1303,
"preview": "Additional IP Rights Grant (Patents)\n\n\"This implementation\" means the copyrightable works distributed by\nGoogle as part "
},
{
"path": "vendor/golang.org/x/net/context/context.go",
"chars": 6169,
"preview": "// Copyright 2014 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license "
},
{
"path": "vendor/golang.org/x/net/context/ctxhttp/cancelreq.go",
"chars": 412,
"preview": "// Copyright 2015 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license "
},
{
"path": "vendor/golang.org/x/net/context/ctxhttp/cancelreq_go14.go",
"chars": 467,
"preview": "// Copyright 2015 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license "
},
{
"path": "vendor/golang.org/x/net/context/ctxhttp/ctxhttp.go",
"chars": 3654,
"preview": "// Copyright 2015 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license "
},
{
"path": "vendor/golang.org/x/net/context/go17.go",
"chars": 2864,
"preview": "// Copyright 2016 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license "
},
{
"path": "vendor/golang.org/x/net/context/pre_go17.go",
"chars": 8132,
"preview": "// Copyright 2014 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license "
},
{
"path": "vendor/golang.org/x/oauth2/.travis.yml",
"chars": 270,
"preview": "language: go\n\ngo:\n - 1.3\n - 1.4\n\ninstall:\n - export GOPATH=\"$HOME/gopath\"\n - mkdir -p \"$GOPATH/src/golang.org/x\"\n -"
},
{
"path": "vendor/golang.org/x/oauth2/AUTHORS",
"chars": 173,
"preview": "# This source code refers to The Go Authors for copyright purposes.\n# The master list of authors is in the main Go distr"
},
{
"path": "vendor/golang.org/x/oauth2/CONTRIBUTING.md",
"chars": 1042,
"preview": "# Contributing to Go\n\nGo is an open source project.\n\nIt is the work of hundreds of contributors. We appreciate your help"
},
{
"path": "vendor/golang.org/x/oauth2/CONTRIBUTORS",
"chars": 170,
"preview": "# This source code was written by the Go contributors.\n# The master list of contributors is in the main Go distribution,"
},
{
"path": "vendor/golang.org/x/oauth2/LICENSE",
"chars": 1483,
"preview": "Copyright (c) 2009 The oauth2 Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or "
},
{
"path": "vendor/golang.org/x/oauth2/README.md",
"chars": 2069,
"preview": "# OAuth2 for Go\n\n[](https://travis-ci.org/golang/o"
},
{
"path": "vendor/golang.org/x/oauth2/client_appengine.go",
"chars": 530,
"preview": "// Copyright 2014 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license "
},
{
"path": "vendor/golang.org/x/oauth2/internal/oauth2.go",
"chars": 2053,
"preview": "// Copyright 2014 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license "
},
{
"path": "vendor/golang.org/x/oauth2/internal/token.go",
"chars": 6830,
"preview": "// Copyright 2014 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license "
},
{
"path": "vendor/golang.org/x/oauth2/internal/transport.go",
"chars": 2058,
"preview": "// Copyright 2014 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license "
},
{
"path": "vendor/golang.org/x/oauth2/oauth2.go",
"chars": 11198,
"preview": "// Copyright 2014 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license "
},
{
"path": "vendor/golang.org/x/oauth2/token.go",
"chars": 4453,
"preview": "// Copyright 2014 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license "
},
{
"path": "vendor/golang.org/x/oauth2/transport.go",
"chars": 3087,
"preview": "// Copyright 2014 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license "
},
{
"path": "vendor/google.golang.org/api/LICENSE",
"chars": 1475,
"preview": "Copyright (c) 2011 Google Inc. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\n"
},
{
"path": "vendor/google.golang.org/api/drive/v3/drive-api.json",
"chars": 75556,
"preview": "{\n \"kind\": \"discovery#restDescription\",\n \"etag\": \"\\\"bRFOOrZKfO9LweMbPqu0kcu6De8/O9_NbpoVnW5GMGl7qWBIajcyrt8\\\"\",\n \"discov"
},
{
"path": "vendor/google.golang.org/api/drive/v3/drive-gen.go",
"chars": 211864,
"preview": "// Package drive provides access to the Drive API.\n//\n// See https://developers.google.com/drive/\n//\n// Usage example:\n/"
},
{
"path": "vendor/google.golang.org/api/gensupport/backoff.go",
"chars": 1186,
"preview": "// Copyright 2016 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license "
},
{
"path": "vendor/google.golang.org/api/gensupport/buffer.go",
"chars": 2405,
"preview": "// Copyright 2016 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license "
},
{
"path": "vendor/google.golang.org/api/gensupport/doc.go",
"chars": 440,
"preview": "// Copyright 2016 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license "
},
{
"path": "vendor/google.golang.org/api/gensupport/json.go",
"chars": 5007,
"preview": "// Copyright 2015 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license "
},
{
"path": "vendor/google.golang.org/api/gensupport/media.go",
"chars": 6205,
"preview": "// Copyright 2016 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license "
},
{
"path": "vendor/google.golang.org/api/gensupport/params.go",
"chars": 1255,
"preview": "// Copyright 2015 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license "
},
{
"path": "vendor/google.golang.org/api/gensupport/resumable.go",
"chars": 5543,
"preview": "// Copyright 2016 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license "
},
{
"path": "vendor/google.golang.org/api/gensupport/retry.go",
"chars": 1736,
"preview": "package gensupport\n\nimport (\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"golang.org/x/net/context\"\n)\n\n// Retry invokes the given"
},
{
"path": "vendor/google.golang.org/api/googleapi/googleapi.go",
"chars": 13202,
"preview": "// Copyright 2011 Google Inc. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that"
},
{
"path": "vendor/google.golang.org/api/googleapi/internal/uritemplates/LICENSE",
"chars": 1057,
"preview": "Copyright (c) 2013 Joshua Tacoma\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis s"
},
{
"path": "vendor/google.golang.org/api/googleapi/internal/uritemplates/uritemplates.go",
"chars": 5224,
"preview": "// Copyright 2013 Joshua Tacoma. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license t"
},
{
"path": "vendor/google.golang.org/api/googleapi/internal/uritemplates/utils.go",
"chars": 357,
"preview": "// Copyright 2016 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license "
},
{
"path": "vendor/google.golang.org/api/googleapi/types.go",
"chars": 4468,
"preview": "// Copyright 2013 Google Inc. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that"
}
]
About this extraction
This page contains the full source code of the prasmussen/gdrive GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 111 files (766.5 KB), approximately 212.9k tokens, and a symbol index with 1240 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.