github.com/ncw/rclone@v1.48.1-0.20190724201158-a35aa1360e3e/backend/drive/upload.go (about)

     1  // Upload for drive
     2  //
     3  // Docs
     4  // Resumable upload: https://developers.google.com/drive/web/manage-uploads#resumable
     5  // Best practices: https://developers.google.com/drive/web/manage-uploads#best-practices
     6  // Files insert: https://developers.google.com/drive/v2/reference/files/insert
     7  // Files update: https://developers.google.com/drive/v2/reference/files/update
     8  //
     9  // This contains code adapted from google.golang.org/api (C) the GO AUTHORS
    10  
    11  package drive
    12  
    13  import (
    14  	"encoding/json"
    15  	"fmt"
    16  	"io"
    17  	"net/http"
    18  	"net/url"
    19  	"regexp"
    20  	"strconv"
    21  
    22  	"github.com/ncw/rclone/fs"
    23  	"github.com/ncw/rclone/fs/fserrors"
    24  	"github.com/ncw/rclone/lib/readers"
    25  	"github.com/pkg/errors"
    26  	"google.golang.org/api/drive/v3"
    27  	"google.golang.org/api/googleapi"
    28  )
    29  
    30  const (
    31  	// statusResumeIncomplete is the code returned by the Google uploader when the transfer is not yet complete.
    32  	statusResumeIncomplete = 308
    33  )
    34  
    35  // resumableUpload is used by the generated APIs to provide resumable uploads.
    36  // It is not used by developers directly.
    37  type resumableUpload struct {
    38  	f      *Fs
    39  	remote string
    40  	// URI is the resumable resource destination provided by the server after specifying "&uploadType=resumable".
    41  	URI string
    42  	// Media is the object being uploaded.
    43  	Media io.Reader
    44  	// MediaType defines the media type, e.g. "image/jpeg".
    45  	MediaType string
    46  	// ContentLength is the full size of the object being uploaded.
    47  	ContentLength int64
    48  	// Return value
    49  	ret *drive.File
    50  }
    51  
    52  // Upload the io.Reader in of size bytes with contentType and info
    53  func (f *Fs) Upload(in io.Reader, size int64, contentType, fileID, remote string, info *drive.File) (*drive.File, error) {
    54  	params := url.Values{
    55  		"alt":        {"json"},
    56  		"uploadType": {"resumable"},
    57  		"fields":     {partialFields},
    58  	}
    59  	if f.isTeamDrive {
    60  		params.Set("supportsTeamDrives", "true")
    61  	}
    62  	if f.opt.KeepRevisionForever {
    63  		params.Set("keepRevisionForever", "true")
    64  	}
    65  	urls := "https://www.googleapis.com/upload/drive/v3/files"
    66  	method := "POST"
    67  	if fileID != "" {
    68  		params.Set("setModifiedDate", "true")
    69  		urls += "/{fileId}"
    70  		method = "PATCH"
    71  	}
    72  	urls += "?" + params.Encode()
    73  	var res *http.Response
    74  	var err error
    75  	err = f.pacer.Call(func() (bool, error) {
    76  		var body io.Reader
    77  		body, err = googleapi.WithoutDataWrapper.JSONReader(info)
    78  		if err != nil {
    79  			return false, err
    80  		}
    81  		var req *http.Request
    82  		req, err = http.NewRequest(method, urls, body)
    83  		if err != nil {
    84  			return false, err
    85  		}
    86  		googleapi.Expand(req.URL, map[string]string{
    87  			"fileId": fileID,
    88  		})
    89  		req.Header.Set("Content-Type", "application/json; charset=UTF-8")
    90  		req.Header.Set("X-Upload-Content-Type", contentType)
    91  		req.Header.Set("X-Upload-Content-Length", fmt.Sprintf("%v", size))
    92  		res, err = f.client.Do(req)
    93  		if err == nil {
    94  			defer googleapi.CloseBody(res)
    95  			err = googleapi.CheckResponse(res)
    96  		}
    97  		return shouldRetry(err)
    98  	})
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  	loc := res.Header.Get("Location")
   103  	rx := &resumableUpload{
   104  		f:             f,
   105  		remote:        remote,
   106  		URI:           loc,
   107  		Media:         in,
   108  		MediaType:     contentType,
   109  		ContentLength: size,
   110  	}
   111  	return rx.Upload()
   112  }
   113  
   114  // Make an http.Request for the range passed in
   115  func (rx *resumableUpload) makeRequest(start int64, body io.ReadSeeker, reqSize int64) *http.Request {
   116  	req, _ := http.NewRequest("POST", rx.URI, body)
   117  	req.ContentLength = reqSize
   118  	if reqSize != 0 {
   119  		req.Header.Set("Content-Range", fmt.Sprintf("bytes %v-%v/%v", start, start+reqSize-1, rx.ContentLength))
   120  	} else {
   121  		req.Header.Set("Content-Range", fmt.Sprintf("bytes */%v", rx.ContentLength))
   122  	}
   123  	req.Header.Set("Content-Type", rx.MediaType)
   124  	return req
   125  }
   126  
   127  // rangeRE matches the transfer status response from the server. $1 is
   128  // the last byte index uploaded.
   129  var rangeRE = regexp.MustCompile(`^0\-(\d+)$`)
   130  
   131  // Query drive for the amount transferred so far
   132  //
   133  // If error is nil, then start should be valid
   134  func (rx *resumableUpload) transferStatus() (start int64, err error) {
   135  	req := rx.makeRequest(0, nil, 0)
   136  	res, err := rx.f.client.Do(req)
   137  	if err != nil {
   138  		return 0, err
   139  	}
   140  	defer googleapi.CloseBody(res)
   141  	if res.StatusCode == http.StatusCreated || res.StatusCode == http.StatusOK {
   142  		return rx.ContentLength, nil
   143  	}
   144  	if res.StatusCode != statusResumeIncomplete {
   145  		err = googleapi.CheckResponse(res)
   146  		if err != nil {
   147  			return 0, err
   148  		}
   149  		return 0, errors.Errorf("unexpected http return code %v", res.StatusCode)
   150  	}
   151  	Range := res.Header.Get("Range")
   152  	if m := rangeRE.FindStringSubmatch(Range); len(m) == 2 {
   153  		start, err = strconv.ParseInt(m[1], 10, 64)
   154  		if err == nil {
   155  			return start, nil
   156  		}
   157  	}
   158  	return 0, errors.Errorf("unable to parse range %q", Range)
   159  }
   160  
   161  // Transfer a chunk - caller must call googleapi.CloseBody(res) if err == nil || res != nil
   162  func (rx *resumableUpload) transferChunk(start int64, chunk io.ReadSeeker, chunkSize int64) (int, error) {
   163  	_, _ = chunk.Seek(0, io.SeekStart)
   164  	req := rx.makeRequest(start, chunk, chunkSize)
   165  	res, err := rx.f.client.Do(req)
   166  	if err != nil {
   167  		return 599, err
   168  	}
   169  	defer googleapi.CloseBody(res)
   170  	if res.StatusCode == statusResumeIncomplete {
   171  		return res.StatusCode, nil
   172  	}
   173  	err = googleapi.CheckResponse(res)
   174  	if err != nil {
   175  		return res.StatusCode, err
   176  	}
   177  
   178  	// When the entire file upload is complete, the server
   179  	// responds with an HTTP 201 Created along with any metadata
   180  	// associated with this resource. If this request had been
   181  	// updating an existing entity rather than creating a new one,
   182  	// the HTTP response code for a completed upload would have
   183  	// been 200 OK.
   184  	//
   185  	// So parse the response out of the body.  We aren't expecting
   186  	// any other 2xx codes, so we parse it unconditionally on
   187  	// StatusCode
   188  	if err = json.NewDecoder(res.Body).Decode(&rx.ret); err != nil {
   189  		return 598, err
   190  	}
   191  
   192  	return res.StatusCode, nil
   193  }
   194  
   195  // Upload uploads the chunks from the input
   196  // It retries each chunk using the pacer and --low-level-retries
   197  func (rx *resumableUpload) Upload() (*drive.File, error) {
   198  	start := int64(0)
   199  	var StatusCode int
   200  	var err error
   201  	buf := make([]byte, int(rx.f.opt.ChunkSize))
   202  	for start < rx.ContentLength {
   203  		reqSize := rx.ContentLength - start
   204  		if reqSize >= int64(rx.f.opt.ChunkSize) {
   205  			reqSize = int64(rx.f.opt.ChunkSize)
   206  		}
   207  		chunk := readers.NewRepeatableLimitReaderBuffer(rx.Media, buf, reqSize)
   208  
   209  		// Transfer the chunk
   210  		err = rx.f.pacer.Call(func() (bool, error) {
   211  			fs.Debugf(rx.remote, "Sending chunk %d length %d", start, reqSize)
   212  			StatusCode, err = rx.transferChunk(start, chunk, reqSize)
   213  			again, err := shouldRetry(err)
   214  			if StatusCode == statusResumeIncomplete || StatusCode == http.StatusCreated || StatusCode == http.StatusOK {
   215  				again = false
   216  				err = nil
   217  			}
   218  			return again, err
   219  		})
   220  		if err != nil {
   221  			return nil, err
   222  		}
   223  
   224  		start += reqSize
   225  	}
   226  	// Resume or retry uploads that fail due to connection interruptions or
   227  	// any 5xx errors, including:
   228  	//
   229  	// 500 Internal Server Error
   230  	// 502 Bad Gateway
   231  	// 503 Service Unavailable
   232  	// 504 Gateway Timeout
   233  	//
   234  	// Use an exponential backoff strategy if any 5xx server error is
   235  	// returned when resuming or retrying upload requests. These errors can
   236  	// occur if a server is getting overloaded. Exponential backoff can help
   237  	// alleviate these kinds of problems during periods of high volume of
   238  	// requests or heavy network traffic.  Other kinds of requests should not
   239  	// be handled by exponential backoff but you can still retry a number of
   240  	// them. When retrying these requests, limit the number of times you
   241  	// retry them. For example your code could limit to ten retries or less
   242  	// before reporting an error.
   243  	//
   244  	// Handle 404 Not Found errors when doing resumable uploads by starting
   245  	// the entire upload over from the beginning.
   246  	if rx.ret == nil {
   247  		return nil, fserrors.RetryErrorf("Incomplete upload - retry, last error %d", StatusCode)
   248  	}
   249  	return rx.ret, nil
   250  }