Archived
1
0
This commit is contained in:
Arnaud Ysmal 2014-02-24 19:22:35 +01:00
parent cb92bcb643
commit 47f279a224
2 changed files with 176 additions and 164 deletions

View File

@ -33,7 +33,7 @@ import (
"os" "os"
) )
// Generate a key by reading length bytes from /dev/random // GenerateKey generates a key by reading length bytes from /dev/random
func GenerateKey(length int) ([]byte, error) { func GenerateKey(length int) ([]byte, error) {
var err error var err error
var fd io.Reader var fd io.Reader
@ -74,13 +74,13 @@ func newDecrypter(key []byte, in io.Reader, size int, newCipher func(key []byte)
return rd, nil return rd, nil
} }
// Create and return a new io.ReadCloser to decrypt the given io.Reader containing size bytes with the given AES key. // NewAESDecrypterReader creates and returns a new io.ReadCloser to decrypt the given io.Reader containing size bytes with the given AES key.
// The AES key should be either 16, 24, or 32 bytes to select AES-128, AES-192, or AES-256. // The AES key should be either 16, 24, or 32 bytes to select AES-128, AES-192, or AES-256.
func NewAESDecrypterReader(key []byte, input io.Reader, size int) (io.ReadCloser, error) { func NewAESDecrypterReader(key []byte, input io.Reader, size int) (io.ReadCloser, error) {
return newDecrypter(key, input, size, aes.NewCipher) return newDecrypter(key, input, size, aes.NewCipher)
} }
// Create and return a new io.ReadCloser to encrypt the given io.Reader containing size bytes with the given AES key. // NewAESCrypterReader creates and returns a new io.ReadCloser to encrypt the given io.Reader containing size bytes with the given AES key.
// The AES key should be either 16, 24, or 32 bytes to select AES-128, AES-192, or AES-256. // The AES key should be either 16, 24, or 32 bytes to select AES-128, AES-192, or AES-256.
func NewAESCrypterReader(key []byte, input io.Reader, size int) (io.ReadCloser, int, error) { func NewAESCrypterReader(key []byte, input io.Reader, size int) (io.ReadCloser, int, error) {
return newCrypter(key, input, size, aes.NewCipher) return newCrypter(key, input, size, aes.NewCipher)
@ -162,8 +162,8 @@ func decrypt(block cipher.Block, in io.Reader, size int, out io.WriteCloser) err
return err return err
} }
// Upload and encrypt size bytes from the input reader to the dst path on Dropbox. // FilesPutAES uploads and encrypts size bytes from the input reader to the dst path on Dropbox.
func (self *Dropbox) FilesPutAES(key []byte, input io.ReadCloser, size int64, dst string, overwrite bool, parentRev string) (*Entry, error) { func (db *Dropbox) FilesPutAES(key []byte, input io.ReadCloser, size int64, dst string, overwrite bool, parentRev string) (*Entry, error) {
var encreader io.ReadCloser var encreader io.ReadCloser
var outsize int var outsize int
var err error var err error
@ -171,11 +171,11 @@ func (self *Dropbox) FilesPutAES(key []byte, input io.ReadCloser, size int64, ds
if encreader, outsize, err = NewAESCrypterReader(key, input, int(size)); err != nil { if encreader, outsize, err = NewAESCrypterReader(key, input, int(size)); err != nil {
return nil, err return nil, err
} }
return self.FilesPut(encreader, int64(outsize), dst, overwrite, parentRev) return db.FilesPut(encreader, int64(outsize), dst, overwrite, parentRev)
} }
// Upload and encrypt the file located in the src path on the local disk to the dst path on Dropbox. // UploadFileAES uploads and encrypts the file located in the src path on the local disk to the dst path on Dropbox.
func (self *Dropbox) UploadFileAES(key []byte, src, dst string, overwrite bool, parentRev string) (*Entry, error) { func (db *Dropbox) UploadFileAES(key []byte, src, dst string, overwrite bool, parentRev string) (*Entry, error) {
var err error var err error
var fd *os.File var fd *os.File
var fsize int64 var fsize int64
@ -190,23 +190,23 @@ func (self *Dropbox) UploadFileAES(key []byte, src, dst string, overwrite bool,
} else { } else {
return nil, err return nil, err
} }
return self.FilesPutAES(key, fd, fsize, dst, overwrite, parentRev) return db.FilesPutAES(key, fd, fsize, dst, overwrite, parentRev)
} }
// Download and decrypt the file located in the src path on Dropbox and return a io.ReadCloser. // DownloadAES downloads and decrypts the file located in the src path on Dropbox and return a io.ReadCloser.
func (self *Dropbox) DownloadAES(key []byte, src, rev string, offset int) (io.ReadCloser, error) { func (db *Dropbox) DownloadAES(key []byte, src, rev string, offset int) (io.ReadCloser, error) {
var in io.ReadCloser var in io.ReadCloser
var size int64 var size int64
var err error var err error
if in, size, err = self.Download(src, rev, offset); err != nil { if in, size, err = db.Download(src, rev, offset); err != nil {
return nil, err return nil, err
} }
return NewAESDecrypterReader(key, in, int(size)) return NewAESDecrypterReader(key, in, int(size))
} }
// Download and decrypt the file located in the src path on Dropbox to the dst file on the local disk. // DownloadToFileAES downloads and decrypts the file located in the src path on Dropbox to the dst file on the local disk.
func (self *Dropbox) DownloadToFileAES(key []byte, src, dst, rev string) error { func (db *Dropbox) DownloadToFileAES(key []byte, src, dst, rev string) error {
var input io.ReadCloser var input io.ReadCloser
var fd *os.File var fd *os.File
var err error var err error
@ -216,7 +216,7 @@ func (self *Dropbox) DownloadToFileAES(key []byte, src, dst, rev string) error {
} }
defer fd.Close() defer fd.Close()
if input, err = self.DownloadAES(key, src, rev, 0); err != nil { if input, err = db.DownloadAES(key, src, rev, 0); err != nil {
os.Remove(dst) os.Remove(dst)
return err return err
} }

View File

@ -42,13 +42,14 @@ import (
"code.google.com/p/stacktic-goauth2/oauth" "code.google.com/p/stacktic-goauth2/oauth"
) )
var ErrNotAuth = errors.New("Authentication required") // ErrNotAuth is the error returned when the OAuth token is not provided
var ErrNotAuth = errors.New("authentication required")
// Information about the user account. // Account represents information about the user account.
type Account struct { type Account struct {
ReferralLink string `json:"referral_link"` // URL for referral. ReferralLink string `json:"referral_link"` // URL for referral.
DisplayName string `json:"display_name"` // User name. DisplayName string `json:"display_name"` // User name.
Uid int `json:"uid"` // User account ID. UID int `json:"uid"` // User account ID.
Country string `json:"country"` // Country ISO code. Country string `json:"country"` // Country ISO code.
QuotaInfo struct { QuotaInfo struct {
Shared int64 `json:"shared"` // Quota for shared files. Shared int64 `json:"shared"` // Quota for shared files.
@ -57,13 +58,13 @@ type Account struct {
} `json:"quota_info"` } `json:"quota_info"`
} }
// Reply of copy_ref. // CopyRef represents thr reply of CopyRef.
type CopyRef struct { type CopyRef struct {
CopyRef string `json:"copy_ref"` // Reference to use on fileops/copy. CopyRef string `json:"copy_ref"` // Reference to use on fileops/copy.
Expires string `json:"expires"` // Expiration date. Expires string `json:"expires"` // Expiration date.
} }
// Reply of delta. // DeltaPage represents the reply of delta.
type DeltaPage struct { type DeltaPage struct {
Reset bool // if true the local state must be cleared. Reset bool // if true the local state must be cleared.
HasMore bool // if true an other call to delta should be made. HasMore bool // if true an other call to delta should be made.
@ -71,21 +72,21 @@ type DeltaPage struct {
Entries []DeltaEntry // List of changed entries. Entries []DeltaEntry // List of changed entries.
} }
// Changed entry. // DeltaEntry represents the the list of changes for a given path.
type DeltaEntry struct { type DeltaEntry struct {
Path string // Path of this entry in lowercase. Path string // Path of this entry in lowercase.
Entry *Entry // nil when this entry does not exists. Entry *Entry // nil when this entry does not exists.
} }
// Reply of longpoll_delta. // DeltaPoll represents the reply of longpoll_delta.
type DeltaPoll struct { type DeltaPoll struct {
Changes bool `json:"changes"` // true if the polled path has changed. Changes bool `json:"changes"` // true if the polled path has changed.
Backoff int `json:"backoff"` // time in second before calling poll again. Backoff int `json:"backoff"` // time in second before calling poll again.
} }
// Reply of chunked_upload. // ChunkUploadResponse represents the reply of chunked_upload.
type ChunkUploadResponse struct { type ChunkUploadResponse struct {
UploadId string `json:"upload_id"` // Unique ID of this upload. UploadID string `json:"upload_id"` // Unique ID of this upload.
Offset int `json:"offset"` // Size in bytes of already sent data. Offset int `json:"offset"` // Size in bytes of already sent data.
Expires string `json:"expires"` // Expiration time of this upload. Expires string `json:"expires"` // Expiration time of this upload.
} }
@ -94,25 +95,36 @@ type ChunkUploadResponse struct {
// Format may be: // Format may be:
// {"error": "reason"} // {"error": "reason"}
// {"error": {"param": "reason"}} // {"error": {"param": "reason"}}
type RequestError struct { type requestError struct {
Error interface{} `json:"error"` // Description of this error. Error interface{} `json:"error"` // Description of this error.
} }
const ( const (
PollMinTimeout = 30 // Default number of entries returned by metadata. // PollMinTimeout is the minimum timeout for longpoll
PollMaxTimeout = 480 // Default number of entries returned by metadata. PollMinTimeout = 30
DefaultChunkSize = 4 * 1024 * 1024 // Maximum size of a file sendable using files_put. // PollMaxTimeout is the maximum timeout for longpoll
MaxPutFileSize = 150 * 1024 * 1024 // Maximum size of a file sendable using files_put. PollMaxTimeout = 480
MetadataLimitMax = 25000 // Maximum number of entries returned by metadata. // DefaultChunkSize is the maximum size of a file sendable using files_put.
MetadataLimitDefault = 10000 // Default number of entries returned by metadata. DefaultChunkSize = 4 * 1024 * 1024
RevisionsLimitMax = 1000 // Maximum number of revisions returned by revisions. // MaxPutFileSize is the maximum size of a file sendable using files_put.
RevisionsLimitDefault = 10 // Default number of revisions returned by revisions. MaxPutFileSize = 150 * 1024 * 1024
SearchLimitMax = 1000 // Maximum number of entries returned by search. // MetadataLimitMax is the maximum number of entries returned by metadata.
SearchLimitDefault = 1000 // Default number of entries returned by search. MetadataLimitMax = 25000
DateFormat = time.RFC1123Z // Format to use when decoding a time. // MetadataLimitDefault is the default number of entries returned by metadata.
MetadataLimitDefault = 10000
// RevisionsLimitMax is the maximum number of revisions returned by revisions.
RevisionsLimitMax = 1000
// RevisionsLimitDefault is the default number of revisions returned by revisions.
RevisionsLimitDefault = 10
// SearchLimitMax is the maximum number of entries returned by search.
SearchLimitMax = 1000
// SearchLimitDefault is the default number of entries returned by search.
SearchLimitDefault = 1000
// DateFormat is the format to use when decoding a time.
DateFormat = time.RFC1123Z
) )
// A metadata entry that describes a file or folder. // Entry represents the metadata of a file or folder.
type Entry struct { type Entry struct {
Bytes int `json:"bytes"` // Size of the file in bytes. Bytes int `json:"bytes"` // Size of the file in bytes.
ClientMtime string `json:"client_mtime"` // Modification time set by the client when added. ClientMtime string `json:"client_mtime"` // Modification time set by the client when added.
@ -146,7 +158,7 @@ type Dropbox struct {
Session oauth.Transport // OAuth 2.0 session. Session oauth.Transport // OAuth 2.0 session.
} }
// Return a new Dropbox configured. // NewDropbox returns a new Dropbox configured.
func NewDropbox() *Dropbox { func NewDropbox() *Dropbox {
return &Dropbox{ return &Dropbox{
RootDirectory: "dropbox", // dropbox or sandbox. RootDirectory: "dropbox", // dropbox or sandbox.
@ -163,36 +175,36 @@ func NewDropbox() *Dropbox {
} }
} }
// Set the clientid (app_key), clientsecret (app_secret). // SetAppInfo sets the clientid (app_key) and clientsecret (app_secret).
// You have to register an application on https://www.dropbox.com/developers/apps. // You have to register an application on https://www.dropbox.com/developers/apps.
func (self *Dropbox) SetAppInfo(clientid, clientsecret string) { func (db *Dropbox) SetAppInfo(clientid, clientsecret string) {
self.Session.Config.ClientId = clientid db.Session.Config.ClientId = clientid
self.Session.Config.ClientSecret = clientsecret db.Session.Config.ClientSecret = clientsecret
} }
// Set access token to avoid calling Auth method. // SetAccessToken sets access token to avoid calling Auth method.
func (self *Dropbox) SetAccessToken(accesstoken string) { func (db *Dropbox) SetAccessToken(accesstoken string) {
self.Session.Token = &oauth.Token{AccessToken: accesstoken} db.Session.Token = &oauth.Token{AccessToken: accesstoken}
} }
// Get OAuth access token. // AccessToken returns the OAuth access token.
func (self *Dropbox) AccessToken() string { func (db *Dropbox) AccessToken() string {
return self.Session.Token.AccessToken return db.Session.Token.AccessToken
} }
// Display URL to authorize this application to connect to your account. // Auth displays the URL to authorize this application to connect to your account.
func (self *Dropbox) Auth() error { func (db *Dropbox) Auth() error {
var code string var code string
fmt.Printf("Please visit:\n%s\nEnter the code: ", fmt.Printf("Please visit:\n%s\nEnter the code: ",
self.Session.Config.AuthCodeURL("")) db.Session.Config.AuthCodeURL(""))
fmt.Scanln(&code) fmt.Scanln(&code)
_, err := self.Session.Exchange(code) _, err := db.Session.Exchange(code)
return err return err
} }
// End the chunked upload by giving a name to the UploadID. // CommitChunkedUpload ends the chunked upload by giving a name to the UploadID.
func (self *Dropbox) CommitChunkedUpload(uploadid, dst string, overwrite bool, parentRev string) (*Entry, error) { func (db *Dropbox) CommitChunkedUpload(uploadid, dst string, overwrite bool, parentRev string) (*Entry, error) {
var err error var err error
var rawurl string var rawurl string
var response *http.Response var response *http.Response
@ -205,16 +217,16 @@ func (self *Dropbox) CommitChunkedUpload(uploadid, dst string, overwrite bool, p
} }
params = &url.Values{ params = &url.Values{
"locale": {self.Locale}, "locale": {db.Locale},
"upload_id": {uploadid}, "upload_id": {uploadid},
"overwrite": {strconv.FormatBool(overwrite)}, "overwrite": {strconv.FormatBool(overwrite)},
} }
if len(parentRev) != 0 { if len(parentRev) != 0 {
params.Set("parent_rev", parentRev) params.Set("parent_rev", parentRev)
} }
rawurl = fmt.Sprintf("%s/commit_chunked_upload/%s/%s?%s", self.APIContentURL, self.RootDirectory, dst, params.Encode()) rawurl = fmt.Sprintf("%s/commit_chunked_upload/%s/%s?%s", db.APIContentURL, db.RootDirectory, dst, params.Encode())
if response, err = self.Session.Client().Post(rawurl, "", nil); err != nil { if response, err = db.Session.Client().Post(rawurl, "", nil); err != nil {
return nil, err return nil, err
} }
defer response.Body.Close() defer response.Body.Close()
@ -225,8 +237,8 @@ func (self *Dropbox) CommitChunkedUpload(uploadid, dst string, overwrite bool, p
return &rv, err return &rv, err
} }
// Send a chunk with a maximum size of chunksize, if there is no session a new one is created. // ChunkedUpload sends a chunk with a maximum size of chunksize, if there is no session a new one is created.
func (self *Dropbox) ChunkedUpload(session *ChunkUploadResponse, input io.ReadCloser, chunksize int) (*ChunkUploadResponse, error) { func (db *Dropbox) ChunkedUpload(session *ChunkUploadResponse, input io.ReadCloser, chunksize int) (*ChunkUploadResponse, error) {
var err error var err error
var rawurl string var rawurl string
var cur ChunkUploadResponse var cur ChunkUploadResponse
@ -241,13 +253,13 @@ func (self *Dropbox) ChunkedUpload(session *ChunkUploadResponse, input io.ReadCl
} }
if session != nil { if session != nil {
rawurl = fmt.Sprintf("%s/chunked_upload?upload_id=%s&offset=%d", self.APIContentURL, session.UploadId, session.Offset) rawurl = fmt.Sprintf("%s/chunked_upload?upload_id=%s&offset=%d", db.APIContentURL, session.UploadID, session.Offset)
} else { } else {
rawurl = fmt.Sprintf("%s/chunked_upload", self.APIContentURL) rawurl = fmt.Sprintf("%s/chunked_upload", db.APIContentURL)
} }
r = &io.LimitedReader{R: input, N: int64(chunksize)} r = &io.LimitedReader{R: input, N: int64(chunksize)}
if response, err = self.Session.Client().Post(rawurl, "application/octet-stream", r); err != nil { if response, err = db.Session.Client().Post(rawurl, "application/octet-stream", r); err != nil {
return nil, err return nil, err
} }
defer response.Body.Close() defer response.Body.Close()
@ -261,21 +273,21 @@ func (self *Dropbox) ChunkedUpload(session *ChunkUploadResponse, input io.ReadCl
return &cur, err return &cur, err
} }
// Upload data from the input reader to the dst path on Dropbox by sending chunks of chunksize. // UploadByChunk uploads data from the input reader to the dst path on Dropbox by sending chunks of chunksize.
func (self *Dropbox) UploadByChunk(input io.ReadCloser, chunksize int, dst string, overwrite bool, parentRev string) (*Entry, error) { func (db *Dropbox) UploadByChunk(input io.ReadCloser, chunksize int, dst string, overwrite bool, parentRev string) (*Entry, error) {
var err error var err error
var cur *ChunkUploadResponse var cur *ChunkUploadResponse
for err == nil { for err == nil {
if cur, err = self.ChunkedUpload(cur, input, chunksize); err != nil && err != io.EOF { if cur, err = db.ChunkedUpload(cur, input, chunksize); err != nil && err != io.EOF {
return nil, err return nil, err
} }
} }
return self.CommitChunkedUpload(cur.UploadId, dst, overwrite, parentRev) return db.CommitChunkedUpload(cur.UploadID, dst, overwrite, parentRev)
} }
// Upload size bytes from the input reader to the dst path on Dropbox. // FilesPut uploads size bytes from the input reader to the dst path on Dropbox.
func (self *Dropbox) FilesPut(input io.ReadCloser, size int64, dst string, overwrite bool, parentRev string) (*Entry, error) { func (db *Dropbox) FilesPut(input io.ReadCloser, size int64, dst string, overwrite bool, parentRev string) (*Entry, error) {
var err error var err error
var rawurl string var rawurl string
var rv Entry var rv Entry
@ -285,7 +297,7 @@ func (self *Dropbox) FilesPut(input io.ReadCloser, size int64, dst string, overw
var body []byte var body []byte
if size > MaxPutFileSize { if size > MaxPutFileSize {
return nil, fmt.Errorf("Could not upload files bigger than 150MB using this method, use UploadByChunk instead") return nil, fmt.Errorf("could not upload files bigger than 150MB using this method, use UploadByChunk instead")
} }
if dst[0] == '/' { if dst[0] == '/' {
dst = dst[1:] dst = dst[1:]
@ -295,13 +307,13 @@ func (self *Dropbox) FilesPut(input io.ReadCloser, size int64, dst string, overw
if len(parentRev) != 0 { if len(parentRev) != 0 {
params.Set("parent_rev", parentRev) params.Set("parent_rev", parentRev)
} }
rawurl = fmt.Sprintf("%s/files_put/%s/%s?%s", self.APIContentURL, self.RootDirectory, dst, params.Encode()) rawurl = fmt.Sprintf("%s/files_put/%s/%s?%s", db.APIContentURL, db.RootDirectory, dst, params.Encode())
if request, err = http.NewRequest("PUT", rawurl, input); err != nil { if request, err = http.NewRequest("PUT", rawurl, input); err != nil {
return nil, err return nil, err
} }
request.Header.Set("Content-Length", strconv.FormatInt(size, 10)) request.Header.Set("Content-Length", strconv.FormatInt(size, 10))
if response, err = self.Session.Client().Do(request); err != nil { if response, err = db.Session.Client().Do(request); err != nil {
return nil, err return nil, err
} }
defer response.Body.Close() defer response.Body.Close()
@ -312,8 +324,8 @@ func (self *Dropbox) FilesPut(input io.ReadCloser, size int64, dst string, overw
return &rv, err return &rv, err
} }
// Upload the file located in the src path on the local disk to the dst path on Dropbox. // UploadFile uploads the file located in the src path on the local disk to the dst path on Dropbox.
func (self *Dropbox) UploadFile(src, dst string, overwrite bool, parentRev string) (*Entry, error) { func (db *Dropbox) UploadFile(src, dst string, overwrite bool, parentRev string) (*Entry, error) {
var err error var err error
var fd *os.File var fd *os.File
var fsize int64 var fsize int64
@ -328,11 +340,11 @@ func (self *Dropbox) UploadFile(src, dst string, overwrite bool, parentRev strin
} else { } else {
return nil, err return nil, err
} }
return self.FilesPut(fd, fsize, dst, overwrite, parentRev) return db.FilesPut(fd, fsize, dst, overwrite, parentRev)
} }
// Get a thumbnail for an image. // Thumbnails gets a thumbnail for an image.
func (self *Dropbox) Thumbnails(src, format, size string) (io.ReadCloser, int64, *Entry, error) { func (db *Dropbox) Thumbnails(src, format, size string) (io.ReadCloser, int64, *Entry, error) {
var response *http.Response var response *http.Response
var rawurl string var rawurl string
var err error var err error
@ -344,7 +356,7 @@ func (self *Dropbox) Thumbnails(src, format, size string) (io.ReadCloser, int64,
case "jpeg", "png": case "jpeg", "png":
break break
default: default:
return nil, 0, nil, fmt.Errorf("Unsupported format '%s' must be jpeg or png", format) return nil, 0, nil, fmt.Errorf("unsupported format '%s' must be jpeg or png", format)
} }
switch size { switch size {
case "": case "":
@ -352,14 +364,14 @@ func (self *Dropbox) Thumbnails(src, format, size string) (io.ReadCloser, int64,
case "xs", "s", "m", "l", "xl": case "xs", "s", "m", "l", "xl":
break break
default: default:
return nil, 0, nil, fmt.Errorf("Unsupported size '%s' must be xs, s, m, l or xl", size) return nil, 0, nil, fmt.Errorf("unsupported size '%s' must be xs, s, m, l or xl", size)
} }
if src[0] == '/' { if src[0] == '/' {
src = src[1:] src = src[1:]
} }
rawurl = fmt.Sprintf("%s/thumbnails/%s/%s?format=%s&size=%s", self.APIContentURL, self.RootDirectory, src, format, size) rawurl = fmt.Sprintf("%s/thumbnails/%s/%s?format=%s&size=%s", db.APIContentURL, db.RootDirectory, src, format, size)
if response, err = self.Session.Client().Get(rawurl); err != nil { if response, err = db.Session.Client().Get(rawurl); err != nil {
return nil, 0, nil, err return nil, 0, nil, err
} }
switch response.StatusCode { switch response.StatusCode {
@ -368,14 +380,14 @@ func (self *Dropbox) Thumbnails(src, format, size string) (io.ReadCloser, int64,
return nil, 0, nil, os.ErrNotExist return nil, 0, nil, os.ErrNotExist
case http.StatusUnsupportedMediaType: case http.StatusUnsupportedMediaType:
response.Body.Close() response.Body.Close()
return nil, 0, nil, fmt.Errorf("The image located at '%s' cannot be converted to a thumbnail", src) return nil, 0, nil, fmt.Errorf("the image located at '%s' cannot be converted to a thumbnail", src)
} }
json.Unmarshal([]byte(response.Header.Get("x-dropbox-metadata")), &entry) json.Unmarshal([]byte(response.Header.Get("x-dropbox-metadata")), &entry)
return response.Body, response.ContentLength, &entry, err return response.Body, response.ContentLength, &entry, err
} }
// Download the file located in the src path on the Dropbox to the dst file on the local disk. // ThumbnailsToFile downloads the file located in the src path on the Dropbox to the dst file on the local disk.
func (self *Dropbox) ThumbnailsToFile(src, dst, format, size string) (*Entry, error) { func (db *Dropbox) ThumbnailsToFile(src, dst, format, size string) (*Entry, error) {
var input io.ReadCloser var input io.ReadCloser
var fd *os.File var fd *os.File
var err error var err error
@ -386,7 +398,7 @@ func (self *Dropbox) ThumbnailsToFile(src, dst, format, size string) (*Entry, er
} }
defer fd.Close() defer fd.Close()
if input, _, entry, err = self.Thumbnails(src, format, size); err != nil { if input, _, entry, err = db.Thumbnails(src, format, size); err != nil {
os.Remove(dst) os.Remove(dst)
return nil, err return nil, err
} }
@ -397,10 +409,10 @@ func (self *Dropbox) ThumbnailsToFile(src, dst, format, size string) (*Entry, er
return entry, err return entry, err
} }
// Request the file located at src, the specific revision may be given. // Download requests the file located at src, the specific revision may be given.
// offset is used in case the download was interrupted. // offset is used in case the download was interrupted.
// A io.ReadCloser to get the file ans its size is returned. // A io.ReadCloser to get the file ans its size is returned.
func (self *Dropbox) Download(src, rev string, offset int) (io.ReadCloser, int64, error) { func (db *Dropbox) Download(src, rev string, offset int) (io.ReadCloser, int64, error) {
var request *http.Request var request *http.Request
var response *http.Response var response *http.Response
var rawurl string var rawurl string
@ -410,7 +422,7 @@ func (self *Dropbox) Download(src, rev string, offset int) (io.ReadCloser, int64
src = src[1:] src = src[1:]
} }
rawurl = fmt.Sprintf("%s/files/%s/%s", self.APIContentURL, self.RootDirectory, src) rawurl = fmt.Sprintf("%s/files/%s/%s", db.APIContentURL, db.RootDirectory, src)
if len(rev) != 0 { if len(rev) != 0 {
rawurl += fmt.Sprintf("?rev=%s", rev) rawurl += fmt.Sprintf("?rev=%s", rev)
} }
@ -421,7 +433,7 @@ func (self *Dropbox) Download(src, rev string, offset int) (io.ReadCloser, int64
request.Header.Set("Range", fmt.Sprintf("bytes=%d-", offset)) request.Header.Set("Range", fmt.Sprintf("bytes=%d-", offset))
} }
if response, err = self.Session.Client().Do(request); err != nil { if response, err = db.Session.Client().Do(request); err != nil {
return nil, 0, err return nil, 0, err
} }
if response.StatusCode == http.StatusNotFound { if response.StatusCode == http.StatusNotFound {
@ -431,9 +443,10 @@ func (self *Dropbox) Download(src, rev string, offset int) (io.ReadCloser, int64
return response.Body, response.ContentLength, err return response.Body, response.ContentLength, err
} }
// Resume the download of the file located in the src path on the Dropbox to the dst file on the local disk. // DownloadToFileResume resumes the download of the file located in the src path on the Dropbox to the dst file on the local disk.
func (self *Dropbox) DownloadToFileResume(src, dst, rev string) error { func (db *Dropbox) DownloadToFileResume(src, dst, rev string) error {
var input io.ReadCloser var input io.ReadCloser
var fi os.FileInfo
var fd *os.File var fd *os.File
var offset int var offset int
var err error var err error
@ -441,14 +454,13 @@ func (self *Dropbox) DownloadToFileResume(src, dst, rev string) error {
if fd, err = os.OpenFile(dst, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644); err != nil { if fd, err = os.OpenFile(dst, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644); err != nil {
return err return err
} }
if fi, err := fd.Stat(); err != nil {
return err
} else {
offset = int(fi.Size())
}
defer fd.Close() defer fd.Close()
if fi, err = fd.Stat(); err != nil {
return err
}
offset = int(fi.Size())
if input, _, err = self.Download(src, rev, offset); err != nil { if input, _, err = db.Download(src, rev, offset); err != nil {
return err return err
} }
defer input.Close() defer input.Close()
@ -456,9 +468,9 @@ func (self *Dropbox) DownloadToFileResume(src, dst, rev string) error {
return err return err
} }
// Download the file located in the src path on the Dropbox to the dst file on the local disk. // DownloadToFile downloads the file located in the src path on the Dropbox to the dst file on the local disk.
// If the destination file exists it will be truncated. // If the destination file exists it will be truncated.
func (self *Dropbox) DownloadToFile(src, dst, rev string) error { func (db *Dropbox) DownloadToFile(src, dst, rev string) error {
var input io.ReadCloser var input io.ReadCloser
var fd *os.File var fd *os.File
var err error var err error
@ -468,7 +480,7 @@ func (self *Dropbox) DownloadToFile(src, dst, rev string) error {
} }
defer fd.Close() defer fd.Close()
if input, _, err = self.Download(src, rev, 0); err != nil { if input, _, err = db.Download(src, rev, 0); err != nil {
os.Remove(dst) os.Remove(dst)
return err return err
} }
@ -479,7 +491,7 @@ func (self *Dropbox) DownloadToFile(src, dst, rev string) error {
return err return err
} }
func (self *Dropbox) doRequest(method, path string, params *url.Values, receiver interface{}) error { func (db *Dropbox) doRequest(method, path string, params *url.Values, receiver interface{}) error {
var body []byte var body []byte
var rawurl string var rawurl string
var response *http.Response var response *http.Response
@ -487,13 +499,13 @@ func (self *Dropbox) doRequest(method, path string, params *url.Values, receiver
var err error var err error
if params == nil { if params == nil {
params = &url.Values{"locale": {self.Locale}} params = &url.Values{"locale": {db.Locale}}
} }
rawurl = fmt.Sprintf("%s/%s?%s", self.APIURL, path, params.Encode()) rawurl = fmt.Sprintf("%s/%s?%s", db.APIURL, path, params.Encode())
if request, err = http.NewRequest(method, rawurl, nil); err != nil { if request, err = http.NewRequest(method, rawurl, nil); err != nil {
return err return err
} }
if response, err = self.Session.Client().Do(request); err != nil { if response, err = db.Session.Client().Do(request); err != nil {
return err return err
} }
defer response.Body.Close() defer response.Body.Close()
@ -504,7 +516,7 @@ func (self *Dropbox) doRequest(method, path string, params *url.Values, receiver
case http.StatusNotFound: case http.StatusNotFound:
return os.ErrNotExist return os.ErrNotExist
case http.StatusBadRequest, http.StatusMethodNotAllowed: case http.StatusBadRequest, http.StatusMethodNotAllowed:
var reqerr RequestError var reqerr requestError
if err = json.Unmarshal(body, &reqerr); err != nil { if err = json.Unmarshal(body, &reqerr); err != nil {
return err return err
} }
@ -517,9 +529,9 @@ func (self *Dropbox) doRequest(method, path string, params *url.Values, receiver
return fmt.Errorf("%s: %s", param, reasonstr) return fmt.Errorf("%s: %s", param, reasonstr)
} }
} }
return fmt.Errorf("Wrong parameter") return fmt.Errorf("wrong parameter")
default: default:
return fmt.Errorf("Request error HTTP code %d", response.StatusCode) return fmt.Errorf("request error HTTP code %d", response.StatusCode)
} }
case http.StatusUnauthorized: case http.StatusUnauthorized:
return ErrNotAuth return ErrNotAuth
@ -528,39 +540,39 @@ func (self *Dropbox) doRequest(method, path string, params *url.Values, receiver
return err return err
} }
// Get account information for the user currently authenticated. // GetAccountInfo gets account information for the user currently authenticated.
func (self *Dropbox) GetAccountInfo() (*Account, error) { func (db *Dropbox) GetAccountInfo() (*Account, error) {
var rv Account var rv Account
err := self.doRequest("GET", "account/info", nil, &rv) err := db.doRequest("GET", "account/info", nil, &rv)
return &rv, err return &rv, err
} }
// Share a file. // Shares shares a file.
func (self *Dropbox) Shares(path string, shortUrl bool) (*Link, error) { func (db *Dropbox) Shares(path string, shortURL bool) (*Link, error) {
var rv Link var rv Link
var params *url.Values var params *url.Values
if shortUrl { if shortURL {
params = &url.Values{"short_url": {strconv.FormatBool(shortUrl)}} params = &url.Values{"short_url": {strconv.FormatBool(shortURL)}}
} }
act := strings.Join([]string{"shares", self.RootDirectory, path}, "/") act := strings.Join([]string{"shares", db.RootDirectory, path}, "/")
err := self.doRequest("POST", act, params, &rv) err := db.doRequest("POST", act, params, &rv)
return &rv, err return &rv, err
} }
// Share a file for streaming. // Media shares a file for streaming (direct access).
func (self *Dropbox) Media(path string) (*Link, error) { func (db *Dropbox) Media(path string) (*Link, error) {
var rv Link var rv Link
act := strings.Join([]string{"media", self.RootDirectory, path}, "/") act := strings.Join([]string{"media", db.RootDirectory, path}, "/")
err := self.doRequest("POST", act, nil, &rv) err := db.doRequest("POST", act, nil, &rv)
return &rv, err return &rv, err
} }
// Search entries matching all the words contained in query contained in path. // Search searches the entries matching all the words contained in query in the given path.
// The maximum number of entries and whether to consider deleted file may be given. // The maximum number of entries and whether to consider deleted file may be given.
func (self *Dropbox) Search(path, query string, fileLimit int, includeDeleted bool) (*[]Entry, error) { func (db *Dropbox) Search(path, query string, fileLimit int, includeDeleted bool) (*[]Entry, error) {
var rv []Entry var rv []Entry
var params *url.Values var params *url.Values
@ -572,13 +584,13 @@ func (self *Dropbox) Search(path, query string, fileLimit int, includeDeleted bo
"file_limit": {strconv.FormatInt(int64(fileLimit), 10)}, "file_limit": {strconv.FormatInt(int64(fileLimit), 10)},
"include_deleted": {strconv.FormatBool(includeDeleted)}, "include_deleted": {strconv.FormatBool(includeDeleted)},
} }
act := strings.Join([]string{"search", self.RootDirectory, path}, "/") act := strings.Join([]string{"search", db.RootDirectory, path}, "/")
err := self.doRequest("GET", act, params, &rv) err := db.doRequest("GET", act, params, &rv)
return &rv, err return &rv, err
} }
// Get modifications since the cursor. // Delta gets modifications since the cursor.
func (self *Dropbox) Delta(cursor, pathPrefix string) (*DeltaPage, error) { func (db *Dropbox) Delta(cursor, pathPrefix string) (*DeltaPage, error) {
var rv DeltaPage var rv DeltaPage
var params *url.Values var params *url.Values
type deltaPageParser struct { type deltaPageParser struct {
@ -596,7 +608,7 @@ func (self *Dropbox) Delta(cursor, pathPrefix string) (*DeltaPage, error) {
if len(pathPrefix) != 0 { if len(pathPrefix) != 0 {
params.Set("path_prefix", pathPrefix) params.Set("path_prefix", pathPrefix)
} }
err := self.doRequest("POST", "delta", params, &dpp) err := db.doRequest("POST", "delta", params, &dpp)
rv = DeltaPage{Reset: dpp.Reset, HasMore: dpp.HasMore, Cursor: dpp.Cursor} rv = DeltaPage{Reset: dpp.Reset, HasMore: dpp.HasMore, Cursor: dpp.Cursor}
rv.Entries = make([]DeltaEntry, 0, len(dpp.Entries)) rv.Entries = make([]DeltaEntry, 0, len(dpp.Entries))
for _, jentry := range dpp.Entries { for _, jentry := range dpp.Entries {
@ -604,7 +616,7 @@ func (self *Dropbox) Delta(cursor, pathPrefix string) (*DeltaPage, error) {
var entry Entry var entry Entry
if len(jentry) != 2 { if len(jentry) != 2 {
return nil, fmt.Errorf("Malformed reply") return nil, fmt.Errorf("malformed reply")
} }
if err = json.Unmarshal(jentry[0], &path); err != nil { if err = json.Unmarshal(jentry[0], &path); err != nil {
@ -622,8 +634,8 @@ func (self *Dropbox) Delta(cursor, pathPrefix string) (*DeltaPage, error) {
return &rv, err return &rv, err
} }
// Wait for a notification to happen. // LongPollDelta waits for a notification to happen.
func (self *Dropbox) LongPollDelta(cursor string, timeout int) (*DeltaPoll, error) { func (db *Dropbox) LongPollDelta(cursor string, timeout int) (*DeltaPoll, error) {
var rv DeltaPoll var rv DeltaPoll
var params *url.Values var params *url.Values
var body []byte var body []byte
@ -635,12 +647,12 @@ func (self *Dropbox) LongPollDelta(cursor string, timeout int) (*DeltaPoll, erro
params = &url.Values{} params = &url.Values{}
if timeout != 0 { if timeout != 0 {
if timeout < PollMinTimeout || timeout > PollMaxTimeout { if timeout < PollMinTimeout || timeout > PollMaxTimeout {
return nil, fmt.Errorf("Timeout out of range [%d; %d]", PollMinTimeout, PollMaxTimeout) return nil, fmt.Errorf("timeout out of range [%d; %d]", PollMinTimeout, PollMaxTimeout)
} }
params.Set("timeout", strconv.FormatInt(int64(timeout), 10)) params.Set("timeout", strconv.FormatInt(int64(timeout), 10))
} }
params.Set("cursor", cursor) params.Set("cursor", cursor)
rawurl = fmt.Sprintf("%s/longpoll_delta?%s", self.APINotifyURL, params.Encode()) rawurl = fmt.Sprintf("%s/longpoll_delta?%s", db.APINotifyURL, params.Encode())
if response, err = client.Get(rawurl); err != nil { if response, err = client.Get(rawurl); err != nil {
return nil, err return nil, err
} }
@ -650,7 +662,7 @@ func (self *Dropbox) LongPollDelta(cursor string, timeout int) (*DeltaPoll, erro
return nil, err return nil, err
} }
if response.StatusCode == http.StatusBadRequest { if response.StatusCode == http.StatusBadRequest {
var reqerr RequestError var reqerr requestError
if err = json.Unmarshal(body, &reqerr); err != nil { if err = json.Unmarshal(body, &reqerr); err != nil {
return nil, err return nil, err
} }
@ -660,13 +672,13 @@ func (self *Dropbox) LongPollDelta(cursor string, timeout int) (*DeltaPoll, erro
return &rv, err return &rv, err
} }
// Get metadata for a file or a directory. // Metadata gets the metadata for a file or a directory.
// If list is true and src is a directory, immediate child will be sent in the Contents field. // If list is true and src is a directory, immediate child will be sent in the Contents field.
// If include_deleted is true, entries deleted will be sent. // If include_deleted is true, entries deleted will be sent.
// hash is the hash of the contents of a directory, it is used to avoid sending data when directory did not change. // hash is the hash of the contents of a directory, it is used to avoid sending data when directory did not change.
// rev is the specific revision to get the metadata from. // rev is the specific revision to get the metadata from.
// limit is the maximum number of entries requested. // limit is the maximum number of entries requested.
func (self *Dropbox) Metadata(src string, list bool, includeDeleted bool, hash, rev string, limit int) (*Entry, error) { func (db *Dropbox) Metadata(src string, list bool, includeDeleted bool, hash, rev string, limit int) (*Entry, error) {
var rv Entry var rv Entry
var params *url.Values var params *url.Values
@ -687,77 +699,77 @@ func (self *Dropbox) Metadata(src string, list bool, includeDeleted bool, hash,
params.Set("hash", hash) params.Set("hash", hash)
} }
act := strings.Join([]string{"metadata", self.RootDirectory, src}, "/") act := strings.Join([]string{"metadata", db.RootDirectory, src}, "/")
err := self.doRequest("GET", act, params, &rv) err := db.doRequest("GET", act, params, &rv)
return &rv, err return &rv, err
} }
// Get a reference to a file. // CopyRef gets a reference to a file.
// This reference can be used to copy this file to another user's Dropbox by passing it to the Copy method. // This reference can be used to copy this file to another user's Dropbox by passing it to the Copy method.
func (self *Dropbox) CopyRef(src string) (*CopyRef, error) { func (db *Dropbox) CopyRef(src string) (*CopyRef, error) {
var rv CopyRef var rv CopyRef
act := strings.Join([]string{"copy_ref", self.RootDirectory, src}, "/") act := strings.Join([]string{"copy_ref", db.RootDirectory, src}, "/")
err := self.doRequest("GET", act, nil, &rv) err := db.doRequest("GET", act, nil, &rv)
return &rv, err return &rv, err
} }
// Get a list of revisions for a file. // Revisions gets the list of revisions for a file.
func (self *Dropbox) Revisions(src string, revLimit int) (*[]Entry, error) { func (db *Dropbox) Revisions(src string, revLimit int) (*[]Entry, error) {
var rv []Entry var rv []Entry
if revLimit <= 0 { if revLimit <= 0 {
revLimit = RevisionsLimitDefault revLimit = RevisionsLimitDefault
} else if revLimit > RevisionsLimitMax { } else if revLimit > RevisionsLimitMax {
revLimit = RevisionsLimitMax revLimit = RevisionsLimitMax
} }
act := strings.Join([]string{"revisions", self.RootDirectory, src}, "/") act := strings.Join([]string{"revisions", db.RootDirectory, src}, "/")
err := self.doRequest("GET", act, err := db.doRequest("GET", act,
&url.Values{"rev_limit": {strconv.FormatInt(int64(revLimit), 10)}}, &rv) &url.Values{"rev_limit": {strconv.FormatInt(int64(revLimit), 10)}}, &rv)
return &rv, err return &rv, err
} }
// Restore a deleted file at the corresponding revision. // Restore restores a deleted file at the corresponding revision.
func (self *Dropbox) Restore(src string, rev string) (*Entry, error) { func (db *Dropbox) Restore(src string, rev string) (*Entry, error) {
var rv Entry var rv Entry
act := strings.Join([]string{"restore", self.RootDirectory, src}, "/") act := strings.Join([]string{"restore", db.RootDirectory, src}, "/")
err := self.doRequest("POST", act, &url.Values{"rev": {rev}}, &rv) err := db.doRequest("POST", act, &url.Values{"rev": {rev}}, &rv)
return &rv, err return &rv, err
} }
// Copy a file. // Copy copies a file.
// If isRef is true src must be a reference from CopyRef instead of a path. // If isRef is true src must be a reference from CopyRef instead of a path.
func (self *Dropbox) Copy(src, dst string, isRef bool) (*Entry, error) { func (db *Dropbox) Copy(src, dst string, isRef bool) (*Entry, error) {
var rv Entry var rv Entry
params := &url.Values{"root": {self.RootDirectory}, "to_path": {dst}} params := &url.Values{"root": {db.RootDirectory}, "to_path": {dst}}
if isRef { if isRef {
params.Set("from_path", src) params.Set("from_path", src)
} else { } else {
params.Set("from_copy_ref", src) params.Set("from_copy_ref", src)
} }
err := self.doRequest("POST", "fileops/copy", params, &rv) err := db.doRequest("POST", "fileops/copy", params, &rv)
return &rv, err return &rv, err
} }
// Create a new directory. // CreateFolder creates a new directory.
func (self *Dropbox) CreateFolder(path string) (*Entry, error) { func (db *Dropbox) CreateFolder(path string) (*Entry, error) {
var rv Entry var rv Entry
err := self.doRequest("POST", "fileops/create_folder", err := db.doRequest("POST", "fileops/create_folder",
&url.Values{"root": {self.RootDirectory}, "path": {path}}, &rv) &url.Values{"root": {db.RootDirectory}, "path": {path}}, &rv)
return &rv, err return &rv, err
} }
// Remove a file or directory (it is a recursive delete). // Delete removes a file or directory (it is a recursive delete).
func (self *Dropbox) Delete(path string) (*Entry, error) { func (db *Dropbox) Delete(path string) (*Entry, error) {
var rv Entry var rv Entry
err := self.doRequest("POST", "fileops/delete", err := db.doRequest("POST", "fileops/delete",
&url.Values{"root": {self.RootDirectory}, "path": {path}}, &rv) &url.Values{"root": {db.RootDirectory}, "path": {path}}, &rv)
return &rv, err return &rv, err
} }
// Move a file or directory. // Move moves a file or directory.
func (self *Dropbox) Move(src, dst string) (*Entry, error) { func (db *Dropbox) Move(src, dst string) (*Entry, error) {
var rv Entry var rv Entry
err := self.doRequest("POST", "fileops/move", err := db.doRequest("POST", "fileops/move",
&url.Values{"root": {self.RootDirectory}, &url.Values{"root": {db.RootDirectory},
"from_path": {src}, "from_path": {src},
"to_path": {dst}}, &rv) "to_path": {dst}}, &rv)
return &rv, err return &rv, err