Home | History | Annotate | Download | only in drive
      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 {
     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 
    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 
    135 DriveUploader::~DriveUploader() {}
    136 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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