[
  {
    "path": "LICENSE",
    "content": "(The MIT License)\n\nCopyright (c) 2015 TJ Holowaychuk &lt;tj@tjholowaychuk.coma&gt;\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n'Software'), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."
  },
  {
    "path": "Readme.md",
    "content": "\n[![GoDoc](https://godoc.org/github.com/tj/go-dropbox?status.svg)](https://godoc.org/github.com/tj/go-dropbox) [![Build Status](https://semaphoreci.com/api/v1/projects/bc0bfd8b-73c9-45ba-b988-00f9e285e6ef/617305/badge.svg)](https://semaphoreci.com/tj/go-dropbox)\n\n# Dropbox\n\n Simple Dropbox v2 client for Go.\n\n For a higher level client take a look at [go-dropy](https://github.com/tj/go-dropy).\n\n## About\n\n Modelled more or less 1:1 with the API for consistency and parity with the [official documentation](https://www.dropbox.com/developers/documentation/http). More sugar should be implemented on top.\n\n## Testing\n\n To manually run tests use the test account access token:\n\n```\n$ export DROPBOX_ACCESS_TOKEN=oENFkq_oIVAAAAAAAAAAC8gE3wIUFMEraPBL-D71Aq2C4zuh1l4oDn5FiWSdVVlL\n$ go test -v\n```\n\n# License\n\nMIT"
  },
  {
    "path": "client.go",
    "content": "package dropbox\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"strings\"\n)\n\n// Client implements a Dropbox client. You may use the Files and Users\n// clients directly if preferred, however Client exposes them both.\ntype Client struct {\n\t*Config\n\tUsers   *Users\n\tFiles   *Files\n\tSharing *Sharing\n}\n\n// New client.\nfunc New(config *Config) *Client {\n\tc := &Client{Config: config}\n\tc.Users = &Users{c}\n\tc.Files = &Files{c}\n\tc.Sharing = &Sharing{c}\n\treturn c\n}\n\n// call rpc style endpoint.\nfunc (c *Client) call(path string, in interface{}) (io.ReadCloser, error) {\n\turl := \"https://api.dropboxapi.com/2\" + path\n\n\tbody, err := json.Marshal(in)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treq, err := http.NewRequest(\"POST\", url, bytes.NewReader(body))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treq.Header.Set(\"Authorization\", \"Bearer \"+c.AccessToken)\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\n\tr, _, err := c.do(req)\n\treturn r, err\n}\n\n// download style endpoint.\nfunc (c *Client) download(path string, in interface{}, r io.Reader) (io.ReadCloser, int64, error) {\n\turl := \"https://content.dropboxapi.com/2\" + path\n\n\tbody, err := json.Marshal(in)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\treq, err := http.NewRequest(\"POST\", url, r)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\treq.Header.Set(\"Authorization\", \"Bearer \"+c.AccessToken)\n\treq.Header.Set(\"Dropbox-API-Arg\", string(body))\n\n\tif r != nil {\n\t\treq.Header.Set(\"Content-Type\", \"application/octet-stream\")\n\t}\n\n\treturn c.do(req)\n}\n\n// perform the request.\nfunc (c *Client) do(req *http.Request) (io.ReadCloser, int64, error) {\n\tres, err := c.HTTPClient.Do(req)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\tif res.StatusCode < 400 {\n\t\treturn res.Body, res.ContentLength, err\n\t}\n\n\tdefer res.Body.Close()\n\n\te := &Error{\n\t\tStatus:     http.StatusText(res.StatusCode),\n\t\tStatusCode: res.StatusCode,\n\t}\n\n\tkind := res.Header.Get(\"Content-Type\")\n\n\tif strings.Contains(kind, \"text/plain\") {\n\t\tif b, err := ioutil.ReadAll(res.Body); err == nil {\n\t\t\te.Summary = string(b)\n\t\t\treturn nil, 0, e\n\t\t}\n\t\treturn nil, 0, err\n\t}\n\n\tif err := json.NewDecoder(res.Body).Decode(e); err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\treturn nil, 0, e\n}\n"
  },
  {
    "path": "client_test.go",
    "content": "package dropbox\n\nimport (\n\t\"testing\"\n\n\t\"github.com/segmentio/go-env\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc client() *Client {\n\ttoken := env.MustGet(\"DROPBOX_ACCESS_TOKEN\")\n\treturn New(NewConfig(token))\n}\n\nfunc TestClient_error_text(t *testing.T) {\n\tc := client()\n\n\t_, err := c.Files.Download(&DownloadInput{\n\t\tPath: \"asdfasdfasdf\",\n\t})\n\n\tassert.Error(t, err)\n\n\te := err.(*Error)\n\tassert.Contains(t, e.Error(), \"Error in call\")\n\tassert.Equal(t, \"Bad Request\", e.Status)\n\tassert.Equal(t, 400, e.StatusCode)\n}\n\nfunc TestClient_error_json(t *testing.T) {\n\tc := client()\n\n\t_, err := c.Files.Download(&DownloadInput{\"/nothing\"})\n\tassert.Error(t, err)\n\n\te := err.(*Error)\n\tassert.Contains(t, e.Error(), \"path/not_found\")\n\tassert.Equal(t, \"Conflict\", e.Status)\n\tassert.Equal(t, 409, e.StatusCode)\n}\n"
  },
  {
    "path": "config.go",
    "content": "package dropbox\n\nimport (\n\t\"net/http\"\n)\n\n// Config for the Dropbox clients.\ntype Config struct {\n\tHTTPClient  *http.Client\n\tAccessToken string\n}\n\n// NewConfig with the given access token.\nfunc NewConfig(accessToken string) *Config {\n\treturn &Config{\n\t\tHTTPClient:  http.DefaultClient,\n\t\tAccessToken: accessToken,\n\t}\n}\n"
  },
  {
    "path": "doc.go",
    "content": "// Package dropbox implements a simple v2 client.\npackage dropbox\n"
  },
  {
    "path": "error.go",
    "content": "package dropbox\n\n// Error response.\ntype Error struct {\n\tStatus     string\n\tStatusCode int\n\tSummary    string `json:\"error_summary\"`\n}\n\n// Error string.\nfunc (e *Error) Error() string {\n\treturn e.Summary\n}\n"
  },
  {
    "path": "example_test.go",
    "content": "package dropbox_test\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\n\t\"github.com/tj/go-dropbox\"\n)\n\n// Example using the Client, which provides both User and File clients.\nfunc Example() {\n\td := dropbox.New(dropbox.NewConfig(\"<token>\"))\n\n\tfile, _ := os.Open(\"Readme.md\")\n\n\td.Files.Upload(&dropbox.UploadInput{\n\t\tPath:   \"Readme.md\",\n\t\tReader: file,\n\t\tMute:   true,\n\t})\n}\n\n// Example using the Files client directly.\nfunc Example_files() {\n\tfiles := dropbox.NewFiles(dropbox.NewConfig(\"<token>\"))\n\n\tout, _ := files.Download(&dropbox.DownloadInput{\n\t\tPath: \"Readme.md\",\n\t})\n\n\tio.Copy(os.Stdout, out.Body)\n}\n\n// Example using the Users client directly.\nfunc Example_users() {\n\tusers := dropbox.NewUsers(dropbox.NewConfig(\"<token>\"))\n\tout, _ := users.GetCurrentAccount()\n\tfmt.Printf(\"%v\\n\", out)\n}\n"
  },
  {
    "path": "files.go",
    "content": "package dropbox\n\nimport (\n\t\"crypto/sha256\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"time\"\n)\n\n// Files client for files and folders.\ntype Files struct {\n\t*Client\n}\n\n// NewFiles client.\nfunc NewFiles(config *Config) *Files {\n\treturn &Files{\n\t\tClient: &Client{\n\t\t\tConfig: config,\n\t\t},\n\t}\n}\n\n// WriteMode determines what to do if the file already exists.\ntype WriteMode string\n\n// Supported write modes.\nconst (\n\tWriteModeAdd       WriteMode = \"add\"\n\tWriteModeOverwrite           = \"overwrite\"\n)\n\n// Dimensions specifies the dimensions of a photo or video.\ntype Dimensions struct {\n\tWidth  uint64 `json:\"width\"`\n\tHeight uint64 `json:\"height\"`\n}\n\n// GPSCoordinates specifies the GPS coordinate of a photo or video.\ntype GPSCoordinates struct {\n\tLatitude  float64 `json:\"latitude\"`\n\tLongitude float64 `json:\"longitude\"`\n}\n\n// PhotoMetadata specifies metadata for a photo.\ntype PhotoMetadata struct {\n\tDimensions *Dimensions     `json:\"dimensions,omitempty\"`\n\tLocation   *GPSCoordinates `json:\"location,omitempty\"`\n\tTimeTaken  time.Time       `json:\"time_taken,omitempty\"`\n}\n\n// VideoMetadata specifies metadata for a video.\ntype VideoMetadata struct {\n\tDimensions *Dimensions     `json:\"dimensions,omitempty\"`\n\tLocation   *GPSCoordinates `json:\"location,omitempty\"`\n\tTimeTaken  time.Time       `json:\"time_taken,omitempty\"`\n\tDuration   uint64          `json:\"duration,omitempty\"`\n}\n\n// MediaMetadata provides metadata for a photo or video.\ntype MediaMetadata struct {\n\tPhoto *PhotoMetadata `json:\"photo,omitempty\"`\n\tVideo *VideoMetadata `json:\"video,omitempty\"`\n}\n\n// MediaInfo provides additional information for a photo or video file.\ntype MediaInfo struct {\n\tPending  bool           `json:\"pending\"`\n\tMetadata *MediaMetadata `json:\"metadata,omitempty\"`\n}\n\n// FileSharingInfo for a file which is contained in a shared folder.\ntype FileSharingInfo struct {\n\tReadOnly             bool   `json:\"read_only\"`\n\tParentSharedFolderID string `json:\"parent_shared_folder_id\"`\n\tModifiedBy           string `json:\"modified_by,omitempty\"`\n}\n\n// Metadata for a file or folder.\ntype Metadata struct {\n\tTag            string           `json:\".tag\"`\n\tName           string           `json:\"name\"`\n\tPathLower      string           `json:\"path_lower\"`\n\tPathDisplay    string           `json:\"path_display\"`\n\tClientModified time.Time        `json:\"client_modified\"`\n\tServerModified time.Time        `json:\"server_modified\"`\n\tRev            string           `json:\"rev\"`\n\tSize           uint64           `json:\"size\"`\n\tID             string           `json:\"id\"`\n\tMediaInfo      *MediaInfo       `json:\"media_info,omitempty\"`\n\tSharingInfo    *FileSharingInfo `json:\"sharing_info,omitempty\"`\n\tContentHash    string           `json:\"content_hash,omitempty\"`\n}\n\n// GetMetadataInput request input.\ntype GetMetadataInput struct {\n\tPath             string `json:\"path\"`\n\tIncludeMediaInfo bool   `json:\"include_media_info\"`\n}\n\n// GetMetadataOutput request output.\ntype GetMetadataOutput struct {\n\tMetadata\n}\n\n// GetMetadata returns the metadata for a file or folder.\nfunc (c *Files) GetMetadata(in *GetMetadataInput) (out *GetMetadataOutput, err error) {\n\tbody, err := c.call(\"/files/get_metadata\", in)\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer body.Close()\n\n\terr = json.NewDecoder(body).Decode(&out)\n\treturn\n}\n\n// CreateFolderInput request input.\ntype CreateFolderInput struct {\n\tPath string `json:\"path\"`\n}\n\n// CreateFolderOutput request output.\ntype CreateFolderOutput struct {\n\tName      string `json:\"name\"`\n\tPathLower string `json:\"path_lower\"`\n\tID        string `json:\"id\"`\n}\n\n// CreateFolder creates a folder.\nfunc (c *Files) CreateFolder(in *CreateFolderInput) (out *CreateFolderOutput, err error) {\n\tbody, err := c.call(\"/files/create_folder\", in)\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer body.Close()\n\n\terr = json.NewDecoder(body).Decode(&out)\n\treturn\n}\n\n// DeleteInput request input.\ntype DeleteInput struct {\n\tPath string `json:\"path\"`\n}\n\n// DeleteOutput request output.\ntype DeleteOutput struct {\n\tMetadata\n}\n\n// Delete a file or folder and its contents.\nfunc (c *Files) Delete(in *DeleteInput) (out *DeleteOutput, err error) {\n\tbody, err := c.call(\"/files/delete\", in)\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer body.Close()\n\n\terr = json.NewDecoder(body).Decode(&out)\n\treturn\n}\n\n// PermanentlyDeleteInput request input.\ntype PermanentlyDeleteInput struct {\n\tPath string `json:\"path\"`\n}\n\n// PermanentlyDelete a file or folder and its contents.\nfunc (c *Files) PermanentlyDelete(in *PermanentlyDeleteInput) (err error) {\n\tbody, err := c.call(\"/files/delete\", in)\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer body.Close()\n\n\treturn\n}\n\n// CopyInput request input.\ntype CopyInput struct {\n\tFromPath string `json:\"from_path\"`\n\tToPath   string `json:\"to_path\"`\n}\n\n// CopyOutput request output.\ntype CopyOutput struct {\n\tMetadata\n}\n\n// Copy a file or folder to a different location.\nfunc (c *Files) Copy(in *CopyInput) (out *CopyOutput, err error) {\n\tbody, err := c.call(\"/files/copy\", in)\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer body.Close()\n\n\terr = json.NewDecoder(body).Decode(&out)\n\treturn\n}\n\n// MoveInput request input.\ntype MoveInput struct {\n\tFromPath string `json:\"from_path\"`\n\tToPath   string `json:\"to_path\"`\n}\n\n// MoveOutput request output.\ntype MoveOutput struct {\n\tMetadata\n}\n\n// Move a file or folder to a different location.\nfunc (c *Files) Move(in *MoveInput) (out *MoveOutput, err error) {\n\tbody, err := c.call(\"/files/move\", in)\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer body.Close()\n\n\terr = json.NewDecoder(body).Decode(&out)\n\treturn\n}\n\n// RestoreInput request input.\ntype RestoreInput struct {\n\tPath string `json:\"path\"`\n\tRev  string `json:\"rev\"`\n}\n\n// RestoreOutput request output.\ntype RestoreOutput struct {\n\tMetadata\n}\n\n// Restore a file to a specific revision.\nfunc (c *Files) Restore(in *RestoreInput) (out *RestoreOutput, err error) {\n\tbody, err := c.call(\"/files/restore\", in)\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer body.Close()\n\n\terr = json.NewDecoder(body).Decode(&out)\n\treturn\n}\n\n// ListFolderInput request input.\ntype ListFolderInput struct {\n\tPath             string `json:\"path\"`\n\tRecursive        bool   `json:\"recursive\"`\n\tIncludeMediaInfo bool   `json:\"include_media_info\"`\n\tIncludeDeleted   bool   `json:\"include_deleted\"`\n}\n\n// ListFolderOutput request output.\ntype ListFolderOutput struct {\n\tCursor  string `json:\"cursor\"`\n\tHasMore bool   `json:\"has_more\"`\n\tEntries []*Metadata\n}\n\n// ListFolder returns the metadata for a file or folder.\nfunc (c *Files) ListFolder(in *ListFolderInput) (out *ListFolderOutput, err error) {\n\tin.Path = normalizePath(in.Path)\n\n\tbody, err := c.call(\"/files/list_folder\", in)\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer body.Close()\n\n\terr = json.NewDecoder(body).Decode(&out)\n\treturn\n}\n\n// ListFolderContinueInput request input.\ntype ListFolderContinueInput struct {\n\tCursor string `json:\"cursor\"`\n}\n\n// ListFolderContinue pagenates using the cursor from ListFolder.\nfunc (c *Files) ListFolderContinue(in *ListFolderContinueInput) (out *ListFolderOutput, err error) {\n\tbody, err := c.call(\"/files/list_folder/continue\", in)\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer body.Close()\n\n\terr = json.NewDecoder(body).Decode(&out)\n\treturn\n}\n\n// SearchMode determines how a search is performed.\ntype SearchMode string\n\n// Supported search modes.\nconst (\n\tSearchModeFilename           SearchMode = \"filename\"\n\tSearchModeFilenameAndContent            = \"filename_and_content\"\n\tSearchModeDeletedFilename               = \"deleted_filename\"\n)\n\n// SearchMatchType represents the type of match made.\ntype SearchMatchType string\n\n// Supported search match types.\nconst (\n\tSearchMatchFilename SearchMatchType = \"filename\"\n\tSearchMatchContent                  = \"content\"\n\tSearchMatchBoth                     = \"both\"\n)\n\n// SearchMatch represents a matched file or folder.\ntype SearchMatch struct {\n\tMatchType struct {\n\t\tTag SearchMatchType `json:\".tag\"`\n\t} `json:\"match_type\"`\n\tMetadata *Metadata `json:\"metadata\"`\n}\n\n// SearchInput request input.\ntype SearchInput struct {\n\tPath       string     `json:\"path\"`\n\tQuery      string     `json:\"query\"`\n\tStart      uint64     `json:\"start,omitempty\"`\n\tMaxResults uint64     `json:\"max_results,omitempty\"`\n\tMode       SearchMode `json:\"mode\"`\n}\n\n// SearchOutput request output.\ntype SearchOutput struct {\n\tMatches []*SearchMatch `json:\"matches\"`\n\tMore    bool           `json:\"more\"`\n\tStart   uint64         `json:\"start\"`\n}\n\n// Search for files and folders.\nfunc (c *Files) Search(in *SearchInput) (out *SearchOutput, err error) {\n\tin.Path = normalizePath(in.Path)\n\n\tif in.Mode == \"\" {\n\t\tin.Mode = SearchModeFilename\n\t}\n\n\tbody, err := c.call(\"/files/search\", in)\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer body.Close()\n\n\terr = json.NewDecoder(body).Decode(&out)\n\treturn\n}\n\n// UploadInput request input.\ntype UploadInput struct {\n\tPath           string    `json:\"path\"`\n\tMode           WriteMode `json:\"mode\"`\n\tAutoRename     bool      `json:\"autorename\"`\n\tMute           bool      `json:\"mute\"`\n\tClientModified string    `json:\"client_modified,omitempty\"`\n\tReader         io.Reader `json:\"-\"`\n}\n\n// UploadOutput request output.\ntype UploadOutput struct {\n\tMetadata\n}\n\n// Upload a file smaller than 150MB.\nfunc (c *Files) Upload(in *UploadInput) (out *UploadOutput, err error) {\n\tbody, _, err := c.download(\"/files/upload\", in, in.Reader)\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer body.Close()\n\n\terr = json.NewDecoder(body).Decode(&out)\n\treturn\n}\n\n// DownloadInput request input.\ntype DownloadInput struct {\n\tPath string `json:\"path\"`\n}\n\n// DownloadOutput request output.\ntype DownloadOutput struct {\n\tBody   io.ReadCloser\n\tLength int64\n}\n\n// Download a file.\nfunc (c *Files) Download(in *DownloadInput) (out *DownloadOutput, err error) {\n\tbody, l, err := c.download(\"/files/download\", in, nil)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tout = &DownloadOutput{body, l}\n\treturn\n}\n\n// ThumbnailFormat determines the format of the thumbnail.\ntype ThumbnailFormat string\n\nconst (\n\t// GetThumbnailFormatJPEG specifies a JPG thumbnail\n\tGetThumbnailFormatJPEG ThumbnailFormat = \"jpeg\"\n\t// GetThumbnailFormatPNG specifies a PNG thumbnail\n\tGetThumbnailFormatPNG = \"png\"\n)\n\n// ThumbnailSize determines the size of the thumbnail.\ntype ThumbnailSize string\n\nconst (\n\t// GetThumbnailSizeW32H32 specifies a size of 32 by 32 px\n\tGetThumbnailSizeW32H32 ThumbnailSize = \"w32h32\"\n\t// GetThumbnailSizeW64H64 specifies a size of 64 by 64 px\n\tGetThumbnailSizeW64H64 = \"w64h64\"\n\t// GetThumbnailSizeW128H128 specifies a size of 128 by 128 px\n\tGetThumbnailSizeW128H128 = \"w128h128\"\n\t// GetThumbnailSizeW640H480 specifies a size of 640 by 480 px\n\tGetThumbnailSizeW640H480 = \"w640h480\"\n\t// GetThumbnailSizeW1024H768 specifies a size of 1024 by 768 px\n\tGetThumbnailSizeW1024H768 = \"w1024h768\"\n)\n\n// GetThumbnailInput request input.\ntype GetThumbnailInput struct {\n\tPath   string          `json:\"path\"`\n\tFormat ThumbnailFormat `json:\"format\"`\n\tSize   ThumbnailSize   `json:\"size\"`\n}\n\n// GetThumbnailOutput request output.\ntype GetThumbnailOutput struct {\n\tBody   io.ReadCloser\n\tLength int64\n}\n\n// GetThumbnail a thumbnail for a file. Currently thumbnails are only generated for the\n// files with the following extensions: png, jpeg, png, tiff, tif, gif and bmp.\nfunc (c *Files) GetThumbnail(in *GetThumbnailInput) (out *GetThumbnailOutput, err error) {\n\tbody, l, err := c.download(\"/files/get_thumbnail\", in, nil)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tout = &GetThumbnailOutput{body, l}\n\treturn\n}\n\n// GetPreviewInput request input.\ntype GetPreviewInput struct {\n\tPath string `json:\"path\"`\n}\n\n// GetPreviewOutput request output.\ntype GetPreviewOutput struct {\n\tBody   io.ReadCloser\n\tLength int64\n}\n\n// GetPreview a preview for a file. Currently previews are only generated for the\n// files with the following extensions: .doc, .docx, .docm, .ppt, .pps, .ppsx,\n// .ppsm, .pptx, .pptm, .xls, .xlsx, .xlsm, .rtf\nfunc (c *Files) GetPreview(in *GetPreviewInput) (out *GetPreviewOutput, err error) {\n\tbody, l, err := c.download(\"/files/get_preview\", in, nil)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tout = &GetPreviewOutput{body, l}\n\treturn\n}\n\n// ListRevisionsInput request input.\ntype ListRevisionsInput struct {\n\tPath  string `json:\"path\"`\n\tLimit uint64 `json:\"limit,omitempty\"`\n}\n\n// ListRevisionsOutput request output.\ntype ListRevisionsOutput struct {\n\tIsDeleted bool\n\tEntries   []*Metadata\n}\n\n// ListRevisions gets the revisions of the specified file.\nfunc (c *Files) ListRevisions(in *ListRevisionsInput) (out *ListRevisionsOutput, err error) {\n\tbody, err := c.call(\"/files/list_revisions\", in)\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer body.Close()\n\n\terr = json.NewDecoder(body).Decode(&out)\n\treturn\n}\n\n// Normalize path so people can use \"/\" as they expect.\nfunc normalizePath(s string) string {\n\tif s == \"/\" {\n\t\treturn \"\"\n\t}\n\treturn s\n}\n\nconst hashBlockSize = 4 * 1024 * 1024\n\n// ContentHash returns the Dropbox content_hash for a io.Reader.\n// See https://www.dropbox.com/developers/reference/content-hash\nfunc ContentHash(r io.Reader) (string, error) {\n\tbuf := make([]byte, hashBlockSize)\n\tresultHash := sha256.New()\n\tn, err := r.Read(buf)\n\tif err != nil && err != io.EOF {\n\t\treturn \"\", err\n\t}\n\tif n > 0 {\n\t\tbufHash := sha256.Sum256(buf[:n])\n\t\tresultHash.Write(bufHash[:])\n\t}\n\tfor n == hashBlockSize && err == nil {\n\t\tn, err = r.Read(buf)\n\t\tif err != nil && err != io.EOF {\n\t\t\treturn \"\", err\n\t\t}\n\t\tif n > 0 {\n\t\t\tbufHash := sha256.Sum256(buf[:n])\n\t\t\tresultHash.Write(bufHash[:])\n\t\t}\n\t}\n\treturn fmt.Sprintf(\"%x\", resultHash.Sum(nil)), nil\n}\n\n// FileContentHash returns the Dropbox content_hash for a local file.\n// See https://www.dropbox.com/developers/reference/content-hash\nfunc FileContentHash(filename string) (string, error) {\n\tf, err := os.Open(filename)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer f.Close()\n\treturn ContentHash(f)\n}\n"
  },
  {
    "path": "files_test.go",
    "content": "package dropbox\n\nimport (\n\t\"bytes\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/ungerik/go-dry\"\n)\n\nfunc TestFiles_Upload(t *testing.T) {\n\tc := client()\n\n\tfile, err := os.Open(\"Readme.md\")\n\tassert.NoError(t, err, \"error opening file\")\n\tdefer file.Close()\n\n\tout, err := c.Files.Upload(&UploadInput{\n\t\tMute:   true,\n\t\tMode:   WriteModeOverwrite,\n\t\tPath:   \"/Readme.md\",\n\t\tReader: file,\n\t})\n\n\tassert.NoError(t, err, \"error uploading file\")\n\tassert.Equal(t, \"/readme.md\", out.PathLower)\n}\n\nfunc TestFiles_Download(t *testing.T) {\n\tc := client()\n\n\tout, err := c.Files.Download(&DownloadInput{\"/Readme.md\"})\n\n\tassert.NoError(t, err, \"error downloading\")\n\tdefer out.Body.Close()\n\n\tfi, err := os.Lstat(\"Readme.md\")\n\tassert.NoError(t, err, \"error getting local file info\")\n\tassert.Equal(t, fi.Size(), out.Length, \"Readme.md length mismatch\")\n\n\tremote, err := ioutil.ReadAll(out.Body)\n\tassert.NoError(t, err, \"error reading remote\")\n\n\tlocal, err := ioutil.ReadFile(\"Readme.md\")\n\tassert.NoError(t, err, \"error reading local\")\n\n\tassert.Equal(t, local, remote, \"Readme.md mismatch\")\n}\n\nfunc TestFiles_GetMetadata(t *testing.T) {\n\tc := client()\n\n\tout, err := c.Files.GetMetadata(&GetMetadataInput{\n\t\tPath: \"/Readme.md\",\n\t})\n\tassert.NoError(t, err)\n\tassert.Equal(t, \"file\", out.Tag)\n}\n\nfunc TestFiles_ListFolder(t *testing.T) {\n\tt.Parallel()\n\tc := client()\n\n\tout, err := c.Files.ListFolder(&ListFolderInput{\n\t\tPath: \"/list\",\n\t})\n\n\tassert.NoError(t, err)\n\tassert.Equal(t, 2000, len(out.Entries))\n\tassert.True(t, out.HasMore)\n}\n\nfunc TestFiles_ListFolder_root(t *testing.T) {\n\tt.Parallel()\n\tc := client()\n\n\t_, err := c.Files.ListFolder(&ListFolderInput{\n\t\tPath: \"/\",\n\t})\n\n\tassert.NoError(t, err)\n}\n\nfunc TestFiles_Search(t *testing.T) {\n\tc := client()\n\n\tout, err := c.Files.Search(&SearchInput{\n\t\tPath:  \"/\",\n\t\tQuery: \"hello\",\n\t})\n\n\tassert.NoError(t, err)\n\tassert.Equal(t, 2, len(out.Matches))\n}\n\nfunc TestFiles_Delete(t *testing.T) {\n\tc := client()\n\n\tout, err := c.Files.Delete(&DeleteInput{\n\t\tPath: \"/Readme.md\",\n\t})\n\n\tassert.NoError(t, err)\n\tassert.Equal(t, \"/readme.md\", out.PathLower)\n}\n\n// A gray, 64 by 64 px PNG\nvar grayPng = []byte{\n\t0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49,\n\t0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x08, 0x02,\n\t0x00, 0x00, 0x00, 0x25, 0x0b, 0xe6, 0x89, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41,\n\t0x4d, 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00, 0x00, 0x00,\n\t0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xae, 0xce, 0x1c, 0xe9, 0x00, 0x00, 0x00,\n\t0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0e, 0xc3, 0x00, 0x00, 0x0e, 0xc3,\n\t0x01, 0xc7, 0x6f, 0xa8, 0x64, 0x00, 0x00, 0x00, 0x18, 0x74, 0x45, 0x58, 0x74,\n\t0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x00, 0x70, 0x61, 0x69, 0x6e,\n\t0x74, 0x2e, 0x6e, 0x65, 0x74, 0x20, 0x34, 0x2e, 0x30, 0x2e, 0x36, 0xfc, 0x8c,\n\t0x63, 0xdf, 0x00, 0x00, 0x00, 0x4c, 0x49, 0x44, 0x41, 0x54, 0x68, 0xde, 0xed,\n\t0xcf, 0x31, 0x0d, 0x00, 0x00, 0x08, 0x03, 0x30, 0xe6, 0x7e, 0xb2, 0x91, 0xc0,\n\t0x4d, 0xd2, 0x3a, 0x68, 0xda, 0xce, 0x67, 0x11, 0x10, 0x10, 0x10, 0x10, 0x10,\n\t0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,\n\t0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,\n\t0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,\n\t0x10, 0x10, 0xb8, 0x2c, 0x4a, 0x27, 0x66, 0x41, 0xb9, 0xd3, 0xef, 0xa3, 0x00,\n\t0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,\n}\n\nfunc TestFiles_GetThumbnail(t *testing.T) {\n\tc := client()\n\t// REVIEW(bg): This feels a bit sloppy...\n\t{\n\t\tbuf := bytes.NewBuffer(grayPng)\n\t\t_, err := c.Files.Upload(&UploadInput{\n\t\t\tMute:   true,\n\t\t\tMode:   WriteModeOverwrite,\n\t\t\tPath:   \"/gray.png\",\n\t\t\tReader: buf,\n\t\t})\n\t\tassert.NoError(t, err, \"error uploading file\")\n\t}\n\tout, err := c.Files.GetThumbnail(&GetThumbnailInput{\"/gray.png\", GetThumbnailFormatJPEG, GetThumbnailSizeW32H32})\n\tassert.NoError(t, err)\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer out.Body.Close()\n\n\tassert.NotEmpty(t, out.Length, \"length should not be 0\")\n\n\tbuf := make([]byte, 11)\n\t_, err = out.Body.Read(buf)\n\tassert.NoError(t, err)\n\tassert.Equal(t, []byte{\n\t\t0xff, 0xd8, // JPEG SOI marker\n\t\t0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, // JFIF tag\n\t}, buf, \"should have jpeg header\")\n}\n\nfunc TestFiles_GetPreview(t *testing.T) {\n\tc := client()\n\n\tout, err := c.Files.GetPreview(&GetPreviewInput{\"/sample.ppt\"})\n\tdefer out.Body.Close()\n\n\tassert.NoError(t, err)\n\n\tassert.NotEmpty(t, out.Length, \"length should not be 0\")\n\n\tbuf := make([]byte, 4)\n\t_, err = out.Body.Read(buf)\n\tassert.NoError(t, err)\n\tassert.Equal(t, []byte{0x25, 0x50, 0x44, 0x46}, buf, \"should have pdf magic number\")\n}\n\nfunc TestFiles_ListRevisions(t *testing.T) {\n\tc := client()\n\n\tout, err := c.Files.ListRevisions(&ListRevisionsInput{Path: \"/sample.ppt\"})\n\n\tassert.NoError(t, err)\n\tassert.NotEmpty(t, out.Entries)\n\tassert.False(t, out.IsDeleted)\n}\n\nfunc TestFiles_ContentHash(t *testing.T) {\n\tdata, err := dry.FileGetBytes(\"https://www.dropbox.com/static/images/developers/milky-way-nasa.jpg\", time.Second*5)\n\tassert.NoError(t, err)\n\n\thash, err := ContentHash(bytes.NewBuffer(data))\n\tassert.NoError(t, err)\n\n\tassert.Equal(t, \"485291fa0ee50c016982abbfa943957bcd231aae0492ccbaa22c58e3997b35e0\", hash)\n}\n"
  },
  {
    "path": "sharing.go",
    "content": "package dropbox\n\nimport (\n\t\"encoding/json\"\n\t\"time\"\n)\n\n// Sharing client.\ntype Sharing struct {\n\t*Client\n}\n\n// NewSharing client.\nfunc NewSharing(config *Config) *Sharing {\n\treturn &Sharing{\n\t\tClient: &Client{\n\t\t\tConfig: config,\n\t\t},\n\t}\n}\n\n// CreateSharedLinkInput request input.\ntype CreateSharedLinkInput struct {\n\tPath string `json:\"path\"`\n}\n\n// CreateSharedLinkOutput request output.\ntype CreateSharedLinkOutput struct {\n\tURL             string `json:\"url\"`\n\tPath            string `json:\"path\"`\n\tVisibilityModel struct {\n\t\tTag VisibilityType `json:\".tag\"`\n\t} `json:\"visibility\"`\n\tExpires time.Time `json:\"expires,omitempty\"`\n}\n\n// VisibilityType determines who can access the link.\ntype VisibilityType string\n\n// Visibility types supported.\nconst (\n\tPublic           VisibilityType = \"public\"\n\tTeamOnly                        = \"team_only\"\n\tPassword                        = \"password\"\n\tTeamAndPassword                 = \"team_and_password\"\n\tSharedFolderOnly                = \"shared_folder_only\"\n)\n\n// CreateSharedLink returns a shared link.\nfunc (c *Sharing) CreateSharedLink(in *CreateSharedLinkInput) (out *CreateSharedLinkOutput, err error) {\n\tbody, err := c.call(\"/sharing/create_shared_link_with_settings\", in)\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer body.Close()\n\n\terr = json.NewDecoder(body).Decode(&out)\n\treturn\n}\n\n// ListShareLinksInput request input.\ntype ListShareLinksInput struct {\n\tPath string `json:\"path\"`\n}\n\n// SharedLinkOutput request output.\ntype SharedLinkOutput struct {\n\tURL             string `json:\"url\"`\n\tPath            string `json:\"path\"`\n\tVisibilityModel struct {\n\t\tTag VisibilityType `json:\".tag\"`\n\t} `json:\"visibility\"`\n\tExpires time.Time `json:\"expires,omitempty\"`\n}\n\n// ListShareLinksOutput request output.\ntype ListShareLinksOutput struct {\n\tLinks []SharedLinkOutput `json:\"links\"`\n}\n\n// ListSharedLinks gets shared links of input.\nfunc (c *Sharing) ListSharedLinks(in *ListShareLinksInput) (out *ListShareLinksOutput, err error) {\n\tendpoint := \"/sharing/list_shared_links\"\n\tbody, err := c.call(endpoint, in)\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer body.Close()\n\n\terr = json.NewDecoder(body).Decode(&out)\n\treturn\n}\n\n// ListSharedFolderInput request input.\ntype ListSharedFolderInput struct {\n\tLimit   uint64         `json:\"limit\"`\n\tActions []FolderAction `json:\"actions,omitempty\"`\n}\n\n// FolderAction defines actions that may be taken on shared folders.\ntype FolderAction struct {\n\tChangeOptions string\n}\n\n// ListSharedFolderOutput lists metadata about shared folders with a cursor to retrieve the next page.\ntype ListSharedFolderOutput struct {\n\tEntries []SharedFolderMetadata `json:\"entries\"`\n\tCursor  string                 `json:\"cursor\"`\n}\n\n// ListSharedFolders returns the list of all shared folders the current user has access to.\nfunc (c *Sharing) ListSharedFolders(in *ListSharedFolderInput) (out *ListSharedFolderOutput, err error) {\n\tbody, err := c.call(\"/sharing/list_folders\", in)\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer body.Close()\n\n\terr = json.NewDecoder(body).Decode(&out)\n\treturn\n}\n\n// ListSharedFolderContinueInput request input.\ntype ListSharedFolderContinueInput struct {\n\tCursor string `json:\"cursor\"`\n}\n\n// ListSharedFoldersContinue returns the list of all shared folders the current user has access to.\nfunc (c *Sharing) ListSharedFoldersContinue(in *ListSharedFolderContinueInput) (out *ListSharedFolderOutput, err error) {\n\tbody, err := c.call(\"/sharing/list_folders/continue\", in)\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer body.Close()\n\n\terr = json.NewDecoder(body).Decode(&out)\n\treturn\n}\n\n// SharedFolderMetadata includes basic information about the shared folder.\ntype SharedFolderMetadata struct {\n\tAccessType struct {\n\t\tTag AccessType `json:\".tag\"`\n\t} `json:\"access_type\"`\n\tIsTeamFolder   bool         `json:\"is_team_folder\"`\n\tPolicy         FolderPolicy `json:\"policy\"`\n\tName           string       `json:\"name\"`\n\tSharedFolderID string       `json:\"shared_folder_id\"`\n\tTimeInvited    time.Time    `json:\"time_invited\"`\n\tOwnerTeam      struct {\n\t\tID   string `json:\"id\"`\n\t\tName string `json:\"name\"`\n\t} `json:\"owner_team\"`\n\tParentSharedFolderID string   `json:\"parent_shared_folder_id\"`\n\tPathLower            string   `json:\"path_lower\"`\n\tPermissions          []string `json:\"permissions\"`\n}\n\n// FolderPolicy enumerates the policies governing this shared folder.\ntype FolderPolicy struct {\n\tACLUpdatePolicy struct {\n\t\tTag ACLUpdatePolicy `json:\".tag\"`\n\t} `json:\"acl_update_policy\"`\n\tSharedLinkPolicy struct {\n\t\tTag SharedLinkPolicy `json:\".tag\"`\n\t} `json:\"shared_link_policy\"`\n\tMemberPolicy struct {\n\t\tTag MemberPolicy `json:\".tag\"`\n\t} `json:\"member_policy\"`\n\tResolvedMemberPolicy struct {\n\t\tTag MemberPolicy `json:\".tag\"`\n\t} `json:\"resolved_member_policy\"`\n}\n\n// AccessType determines the level of access to a shared folder.\ntype AccessType string\n\n// Access types supported.\nconst (\n\tOwner           AccessType = \"owner\"\n\tEditor                     = \"editor\"\n\tViewer                     = \"viewer\"\n\tViewerNoComment            = \"viewer_no_comment\"\n)\n\n// ACLUpdatePolicy determines who can add and remove members from this shared folder.\ntype ACLUpdatePolicy string\n\n// ACLUpdatePolicy types supported.\nconst (\n\tACLUpdatePolicyOwner   ACLUpdatePolicy = \"owner\"\n\tACLUpdatePolicyEditors                 = \"editors\"\n)\n\n// SharedLinkPolicy governs who can view shared links.\ntype SharedLinkPolicy string\n\n// SharedLinkPolicy types supported.\nconst (\n\tSharedLinkPolicyAnyone  SharedLinkPolicy = \"anyone\"\n\tSharedLinkPolicyMembers                  = \"members\"\n)\n\n// MemberPolicy determines who can be a member of this shared folder, as set on the folder itself.\ntype MemberPolicy string\n\n// MemberPolicy types supported.\nconst (\n\tMemberPolicyTeam   MemberPolicy = \"team\"\n\tMemberPolicyAnyone              = \"anyone\"\n)\n"
  },
  {
    "path": "sharing_test.go",
    "content": "package dropbox\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestSharing_CreateSharedLink(t *testing.T) {\n\tc := client()\n\tout, err := c.Sharing.CreateSharedLink(&CreateSharedLinkInput{\n\t\tPath: \"/hello.txt\",\n\t})\n\n\tassert.NoError(t, err, \"error sharing file\")\n\tassert.Equal(t, \"/hello.txt\", out.Path)\n}\n\nfunc TestSharing_ListSharedFolder(t *testing.T) {\n\tc := client()\n\tout, err := c.Sharing.ListSharedFolders(&ListSharedFolderInput{\n\t\tLimit: 1,\n\t})\n\n\tassert.NoError(t, err, \"listing shared folders\")\n\tassert.NotEmpty(t, out.Entries, \"output should be non-empty\")\n\n\tfor out.Cursor != \"\" {\n\t\tout, err = c.Sharing.ListSharedFoldersContinue(&ListSharedFolderContinueInput{\n\t\t\tCursor: out.Cursor,\n\t\t})\n\n\t\tassert.NoError(t, err, \"listing shared folders\")\n\t\tassert.NotEmpty(t, out.Entries, \"output should be non-empty\")\n\t}\n}\n"
  },
  {
    "path": "users.go",
    "content": "package dropbox\n\nimport (\n\t\"encoding/json\"\n)\n\n// Users client for user accounts.\ntype Users struct {\n\t*Client\n}\n\n// NewUsers client.\nfunc NewUsers(config *Config) *Users {\n\treturn &Users{\n\t\tClient: &Client{\n\t\t\tConfig: config,\n\t\t},\n\t}\n}\n\n// GetAccountInput request input.\ntype GetAccountInput struct {\n\tAccountID string `json:\"account_id\"`\n}\n\n// GetAccountOutput request output.\ntype GetAccountOutput struct {\n\tAccountID string `json:\"account_id\"`\n\tName      struct {\n\t\tGivenName    string `json:\"given_name\"`\n\t\tSurname      string `json:\"surname\"`\n\t\tFamiliarName string `json:\"familiar_name\"`\n\t\tDisplayName  string `json:\"display_name\"`\n\t} `json:\"name\"`\n}\n\n// GetAccount returns information about a user's account.\nfunc (c *Users) GetAccount(in *GetAccountInput) (out *GetAccountOutput, err error) {\n\tbody, err := c.call(\"/users/get_account\", in)\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer body.Close()\n\n\terr = json.NewDecoder(body).Decode(&out)\n\treturn\n}\n\n// GetCurrentAccountOutput request output.\ntype GetCurrentAccountOutput struct {\n\tAccountID string `json:\"account_id\"`\n\tName      struct {\n\t\tGivenName    string `json:\"given_name\"`\n\t\tSurname      string `json:\"surname\"`\n\t\tFamiliarName string `json:\"familiar_name\"`\n\t\tDisplayName  string `json:\"display_name\"`\n\t} `json:\"name\"`\n\tEmail        string `json:\"email\"`\n\tLocale       string `json:\"locale\"`\n\tReferralLink string `json:\"referral_link\"`\n\tIsPaired     bool   `json:\"is_paired\"`\n\tAccountType  struct {\n\t\tTag string `json:\".tag\"`\n\t} `json:\"account_type\"`\n\tCountry string `json:\"country\"`\n}\n\n// GetCurrentAccount returns information about the current user's account.\nfunc (c *Users) GetCurrentAccount() (out *GetCurrentAccountOutput, err error) {\n\tbody, err := c.call(\"/users/get_current_account\", nil)\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer body.Close()\n\n\terr = json.NewDecoder(body).Decode(&out)\n\treturn\n}\n\n// GetSpaceUsageOutput request output.\ntype GetSpaceUsageOutput struct {\n\tUsed       uint64 `json:\"used\"`\n\tAllocation struct {\n\t\tUsed      uint64 `json:\"used\"`\n\t\tAllocated uint64 `json:\"allocated\"`\n\t} `json:\"allocation\"`\n}\n\n// GetSpaceUsage returns space usage information for the current user's account.\nfunc (c *Users) GetSpaceUsage() (out *GetSpaceUsageOutput, err error) {\n\tbody, err := c.call(\"/users/get_space_usage\", nil)\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer body.Close()\n\n\terr = json.NewDecoder(body).Decode(&out)\n\treturn\n}\n"
  },
  {
    "path": "users_test.go",
    "content": "package dropbox\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestUsers_GetCurrentAccount(t *testing.T) {\n\tc := client()\n\t_, err := c.Users.GetCurrentAccount()\n\tassert.NoError(t, err)\n}\n"
  }
]