This commit is contained in:
bel
2020-01-13 03:37:51 +00:00
commit c8eb52f9ba
2023 changed files with 702080 additions and 0 deletions

28
.rclone_repo/vendor/github.com/ncw/go-acd/.gitignore generated vendored Executable file
View File

@@ -0,0 +1,28 @@
# 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
# IDE artifacts
*.iml
.idea/

22
.rclone_repo/vendor/github.com/ncw/go-acd/.travis.yml generated vendored Executable file
View File

@@ -0,0 +1,22 @@
# enable container-based infrastructure by setting sudo to false
sudo: false
language: go
go:
- 1.4
- tip
matrix:
allow_failures:
- go: tip
branches:
only:
- master
install:
- wget -qO- https://raw.githubusercontent.com/pote/gpm/v1.3.2/bin/gpm | bash
script:
- make test testrace

6
.rclone_repo/vendor/github.com/ncw/go-acd/Godeps generated vendored Executable file
View File

@@ -0,0 +1,6 @@
# Dependencies can by installed via [gpm](https://github.com/pote/gpm)
github.com/google/go-querystring/query
# for tests
github.com/stretchr/testify

14
.rclone_repo/vendor/github.com/ncw/go-acd/LICENSE generated vendored Executable file
View File

@@ -0,0 +1,14 @@
Copyright (c) 2015, Serge Gebhardt <>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

55
.rclone_repo/vendor/github.com/ncw/go-acd/Makefile generated vendored Executable file
View File

@@ -0,0 +1,55 @@
TEST?=./...
VETARGS?=-asmdecl -atomic -bool -buildtags -copylocks -methods -nilfunc -printf -rangeloops -shift -structtags -unsafeptr
default: test
# test runs the unit tests and vets the code
test:
ACD_ACC= go test $(TEST) $(TESTARGS) -timeout=30s -parallel=4
@$(MAKE) fmt
@$(MAKE) vet
# testacc runs acceptance tests
testacc:
@if [ "$(TEST)" = "./..." ]; then \
echo "ERROR: Set TEST to a specific package"; \
exit 1; \
fi
ACD_ACC=1 go test $(TEST) -v $(TESTARGS) -timeout 45m
# testrace runs the race checker
testrace:
ACD_ACC= go test -race $(TEST) $(TESTARGS)
# updatedeps installs all the dependencies needed to run
# and build
updatedeps:
@gpm
cover:
@go tool cover 2>/dev/null; if [ $$? -eq 3 ]; then \
go get -u golang.org/x/tools/cmd/cover; \
fi
go test $(TEST) -coverprofile=coverage.out
go tool cover -html=coverage.out
rm coverage.out
# fmt formats the Go source code
fmt:
@go list ./... \
| xargs go fmt
# vet runs the Go source code static analysis tool `vet` to find
# any common errors.
vet:
@go tool vet 2>/dev/null ; if [ $$? -eq 3 ]; then \
go get golang.org/x/tools/cmd/vet; \
fi
@go list -f '{{.Dir}}' ./... \
| xargs go tool vet ; if [ $$? -eq 1 ]; then \
echo ""; \
echo "Vet found suspicious constructs. Please check the reported constructs"; \
echo "and fix them if necessary before submitting the code for reviewal."; \
fi
.PHONY: default test vet

9
.rclone_repo/vendor/github.com/ncw/go-acd/README.md generated vendored Executable file
View File

