1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/drive/drive_uploader.h"
6
7 #include <algorithm>
8
9 #include "base/bind.h"
10 #include "base/callback.h"
11 #include "base/files/file_util.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/task_runner_util.h"
14 #include "chrome/browser/drive/drive_service_interface.h"
15 #include "content/public/browser/browser_thread.h"
16 #include "content/public/browser/power_save_blocker.h"
17 #include "google_apis/drive/drive_api_parser.h"
18
19 using content::BrowserThread;
20 using google_apis::CancelCallback;
21 using google_apis::FileResource;
22 using google_apis::GDATA_CANCELLED;
23 using google_apis::GDataErrorCode;
24 using google_apis::GDATA_NO_SPACE;
25 using google_apis::HTTP_CONFLICT;
26 using google_apis::HTTP_CREATED;
27 using google_apis::HTTP_FORBIDDEN;
28 using google_apis::HTTP_NOT_FOUND;
29 using google_apis::HTTP_PRECONDITION;
30 using google_apis::HTTP_RESUME_INCOMPLETE;
31 using google_apis::HTTP_SUCCESS;
32 using google_apis::ProgressCallback;
33 using google_apis::UploadRangeResponse;
34
35 namespace drive {
36
37 namespace {
38 // Upload data is split to multiple HTTP request each conveying kUploadChunkSize
39 // bytes (except the request for uploading the last chunk of data).
40 // The value must be a multiple of 512KB according to the spec of GData WAPI and
41 // Drive API v2. It is set to a smaller value than 2^31 for working around
42 // server side error (crbug.com/264089).
43 const int64 kUploadChunkSize = (1LL << 30); // 1GB
44 } // namespace
45
46 // Structure containing current upload information of file, passed between
47 // DriveServiceInterface methods and callbacks.
48 struct DriveUploader::UploadFileInfo {
UploadFileInfodrive::DriveUploader::UploadFileInfo49 UploadFileInfo(const base::FilePath& local_path,
50 const std::string& content_type,
51 const UploadCompletionCallback& callback,
52 const ProgressCallback& progress_callback)
53 : file_path(local_path),
54 content_type(content_type),
55 completion_callback(callback),
56 progress_callback(progress_callback),
57 content_length(0),
58 next_start_position(-1),
59 power_save_blocker(content::PowerSaveBlocker::Create(
60 content::PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension,
61 "Upload in progress")),
62 cancelled(false),
63 weak_ptr_factory_(this) {
64 }
65
~UploadFileInfodrive::DriveUploader::UploadFileInfo66 ~UploadFileInfo() {
67 }
68
69 // Useful for printf debugging.
DebugStringdrive::DriveUploader::UploadFileInfo70 std::string DebugString() const {
71 return "file_path=[" + file_path.AsUTF8Unsafe() +
72 "], content_type=[" + content_type +
73 "], content_length=[" + base::UintToString(content_length) +
74 "]";
75 }
76
77 // Returns the callback to cancel the upload represented by this struct.
GetCancelCallbackdrive::DriveUploader::UploadFileInfo78 CancelCallback GetCancelCallback() {
79 return base::Bind(&UploadFileInfo::Cancel, weak_ptr_factory_.GetWeakPtr());
80 }
81
82 // The local file path of the file to be uploaded.
83 const base::FilePath file_path;
84
85 // Content-Type of file.
86 const std::string content_type;
87
88 // Callback to be invoked once the upload has finished.
89 const UploadCompletionCallback completion_callback;
90
91 // Callback to periodically notify the upload progress.
92 const ProgressCallback progress_callback;
93
94 // Location URL where file is to be uploaded to, returned from
95 // InitiateUpload. Used for the subsequent ResumeUpload requests.
96 GURL upload_location;
97
98 // Header content-Length.
99 int64 content_length;
100
101 int64 next_start_position;
102
103 // Blocks system suspend while upload is in progress.
104 scoped_ptr<content::PowerSaveBlocker> power_save_blocker;
105
106 // Fields for implementing cancellation. |cancel_callback| is non-null if
107 // there is an in-flight HTTP request. In that case, |cancell_callback| will
108 // cancel the operation. |cancelled| is initially false and turns to true
109 // once Cancel() is called. DriveUploader will check this field before after
110 // an async task other than HTTP requests and cancels the subsequent requests
111 // if this is flagged to true.
112 CancelCallback cancel_callback;
113 bool cancelled;
114
115 private:
116 // Cancels the upload represented by this struct.
Canceldrive::DriveUploader::UploadFileInfo117 void Cancel() {
118 cancelled = true;
119 if (!cancel_callback.is_null())
120 cancel_callback.Run();
121 }
122
123 base::WeakPtrFactory<UploadFileInfo> weak_ptr_factory_;
124 DISALLOW_COPY_AND_ASSIGN(UploadFileInfo);
125 };
126
DriveUploader(DriveServiceInterface * drive_service,const scoped_refptr<base::TaskRunner> & blocking_task_runner)127 DriveUploader::DriveUploader(
128 DriveServiceInterface* drive_service,
129 const scoped_refptr<base::TaskRunner>& blocking_task_runner)
130 : drive_service_(drive_service),
131 blocking_task_runner_(blocking_task_runner),
132 weak_ptr_factory_(this) {
133 }
134
~DriveUploader()135 DriveUploader::~DriveUploader() {}
136
UploadNewFile(const std::string & parent_resource_id,const base::FilePath & local_file_path,const std::string & title,const std::string & content_type,const UploadNewFileOptions & options,const UploadCompletionCallback & callback,const ProgressCallback & progress_callback)137 CancelCallback DriveUploader::UploadNewFile(
138 const std::string& parent_resource_id,
139 const base::FilePath& local_file_path,
140 const std::string& title,
141 const std::string& content_type,
142 const UploadNewFileOptions& options,
143 const UploadCompletionCallback& callback,
144 const ProgressCallback& progress_callback) {
145 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
146 DCHECK(!parent_resource_id.empty());
147 DCHECK(!local_file_path.empty());
148 DCHECK(!title.empty());
149 DCHECK(!content_type.empty());
150 DCHECK(!callback.is_null());
151
152 return StartUploadFile(
153 scoped_ptr<UploadFileInfo>(new UploadFileInfo(local_file_path,
154 content_type,
155 callback,
156 progress_callback)),
157 base::Bind(&DriveUploader::StartInitiateUploadNewFile,
158 weak_ptr_factory_.GetWeakPtr(),
159 parent_resource_id,
160 title,
161 options));
162 }
163
UploadExistingFile(const std::string & resource_id,const base::FilePath & local_file_path,const std::string & content_type,const UploadExistingFileOptions & options,const UploadCompletionCallback & callback,const ProgressCallback & progress_callback)164 CancelCallback DriveUploader::UploadExistingFile(
165 const std::string& resource_id,
166 const base::FilePath& local_file_path,
167 const std::string& content_type,
168 const UploadExistingFileOptions& options,
169 const UploadCompletionCallback& callback,
170 const ProgressCallback& progress_callback) {
171 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
172 DCHECK(!resource_id.empty());
173 DCHECK(!local_file_path.empty());
174 DCHECK(!content_type.empty());
175 DCHECK(!callback.is_null());
176
177 return StartUploadFile(
178 scoped_ptr<UploadFileInfo>(new UploadFileInfo(local_file_path,
179 content_type,
180 callback,
181 progress_callback)),
182 base::Bind(&DriveUploader::StartInitiateUploadExistingFile,
183 weak_ptr_factory_.GetWeakPtr(),
184 resource_id,
185 options));
186 }
187
ResumeUploadFile(const GURL & upload_location,const base::FilePath & local_file_path,const std::string & content_type,const UploadCompletionCallback & callback,const ProgressCallback & progress_callback)188 CancelCallback DriveUploader::ResumeUploadFile(
189 const GURL& upload_location,
190 const base::FilePath& local_file_path,
191 const std::string& content_type,
192 const UploadCompletionCallback& callback,
193 const ProgressCallback& progress_callback) {
194 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
195 DCHECK(!local_file_path.empty());
196 DCHECK(!content_type.empty());
197 DCHECK(!callback.is_null());
198
199 scoped_ptr<UploadFileInfo> upload_file_info(new UploadFileInfo(
200 local_file_path, content_type,
201 callback, progress_callback));
202 upload_file_info->upload_location = upload_location;
203
204 return StartUploadFile(
205 upload_file_info.Pass(),
206 base::Bind(&DriveUploader::StartGetUploadStatus,
207 weak_ptr_factory_.GetWeakPtr()));
208 }
209
StartUploadFile(scoped_ptr<UploadFileInfo> upload_file_info,const StartInitiateUploadCallback & start_initiate_upload_callback)210 CancelCallback DriveUploader::StartUploadFile(
211 scoped_ptr<UploadFileInfo> upload_file_info,
212 const StartInitiateUploadCallback& start_initiate_upload_callback) {
213 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
214 DVLOG(1) << "Uploading file: " << upload_file_info->DebugString();
215
216 UploadFileInfo* info_ptr = upload_file_info.get();
217 base::PostTaskAndReplyWithResult(
218 blocking_task_runner_.get(),
219 FROM_HERE,
220 base::Bind(&base::GetFileSize,
221 info_ptr->file_path,
222 &info_ptr->content_length),
223 base::Bind(&DriveUploader::StartUploadFileAfterGetFileSize,
224 weak_ptr_factory_.GetWeakPtr(),
225 base::Passed(&upload_file_info),
226 start_initiate_upload_callback));
227 return info_ptr->GetCancelCallback();
228 }
229
StartUploadFileAfterGetFileSize(scoped_ptr<UploadFileInfo> upload_file_info,const StartInitiateUploadCallback & start_initiate_upload_callback,bool get_file_size_result)230 void DriveUploader::StartUploadFileAfterGetFileSize(
231 scoped_ptr<UploadFileInfo> upload_file_info,
232 const StartInitiateUploadCallback& start_initiate_upload_callback,
233 bool get_file_size_result) {
234 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
235
236 if (!get_file_size_result) {
237 UploadFailed(upload_file_info.Pass(), HTTP_NOT_FOUND);
238 return;
239 }
240 DCHECK_GE(upload_file_info->content_length, 0);
241
242 if (upload_file_info->cancelled) {
243 UploadFailed(upload_file_info.Pass(), GDATA_CANCELLED);
244 return;
245 }
246 start_initiate_upload_callback.Run(upload_file_info.Pass());
247 }
248
StartInitiateUploadNewFile(const std::string & parent_resource_id,const std::string & title,const UploadNewFileOptions & options,scoped_ptr<UploadFileInfo> upload_file_info)249 void DriveUploader::StartInitiateUploadNewFile(
250 const std::string& parent_resource_id,
251 const std::string& title,
252 const UploadNewFileOptions& options,
253 scoped_ptr<UploadFileInfo> upload_file_info) {
254 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
255
256 UploadFileInfo* info_ptr = upload_file_info.get();
257 info_ptr->cancel_callback = drive_service_->InitiateUploadNewFile(
258 info_ptr->content_type,
259 info_ptr->content_length,
260 parent_resource_id,
261 title,
262 options,
263 base::Bind(&DriveUploader::OnUploadLocationReceived,
264 weak_ptr_factory_.GetWeakPtr(),
265 base::Passed(&upload_file_info)));
266 }
267
StartInitiateUploadExistingFile(const std::string & resource_id,const UploadExistingFileOptions & options,scoped_ptr<UploadFileInfo> upload_file_info)268 void DriveUploader::StartInitiateUploadExistingFile(
269 const std::string& resource_id,
270 const UploadExistingFileOptions& options,
271 scoped_ptr<UploadFileInfo> upload_file_info) {
272 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
273
274 UploadFileInfo* info_ptr = upload_file_info.get();
275 info_ptr->cancel_callback = drive_service_->InitiateUploadExistingFile(
276 info_ptr->content_type,
277 info_ptr->content_length,
278 resource_id,
279 options,
280 base::Bind(&DriveUploader::OnUploadLocationReceived,
281 weak_ptr_factory_.GetWeakPtr(),
282 base::Passed(&upload_file_info)));
283 }
284
OnUploadLocationReceived(scoped_ptr<UploadFileInfo> upload_file_info,GDataErrorCode code,const GURL & upload_location)285 void DriveUploader::OnUploadLocationReceived(
286 scoped_ptr<UploadFileInfo> upload_file_info,
287 GDataErrorCode code,
288 const GURL& upload_location) {
289 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
290
291 DVLOG(1) << "Got upload location [" << upload_location.spec()
292 << "] for [" << upload_file_info->file_path.value() << "]";
293
294 if (code != HTTP_SUCCESS) {
295 if (code == HTTP_PRECONDITION)
296 code = HTTP_CONFLICT; // ETag mismatch.
297 UploadFailed(upload_file_info.Pass(), code);
298 return;
299 }
300
301 upload_file_info->upload_location = upload_location;
302 upload_file_info->next_start_position = 0;
303 UploadNextChunk(upload_file_info.Pass());
304 }
305
StartGetUploadStatus(scoped_ptr<UploadFileInfo> upload_file_info)306 void DriveUploader::StartGetUploadStatus(
307 scoped_ptr<UploadFileInfo> upload_file_info) {
308 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
309 DCHECK(upload_file_info);
310
311 UploadFileInfo* info_ptr = upload_file_info.get();
312 info_ptr->cancel_callback = drive_service_->GetUploadStatus(
313 info_ptr->upload_location,
314 info_ptr->content_length,
315 base::Bind(&DriveUploader::OnUploadRangeResponseReceived,
316 weak_ptr_factory_.GetWeakPtr(),
317 base::Passed(&upload_file_info)));
318 }
319
UploadNextChunk(scoped_ptr<UploadFileInfo> upload_file_info)320 void DriveUploader::UploadNextChunk(
321 scoped_ptr<UploadFileInfo> upload_file_info) {
322 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
323 DCHECK(upload_file_info);
324 DCHECK_GE(upload_file_info->next_start_position, 0);
325 DCHECK_LE(upload_file_info->next_start_position,
326 upload_file_info->content_length);
327
328 if (upload_file_info->cancelled) {
329 UploadFailed(upload_file_info.Pass(), GDATA_CANCELLED);
330 return;
331 }
332
333 // Limit the size of data uploaded per each request by kUploadChunkSize.
334 const int64 end_position = std::min(
335 upload_file_info->content_length,
336 upload_file_info->next_start_position + kUploadChunkSize);
337
338 UploadFileInfo* info_ptr = upload_file_info.get();
339 info_ptr->cancel_callback = drive_service_->ResumeUpload(
340 info_ptr->upload_location,
341 info_ptr->next_start_position,
342 end_position,
343 info_ptr->content_length,
344 info_ptr->content_type,
345 info_ptr->file_path,
346 base::Bind(&DriveUploader::OnUploadRangeResponseReceived,
347 weak_ptr_factory_.GetWeakPtr(),
348 base::Passed(&upload_file_info)),
349 base::Bind(&DriveUploader::OnUploadProgress,
350 weak_ptr_factory_.GetWeakPtr(),
351 info_ptr->progress_callback,
352 info_ptr->next_start_position,
353 info_ptr->content_length));
354 }
355
OnUploadRangeResponseReceived(scoped_ptr<UploadFileInfo> upload_file_info,const UploadRangeResponse & response,scoped_ptr<FileResource> entry)356 void DriveUploader::OnUploadRangeResponseReceived(
357 scoped_ptr<UploadFileInfo> upload_file_info,
358 const UploadRangeResponse& response,
359 scoped_ptr<FileResource> entry) {
360 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
361
362 if (response.code == HTTP_CREATED || response.code == HTTP_SUCCESS) {
363 // When uploading a new file, we expect HTTP_CREATED, and when uploading
364 // an existing file (to overwrite), we expect HTTP_SUCCESS.
365 // There is an exception: if we uploading an empty file, uploading a new
366 // file also returns HTTP_SUCCESS on Drive API v2. The correct way of the
367 // fix should be uploading the metadata only. However, to keep the
368 // compatibility with GData WAPI during the migration period, we just
369 // relax the condition here.
370 // TODO(hidehiko): Upload metadata only for empty files, after GData WAPI
371 // code is gone.
372 DVLOG(1) << "Successfully created uploaded file=["
373 << upload_file_info->file_path.value() << "]";
374
375 // Done uploading.
376 upload_file_info->completion_callback.Run(
377 HTTP_SUCCESS, GURL(), entry.Pass());
378 return;
379 }
380
381 // ETag mismatch.
382 if (response.code == HTTP_PRECONDITION) {
383 UploadFailed(upload_file_info.Pass(), HTTP_CONFLICT);
384 return;
385 }
386
387 // If code is 308 (RESUME_INCOMPLETE) and |range_received| starts with 0
388 // (meaning that the data is uploaded from the beginning of the file),
389 // proceed to upload the next chunk.
390 if (response.code != HTTP_RESUME_INCOMPLETE ||
391 response.start_position_received != 0) {
392 DVLOG(1)
393 << "UploadNextChunk http code=" << response.code
394 << ", start_position_received=" << response.start_position_received
395 << ", end_position_received=" << response.end_position_received;
396 UploadFailed(
397 upload_file_info.Pass(),
398 response.code == HTTP_FORBIDDEN ? GDATA_NO_SPACE : response.code);
399 return;
400 }
401
402 DVLOG(1) << "Received range " << response.start_position_received
403 << "-" << response.end_position_received
404 << " for [" << upload_file_info->file_path.value() << "]";
405
406 upload_file_info->next_start_position = response.end_position_received;
407 UploadNextChunk(upload_file_info.Pass());
408 }
409
OnUploadProgress(const ProgressCallback & callback,int64 start_position,int64 total_size,int64 progress_of_chunk,int64 total_of_chunk)410 void DriveUploader::OnUploadProgress(const ProgressCallback& callback,
411 int64 start_position,
412 int64 total_size,
413 int64 progress_of_chunk,
414 int64 total_of_chunk) {
415 if (!callback.is_null())
416 callback.Run(start_position + progress_of_chunk, total_size);
417 }
418
UploadFailed(scoped_ptr<UploadFileInfo> upload_file_info,GDataErrorCode error)419 void DriveUploader::UploadFailed(scoped_ptr<UploadFileInfo> upload_file_info,
420 GDataErrorCode error) {
421 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
422
423 DVLOG(1) << "Upload failed " << upload_file_info->DebugString();
424
425 if (upload_file_info->next_start_position < 0) {
426 // Discard the upload location because no request could succeed with it.
427 // Maybe it's obsolete.
428 upload_file_info->upload_location = GURL();
429 }
430
431 upload_file_info->completion_callback.Run(
432 error, upload_file_info->upload_location, scoped_ptr<FileResource>());
433 }
434
435 } // namespace drive
436