drive_uploader.cc revision 58537e28ecd584eab876aee8be7156509866d23a
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/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 "chrome/browser/google_apis/gdata_wapi_parser.h"
16#include "content/public/browser/browser_thread.h"
17#include "content/public/browser/power_save_blocker.h"
18
19using content::BrowserThread;
20using google_apis::CancelCallback;
21using google_apis::GDATA_CANCELLED;
22using google_apis::GDataErrorCode;
23using google_apis::GDATA_NO_SPACE;
24using google_apis::HTTP_CONFLICT;
25using google_apis::HTTP_CREATED;
26using google_apis::HTTP_FORBIDDEN;
27using google_apis::HTTP_NOT_FOUND;
28using google_apis::HTTP_PRECONDITION;
29using google_apis::HTTP_RESUME_INCOMPLETE;
30using google_apis::HTTP_SUCCESS;
31using google_apis::ProgressCallback;
32using google_apis::ResourceEntry;
33using google_apis::UploadRangeResponse;
34
35namespace drive {
36
37namespace {
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).
43const int64 kUploadChunkSize = (1LL << 30);  // 1GB
44}  // namespace
45
46// Structure containing current upload information of file, passed between
47// DriveServiceInterface methods and callbacks.
48struct 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) {
64  }
65
66  ~UploadFileInfo() {
67  }
68
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           "]";
75  }
76
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());
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.
117  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
127DriveUploader::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) {
132}
133
134DriveUploader::~DriveUploader() {}
135
136CancelCallback 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 UploadCompletionCallback& callback,
142    const ProgressCallback& progress_callback) {
143  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
144  DCHECK(!parent_resource_id.empty());
145  DCHECK(!local_file_path.empty());
146  DCHECK(!title.empty());
147  DCHECK(!content_type.empty());
148  DCHECK(!callback.is_null());
149
150  return StartUploadFile(
151      scoped_ptr<UploadFileInfo>(new UploadFileInfo(local_file_path,
152                                                    content_type,
153                                                    callback,
154                                                    progress_callback)),
155      base::Bind(&DriveUploader::StartInitiateUploadNewFile,
156                 weak_ptr_factory_.GetWeakPtr(),
157                 parent_resource_id,
158                 title));
159}
160
161CancelCallback DriveUploader::UploadExistingFile(
162    const std::string& resource_id,
163    const base::FilePath& local_file_path,
164    const std::string& content_type,
165    const std::string& etag,
166    const UploadCompletionCallback& callback,
167    const ProgressCallback& progress_callback) {
168  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
169  DCHECK(!resource_id.empty());
170  DCHECK(!local_file_path.empty());
171  DCHECK(!content_type.empty());
172  DCHECK(!callback.is_null());
173
174  return StartUploadFile(
175      scoped_ptr<UploadFileInfo>(new UploadFileInfo(local_file_path,
176                                                    content_type,
177                                                    callback,
178                                                    progress_callback)),
179      base::Bind(&DriveUploader::StartInitiateUploadExistingFile,
180                 weak_ptr_factory_.GetWeakPtr(),
181                 resource_id,
182                 etag));
183}
184
185CancelCallback DriveUploader::ResumeUploadFile(
186    const GURL& upload_location,
187    const base::FilePath& local_file_path,
188    const std::string& content_type,
189    const UploadCompletionCallback& callback,
190    const ProgressCallback& progress_callback) {
191  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
192  DCHECK(!local_file_path.empty());
193  DCHECK(!content_type.empty());
194  DCHECK(!callback.is_null());
195
196  scoped_ptr<UploadFileInfo> upload_file_info(new UploadFileInfo(
197      local_file_path, content_type,
198      callback, progress_callback));
199  upload_file_info->upload_location = upload_location;
200
201  return StartUploadFile(
202      upload_file_info.Pass(),
203      base::Bind(&DriveUploader::StartGetUploadStatus,
204                 weak_ptr_factory_.GetWeakPtr()));
205}
206
207CancelCallback DriveUploader::StartUploadFile(
208    scoped_ptr<UploadFileInfo> upload_file_info,
209    const StartInitiateUploadCallback& start_initiate_upload_callback) {
210  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
211  DVLOG(1) << "Uploading file: " << upload_file_info->DebugString();
212
213  UploadFileInfo* info_ptr = upload_file_info.get();
214  base::PostTaskAndReplyWithResult(
215      blocking_task_runner_.get(),
216      FROM_HERE,
217      base::Bind(&file_util::GetFileSize,
218                 info_ptr->file_path,
219                 &info_ptr->content_length),
220      base::Bind(&DriveUploader::StartUploadFileAfterGetFileSize,
221                 weak_ptr_factory_.GetWeakPtr(),
222                 base::Passed(&upload_file_info),
223                 start_initiate_upload_callback));
224  return info_ptr->GetCancelCallback();
225}
226
227void DriveUploader::StartUploadFileAfterGetFileSize(
228    scoped_ptr<UploadFileInfo> upload_file_info,
229    const StartInitiateUploadCallback& start_initiate_upload_callback,
230    bool get_file_size_result) {
231  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
232
233  if (!get_file_size_result) {
234    UploadFailed(upload_file_info.Pass(), HTTP_NOT_FOUND);
235    return;
236  }
237  DCHECK_GE(upload_file_info->content_length, 0);
238
239  if (upload_file_info->cancelled) {
240    UploadFailed(upload_file_info.Pass(), GDATA_CANCELLED);
241    return;
242  }
243  start_initiate_upload_callback.Run(upload_file_info.Pass());
244}
245
246void DriveUploader::StartInitiateUploadNewFile(
247    const std::string& parent_resource_id,
248    const std::string& title,
249    scoped_ptr<UploadFileInfo> upload_file_info) {
250  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
251
252  UploadFileInfo* info_ptr = upload_file_info.get();
253  info_ptr->cancel_callback = drive_service_->InitiateUploadNewFile(
254      info_ptr->content_type,
255      info_ptr->content_length,
256      parent_resource_id,
257      title,
258      base::Bind(&DriveUploader::OnUploadLocationReceived,
259                 weak_ptr_factory_.GetWeakPtr(),
260                 base::Passed(&upload_file_info)));
261}
262
263void DriveUploader::StartInitiateUploadExistingFile(
264    const std::string& resource_id,
265    const std::string& etag,
266    scoped_ptr<UploadFileInfo> upload_file_info) {
267  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
268
269  UploadFileInfo* info_ptr = upload_file_info.get();
270  info_ptr->cancel_callback = drive_service_->InitiateUploadExistingFile(
271      info_ptr->content_type,
272      info_ptr->content_length,
273      resource_id,
274      etag,
275      base::Bind(&DriveUploader::OnUploadLocationReceived,
276                 weak_ptr_factory_.GetWeakPtr(),
277                 base::Passed(&upload_file_info)));
278}
279
280void DriveUploader::OnUploadLocationReceived(
281    scoped_ptr<UploadFileInfo> upload_file_info,
282    GDataErrorCode code,
283    const GURL& upload_location) {
284  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
285
286  DVLOG(1) << "Got upload location [" << upload_location.spec()
287           << "] for [" << upload_file_info->file_path.value() << "]";
288
289  if (code != HTTP_SUCCESS) {
290    // TODO(achuith): Handle error codes from Google Docs server.
291    if (code == HTTP_PRECONDITION) {
292      // ETag mismatch.
293      UploadFailed(upload_file_info.Pass(), HTTP_CONFLICT);
294      return;
295    }
296    UploadFailed(upload_file_info.Pass(), code);
297    return;
298  }
299
300  upload_file_info->upload_location = upload_location;
301  upload_file_info->next_start_position = 0;
302  UploadNextChunk(upload_file_info.Pass());
303}
304
305void DriveUploader::StartGetUploadStatus(
306    scoped_ptr<UploadFileInfo> upload_file_info) {
307  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
308  DCHECK(upload_file_info);
309
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)));
317}
318
319void 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);
326
327  if (upload_file_info->cancelled) {
328    UploadFailed(upload_file_info.Pass(), GDATA_CANCELLED);
329    return;
330  }
331
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);
336
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));
353}
354
355void DriveUploader::OnUploadRangeResponseReceived(
356    scoped_ptr<UploadFileInfo> upload_file_info,
357    const UploadRangeResponse& response,
358    scoped_ptr<ResourceEntry> entry) {
359  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
360
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() << "]";
373
374    // Done uploading.
375    upload_file_info->completion_callback.Run(
376        HTTP_SUCCESS, GURL(), entry.Pass());
377    return;
378  }
379
380  // ETag mismatch.
381  if (response.code == HTTP_PRECONDITION) {
382    UploadFailed(upload_file_info.Pass(), HTTP_CONFLICT);
383    return;
384  }
385
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;
399  }
400
401  DVLOG(1) << "Received range " << response.start_position_received
402           << "-" << response.end_position_received
403           << " for [" << upload_file_info->file_path.value() << "]";
404
405  upload_file_info->next_start_position = response.end_position_received;
406  UploadNextChunk(upload_file_info.Pass());
407}
408
409void 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);
416}
417
418void DriveUploader::UploadFailed(scoped_ptr<UploadFileInfo> upload_file_info,
419                                 GDataErrorCode error) {
420  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
421
422  DVLOG(1) << "Upload failed " << upload_file_info->DebugString();
423
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();
428  }
429
430  upload_file_info->completion_callback.Run(
431      error, upload_file_info->upload_location, scoped_ptr<ResourceEntry>());
432}
433
434}  // namespace drive
435