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