Home | History | Annotate | Download | only in file_system
      1 // Copyright 2013 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/chromeos/drive/file_system/download_operation.h"
      6 
      7 #include "base/callback_helpers.h"
      8 #include "base/files/file_path.h"
      9 #include "base/files/file_util.h"
     10 #include "base/logging.h"
     11 #include "base/task_runner_util.h"
     12 #include "chrome/browser/chromeos/drive/drive.pb.h"
     13 #include "chrome/browser/chromeos/drive/file_cache.h"
     14 #include "chrome/browser/chromeos/drive/file_change.h"
     15 #include "chrome/browser/chromeos/drive/file_errors.h"
     16 #include "chrome/browser/chromeos/drive/file_system/operation_delegate.h"
     17 #include "chrome/browser/chromeos/drive/file_system_util.h"
     18 #include "chrome/browser/chromeos/drive/job_scheduler.h"
     19 #include "chrome/browser/chromeos/drive/resource_metadata.h"
     20 #include "content/public/browser/browser_thread.h"
     21 #include "google_apis/drive/gdata_errorcode.h"
     22 
     23 using content::BrowserThread;
     24 
     25 namespace drive {
     26 namespace file_system {
     27 namespace {
     28 
     29 // Generates an unused file path with |extension| to |out_path|, as a descendant
     30 // of |dir|, with its parent directory created.
     31 bool GeneratesUniquePathWithExtension(
     32     const base::FilePath& dir,
     33     const base::FilePath::StringType& extension,
     34     base::FilePath* out_path) {
     35   base::FilePath subdir;
     36   if (!base::CreateTemporaryDirInDir(dir, base::FilePath::StringType(),
     37                                      &subdir)) {
     38     return false;
     39   }
     40   *out_path = subdir.Append(FILE_PATH_LITERAL("tmp") + extension);
     41   return true;
     42 }
     43 
     44 // Prepares for downloading the file. Allocates the enough space for the file
     45 // in the cache.
     46 // If succeeded, returns FILE_ERROR_OK with |temp_download_file| storing the
     47 // path to the file in the cache.
     48 FileError PrepareForDownloadFile(internal::FileCache* cache,
     49                                  int64 expected_file_size,
     50                                  const base::FilePath& temporary_file_directory,
     51                                  base::FilePath* temp_download_file) {
     52   DCHECK(cache);
     53   DCHECK(temp_download_file);
     54 
     55   // Ensure enough space in the cache.
     56   if (!cache->FreeDiskSpaceIfNeededFor(expected_file_size))
     57     return FILE_ERROR_NO_LOCAL_SPACE;
     58 
     59   return base::CreateTemporaryFileInDir(
     60       temporary_file_directory,
     61       temp_download_file) ? FILE_ERROR_OK : FILE_ERROR_FAILED;
     62 }
     63 
     64 // If the resource is a hosted document, creates a JSON file representing the
     65 // resource locally, and returns FILE_ERROR_OK with |cache_file_path| storing
     66 // the path to the JSON file.
     67 // If the resource is a regular file and its local cache is available,
     68 // returns FILE_ERROR_OK with |cache_file_path| storing the path to the
     69 // cache file.
     70 // If the resource is a regular file but its local cache is NOT available,
     71 // returns FILE_ERROR_OK, but |cache_file_path| is kept empty.
     72 // Otherwise returns error code.
     73 FileError CheckPreConditionForEnsureFileDownloaded(
     74     internal::ResourceMetadata* metadata,
     75     internal::FileCache* cache,
     76     const base::FilePath& temporary_file_directory,
     77     const std::string& local_id,
     78     ResourceEntry* entry,
     79     base::FilePath* cache_file_path,
     80     base::FilePath* temp_download_file_path) {
     81   DCHECK(metadata);
     82   DCHECK(cache);
     83   DCHECK(cache_file_path);
     84 
     85   FileError error = metadata->GetResourceEntryById(local_id, entry);
     86   if (error != FILE_ERROR_OK)
     87     return error;
     88 
     89   if (entry->file_info().is_directory())
     90     return FILE_ERROR_NOT_A_FILE;
     91 
     92   // For a hosted document, we create a special JSON file to represent the
     93   // document instead of fetching the document content in one of the exported
     94   // formats. The JSON file contains the edit URL and resource ID of the
     95   // document.
     96   if (entry->file_specific_info().is_hosted_document()) {
     97     base::FilePath::StringType extension = base::FilePath::FromUTF8Unsafe(
     98         entry->file_specific_info().document_extension()).value();
     99     base::FilePath gdoc_file_path;
    100     base::File::Info file_info;
    101     // We add the gdoc file extension in the temporary file, so that in cross
    102     // profile drag-and-drop between Drive folders, the destination profiles's
    103     // CopyOperation can detect the special JSON file only by the path.
    104     if (!GeneratesUniquePathWithExtension(temporary_file_directory,
    105                                           extension,
    106                                           &gdoc_file_path) ||
    107         !util::CreateGDocFile(gdoc_file_path,
    108                               GURL(entry->file_specific_info().alternate_url()),
    109                               entry->resource_id()) ||
    110         !base::GetFileInfo(gdoc_file_path,
    111                            reinterpret_cast<base::File::Info*>(&file_info)))
    112       return FILE_ERROR_FAILED;
    113 
    114     *cache_file_path = gdoc_file_path;
    115     entry->mutable_file_info()->set_size(file_info.size);
    116     return FILE_ERROR_OK;
    117   }
    118 
    119   if (!entry->file_specific_info().cache_state().is_present()) {
    120     // This file has no cache file.
    121     if (!entry->resource_id().empty()) {
    122       // This entry exists on the server, leave |cache_file_path| empty to
    123       // start download.
    124       return PrepareForDownloadFile(cache, entry->file_info().size(),
    125                                     temporary_file_directory,
    126                                     temp_download_file_path);
    127     }
    128 
    129     // This entry does not exist on the server, store an empty file and mark it
    130     // as dirty.
    131     base::FilePath empty_file;
    132     if (!base::CreateTemporaryFileInDir(temporary_file_directory, &empty_file))
    133       return FILE_ERROR_FAILED;
    134     error = cache->Store(local_id, std::string(), empty_file,
    135                          internal::FileCache::FILE_OPERATION_MOVE);
    136     if (error != FILE_ERROR_OK)
    137       return error;
    138 
    139     error = metadata->GetResourceEntryById(local_id, entry);
    140     if (error != FILE_ERROR_OK)
    141       return error;
    142   }
    143 
    144   // Leave |cache_file_path| empty when the stored file is obsolete and has no
    145   // local modification.
    146   if (!entry->file_specific_info().cache_state().is_dirty() &&
    147       entry->file_specific_info().md5() !=
    148       entry->file_specific_info().cache_state().md5()) {
    149     return PrepareForDownloadFile(cache, entry->file_info().size(),
    150                                   temporary_file_directory,
    151                                   temp_download_file_path);
    152   }
    153 
    154   // Fill |cache_file_path| with the path to the cached file.
    155   error = cache->GetFile(local_id, cache_file_path);
    156   if (error != FILE_ERROR_OK)
    157     return error;
    158 
    159   // If the cache file is to be returned as the download result, the file info
    160   // of the cache needs to be returned via |entry|.
    161   // TODO(kinaba): crbug.com/246469. The logic below is similar to that in
    162   // drive::FileSystem::CheckLocalModificationAndRun. We should merge them.
    163   base::File::Info file_info;
    164   if (base::GetFileInfo(*cache_file_path, &file_info))
    165     entry->mutable_file_info()->set_size(file_info.size);
    166 
    167   return FILE_ERROR_OK;
    168 }
    169 
    170 struct CheckPreconditionForEnsureFileDownloadedParams {
    171   internal::ResourceMetadata* metadata;
    172   internal::FileCache* cache;
    173   base::FilePath temporary_file_directory;
    174 };
    175 
    176 // Calls CheckPreConditionForEnsureFileDownloaded() with the entry specified by
    177 // the given ID. Also fills |drive_file_path| with the path of the entry.
    178 FileError CheckPreConditionForEnsureFileDownloadedByLocalId(
    179     const CheckPreconditionForEnsureFileDownloadedParams& params,
    180     const std::string& local_id,
    181     base::FilePath* drive_file_path,
    182     base::FilePath* cache_file_path,
    183     base::FilePath* temp_download_file_path,
    184     ResourceEntry* entry) {
    185   FileError error = params.metadata->GetFilePath(local_id, drive_file_path);
    186   if (error != FILE_ERROR_OK)
    187     return error;
    188   return CheckPreConditionForEnsureFileDownloaded(
    189       params.metadata, params.cache, params.temporary_file_directory, local_id,
    190       entry, cache_file_path, temp_download_file_path);
    191 }
    192 
    193 // Calls CheckPreConditionForEnsureFileDownloaded() with the entry specified by
    194 // the given file path.
    195 FileError CheckPreConditionForEnsureFileDownloadedByPath(
    196     const CheckPreconditionForEnsureFileDownloadedParams& params,
    197     const base::FilePath& file_path,
    198     base::FilePath* cache_file_path,
    199     base::FilePath* temp_download_file_path,
    200     ResourceEntry* entry) {
    201   std::string local_id;
    202   FileError error = params.metadata->GetIdByPath(file_path, &local_id);
    203   if (error != FILE_ERROR_OK)
    204     return error;
    205   return CheckPreConditionForEnsureFileDownloaded(
    206       params.metadata, params.cache, params.temporary_file_directory, local_id,
    207       entry, cache_file_path, temp_download_file_path);
    208 }
    209 
    210 // Stores the downloaded file at |downloaded_file_path| into |cache|.
    211 // If succeeded, returns FILE_ERROR_OK with |cache_file_path| storing the
    212 // path to the cache file.
    213 // If failed, returns an error code with deleting |downloaded_file_path|.
    214 FileError UpdateLocalStateForDownloadFile(
    215     internal::ResourceMetadata* metadata,
    216     internal::FileCache* cache,
    217     const ResourceEntry& entry_before_download,
    218     google_apis::GDataErrorCode gdata_error,
    219     const base::FilePath& downloaded_file_path,
    220     ResourceEntry* entry_after_update,
    221     base::FilePath* cache_file_path) {
    222   DCHECK(cache);
    223 
    224   // Downloaded file should be deleted on errors.
    225   base::ScopedClosureRunner file_deleter(base::Bind(
    226       base::IgnoreResult(&base::DeleteFile),
    227       downloaded_file_path, false /* recursive */));
    228 
    229   FileError error = GDataToFileError(gdata_error);
    230   if (error != FILE_ERROR_OK)
    231     return error;
    232 
    233   const std::string& local_id = entry_before_download.local_id();
    234 
    235   // Do not overwrite locally edited file with server side contents.
    236   ResourceEntry entry;
    237   error = metadata->GetResourceEntryById(local_id, &entry);
    238   if (error != FILE_ERROR_OK)
    239     return error;
    240   if (entry.file_specific_info().cache_state().is_dirty())
    241     return FILE_ERROR_IN_USE;
    242 
    243   // Here the download is completed successfully, so store it into the cache.
    244   error = cache->Store(local_id,
    245                        entry_before_download.file_specific_info().md5(),
    246                        downloaded_file_path,
    247                        internal::FileCache::FILE_OPERATION_MOVE);
    248   if (error != FILE_ERROR_OK)
    249     return error;
    250   base::Closure unused_file_deleter_closure = file_deleter.Release();
    251 
    252   error = metadata->GetResourceEntryById(local_id, entry_after_update);
    253   if (error != FILE_ERROR_OK)
    254     return error;
    255 
    256   return cache->GetFile(local_id, cache_file_path);
    257 }
    258 
    259 }  // namespace
    260 
    261 class DownloadOperation::DownloadParams {
    262  public:
    263   DownloadParams(
    264       const GetFileContentInitializedCallback initialized_callback,
    265       const google_apis::GetContentCallback get_content_callback,
    266       const GetFileCallback completion_callback,
    267       scoped_ptr<ResourceEntry> entry)
    268       : initialized_callback_(initialized_callback),
    269         get_content_callback_(get_content_callback),
    270         completion_callback_(completion_callback),
    271         entry_(entry.Pass()),
    272         was_cancelled_(false),
    273         weak_ptr_factory_(this) {
    274     DCHECK(!completion_callback_.is_null());
    275     DCHECK(entry_);
    276   }
    277 
    278   base::Closure GetCancelClosure() {
    279     return base::Bind(&DownloadParams::Cancel, weak_ptr_factory_.GetWeakPtr());
    280   }
    281 
    282   void OnCacheFileFound(const base::FilePath& cache_file_path) {
    283     if (!initialized_callback_.is_null()) {
    284       initialized_callback_.Run(FILE_ERROR_OK, cache_file_path,
    285                                 make_scoped_ptr(new ResourceEntry(*entry_)));
    286     }
    287     completion_callback_.Run(FILE_ERROR_OK, cache_file_path, entry_.Pass());
    288   }
    289 
    290   void OnStartDownloading(const base::Closure& cancel_download_closure) {
    291     cancel_download_closure_ = cancel_download_closure;
    292     if (initialized_callback_.is_null()) {
    293       return;
    294     }
    295 
    296     DCHECK(entry_);
    297     initialized_callback_.Run(FILE_ERROR_OK, base::FilePath(),
    298                               make_scoped_ptr(new ResourceEntry(*entry_)));
    299   }
    300 
    301   void OnError(FileError error) const {
    302     completion_callback_.Run(
    303         error, base::FilePath(), scoped_ptr<ResourceEntry>());
    304   }
    305 
    306   void OnDownloadCompleted(const base::FilePath& cache_file_path,
    307                            scoped_ptr<ResourceEntry> entry) const {
    308     completion_callback_.Run(FILE_ERROR_OK, cache_file_path, entry.Pass());
    309   }
    310 
    311   const google_apis::GetContentCallback& get_content_callback() const {
    312     return get_content_callback_;
    313   }
    314 
    315   const ResourceEntry& entry() const { return *entry_; }
    316 
    317   bool was_cancelled() const { return was_cancelled_; }
    318 
    319  private:
    320   void Cancel() {
    321     was_cancelled_ = true;
    322     if (!cancel_download_closure_.is_null())
    323       cancel_download_closure_.Run();
    324   }
    325 
    326   const GetFileContentInitializedCallback initialized_callback_;
    327   const google_apis::GetContentCallback get_content_callback_;
    328   const GetFileCallback completion_callback_;
    329 
    330   scoped_ptr<ResourceEntry> entry_;
    331   base::Closure cancel_download_closure_;
    332   bool was_cancelled_;
    333 
    334   base::WeakPtrFactory<DownloadParams> weak_ptr_factory_;
    335   DISALLOW_COPY_AND_ASSIGN(DownloadParams);
    336 };
    337 
    338 DownloadOperation::DownloadOperation(
    339     base::SequencedTaskRunner* blocking_task_runner,
    340     OperationDelegate* delegate,
    341     JobScheduler* scheduler,
    342     internal::ResourceMetadata* metadata,
    343     internal::FileCache* cache,
    344     const base::FilePath& temporary_file_directory)
    345     : blocking_task_runner_(blocking_task_runner),
    346       delegate_(delegate),
    347       scheduler_(scheduler),
    348       metadata_(metadata),
    349       cache_(cache),
    350       temporary_file_directory_(temporary_file_directory),
    351       weak_ptr_factory_(this) {
    352 }
    353 
    354 DownloadOperation::~DownloadOperation() {
    355 }
    356 
    357 base::Closure DownloadOperation::EnsureFileDownloadedByLocalId(
    358     const std::string& local_id,
    359     const ClientContext& context,
    360     const GetFileContentInitializedCallback& initialized_callback,
    361     const google_apis::GetContentCallback& get_content_callback,
    362     const GetFileCallback& completion_callback) {
    363   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    364   DCHECK(!completion_callback.is_null());
    365 
    366   CheckPreconditionForEnsureFileDownloadedParams params;
    367   params.metadata = metadata_;
    368   params.cache = cache_;
    369   params.temporary_file_directory = temporary_file_directory_;
    370   base::FilePath* drive_file_path = new base::FilePath;
    371   base::FilePath* cache_file_path = new base::FilePath;
    372   base::FilePath* temp_download_file_path = new base::FilePath;
    373   ResourceEntry* entry = new ResourceEntry;
    374   scoped_ptr<DownloadParams> download_params(new DownloadParams(
    375       initialized_callback, get_content_callback, completion_callback,
    376       make_scoped_ptr(entry)));
    377   base::Closure cancel_closure = download_params->GetCancelClosure();
    378   base::PostTaskAndReplyWithResult(
    379       blocking_task_runner_.get(),
    380       FROM_HERE,
    381       base::Bind(&CheckPreConditionForEnsureFileDownloadedByLocalId,
    382                  params,
    383                  local_id,
    384                  drive_file_path,
    385                  cache_file_path,
    386                  temp_download_file_path,
    387                  entry),
    388       base::Bind(&DownloadOperation::EnsureFileDownloadedAfterCheckPreCondition,
    389                  weak_ptr_factory_.GetWeakPtr(),
    390                  base::Passed(&download_params),
    391                  context,
    392                  base::Owned(drive_file_path),
    393                  base::Owned(cache_file_path),
    394                  base::Owned(temp_download_file_path)));
    395   return cancel_closure;
    396 }
    397 
    398 base::Closure DownloadOperation::EnsureFileDownloadedByPath(
    399     const base::FilePath& file_path,
    400     const ClientContext& context,
    401     const GetFileContentInitializedCallback& initialized_callback,
    402     const google_apis::GetContentCallback& get_content_callback,
    403     const GetFileCallback& completion_callback) {
    404   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    405   DCHECK(!completion_callback.is_null());
    406 
    407   CheckPreconditionForEnsureFileDownloadedParams params;
    408   params.metadata = metadata_;
    409   params.cache = cache_;
    410   params.temporary_file_directory = temporary_file_directory_;
    411   base::FilePath* drive_file_path = new base::FilePath(file_path);
    412   base::FilePath* cache_file_path = new base::FilePath;
    413   base::FilePath* temp_download_file_path = new base::FilePath;
    414   ResourceEntry* entry = new ResourceEntry;
    415   scoped_ptr<DownloadParams> download_params(new DownloadParams(
    416       initialized_callback, get_content_callback, completion_callback,
    417       make_scoped_ptr(entry)));
    418   base::Closure cancel_closure = download_params->GetCancelClosure();
    419   base::PostTaskAndReplyWithResult(
    420       blocking_task_runner_.get(),
    421       FROM_HERE,
    422       base::Bind(&CheckPreConditionForEnsureFileDownloadedByPath,
    423                  params,
    424                  file_path,
    425                  cache_file_path,
    426                  temp_download_file_path,
    427                  entry),
    428       base::Bind(&DownloadOperation::EnsureFileDownloadedAfterCheckPreCondition,
    429                  weak_ptr_factory_.GetWeakPtr(),
    430                  base::Passed(&download_params),
    431                  context,
    432                  base::Owned(drive_file_path),
    433                  base::Owned(cache_file_path),
    434                  base::Owned(temp_download_file_path)));
    435   return cancel_closure;
    436 }
    437 
    438 void DownloadOperation::EnsureFileDownloadedAfterCheckPreCondition(
    439     scoped_ptr<DownloadParams> params,
    440     const ClientContext& context,
    441     base::FilePath* drive_file_path,
    442     base::FilePath* cache_file_path,
    443     base::FilePath* temp_download_file_path,
    444     FileError error) {
    445   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    446   DCHECK(params);
    447   DCHECK(drive_file_path);
    448   DCHECK(cache_file_path);
    449 
    450   if (error != FILE_ERROR_OK) {
    451     // During precondition check, an error is found.
    452     params->OnError(error);
    453     return;
    454   }
    455 
    456   if (!cache_file_path->empty()) {
    457     // The cache file is found.
    458     params->OnCacheFileFound(*cache_file_path);
    459     return;
    460   }
    461 
    462   if (params->was_cancelled()) {
    463     params->OnError(FILE_ERROR_ABORT);
    464     return;
    465   }
    466 
    467   DCHECK(!params->entry().resource_id().empty());
    468   DownloadParams* params_ptr = params.get();
    469   JobID id = scheduler_->DownloadFile(
    470       *drive_file_path,
    471       params_ptr->entry().file_info().size(),
    472       *temp_download_file_path,
    473       params_ptr->entry().resource_id(),
    474       context,
    475       base::Bind(&DownloadOperation::EnsureFileDownloadedAfterDownloadFile,
    476                  weak_ptr_factory_.GetWeakPtr(),
    477                  *drive_file_path,
    478                  base::Passed(&params)),
    479       params_ptr->get_content_callback());
    480 
    481   // Notify via |initialized_callback| if necessary.
    482   params_ptr->OnStartDownloading(
    483       base::Bind(&DownloadOperation::CancelJob,
    484                  weak_ptr_factory_.GetWeakPtr(), id));
    485 }
    486 
    487 void DownloadOperation::EnsureFileDownloadedAfterDownloadFile(
    488     const base::FilePath& drive_file_path,
    489     scoped_ptr<DownloadParams> params,
    490     google_apis::GDataErrorCode gdata_error,
    491     const base::FilePath& downloaded_file_path) {
    492   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    493 
    494   DownloadParams* params_ptr = params.get();
    495   ResourceEntry* entry_after_update = new ResourceEntry;
    496   base::FilePath* cache_file_path = new base::FilePath;
    497   base::PostTaskAndReplyWithResult(
    498       blocking_task_runner_.get(),
    499       FROM_HERE,
    500       base::Bind(&UpdateLocalStateForDownloadFile,
    501                  metadata_,
    502                  cache_,
    503                  params_ptr->entry(),
    504                  gdata_error,
    505                  downloaded_file_path,
    506                  entry_after_update,
    507                  cache_file_path),
    508       base::Bind(&DownloadOperation::EnsureFileDownloadedAfterUpdateLocalState,
    509                  weak_ptr_factory_.GetWeakPtr(),
    510                  drive_file_path,
    511                  base::Passed(&params),
    512                  base::Passed(make_scoped_ptr(entry_after_update)),
    513                  base::Owned(cache_file_path)));
    514 }
    515 
    516 void DownloadOperation::EnsureFileDownloadedAfterUpdateLocalState(
    517     const base::FilePath& file_path,
    518     scoped_ptr<DownloadParams> params,
    519     scoped_ptr<ResourceEntry> entry_after_update,
    520     base::FilePath* cache_file_path,
    521     FileError error) {
    522   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    523 
    524   if (error != FILE_ERROR_OK) {
    525     params->OnError(error);
    526     return;
    527   }
    528   DCHECK(!entry_after_update->file_info().is_directory());
    529 
    530   FileChange changed_files;
    531   changed_files.Update(
    532       file_path, FileChange::FILE_TYPE_FILE, FileChange::ADD_OR_UPDATE);
    533   // Storing to cache changes the "offline available" status, hence notify.
    534   delegate_->OnFileChangedByOperation(changed_files);
    535   params->OnDownloadCompleted(*cache_file_path, entry_after_update.Pass());
    536 }
    537 
    538 void DownloadOperation::CancelJob(JobID job_id) {
    539   scheduler_->CancelJob(job_id);
    540 }
    541 
    542 }  // namespace file_system
    543 }  // namespace drive
    544