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/file_util.h"
      8 #include "base/files/file_path.h"
      9 #include "base/logging.h"
     10 #include "base/task_runner_util.h"
     11 #include "chrome/browser/chromeos/drive/drive.pb.h"
     12 #include "chrome/browser/chromeos/drive/file_cache.h"
     13 #include "chrome/browser/chromeos/drive/file_errors.h"
     14 #include "chrome/browser/chromeos/drive/file_system/operation_observer.h"
     15 #include "chrome/browser/chromeos/drive/file_system_util.h"
     16 #include "chrome/browser/chromeos/drive/job_scheduler.h"
     17 #include "chrome/browser/chromeos/drive/resource_entry_conversion.h"
     18 #include "chrome/browser/chromeos/drive/resource_metadata.h"
     19 #include "content/public/browser/browser_thread.h"
     20 #include "google_apis/drive/gdata_errorcode.h"
     21 
     22 using content::BrowserThread;
     23 
     24 namespace drive {
     25 namespace file_system {
     26 namespace {
     27 
     28 // If the resource is a hosted document, creates a JSON file representing the
     29 // resource locally, and returns FILE_ERROR_OK with |cache_file_path| storing
     30 // the path to the JSON file.
     31 // If the resource is a regular file and its local cache is available,
     32 // returns FILE_ERROR_OK with |cache_file_path| storing the path to the
     33 // cache file.
     34 // If the resource is a regular file but its local cache is NOT available,
     35 // returns FILE_ERROR_OK, but |cache_file_path| is kept empty.
     36 // Otherwise returns error code.
     37 FileError CheckPreConditionForEnsureFileDownloaded(
     38     internal::ResourceMetadata* metadata,
     39     internal::FileCache* cache,
     40     const base::FilePath& temporary_file_directory,
     41     const std::string& local_id,
     42     ResourceEntry* entry,
     43     base::FilePath* cache_file_path) {
     44   DCHECK(metadata);
     45   DCHECK(cache);
     46   DCHECK(cache_file_path);
     47 
     48   FileError error = metadata->GetResourceEntryById(local_id, entry);
     49   if (error != FILE_ERROR_OK)
     50     return error;
     51 
     52   if (entry->file_info().is_directory())
     53     return FILE_ERROR_NOT_A_FILE;
     54 
     55   // The file's entry should have its file specific info.
     56   DCHECK(entry->has_file_specific_info());
     57 
     58   // For a hosted document, we create a special JSON file to represent the
     59   // document instead of fetching the document content in one of the exported
     60   // formats. The JSON file contains the edit URL and resource ID of the
     61   // document.
     62   if (entry->file_specific_info().is_hosted_document()) {
     63     base::FilePath gdoc_file_path;
     64     base::PlatformFileInfo file_info;
     65     if (!base::CreateTemporaryFileInDir(temporary_file_directory,
     66                                         &gdoc_file_path) ||
     67         !util::CreateGDocFile(gdoc_file_path,
     68                               GURL(entry->file_specific_info().alternate_url()),
     69                               entry->resource_id()) ||
     70         !base::GetFileInfo(gdoc_file_path, &file_info))
     71       return FILE_ERROR_FAILED;
     72 
     73     *cache_file_path = gdoc_file_path;
     74     SetPlatformFileInfoToResourceEntry(file_info, entry);
     75     return FILE_ERROR_OK;
     76   }
     77 
     78   // Leave |cache_file_path| empty when no cache entry is found.
     79   FileCacheEntry cache_entry;
     80   if (!cache->GetCacheEntry(local_id, &cache_entry))
     81     return FILE_ERROR_OK;
     82 
     83   // Leave |cache_file_path| empty when the stored file is obsolete and has no
     84   // local modification.
     85   if (!cache_entry.is_dirty() &&
     86       entry->file_specific_info().md5() != cache_entry.md5())
     87     return FILE_ERROR_OK;
     88 
     89   // Fill |cache_file_path| with the path to the cached file.
     90   error = cache->GetFile(local_id, cache_file_path);
     91   if (error != FILE_ERROR_OK)
     92     return error;
     93 
     94   // If the cache file is dirty, the modified file info needs to be stored in
     95   // |entry|.
     96   // TODO(kinaba): crbug.com/246469. The logic below is a duplicate of that in
     97   // drive::FileSystem::CheckLocalModificationAndRun. We should merge them once
     98   // the drive::FS side is also converted to run fully on blocking pool.
     99   if (cache_entry.is_dirty()) {
    100     base::PlatformFileInfo file_info;
    101     if (base::GetFileInfo(*cache_file_path, &file_info))
    102       SetPlatformFileInfoToResourceEntry(file_info, entry);
    103   }
    104 
    105   return FILE_ERROR_OK;
    106 }
    107 
    108 // Calls CheckPreConditionForEnsureFileDownloaded() with the entry specified by
    109 // the given ID. Also fills |drive_file_path| with the path of the entry.
    110 FileError CheckPreConditionForEnsureFileDownloadedByLocalId(
    111     internal::ResourceMetadata* metadata,
    112     internal::FileCache* cache,
    113     const std::string& local_id,
    114     const base::FilePath& temporary_file_directory,
    115     base::FilePath* drive_file_path,
    116     base::FilePath* cache_file_path,
    117     ResourceEntry* entry) {
    118   *drive_file_path = metadata->GetFilePath(local_id);
    119   return CheckPreConditionForEnsureFileDownloaded(
    120       metadata, cache, temporary_file_directory, local_id, entry,
    121       cache_file_path);
    122 }
    123 
    124 // Calls CheckPreConditionForEnsureFileDownloaded() with the entry specified by
    125 // the given file path.
    126 FileError CheckPreConditionForEnsureFileDownloadedByPath(
    127     internal::ResourceMetadata* metadata,
    128     internal::FileCache* cache,
    129     const base::FilePath& file_path,
    130     const base::FilePath& temporary_file_directory,
    131     base::FilePath* cache_file_path,
    132     ResourceEntry* entry) {
    133   std::string local_id;
    134   FileError error = metadata->GetIdByPath(file_path, &local_id);
    135   if (error != FILE_ERROR_OK)
    136     return error;
    137   return CheckPreConditionForEnsureFileDownloaded(
    138       metadata, cache, temporary_file_directory, local_id, entry,
    139       cache_file_path);
    140 }
    141 
    142 // Creates a file with unique name in |dir| and stores the path to |temp_file|.
    143 // Additionally, sets the permission of the file to allow read access from
    144 // others and group member users (i.e, "-rw-r--r--").
    145 // We need this wrapper because Drive cache files may be read from other
    146 // processes (e.g., cros_disks for mounting zip files).
    147 bool CreateTemporaryReadableFileInDir(const base::FilePath& dir,
    148                                       base::FilePath* temp_file) {
    149   if (!base::CreateTemporaryFileInDir(dir, temp_file))
    150     return false;
    151   return base::SetPosixFilePermissions(
    152       *temp_file,
    153       base::FILE_PERMISSION_READ_BY_USER |
    154       base::FILE_PERMISSION_WRITE_BY_USER |
    155       base::FILE_PERMISSION_READ_BY_GROUP |
    156       base::FILE_PERMISSION_READ_BY_OTHERS);
    157 }
    158 
    159 // Prepares for downloading the file. Allocates the enough space for the file
    160 // in the cache.
    161 // If succeeded, returns FILE_ERROR_OK with |temp_download_file| storing the
    162 // path to the file in the cache.
    163 FileError PrepareForDownloadFile(internal::FileCache* cache,
    164                                  int64 expected_file_size,
    165                                  const base::FilePath& temporary_file_directory,
    166                                  base::FilePath* temp_download_file) {
    167   DCHECK(cache);
    168   DCHECK(temp_download_file);
    169 
    170   // Ensure enough space in the cache.
    171   if (!cache->FreeDiskSpaceIfNeededFor(expected_file_size))
    172     return FILE_ERROR_NO_LOCAL_SPACE;
    173 
    174   // Create the temporary file which will store the downloaded content.
    175   return CreateTemporaryReadableFileInDir(
    176       temporary_file_directory,
    177       temp_download_file) ? FILE_ERROR_OK : FILE_ERROR_FAILED;
    178 }
    179 
    180 // Stores the downloaded file at |downloaded_file_path| into |cache|.
    181 // If succeeded, returns FILE_ERROR_OK with |cache_file_path| storing the
    182 // path to the cache file.
    183 // If failed, returns an error code with deleting |downloaded_file_path|.
    184 FileError UpdateLocalStateForDownloadFile(
    185     internal::FileCache* cache,
    186     const std::string& local_id,
    187     const std::string& md5,
    188     google_apis::GDataErrorCode gdata_error,
    189     const base::FilePath& downloaded_file_path,
    190     base::FilePath* cache_file_path) {
    191   DCHECK(cache);
    192 
    193   FileError error = GDataToFileError(gdata_error);
    194   if (error != FILE_ERROR_OK) {
    195     base::DeleteFile(downloaded_file_path, false /* recursive */);
    196     return error;
    197   }
    198 
    199   // Here the download is completed successfully, so store it into the cache.
    200   error = cache->Store(local_id, md5, downloaded_file_path,
    201                        internal::FileCache::FILE_OPERATION_MOVE);
    202   if (error != FILE_ERROR_OK) {
    203     base::DeleteFile(downloaded_file_path, false /* recursive */);
    204     return error;
    205   }
    206 
    207   return cache->GetFile(local_id, cache_file_path);
    208 }
    209 
    210 }  // namespace
    211 
    212 class DownloadOperation::DownloadParams {
    213  public:
    214   DownloadParams(
    215       const GetFileContentInitializedCallback initialized_callback,
    216       const google_apis::GetContentCallback get_content_callback,
    217       const GetFileCallback completion_callback,
    218       scoped_ptr<ResourceEntry> entry)
    219       : initialized_callback_(initialized_callback),
    220         get_content_callback_(get_content_callback),
    221         completion_callback_(completion_callback),
    222         entry_(entry.Pass()) {
    223     DCHECK(!completion_callback_.is_null());
    224     DCHECK(entry_);
    225   }
    226 
    227   void OnCacheFileFound(const base::FilePath& cache_file_path) const {
    228     if (initialized_callback_.is_null())
    229       return;
    230 
    231     DCHECK(entry_);
    232     initialized_callback_.Run(
    233         FILE_ERROR_OK, make_scoped_ptr(new ResourceEntry(*entry_)),
    234         cache_file_path, base::Closure());
    235   }
    236 
    237   void OnStartDownloading(const base::Closure& cancel_download_closure) const {
    238     if (initialized_callback_.is_null()) {
    239       return;
    240     }
    241 
    242     DCHECK(entry_);
    243     initialized_callback_.Run(
    244         FILE_ERROR_OK, make_scoped_ptr(new ResourceEntry(*entry_)),
    245         base::FilePath(), cancel_download_closure);
    246   }
    247 
    248   void OnError(FileError error) const {
    249     completion_callback_.Run(
    250         error, base::FilePath(), scoped_ptr<ResourceEntry>());
    251   }
    252 
    253   void OnComplete(const base::FilePath& cache_file_path) {
    254     completion_callback_.Run(FILE_ERROR_OK, cache_file_path, entry_.Pass());
    255   }
    256 
    257   const google_apis::GetContentCallback& get_content_callback() const {
    258     return get_content_callback_;
    259   }
    260 
    261   const ResourceEntry& entry() const { return *entry_; }
    262 
    263  private:
    264   const GetFileContentInitializedCallback initialized_callback_;
    265   const google_apis::GetContentCallback get_content_callback_;
    266   const GetFileCallback completion_callback_;
    267 
    268   scoped_ptr<ResourceEntry> entry_;
    269 
    270   DISALLOW_COPY_AND_ASSIGN(DownloadParams);
    271 };
    272 
    273 DownloadOperation::DownloadOperation(
    274     base::SequencedTaskRunner* blocking_task_runner,
    275     OperationObserver* observer,
    276     JobScheduler* scheduler,
    277     internal::ResourceMetadata* metadata,
    278     internal::FileCache* cache,
    279     const base::FilePath& temporary_file_directory)
    280     : blocking_task_runner_(blocking_task_runner),
    281       observer_(observer),
    282       scheduler_(scheduler),
    283       metadata_(metadata),
    284       cache_(cache),
    285       temporary_file_directory_(temporary_file_directory),
    286       weak_ptr_factory_(this) {
    287 }
    288 
    289 DownloadOperation::~DownloadOperation() {
    290 }
    291 
    292 void DownloadOperation::EnsureFileDownloadedByLocalId(
    293     const std::string& local_id,
    294     const ClientContext& context,
    295     const GetFileContentInitializedCallback& initialized_callback,
    296     const google_apis::GetContentCallback& get_content_callback,
    297     const GetFileCallback& completion_callback) {
    298   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    299   DCHECK(!completion_callback.is_null());
    300 
    301   base::FilePath* drive_file_path = new base::FilePath;
    302   base::FilePath* cache_file_path = new base::FilePath;
    303   ResourceEntry* entry = new ResourceEntry;
    304   scoped_ptr<DownloadParams> params(new DownloadParams(
    305       initialized_callback, get_content_callback, completion_callback,
    306       make_scoped_ptr(entry)));
    307   base::PostTaskAndReplyWithResult(
    308       blocking_task_runner_.get(),
    309       FROM_HERE,
    310       base::Bind(&CheckPreConditionForEnsureFileDownloadedByLocalId,
    311                  base::Unretained(metadata_),
    312                  base::Unretained(cache_),
    313                  local_id,
    314                  temporary_file_directory_,
    315                  drive_file_path,
    316                  cache_file_path,
    317                  entry),
    318       base::Bind(&DownloadOperation::EnsureFileDownloadedAfterCheckPreCondition,
    319                  weak_ptr_factory_.GetWeakPtr(),
    320                  base::Passed(&params),
    321                  context,
    322                  base::Owned(drive_file_path),
    323                  base::Owned(cache_file_path)));
    324 }
    325 
    326 void DownloadOperation::EnsureFileDownloadedByPath(
    327     const base::FilePath& file_path,
    328     const ClientContext& context,
    329     const GetFileContentInitializedCallback& initialized_callback,
    330     const google_apis::GetContentCallback& get_content_callback,
    331     const GetFileCallback& completion_callback) {
    332   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    333   DCHECK(!completion_callback.is_null());
    334 
    335   base::FilePath* drive_file_path = new base::FilePath(file_path);
    336   base::FilePath* cache_file_path = new base::FilePath;
    337   ResourceEntry* entry = new ResourceEntry;
    338   scoped_ptr<DownloadParams> params(new DownloadParams(
    339       initialized_callback, get_content_callback, completion_callback,
    340       make_scoped_ptr(entry)));
    341   base::PostTaskAndReplyWithResult(
    342       blocking_task_runner_.get(),
    343       FROM_HERE,
    344       base::Bind(&CheckPreConditionForEnsureFileDownloadedByPath,
    345                  base::Unretained(metadata_),
    346                  base::Unretained(cache_),
    347                  file_path,
    348                  temporary_file_directory_,
    349                  cache_file_path,
    350                  entry),
    351       base::Bind(&DownloadOperation::EnsureFileDownloadedAfterCheckPreCondition,
    352                  weak_ptr_factory_.GetWeakPtr(),
    353                  base::Passed(&params),
    354                  context,
    355                  base::Owned(drive_file_path),
    356                  base::Owned(cache_file_path)));
    357 }
    358 
    359 void DownloadOperation::EnsureFileDownloadedAfterCheckPreCondition(
    360     scoped_ptr<DownloadParams> params,
    361     const ClientContext& context,
    362     base::FilePath* drive_file_path,
    363     base::FilePath* cache_file_path,
    364     FileError error) {
    365   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    366   DCHECK(params);
    367   DCHECK(drive_file_path);
    368   DCHECK(cache_file_path);
    369 
    370   if (error != FILE_ERROR_OK) {
    371     // During precondition check, an error is found.
    372     params->OnError(error);
    373     return;
    374   }
    375 
    376   if (!cache_file_path->empty()) {
    377     // The cache file is found.
    378     params->OnCacheFileFound(*cache_file_path);
    379     params->OnComplete(*cache_file_path);
    380     return;
    381   }
    382 
    383   // If cache file is not found, try to download the file from the server
    384   // instead. Check if we have enough space, based on the expected file size.
    385   // - if we don't have enough space, try to free up the disk space
    386   // - if we still don't have enough space, return "no space" error
    387   // - if we have enough space, start downloading the file from the server
    388   int64 size = params->entry().file_info().size();
    389   base::FilePath* temp_download_file_path = new base::FilePath;
    390   base::PostTaskAndReplyWithResult(
    391       blocking_task_runner_.get(),
    392       FROM_HERE,
    393       base::Bind(&PrepareForDownloadFile,
    394                  base::Unretained(cache_),
    395                  size,
    396                  temporary_file_directory_,
    397                  temp_download_file_path),
    398       base::Bind(
    399           &DownloadOperation::EnsureFileDownloadedAfterPrepareForDownloadFile,
    400           weak_ptr_factory_.GetWeakPtr(),
    401           base::Passed(&params),
    402           context,
    403           *drive_file_path,
    404           base::Owned(temp_download_file_path)));
    405 }
    406 
    407 void DownloadOperation::EnsureFileDownloadedAfterPrepareForDownloadFile(
    408     scoped_ptr<DownloadParams> params,
    409     const ClientContext& context,
    410     const base::FilePath& drive_file_path,
    411     base::FilePath* temp_download_file_path,
    412     FileError error) {
    413   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    414   DCHECK(params);
    415   DCHECK(temp_download_file_path);
    416 
    417   if (error != FILE_ERROR_OK) {
    418     params->OnError(error);
    419     return;
    420   }
    421 
    422   DownloadParams* params_ptr = params.get();
    423   JobID id = scheduler_->DownloadFile(
    424       drive_file_path,
    425       params_ptr->entry().file_info().size(),
    426       *temp_download_file_path,
    427       params_ptr->entry().resource_id(),
    428       context,
    429       base::Bind(&DownloadOperation::EnsureFileDownloadedAfterDownloadFile,
    430                  weak_ptr_factory_.GetWeakPtr(),
    431                  drive_file_path,
    432                  base::Passed(&params)),
    433       params_ptr->get_content_callback());
    434 
    435   // Notify via |initialized_callback| if necessary.
    436   params_ptr->OnStartDownloading(
    437       base::Bind(&DownloadOperation::CancelJob,
    438                  weak_ptr_factory_.GetWeakPtr(), id));
    439 }
    440 
    441 void DownloadOperation::EnsureFileDownloadedAfterDownloadFile(
    442     const base::FilePath& drive_file_path,
    443     scoped_ptr<DownloadParams> params,
    444     google_apis::GDataErrorCode gdata_error,
    445     const base::FilePath& downloaded_file_path) {
    446   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    447 
    448   const std::string& local_id = params->entry().local_id();
    449   const std::string& md5 = params->entry().file_specific_info().md5();
    450   base::FilePath* cache_file_path = new base::FilePath;
    451   base::PostTaskAndReplyWithResult(
    452       blocking_task_runner_.get(),
    453       FROM_HERE,
    454       base::Bind(&UpdateLocalStateForDownloadFile,
    455                  base::Unretained(cache_),
    456                  local_id,
    457                  md5,
    458                  gdata_error,
    459                  downloaded_file_path,
    460                  cache_file_path),
    461       base::Bind(&DownloadOperation::EnsureFileDownloadedAfterUpdateLocalState,
    462                  weak_ptr_factory_.GetWeakPtr(),
    463                  drive_file_path,
    464                  base::Passed(&params),
    465                  base::Owned(cache_file_path)));
    466 }
    467 
    468 void DownloadOperation::EnsureFileDownloadedAfterUpdateLocalState(
    469     const base::FilePath& file_path,
    470     scoped_ptr<DownloadParams> params,
    471     base::FilePath* cache_file_path,
    472     FileError error) {
    473   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    474 
    475   if (error != FILE_ERROR_OK) {
    476     params->OnError(error);
    477     return;
    478   }
    479 
    480   // Storing to cache changes the "offline available" status, hence notify.
    481   observer_->OnDirectoryChangedByOperation(file_path.DirName());
    482   params->OnComplete(*cache_file_path);
    483 }
    484 
    485 void DownloadOperation::CancelJob(JobID job_id) {
    486   scheduler_->CancelJob(job_id);
    487 }
    488 
    489 }  // namespace file_system
    490 }  // namespace drive
    491