Componentize AccountReconcilor.
[chromium-blink-merge.git] / chrome / browser / drive / drive_uploader.cc
blob6ef9545ad276262d4c8f36de02be554b56629861
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.
5 #include "chrome/browser/drive/drive_uploader.h"
7 #include <algorithm>
9 #include "base/bind.h"
10 #include "base/callback.h"
11 #include "base/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/gdata_wapi_parser.h"
19 using content::BrowserThread;
20 using google_apis::CancelCallback;
21 using google_apis::GDATA_CANCELLED;
22 using google_apis::GDataErrorCode;
23 using google_apis::GDATA_NO_SPACE;
24 using google_apis::HTTP_CONFLICT;
25 using google_apis::HTTP_CREATED;
26 using google_apis::HTTP_FORBIDDEN;
27 using google_apis::HTTP_NOT_FOUND;
28 using google_apis::HTTP_PRECONDITION;
29 using google_apis::HTTP_RESUME_INCOMPLETE;
30 using google_apis::HTTP_SUCCESS;
31 using google_apis::ProgressCallback;
32 using google_apis::ResourceEntry;
33 using google_apis::UploadRangeResponse;
35 namespace drive {
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
46 // Structure containing current upload information of file, passed between
47 // DriveServiceInterface methods and callbacks.
48 struct DriveUploader::UploadFileInfo {
49 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) {
66 ~UploadFileInfo() {
69 // Useful for printf debugging.
70 std::string DebugString() const {
71 return "file_path=[" + file_path.AsUTF8Unsafe() +
72 "], content_type=[" + content_type +
73 "], content_length=[" + base::UintToString(content_length) +
74 "]";
77 // Returns the callback to cancel the upload represented by this struct.
78 CancelCallback GetCancelCallback() {
79 return base::Bind(&UploadFileInfo::Cancel, weak_ptr_factory_.GetWeakPtr());
82 // The local file path of the file to be uploaded.
83 const base::FilePath file_path;
85 // Content-Type of file.
86 const std::string content_type;
88 // Callback to be invoked once the upload has finished.
89 const UploadCompletionCallback completion_callback;
91 // Callback to periodically notify the upload progress.
92 const ProgressCallback progress_callback;
94 // Location URL where file is to be uploaded to, returned from
95 // InitiateUpload. Used for the subsequent ResumeUpload requests.
96 GURL upload_location;
98 // Header content-Length.
99 int64 content_length;
101 int64 next_start_position;
103 // Blocks system suspend while upload is in progress.
104 scoped_ptr<content::PowerSaveBlocker> power_save_blocker;
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;
115 private:
116 // Cancels the upload represented by this struct.
117 void Cancel() {
118 cancelled = true;
119 if (!cancel_callback.is_null())
120 cancel_callback.Run();
123 base::WeakPtrFactory<UploadFileInfo> weak_ptr_factory_;
124 DISALLOW_COPY_AND_ASSIGN(UploadFileInfo);
127 DriveUploader::DriveUploader(DriveServiceInterface* drive_service,
128 base::TaskRunner* blocking_task_runner)
129 : drive_service_(drive_service),
130 blocking_task_runner_(blocking_task_runner),
131 weak_ptr_factory_(this) {
134 DriveUploader::~DriveUploader() {}
136 CancelCallback DriveUploader::UploadNewFile(
137 const std::string& parent_resource_id,
138 const base::FilePath& local_file_path,
139 const std::string& title,
140 const std::string& content_type,
141 const UploadNewFileOptions& options,
142 const UploadCompletionCallback& callback,
143 const ProgressCallback& progress_callback) {
144 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
145 DCHECK(!parent_resource_id.empty());
146 DCHECK(!local_file_path.empty());
147 DCHECK(!title.empty());
148 DCHECK(!content_type.empty());
149 DCHECK(!callback.is_null());
151 return StartUploadFile(
152 scoped_ptr<UploadFileInfo>(new UploadFileInfo(local_file_path,
153 content_type,
154 callback,
155 progress_callback)),
156 base::Bind(&DriveUploader::StartInitiateUploadNewFile,
157 weak_ptr_factory_.GetWeakPtr(),
158 parent_resource_id,
159 title,
160 options));
163 CancelCallback DriveUploader::UploadExistingFile(
164 const std::string& resource_id,
165 const base::FilePath& local_file_path,
166 const std::string& content_type,
167 const UploadExistingFileOptions& options,
168 const UploadCompletionCallback& callback,
169 const ProgressCallback& progress_callback) {
170 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
171 DCHECK(!resource_id.empty());
172 DCHECK(!local_file_path.empty());
173 DCHECK(!content_type.empty());
174 DCHECK(!callback.is_null());
176 return StartUploadFile(
177 scoped_ptr<UploadFileInfo>(new UploadFileInfo(local_file_path,
178 content_type,
179 callback,
180 progress_callback)),
181 base::Bind(&DriveUploader::StartInitiateUploadExistingFile,
182 weak_ptr_factory_.GetWeakPtr(),
183 resource_id,
184 options));
187 CancelCallback DriveUploader::ResumeUploadFile(
188 const GURL& upload_location,
189 const base::FilePath& local_file_path,
190 const std::string& content_type,
191 const UploadCompletionCallback& callback,
192 const ProgressCallback& progress_callback) {
193 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
194 DCHECK(!local_file_path.empty());
195 DCHECK(!content_type.empty());
196 DCHECK(!callback.is_null());
198 scoped_ptr<UploadFileInfo> upload_file_info(new UploadFileInfo(
199 local_file_path, content_type,
200 callback, progress_callback));
201 upload_file_info->upload_location = upload_location;
203 return StartUploadFile(
204 upload_file_info.Pass(),
205 base::Bind(&DriveUploader::StartGetUploadStatus,
206 weak_ptr_factory_.GetWeakPtr()));
209 CancelCallback DriveUploader::StartUploadFile(
210 scoped_ptr<UploadFileInfo> upload_file_info,
211 const StartInitiateUploadCallback& start_initiate_upload_callback) {
212 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
213 DVLOG(1) << "Uploading file: " << upload_file_info->DebugString();
215 UploadFileInfo* info_ptr = upload_file_info.get();
216 base::PostTaskAndReplyWithResult(
217 blocking_task_runner_.get(),
218 FROM_HERE,
219 base::Bind(&base::GetFileSize,
220 info_ptr->file_path,
221 &info_ptr->content_length),
222 base::Bind(&DriveUploader::StartUploadFileAfterGetFileSize,
223 weak_ptr_factory_.GetWeakPtr(),
224 base::Passed(&upload_file_info),
225 start_initiate_upload_callback));
226 return info_ptr->GetCancelCallback();
229 void DriveUploader::StartUploadFileAfterGetFileSize(
230 scoped_ptr<UploadFileInfo> upload_file_info,
231 const StartInitiateUploadCallback& start_initiate_upload_callback,
232 bool get_file_size_result) {
233 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
235 if (!get_file_size_result) {
236 UploadFailed(upload_file_info.Pass(), HTTP_NOT_FOUND);
237 return;
239 DCHECK_GE(upload_file_info->content_length, 0);
241 if (upload_file_info->cancelled) {
242 UploadFailed(upload_file_info.Pass(), GDATA_CANCELLED);
243 return;
245 start_initiate_upload_callback.Run(upload_file_info.Pass());
248 void DriveUploader::StartInitiateUploadNewFile(
249 const std::string& parent_resource_id,
250 const std::string& title,
251 const UploadNewFileOptions& options,
252 scoped_ptr<UploadFileInfo> upload_file_info) {
253 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
255 UploadFileInfo* info_ptr = upload_file_info.get();
256 info_ptr->cancel_callback = drive_service_->InitiateUploadNewFile(
257 info_ptr->content_type,
258 info_ptr->content_length,
259 parent_resource_id,
260 title,
261 options,
262 base::Bind(&DriveUploader::OnUploadLocationReceived,
263 weak_ptr_factory_.GetWeakPtr(),
264 base::Passed(&upload_file_info)));
267 void DriveUploader::StartInitiateUploadExistingFile(
268 const std::string& resource_id,
269 const UploadExistingFileOptions& options,
270 scoped_ptr<UploadFileInfo> upload_file_info) {
271 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
273 UploadFileInfo* info_ptr = upload_file_info.get();
274 info_ptr->cancel_callback = drive_service_->InitiateUploadExistingFile(
275 info_ptr->content_type,
276 info_ptr->content_length,
277 resource_id,
278 options,
279 base::Bind(&DriveUploader::OnUploadLocationReceived,
280 weak_ptr_factory_.GetWeakPtr(),
281 base::Passed(&upload_file_info)));
284 void DriveUploader::OnUploadLocationReceived(
285 scoped_ptr<UploadFileInfo> upload_file_info,
286 GDataErrorCode code,
287 const GURL& upload_location) {
288 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
290 DVLOG(1) << "Got upload location [" << upload_location.spec()
291 << "] for [" << upload_file_info->file_path.value() << "]";
293 if (code != HTTP_SUCCESS) {
294 if (code == HTTP_PRECONDITION)
295 code = HTTP_CONFLICT; // ETag mismatch.
296 UploadFailed(upload_file_info.Pass(), code);
297 return;
300 upload_file_info->upload_location = upload_location;
301 upload_file_info->next_start_position = 0;
302 UploadNextChunk(upload_file_info.Pass());
305 void DriveUploader::StartGetUploadStatus(
306 scoped_ptr<UploadFileInfo> upload_file_info) {
307 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
308 DCHECK(upload_file_info);
310 UploadFileInfo* info_ptr = upload_file_info.get();
311 info_ptr->cancel_callback = drive_service_->GetUploadStatus(
312 info_ptr->upload_location,
313 info_ptr->content_length,
314 base::Bind(&DriveUploader::OnUploadRangeResponseReceived,
315 weak_ptr_factory_.GetWeakPtr(),
316 base::Passed(&upload_file_info)));
319 void DriveUploader::UploadNextChunk(
320 scoped_ptr<UploadFileInfo> upload_file_info) {
321 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
322 DCHECK(upload_file_info);
323 DCHECK_GE(upload_file_info->next_start_position, 0);
324 DCHECK_LE(upload_file_info->next_start_position,
325 upload_file_info->content_length);
327 if (upload_file_info->cancelled) {
328 UploadFailed(upload_file_info.Pass(), GDATA_CANCELLED);
329 return;
332 // Limit the size of data uploaded per each request by kUploadChunkSize.
333 const int64 end_position = std::min(
334 upload_file_info->content_length,
335 upload_file_info->next_start_position + kUploadChunkSize);
337 UploadFileInfo* info_ptr = upload_file_info.get();
338 info_ptr->cancel_callback = drive_service_->ResumeUpload(
339 info_ptr->upload_location,
340 info_ptr->next_start_position,
341 end_position,
342 info_ptr->content_length,
343 info_ptr->content_type,
344 info_ptr->file_path,
345 base::Bind(&DriveUploader::OnUploadRangeResponseReceived,
346 weak_ptr_factory_.GetWeakPtr(),
347 base::Passed(&upload_file_info)),
348 base::Bind(&DriveUploader::OnUploadProgress,
349 weak_ptr_factory_.GetWeakPtr(),
350 info_ptr->progress_callback,
351 info_ptr->next_start_position,
352 info_ptr->content_length));
355 void DriveUploader::OnUploadRangeResponseReceived(
356 scoped_ptr<UploadFileInfo> upload_file_info,
357 const UploadRangeResponse& response,
358 scoped_ptr<ResourceEntry> entry) {
359 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
361 if (response.code == HTTP_CREATED || response.code == HTTP_SUCCESS) {
362 // When uploading a new file, we expect HTTP_CREATED, and when uploading
363 // an existing file (to overwrite), we expect HTTP_SUCCESS.
364 // There is an exception: if we uploading an empty file, uploading a new
365 // file also returns HTTP_SUCCESS on Drive API v2. The correct way of the
366 // fix should be uploading the metadata only. However, to keep the
367 // compatibility with GData WAPI during the migration period, we just
368 // relax the condition here.
369 // TODO(hidehiko): Upload metadata only for empty files, after GData WAPI
370 // code is gone.
371 DVLOG(1) << "Successfully created uploaded file=["
372 << upload_file_info->file_path.value() << "]";
374 // Done uploading.
375 upload_file_info->completion_callback.Run(
376 HTTP_SUCCESS, GURL(), entry.Pass());
377 return;
380 // ETag mismatch.
381 if (response.code == HTTP_PRECONDITION) {
382 UploadFailed(upload_file_info.Pass(), HTTP_CONFLICT);
383 return;
386 // If code is 308 (RESUME_INCOMPLETE) and |range_received| starts with 0
387 // (meaning that the data is uploaded from the beginning of the file),
388 // proceed to upload the next chunk.
389 if (response.code != HTTP_RESUME_INCOMPLETE ||
390 response.start_position_received != 0) {
391 DVLOG(1)
392 << "UploadNextChunk http code=" << response.code
393 << ", start_position_received=" << response.start_position_received
394 << ", end_position_received=" << response.end_position_received;
395 UploadFailed(
396 upload_file_info.Pass(),
397 response.code == HTTP_FORBIDDEN ? GDATA_NO_SPACE : response.code);
398 return;
401 DVLOG(1) << "Received range " << response.start_position_received
402 << "-" << response.end_position_received
403 << " for [" << upload_file_info->file_path.value() << "]";
405 upload_file_info->next_start_position = response.end_position_received;
406 UploadNextChunk(upload_file_info.Pass());
409 void DriveUploader::OnUploadProgress(const ProgressCallback& callback,
410 int64 start_position,
411 int64 total_size,
412 int64 progress_of_chunk,
413 int64 total_of_chunk) {
414 if (!callback.is_null())
415 callback.Run(start_position + progress_of_chunk, total_size);
418 void DriveUploader::UploadFailed(scoped_ptr<UploadFileInfo> upload_file_info,
419 GDataErrorCode error) {
420 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
422 DVLOG(1) << "Upload failed " << upload_file_info->DebugString();
424 if (upload_file_info->next_start_position < 0) {
425 // Discard the upload location because no request could succeed with it.
426 // Maybe it's obsolete.
427 upload_file_info->upload_location = GURL();
430 upload_file_info->completion_callback.Run(
431 error, upload_file_info->upload_location, scoped_ptr<ResourceEntry>());
434 } // namespace drive