@@ -0,0 +1,9 @@
# go-acd [![Build Status](https://travis-ci.org/sgeb/go-acd.svg?branch=master)](https://travis-ci.org/sgeb/go-acd)
Go library for accessing the Amazon Cloud Drive.
This library is the basis for [`acdcli`](https://github.com/sgeb/acdcli).
Still work in progress. Focusing on read-only operations at first. Refer to the
[milestones](https://github.com/sgeb/go-acd/milestones) and
[issues](https://github.com/sgeb/go-acd/issues) for planned features.

142
.rclone_repo/vendor/github.com/ncw/go-acd/account.go generated vendored Executable file
View File

@@ -0,0 +1,142 @@
// Copyright (c) 2015 Serge Gebhardt. All rights reserved.
//
// Use of this source code is governed by the ISC
// license that can be found in the LICENSE file.
package acd
import (
"net/http"
"net/url"
"time"
)
// AccountService provides access to the account related functions
// in the Amazon Cloud Drive API.
//
// See: https://developer.amazon.com/public/apis/experience/cloud-drive/content/account
type AccountService struct {
client *Client
}
// AccountEndpoints represents information about the current customer's endpoints
type AccountEndpoints struct {
CustomerExists bool `json:"customerExists"`
ContentURL string `json:"contentUrl"`
MetadataURL string `json:"metadataUrl"`
}
// GetEndpoints retrives the current endpoints for this customer
//
// It also updates the endpoints in the client
func (s *AccountService) GetEndpoints() (*AccountEndpoints, *http.Response, error) {
req, err := s.client.NewMetadataRequest("GET", "account/endpoint", nil)
if err != nil {
return nil, nil, err
}
endpoints := &AccountEndpoints{}
resp, err := s.client.Do(req, endpoints)
if err != nil {
return nil, resp, err
}
// Update the client endpoints
if endpoints.MetadataURL != "" {
u, err := url.Parse(endpoints.MetadataURL)
if err == nil {
s.client.MetadataURL = u
}
}
if endpoints.ContentURL != "" {
u, err := url.Parse(endpoints.ContentURL)
if err == nil {
s.client.ContentURL = u
}
}
return endpoints, resp, err
}
// AccountInfo represents information about an Amazon Cloud Drive account.
type AccountInfo struct {
TermsOfUse *string `json:"termsOfUse"`
Status *string `json:"status"`
}
// GetInfo provides information about the current user account like
// the status and the accepted “Terms Of Use”.
func (s *AccountService) GetInfo() (*AccountInfo, *http.Response, error) {
req, err := s.client.NewMetadataRequest("GET", "account/info", nil)
if err != nil {
return nil, nil, err
}
accountInfo := &AccountInfo{}
resp, err := s.client.Do(req, accountInfo)
if err != nil {
return nil, resp, err
}
return accountInfo, resp, err
}
// AccountQuota represents information about the account quotas.
type AccountQuota struct {
Quota *uint64 `json:"quota"`
LastCalculated *time.Time `json:"lastCalculated"`
Available *uint64 `json:"available"`
}
// GetQuota gets account quota and storage availability information.
func (s *AccountService) GetQuota() (*AccountQuota, *http.Response, error) {
req, err := s.client.NewMetadataRequest("GET", "account/quota", nil)
if err != nil {
return nil, nil, err
}
accountQuota := &AccountQuota{}
resp, err := s.client.Do(req, accountQuota)
if err != nil {
return nil, resp, err
}
return accountQuota, resp, err
}
// AccountUsage represents information about the account usage.
type AccountUsage struct {
LastCalculated *time.Time `json:"lastCalculated"`
Other *CategoryUsage `json:"other"`
Doc *CategoryUsage `json:"doc"`
Photo *CategoryUsage `json:"photo"`
Video *CategoryUsage `json:"video"`
}
// CategoryUsage defines Total and Billable UsageNumbers
type CategoryUsage struct {
Total *UsageNumbers `json:"total"`
Billable *UsageNumbers `json:"billable"`
}
// UsageNumbers defines Bytes and Count for a metered count
type UsageNumbers struct {
Bytes *uint64 `json:"bytes"`
Count *uint64 `json:"count"`
}
// GetUsage gets Account Usage information broken down by content category.
func (s *AccountService) GetUsage() (*AccountUsage, *http.Response, error) {
req, err := s.client.NewMetadataRequest("GET", "account/usage", nil)
if err != nil {
return nil, nil, err
}
accountUsage := &AccountUsage{}
resp, err := s.client.Do(req, accountUsage)
if err != nil {
return nil, resp, err
}
return accountUsage, resp, err
}

85
.rclone_repo/vendor/github.com/ncw/go-acd/changes.go generated vendored Executable file
View File

@@ -0,0 +1,85 @@
package acd
import (
"encoding/json"
"io"
"net/http"
)
// ChangesService provides access to incemental changes in the Amazon Cloud Drive API.
//
// See: https://developer.amazon.com/public/apis/experience/cloud-drive/content/changes
type ChangesService struct {
client *Client
}
// A ChangeSet is collection of node changes as received from the Changes API
type ChangeSet struct {
Checkpoint string `json:"checkpoint"`
Nodes []*Node `json:"nodes"`
Reset bool `json:"reset"`
StatusCode int `json:"statusCode"`
End bool `json:"end"`
}
// ChangesOptions contains all possible arguments for the Changes API
type ChangesOptions struct {
Checkpoint string `json:"checkpoint,omitempty"`
ChunkSize int `json:"chunkSize,omitempty"`
MaxNodes int `json:"maxNodes,omitempty"`
IncludePurged bool `json:"includePurged,omitempty,string"`
}
// GetChanges returns all the changes since opts.Checkpoint
func (s *ChangesService) GetChanges(opts *ChangesOptions) ([]*ChangeSet, *http.Response, error) {
var changeSets []*ChangeSet
resp, err := s.GetChangesFunc(opts, func(cs *ChangeSet, err error) error {
if err != nil {
return err
}
changeSets = append(changeSets, cs)
return nil
})
return changeSets, resp, err
}
// GetChangesChan gets all the changes since opts.Checkpoint sending each ChangeSet to the channel.
// The provided channel is closed before returning
func (s *ChangesService) GetChangesChan(opts *ChangesOptions, ch chan<- *ChangeSet) (*http.Response, error) {
defer close(ch)
return s.GetChangesFunc(opts, func(cs *ChangeSet, err error) error {
if err != nil {
return err
}
ch <- cs
return nil
})
}
// GetChangesFunc gets all the changes since opts.Checkpoint and calls f with the ChangeSet or the error received.
// If f returns a non nil value, GetChangesFunc exits and returns the given error.
func (s *ChangesService) GetChangesFunc(opts *ChangesOptions, f func(*ChangeSet, error) error) (*http.Response, error) {
req, err := s.client.NewMetadataRequest("POST", "changes", opts)
if err != nil {
return nil, err
}
resp, err := s.client.Do(req, nil)
if err != nil {
return resp, err
}
defer resp.Body.Close()
decoder := json.NewDecoder(resp.Body)
for {
changeSet := &ChangeSet{}
err := decoder.Decode(&changeSet)
if err == io.EOF {
return resp, nil
}
err = f(changeSet, err)
if err != nil {
return resp, err
}
}
}

195
.rclone_repo/vendor/github.com/ncw/go-acd/client.go generated vendored Executable file
View File

@@ -0,0 +1,195 @@
// Copyright (c) 2015 Serge Gebhardt. All rights reserved.
//
// Use of this source code is governed by the ISC
// license that can be found in the LICENSE file.
package acd
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strings"
)
const (
// LibraryVersion is the current version of this library
LibraryVersion = "0.1.0"
defaultMetadataURL = "https://drive.amazonaws.com/drive/v1/"
defaultContentURL = "https://content-na.drive.amazonaws.com/cdproxy/"
userAgent = "go-acd/" + LibraryVersion
)
// A Client manages communication with the Amazon Cloud Drive API.
type Client struct {
// HTTP client used to communicate with the API.
httpClient *http.Client
// Metadata URL for API requests. Defaults to the public Amazon Cloud Drive API.
// MetadataURL should always be specified with a trailing slash.
MetadataURL *url.URL
// Content URL for API requests. Defaults to the public Amazon Cloud Drive API.
// ContentURL should always be specified with a trailing slash.
ContentURL *url.URL
// User agent used when communicating with the API.
UserAgent string
// Services used for talking to different parts of the API.
Account *AccountService
Nodes *NodesService
Changes *ChangesService
}
// NewClient returns a new Amazon Cloud Drive API client. If a nil httpClient is
// provided, http.DefaultClient will be used. To use API methods which require
// authentication, provide an http.Client that will perform the authentication
// for you (such as that provided by the golang.org/x/oauth2 library).
func NewClient(httpClient *http.Client) *Client {
if httpClient == nil {
httpClient = http.DefaultClient
}
metadataURL, _ := url.Parse(defaultMetadataURL)
contentURL, _ := url.Parse(defaultContentURL)
c := &Client{
httpClient: httpClient,
MetadataURL: metadataURL,
ContentURL: contentURL,
UserAgent: userAgent,
}
c.Account = &AccountService{client: c}
c.Nodes = &NodesService{client: c}
c.Changes = &ChangesService{client: c}
return c
}
// NewMetadataRequest creates an API request for metadata. A relative URL can be
// provided in urlStr, in which case it is resolved relative to the MetadataURL
// of the Client. Relative URLs should always be specified without a preceding
// slash. If specified, the value pointed to by body is JSON encoded and included
// as the request body.
func (c *Client) NewMetadataRequest(method, urlStr string, body interface{}) (*http.Request, error) {
return c.newRequest(c.MetadataURL, method, urlStr, body)
}
// NewContentRequest creates an API request for content. A relative URL can be
// provided in urlStr, in which case it is resolved relative to the ContentURL
// of the Client. Relative URLs should always be specified without a preceding
// slash. If specified, the value pointed to by body is JSON encoded and included
// as the request body.
func (c *Client) NewContentRequest(method, urlStr string, body interface{}) (*http.Request, error) {
return c.newRequest(c.ContentURL, method, urlStr, body)
}
// newRequest creates an API request. A relative URL can be provided in urlStr,
// in which case it is resolved relative to base URL.
// Relative URLs should always be specified without a preceding slash. If
// specified, the value pointed to by body is JSON encoded and included as the
// request body.
func (c *Client) newRequest(base *url.URL, method, urlStr string, body interface{}) (*http.Request, error) {
rel, err := url.Parse(urlStr)
if err != nil {
return nil, err
}
u := base.ResolveReference(rel)
bodyReader, ok := body.(io.Reader)
if !ok && body != nil {
buf := &bytes.Buffer{}
err := json.NewEncoder(buf).Encode(body)
if err != nil {
return nil, err
}
bodyReader = buf
}
req, err := http.NewRequest(method, u.String(), bodyReader)
if err != nil {
return nil, err
}
// req.Header.Add("Accept", mediaTypeV3)
if c.UserAgent != "" {
req.Header.Add("User-Agent", c.UserAgent)
}
return req, nil
}
// Do sends an API request and returns the API response. The API response is
// JSON decoded and stored in the value pointed to by v, or returned as an
// error if an API error has occurred. If v implements the io.Writer
// interface, the raw response body will be written to v, without attempting to
// first decode it. If v is nil then the resp.Body won't be closed - this is
// your responsibility.
//
func (c *Client) Do(req *http.Request, v interface{}) (*http.Response, error) {
//buf, _ := httputil.DumpRequest(req, true)
//buf, _ := httputil.DumpRequest(req, false)
//log.Printf("req = %s", string(buf))
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, err
}
if v != nil {
defer resp.Body.Close()
}
//buf, _ = httputil.DumpResponse(resp, true)
//buf, _ = httputil.DumpResponse(resp, false)
//log.Printf("resp = %s", string(buf))
err = CheckResponse(resp)
if err != nil {
// even though there was an error, we still return the response
// in case the caller wants to inspect it further. We do close the
// Body though
if v == nil {
resp.Body.Close()
}
return resp, err
}
if v != nil {
if w, ok := v.(io.Writer); ok {
io.Copy(w, resp.Body)
} else {
err = json.NewDecoder(resp.Body).Decode(v)
}
}
return resp, err
}
// CheckResponse checks the API response for errors, and returns them if
// present. A response is considered an error if it has a status code outside
// the 200 range.
func CheckResponse(r *http.Response) error {
c := r.StatusCode
if 200 <= c && c <= 299 {
return nil
}
errBody := ""
if data, err := ioutil.ReadAll(r.Body); err == nil {
errBody = strings.TrimSpace(string(data))
}
errMsg := fmt.Sprintf("HTTP code %v: %q: ", c, r.Status)
if errBody == "" {
errMsg += "no response body"
} else {
errMsg += fmt.Sprintf("response body: %q", errBody)
}
return errors.New(errMsg)
}

766
.rclone_repo/vendor/github.com/ncw/go-acd/nodes.go generated vendored Executable file
View File

@@ -0,0 +1,766 @@
// Copyright (c) 2015 Serge Gebhardt. All rights reserved.
//
// Use of this source code is governed by the ISC
// license that can be found in the LICENSE file.
package acd
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"net/url"
"os"
"reflect"
"regexp"
"github.com/google/go-querystring/query"
)
var (
// ErrorNodeNotFound is returned from GetFile, GetFolder, GetNode
ErrorNodeNotFound = errors.New("Node not found")
)
// NodesService provides access to the nodes in the Amazon Cloud Drive API.
//
// See: https://developer.amazon.com/public/apis/experience/cloud-drive/content/nodes
type NodesService struct {
client *Client
}
// GetRoot gets the root folder of the Amazon Cloud Drive.
func (s *NodesService) GetRoot() (*Folder, *http.Response, error) {
opts := &NodeListOptions{Filters: "kind:FOLDER AND isRoot:true"}
roots, resp, err := s.GetNodes(opts)
if err != nil {
return nil, resp, err
}
if len(roots) < 1 {
return nil, resp, errors.New("No root found")
}
return &Folder{roots[0]}, resp, nil
}
// GetAllNodes gets the list of all nodes.
func (s *NodesService) GetAllNodes(opts *NodeListOptions) ([]*Node, *http.Response, error) {
return s.listAllNodes("nodes", opts)
}
// GetNodes gets a list of nodes, up until the limit (either default or the one set in opts).
func (s *NodesService) GetNodes(opts *NodeListOptions) ([]*Node, *http.Response, error) {
nodes, res, err := s.listNodes("nodes", opts)
return nodes, res, err
}
func (s *NodesService) listAllNodes(url string, opts *NodeListOptions) ([]*Node, *http.Response, error) {
// Need opts to maintain state (NodeListOptions.reachedEnd)
if opts == nil {
opts = &NodeListOptions{}
}
result := make([]*Node, 0, 200)
for {
nodes, resp, err := s.listNodes(url, opts)
if err != nil {
return result, resp, err
}
if nodes == nil {
break
}
result = append(result, nodes...)
}
return result, nil, nil
}
func (s *NodesService) listNodes(url string, opts *NodeListOptions) ([]*Node, *http.Response, error) {
if opts != nil && opts.reachedEnd {
return nil, nil, nil
}
url, err := addOptions(url, opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewMetadataRequest("GET", url, nil)
if err != nil {
return nil, nil, err
}
nodeList := &nodeListInternal{}
resp, err := s.client.Do(req, nodeList)
if err != nil {
return nil, resp, err
}
if opts != nil {
if nodeList.NextToken != nil {
opts.StartToken = *nodeList.NextToken
} else {
opts.reachedEnd = true
}
}
nodes := nodeList.Data
for _, node := range nodes {
node.service = s
}
return nodes, resp, nil
}
type nodeListInternal struct {
Count *uint64 `json:"count"`
NextToken *string `json:"nextToken"`
Data []*Node `json:"data"`
}
// Node represents a digital asset on the Amazon Cloud Drive, including files
// and folders, in a parent-child relationship. A node contains only metadata
// (e.g. folder) or it contains metadata and content (e.g. file).
type Node struct {
Id *string `json:"id"`
Name *string `json:"name"`
Kind *string `json:"kind"`
ModifiedDate *string `json:"modifiedDate"`
Parents []string `json:"parents"`
Status *string `json:"status"`
ContentProperties *struct {
Size *uint64 `json:"size"`
Md5 *string `json:"md5"`
ContentType *string `json:"contentType"`
} `json:"contentProperties"`
TempURL string `json:"tempLink"`
service *NodesService
}
// NodeFromId constructs a skeleton Node from an Id and a NodeService
func NodeFromId(ID string, service *NodesService) *Node {
return &Node{
Id: &ID,
service: service,
}
}
// IsFile returns whether the node represents a file.
func (n *Node) IsFile() bool {
return n.Kind != nil && *n.Kind == "FILE"
}
// IsFolder returns whether the node represents a folder.
func (n *Node) IsFolder() bool {
return n.Kind != nil && *n.Kind == "FOLDER"
}
// Typed returns the Node typed as either File or Folder.
func (n *Node) Typed() interface{} {
if n.IsFile() {
return &File{n}
}
if n.IsFolder() {
return &Folder{n}
}
return n
}
// GetTempURL sets the TempURL for the node passed in if it isn't already set
func (n *Node) GetTempURL() (*http.Response, error) {
if n.TempURL != "" {
return nil, nil
}
url := fmt.Sprintf("nodes/%s?tempLink=true", *n.Id)
req, err := n.service.client.NewMetadataRequest("GET", url, nil)
if err != nil {
return nil, err
}
node := &Node{}
resp, err := n.service.client.Do(req, node)
if err != nil {
return resp, err
}
if node.TempURL == "" {
return resp, fmt.Errorf("Couldn't read TempURL")
}
// Set the TempURL in the node
n.TempURL = node.TempURL
return resp, nil
}
// GetMetadata return a pretty-printed JSON string of the node's metadata
func (n *Node) GetMetadata() (string, error) {
url := fmt.Sprintf("nodes/%s?tempLink=true", *n.Id)
req, err := n.service.client.NewMetadataRequest("GET", url, nil)
if err != nil {
return "", err
}
buf := &bytes.Buffer{}
_, err = n.service.client.Do(req, buf)
if err != nil {
return "", err
}
md := &bytes.Buffer{}
err = json.Indent(md, buf.Bytes(), "", " ")
if err != nil {
return "", err
}
return md.String(), nil
}
type replaceParent struct {
FromParent string `json:"fromParent"`
ChildID string `json:"childId"`
}
// ReplaceParent puts Node n below a new parent while removing the old one at the same time.
// This is equivalent to calling AddParent and RemoveParent sequentially, but
// only needs one REST call. Can return a 409 Conflict if there's already a
// file or folder in the new location with the same name as Node n.
func (n *Node) ReplaceParent(oldParentID string, newParentID string) (*http.Response, error) {
body := &replaceParent{
FromParent: oldParentID,
ChildID: *n.Id,
}
url := fmt.Sprintf("nodes/%s/children", newParentID)
req, err := n.service.client.NewMetadataRequest("POST", url, &body)
if err != nil {
return nil, err
}
resp, err := n.service.client.Do(req, nil)
if err != nil {
return resp, err
}
n.Parents = []string{newParentID}
err = resp.Body.Close()
return resp, err
}
// AddParent adds an additional parent to Node n. Can return a 409 Conflict if there's
// already a file or folder below the new parent with the same name as Node n.
func (n *Node) AddParent(newParentID string) (*http.Response, error) {
return n.changeParents(newParentID, true)
}
// RemoveParent removes a parent from Node n. If all parents are removed, the file is instead
// attached to the absolute root folder of AmazonDrive.
func (n *Node) RemoveParent(parentID string) (*http.Response, error) {
return n.changeParents(parentID, false)
}
func (n *Node) changeParents(parentID string, add bool) (*http.Response, error) {
method := "DELETE"
if add {
method = "PUT"
}
url := fmt.Sprintf("nodes/%s/children/%s", parentID, *n.Id)
req, err := n.service.client.NewMetadataRequest(method, url, nil)
if err != nil {
return nil, err
}
resp, err := n.service.client.Do(req, nil)
if err != nil {
return resp, err
}
if add {
n.Parents = append(n.Parents, parentID)
} else {
var removeIndex int
for i := 0; i < len(n.Parents); i++ {
if n.Parents[i] == parentID {
removeIndex = i
break
}
}
n.Parents = append(n.Parents[:removeIndex], n.Parents[removeIndex+1:]...)
}
err = resp.Body.Close()
return resp, err
}
// renameNode is a cut down set of parameters for renaming nodes
type renameNode struct {
Name string `json:"name"`
}
// Rename node
func (n *Node) Rename(newName string) (*Node, *http.Response, error) {
url := fmt.Sprintf("nodes/%s", *n.Id)
metadata := renameNode{
Name: newName,
}
node := &Node{service: n.service}
req, err := n.service.client.NewMetadataRequest("PATCH", url, &metadata)
if err != nil {
return nil, nil, err
}
resp, err := n.service.client.Do(req, node)
return node, resp, err
}
// Trash places Node n into the trash. If the node is a directory it
// places it and all of its contents into the trash.
func (n *Node) Trash() (*http.Response, error) {
url := fmt.Sprintf("trash/%s", *n.Id)
req, err := n.service.client.NewMetadataRequest("PUT", url, nil)
if err != nil {
return nil, err
}
resp, err := n.service.client.Do(req, nil)
if err != nil {
return resp, err
}
err = resp.Body.Close()
if err != nil {
return resp, err
}
return resp, nil
}
// Restore moves a previously trashed Node n back into all its connected parents
func (n *Node) Restore() (*Node, *http.Response, error) {
url := fmt.Sprintf("trash/%s/restore", *n.Id)
req, err := n.service.client.NewMetadataRequest("POST", url, nil)
if err != nil {
return nil, nil, err
}
node := &Node{service: n.service}
resp, err := n.service.client.Do(req, node)
if err != nil {
return nil, resp, err
}
err = resp.Body.Close()
return node, resp, err
}
// File represents a file on the Amazon Cloud Drive.
type File struct {
*Node
}
// OpenHeaders opens the content of the file f for read
//
// Extra headers for the GET can be passed in in headers
//
// You must call in.Close() when finished
func (f *File) OpenHeaders(headers map[string]string) (in io.ReadCloser, resp *http.Response, err error) {
url := fmt.Sprintf("nodes/%s/content", *f.Id)
req, err := f.service.client.NewContentRequest("GET", url, nil)
if err != nil {
return nil, nil, err
}
for k, v := range headers {
req.Header.Add(k, v)
}
resp, err = f.service.client.Do(req, nil)
if err != nil {
return nil, resp, err
}
return resp.Body, resp, nil
}
// Open the content of the file f for read
//
// You must call in.Close() when finished
func (f *File) Open() (in io.ReadCloser, resp *http.Response, err error) {
return f.OpenHeaders(nil)
}
// OpenTempURLHeaders opens the content of the file f for read from the TempURL
//
// Pass in an http Client (without authorization) for the download.
//
// You must call in.Close() when finished
func (f *File) OpenTempURLHeaders(client *http.Client, headers map[string]string) (in io.ReadCloser, resp *http.Response, err error) {
resp, err = f.GetTempURL()
if err != nil {
return nil, resp, err
}
req, err := http.NewRequest("GET", f.TempURL, nil)
if err != nil {
return nil, nil, err
}
if f.service.client.UserAgent != "" {
req.Header.Add("User-Agent", f.service.client.UserAgent)
}
for k, v := range headers {
req.Header.Add(k, v)
}
resp, err = client.Do(req)
if err != nil {
return nil, resp, err
}
return resp.Body, resp, nil
}
// OpenTempURL opens the content of the file f for read from the TempURL
//
// Pass in an http Client (without authorization) for the download.
//
// You must call in.Close() when finished
func (f *File) OpenTempURL(client *http.Client) (in io.ReadCloser, resp *http.Response, err error) {
return f.OpenTempURLHeaders(client, nil)
}
// Download fetches the content of file f and stores it into the file pointed
// to by path. Errors if the file at path already exists. Does not create the
// intermediate directories in path.
func (f *File) Download(path string) (*http.Response, error) {
url := fmt.Sprintf("nodes/%s/content", *f.Id)
req, err := f.service.client.NewContentRequest("GET", url, nil)
if err != nil {
return nil, err
}
out, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0666)
if err != nil {
return nil, err
}
defer out.Close()
resp, err := f.service.client.Do(req, out)
return resp, err
}
// Folder represents a folder on the Amazon Cloud Drive.
type Folder struct {
*Node
}
// FolderFromId constructs a skeleton Folder from an Id and a NodeService
func FolderFromId(ID string, service *NodesService) *Folder {
return &Folder{
Node: NodeFromId(ID, service),
}
}
// GetAllChildren gets the list of all children.
func (f *Folder) GetAllChildren(opts *NodeListOptions) ([]*Node, *http.Response, error) {
url := fmt.Sprintf("nodes/%s/children", *f.Id)
return f.service.listAllNodes(url, opts)
}
// GetChildren gets a list of children, up until the limit (either
// default or the one set in opts).
func (f *Folder) GetChildren(opts *NodeListOptions) ([]*Node, *http.Response, error) {
url := fmt.Sprintf("nodes/%s/children", *f.Id)
return f.service.listNodes(url, opts)
}
// GetFolder gets the subfolder by name. It is an error if not exactly
// one subfolder is found.
//
// If it isn't found then it returns the error ErrorNodeNotFound
func (f *Folder) GetFolder(name string) (*Folder, *http.Response, error) {
n, resp, err := f.GetNode(name)
if err != nil {
return nil, resp, err
}
res, ok := n.Typed().(*Folder)
if !ok {
err := fmt.Errorf("Node '%s' is not a folder", name)
return nil, resp, err
}
return res, resp, nil
}
// createNode is a cut down set of parameters for creating nodes
type createNode struct {
Name string `json:"name"`
Kind string `json:"kind"`
Parents []string `json:"parents"`
}
// CreateFolder makes a new folder with the given name.
//
// The new Folder is returned
func (f *Folder) CreateFolder(name string) (*Folder, *http.Response, error) {
createFolder := createNode{
Name: name,
Kind: "FOLDER",
Parents: []string{*f.Id},
}
req, err := f.service.client.NewMetadataRequest("POST", "nodes", &createFolder)
if err != nil {
return nil, nil, err
}
folder := &Folder{&Node{service: f.service}}
resp, err := f.service.client.Do(req, folder)
if err != nil {
return nil, resp, err
}
return folder, resp, nil
}
// GetFile gets the file by name. It is an error if not exactly one file is found.
//
// If it isn't found then it returns the error ErrorNodeNotFound
func (f *Folder) GetFile(name string) (*File, *http.Response, error) {
n, resp, err := f.GetNode(name)
if err != nil {
return nil, resp, err
}
res, ok := n.Typed().(*File)
if !ok {
err := fmt.Errorf("Node '%s' is not a file", name)
return nil, resp, err
}
return res, resp, nil
}
var escapeForFilterRe = regexp.MustCompile(`([+\-&|!(){}\[\]^'"~*?:\\ ])`)
// EscapeForFilter escapes an abitrary string for use as a filter
// query parameter.
//
// Special characters that are part of the query syntax will be
// escaped. The list of special characters are:
//
// + - & | ! ( ) { } [ ] ^ ' " ~ * ? : \
//
// Additionally, space will be escaped. Characters are escaped by
// using \ before the character.
func EscapeForFilter(s string) string {
return escapeForFilterRe.ReplaceAllString(s, `\$1`)
}
// GetNode gets the node by name. It is an error if not exactly one node is found.
//
// If it isn't found then it returns the error ErrorNodeNotFound
func (f *Folder) GetNode(name string) (*Node, *http.Response, error) {
filter := fmt.Sprintf(`parents:"%v" AND name:"%s"`, *f.Id, EscapeForFilter(name))
opts := &NodeListOptions{Filters: filter}
nodes, resp, err := f.service.GetNodes(opts)
if err != nil {
return nil, resp, err
}
if len(nodes) < 1 {
return nil, resp, ErrorNodeNotFound
}
if len(nodes) > 1 {
err := fmt.Errorf("Too many nodes '%s' found (%v)", name, len(nodes))
return nil, resp, err
}
return nodes[0], resp, nil
}
// WalkNodes walks the given node hierarchy, getting each node along the way, and returns
// the deepest node. If an error occurs, returns the furthest successful node and the list
// of HTTP responses.
func (f *Folder) WalkNodes(names ...string) (*Node, []*http.Response, error) {
resps := make([]*http.Response, 0, len(names))
if len(names) == 0 {
return f.Node, resps, nil
}
// process each node except the last one
fp := f
for _, name := range names[:len(names)-1] {
fn, resp, err := fp.GetFolder(name)
resps = append(resps, resp)
if err != nil {
return fp.Node, resps, err
}
fp = fn
}
// process the last node
nl, resp, err := fp.GetNode(names[len(names)-1])
resps = append(resps, resp)
if err != nil {
return fp.Node, resps, err
}
return nl, resps, nil
}
// Put stores the data read from in at path as name on the Amazon Cloud Drive.
// Errors if the file already exists on the drive.
func (s *NodesService) putOrOverwrite(in io.Reader, httpVerb, url, name, metadata string) (*File, *http.Response, error) {
var bodyReader io.Reader
bodyReader, bodyWriter := io.Pipe()
writer := multipart.NewWriter(bodyWriter)
contentType := writer.FormDataContentType()
contentLength := int64(-1)
buf := make([]byte, 1)
n, err := io.ReadFull(in, buf)
isZeroLength := err == io.EOF
if !isZeroLength && err != nil {
return nil, nil, err
}
in = io.MultiReader(bytes.NewReader(buf[:n]), in)
errChan := make(chan error, 1)
go func() {
defer bodyWriter.Close()
var err error
if metadata != "" {
err = writer.WriteField("metadata", string(metadata))
if err != nil {
errChan <- err
return
}
}
part, err := writer.CreateFormFile("content", name)
if err != nil {
errChan <- err
return
}
if _, err := io.Copy(part, in); err != nil {
errChan <- err
return
}
errChan <- writer.Close()
}()
if isZeroLength {
buf, err := ioutil.ReadAll(bodyReader)
if err != nil {
return nil, nil, err
}
bodyReader = bytes.NewReader(buf)
contentLength = int64(len(buf))
}
req, err := s.client.NewContentRequest(httpVerb, url, bodyReader)
if err != nil {
return nil, nil, err
}
req.ContentLength = contentLength
req.Header.Add("Content-Type", contentType)
file := &File{&Node{service: s}}
resp, err := s.client.Do(req, file)
if err != nil {
return nil, resp, err
}
err = <-errChan
if err != nil {
return nil, resp, err
}
return file, resp, err
}
// Put stores the data read from in at path as name on the Amazon Cloud Drive.
// Errors if the file already exists on the drive.
func (f *Folder) Put(in io.Reader, name string) (*File, *http.Response, error) {
metadata := createNode{
Name: name,
Kind: "FILE",
Parents: []string{*f.Id},
}
metadataJSON, err := json.Marshal(&metadata)
if err != nil {
return nil, nil, err
}
return f.service.putOrOverwrite(in, "POST", "nodes?suppress=deduplication", name, string(metadataJSON))
}
// Overwrite updates the file contents from in
func (f *File) Overwrite(in io.Reader) (*File, *http.Response, error) {
url := fmt.Sprintf("nodes/%s/content", *f.Id)
return f.service.putOrOverwrite(in, "PUT", url, *f.Name, "")
}
// PutSized stores the data read from in at path as name on the Amazon
// Cloud Drive. Errors if the file already exists on the drive.
//
// Deprecated: no longer needed - just use Put
func (f *Folder) PutSized(in io.Reader, _ int64, name string) (*File, *http.Response, error) {
return f.Put(in, name)
}
// OverwriteSized updates the file contents from in
//
// Deprecated: no longer needed - just use Overwrite
func (f *File) OverwriteSized(in io.Reader, _ int64) (*File, *http.Response, error) {
return f.Overwrite(in)
}
// Upload stores the content of file at path as name on the Amazon Cloud Drive.
// Errors if the file already exists on the drive.
func (f *Folder) Upload(path, name string) (*File, *http.Response, error) {
in, err := os.Open(path)
if err != nil {
return nil, nil, err
}
defer in.Close()
return f.Put(in, name)
}
// NodeListOptions holds the options when getting a list of nodes, such as the filter,
// sorting and pagination.
type NodeListOptions struct {
Limit uint `url:"limit,omitempty"`
Filters string `url:"filters,omitempty"`
Sort string `url:"sort,omitempty"`
// Token where to start for next page (internal)
StartToken string `url:"startToken,omitempty"`
reachedEnd bool
}
// addOptions adds the parameters in opts as URL query parameters to s. opts
// must be a struct whose fields may contain "url" tags.
func addOptions(s string, opts interface{}) (string, error) {
v := reflect.ValueOf(opts)
if v.Kind() == reflect.Ptr && v.IsNil() {
return s, nil
}
u, err := url.Parse(s)
if err != nil {
return s, err
}
qs, err := query.Values(opts)
if err != nil {
return s, err
}
u.RawQuery = qs.Encode()
return u.String(), nil
}