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