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
19using content::BrowserThread;
20using google_apis::CancelCallback;
21using google_apis::FileResource;
22using google_apis::GDATA_CANCELLED;
23using google_apis::GDataErrorCode;
24using google_apis::GDATA_NO_SPACE;
25using google_apis::HTTP_CONFLICT;
26using google_apis::HTTP_CREATED;
27using google_apis::HTTP_FORBIDDEN;
28using google_apis::HTTP_NOT_FOUND;
29using google_apis::HTTP_PRECONDITION;
30using google_apis::HTTP_RESUME_INCOMPLETE;
31using google_apis::HTTP_SUCCESS;
32using google_apis::ProgressCallback;
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(
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
135DriveUploader::~DriveUploader() {}
136
137CancelCallback 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
164CancelCallback 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
188CancelCallback 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
210CancelCallback 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
230void 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
249void 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
268void 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
285void 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
306void 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
320void 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
356void 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
410void 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
419void 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