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 }