diff options
| author | Paul Zabelin | 2016-04-17 03:22:31 -0700 | 
|---|---|---|
| committer | Paul Zabelin | 2016-04-17 03:22:31 -0700 | 
| commit | b5eb2866cfceb69b0d4dd4948273d679a884fbb2 (patch) | |
| tree | 1fdb61a7138642a1612bb201434e8ebda141cc8a /vendor/google.golang.org/api/gensupport/resumable.go | |
| parent | 8de8e05c483c6b5f23b14742315f1860211dcef7 (diff) | |
| download | gdrive-b5eb2866cfceb69b0d4dd4948273d679a884fbb2.tar.bz2 | |
add Go dependencies by godep
see https://github.com/tools/godep
Diffstat (limited to 'vendor/google.golang.org/api/gensupport/resumable.go')
| -rw-r--r-- | vendor/google.golang.org/api/gensupport/resumable.go | 198 | 
1 files changed, 198 insertions, 0 deletions
| diff --git a/vendor/google.golang.org/api/gensupport/resumable.go b/vendor/google.golang.org/api/gensupport/resumable.go new file mode 100644 index 0000000..b3e774a --- /dev/null +++ b/vendor/google.golang.org/api/gensupport/resumable.go @@ -0,0 +1,198 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package gensupport + +import ( +	"fmt" +	"io" +	"net/http" +	"sync" +	"time" + +	"golang.org/x/net/context" +	"golang.org/x/net/context/ctxhttp" +) + +const ( +	// statusResumeIncomplete is the code returned by the Google uploader +	// when the transfer is not yet complete. +	statusResumeIncomplete = 308 + +	// statusTooManyRequests is returned by the storage API if the +	// per-project limits have been temporarily exceeded. The request +	// should be retried. +	// https://cloud.google.com/storage/docs/json_api/v1/status-codes#standardcodes +	statusTooManyRequests = 429 +) + +// ResumableUpload is used by the generated APIs to provide resumable uploads. +// It is not used by developers directly. +type ResumableUpload struct { +	Client *http.Client +	// URI is the resumable resource destination provided by the server after specifying "&uploadType=resumable". +	URI       string +	UserAgent string // User-Agent for header of the request +	// Media is the object being uploaded. +	Media *ResumableBuffer +	// MediaType defines the media type, e.g. "image/jpeg". +	MediaType string + +	mu       sync.Mutex // guards progress +	progress int64      // number of bytes uploaded so far + +	// Callback is an optional function that will be periodically called with the cumulative number of bytes uploaded. +	Callback func(int64) + +	// If not specified, a default exponential backoff strategy will be used. +	Backoff BackoffStrategy +} + +// Progress returns the number of bytes uploaded at this point. +func (rx *ResumableUpload) Progress() int64 { +	rx.mu.Lock() +	defer rx.mu.Unlock() +	return rx.progress +} + +// doUploadRequest performs a single HTTP request to upload data. +// off specifies the offset in rx.Media from which data is drawn. +// size is the number of bytes in data. +// final specifies whether data is the final chunk to be uploaded. +func (rx *ResumableUpload) doUploadRequest(ctx context.Context, data io.Reader, off, size int64, final bool) (*http.Response, error) { +	req, err := http.NewRequest("POST", rx.URI, data) +	if err != nil { +		return nil, err +	} + +	req.ContentLength = size +	var contentRange string +	if final { +		if size == 0 { +			contentRange = fmt.Sprintf("bytes */%v", off) +		} else { +			contentRange = fmt.Sprintf("bytes %v-%v/%v", off, off+size-1, off+size) +		} +	} else { +		contentRange = fmt.Sprintf("bytes %v-%v/*", off, off+size-1) +	} +	req.Header.Set("Content-Range", contentRange) +	req.Header.Set("Content-Type", rx.MediaType) +	req.Header.Set("User-Agent", rx.UserAgent) +	return ctxhttp.Do(ctx, rx.Client, req) + +} + +// reportProgress calls a user-supplied callback to report upload progress. +// If old==updated, the callback is not called. +func (rx *ResumableUpload) reportProgress(old, updated int64) { +	if updated-old == 0 { +		return +	} +	rx.mu.Lock() +	rx.progress = updated +	rx.mu.Unlock() +	if rx.Callback != nil { +		rx.Callback(updated) +	} +} + +// transferChunk performs a single HTTP request to upload a single chunk from rx.Media. +func (rx *ResumableUpload) transferChunk(ctx context.Context) (*http.Response, error) { +	chunk, off, size, err := rx.Media.Chunk() + +	done := err == io.EOF +	if !done && err != nil { +		return nil, err +	} + +	res, err := rx.doUploadRequest(ctx, chunk, off, int64(size), done) +	if err != nil { +		return res, err +	} + +	if res.StatusCode == statusResumeIncomplete || res.StatusCode == http.StatusOK { +		rx.reportProgress(off, off+int64(size)) +	} + +	if res.StatusCode == statusResumeIncomplete { +		rx.Media.Next() +	} +	return res, nil +} + +func contextDone(ctx context.Context) bool { +	select { +	case <-ctx.Done(): +		return true +	default: +		return false +	} +} + +// Upload starts the process of a resumable upload with a cancellable context. +// It retries using the provided back off strategy until cancelled or the +// strategy indicates to stop retrying. +// It is called from the auto-generated API code and is not visible to the user. +// rx is private to the auto-generated API code. +// Exactly one of resp or err will be nil.  If resp is non-nil, the caller must call resp.Body.Close. +func (rx *ResumableUpload) Upload(ctx context.Context) (resp *http.Response, err error) { +	var pause time.Duration +	backoff := rx.Backoff +	if backoff == nil { +		backoff = DefaultBackoffStrategy() +	} + +	for { +		// Ensure that we return in the case of cancelled context, even if pause is 0. +		if contextDone(ctx) { +			return nil, ctx.Err() +		} +		select { +		case <-ctx.Done(): +			return nil, ctx.Err() +		case <-time.After(pause): +		} + +		resp, err = rx.transferChunk(ctx) + +		var status int +		if resp != nil { +			status = resp.StatusCode +		} + +		// Check if we should retry the request. +		if shouldRetry(status, err) { +			var retry bool +			pause, retry = backoff.Pause() +			if retry { +				if resp != nil && resp.Body != nil { +					resp.Body.Close() +				} +				continue +			} +		} + +		// If the chunk was uploaded successfully, but there's still +		// more to go, upload the next chunk without any delay. +		if status == statusResumeIncomplete { +			pause = 0 +			backoff.Reset() +			resp.Body.Close() +			continue +		} + +		// It's possible for err and resp to both be non-nil here, but we expose a simpler +		// contract to our callers: exactly one of resp and err will be non-nil.  This means +		// that any response body must be closed here before returning a non-nil error. +		if err != nil { +			if resp != nil && resp.Body != nil { +				resp.Body.Close() +			} +			return nil, err +		} + +		return resp, nil +	} +} | 
