Home | History | Annotate | Download | only in file_system
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/chromeos/drive/file_system/copy_operation.h"
      6 
      7 #include <string>
      8 
      9 #include "base/file_util.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_system/create_file_operation.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/drive/drive_api_util.h"
     20 #include "content/public/browser/browser_thread.h"
     21 #include "google_apis/drive/drive_api_parser.h"
     22 
     23 using content::BrowserThread;
     24 
     25 namespace drive {
     26 namespace file_system {
     27 
     28 namespace {
     29 
     30 FileError PrepareCopy(internal::ResourceMetadata* metadata,
     31                       const base::FilePath& src_path,
     32                       const base::FilePath& dest_path,
     33                       ResourceEntry* src_entry,
     34                       std::string* parent_resource_id) {
     35   FileError error = metadata->GetResourceEntryByPath(src_path, src_entry);
     36   if (error != FILE_ERROR_OK)
     37     return error;
     38 
     39   ResourceEntry parent_entry;
     40   error = metadata->GetResourceEntryByPath(dest_path.DirName(), &parent_entry);
     41   if (error != FILE_ERROR_OK)
     42     return error;
     43 
     44   if (!parent_entry.file_info().is_directory())
     45     return FILE_ERROR_NOT_A_DIRECTORY;
     46 
     47   // Drive File System doesn't support recursive copy.
     48   if (src_entry->file_info().is_directory())
     49     return FILE_ERROR_NOT_A_FILE;
     50 
     51   *parent_resource_id = parent_entry.resource_id();
     52   return FILE_ERROR_OK;
     53 }
     54 
     55 int64 GetFileSize(const base::FilePath& file_path) {
     56   int64 file_size;
     57   if (!base::GetFileSize(file_path, &file_size))
     58     return -1;
     59   return file_size;
     60 }
     61 
     62 // Stores the copied entry and returns its path.
     63 FileError UpdateLocalStateForServerSideCopy(
     64     internal::ResourceMetadata* metadata,
     65     scoped_ptr<google_apis::ResourceEntry> resource_entry,
     66     base::FilePath* file_path) {
     67   DCHECK(resource_entry);
     68 
     69   ResourceEntry entry;
     70   std::string parent_resource_id;
     71   if (!ConvertToResourceEntry(*resource_entry, &entry, &parent_resource_id))
     72     return FILE_ERROR_NOT_A_FILE;
     73 
     74   std::string parent_local_id;
     75   FileError error = metadata->GetIdByResourceId(parent_resource_id,
     76                                                 &parent_local_id);
     77   if (error != FILE_ERROR_OK)
     78     return error;
     79   entry.set_parent_local_id(parent_local_id);
     80 
     81   std::string local_id;
     82   error = metadata->AddEntry(entry, &local_id);
     83   // Depending on timing, the metadata may have inserted via change list
     84   // already. So, FILE_ERROR_EXISTS is not an error.
     85   if (error == FILE_ERROR_EXISTS)
     86     error = metadata->GetIdByResourceId(entry.resource_id(), &local_id);
     87 
     88   if (error == FILE_ERROR_OK)
     89     *file_path = metadata->GetFilePath(local_id);
     90 
     91   return error;
     92 }
     93 
     94 // Stores the file at |local_file_path| to the cache as a content of entry at
     95 // |remote_dest_path|, and marks it dirty.
     96 FileError UpdateLocalStateForScheduleTransfer(
     97     internal::ResourceMetadata* metadata,
     98     internal::FileCache* cache,
     99     const base::FilePath& local_src_path,
    100     const base::FilePath& remote_dest_path,
    101     std::string* local_id) {
    102   FileError error = metadata->GetIdByPath(remote_dest_path, local_id);
    103   if (error != FILE_ERROR_OK)
    104     return error;
    105 
    106   ResourceEntry entry;
    107   error = metadata->GetResourceEntryById(*local_id, &entry);
    108   if (error != FILE_ERROR_OK)
    109     return error;
    110 
    111   error = cache->Store(
    112       *local_id, entry.file_specific_info().md5(), local_src_path,
    113       internal::FileCache::FILE_OPERATION_COPY);
    114   if (error != FILE_ERROR_OK)
    115     return error;
    116 
    117   error = cache->MarkDirty(*local_id);
    118   if (error != FILE_ERROR_OK)
    119     return error;
    120 
    121   return FILE_ERROR_OK;
    122 }
    123 
    124 // Gets the file size of the |local_path|, and the ResourceEntry for the parent
    125 // of |remote_path| to prepare the necessary information for transfer.
    126 FileError PrepareTransferFileFromLocalToRemote(
    127     internal::ResourceMetadata* metadata,
    128     const base::FilePath& local_src_path,
    129     const base::FilePath& remote_dest_path,
    130     std::string* gdoc_resource_id,
    131     std::string* parent_resource_id) {
    132   ResourceEntry parent_entry;
    133   FileError error = metadata->GetResourceEntryByPath(
    134       remote_dest_path.DirName(), &parent_entry);
    135   if (error != FILE_ERROR_OK)
    136     return error;
    137 
    138   // The destination's parent must be a directory.
    139   if (!parent_entry.file_info().is_directory())
    140     return FILE_ERROR_NOT_A_DIRECTORY;
    141 
    142   // Try to parse GDoc File and extract the resource id, if necessary.
    143   // Failing isn't problem. It'd be handled as a regular file, then.
    144   if (util::HasGDocFileExtension(local_src_path)) {
    145     *gdoc_resource_id = util::ReadResourceIdFromGDocFile(local_src_path);
    146     *parent_resource_id = parent_entry.resource_id();
    147   }
    148 
    149   return FILE_ERROR_OK;
    150 }
    151 
    152 }  // namespace
    153 
    154 struct CopyOperation::CopyParams {
    155   base::FilePath dest_file_path;
    156   bool preserve_last_modified;
    157   FileOperationCallback callback;
    158 };
    159 
    160 CopyOperation::CopyOperation(base::SequencedTaskRunner* blocking_task_runner,
    161                              OperationObserver* observer,
    162                              JobScheduler* scheduler,
    163                              internal::ResourceMetadata* metadata,
    164                              internal::FileCache* cache,
    165                              const ResourceIdCanonicalizer& id_canonicalizer)
    166   : blocking_task_runner_(blocking_task_runner),
    167     observer_(observer),
    168     scheduler_(scheduler),
    169     metadata_(metadata),
    170     cache_(cache),
    171     id_canonicalizer_(id_canonicalizer),
    172     create_file_operation_(new CreateFileOperation(blocking_task_runner,
    173                                                    observer,
    174                                                    scheduler,
    175                                                    metadata,
    176                                                    cache)),
    177     weak_ptr_factory_(this) {
    178   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    179 }
    180 
    181 CopyOperation::~CopyOperation() {
    182   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    183 }
    184 
    185 void CopyOperation::Copy(const base::FilePath& src_file_path,
    186                          const base::FilePath& dest_file_path,
    187                          bool preserve_last_modified,
    188                          const FileOperationCallback& callback) {
    189   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    190   DCHECK(!callback.is_null());
    191 
    192   CopyParams params;
    193   params.dest_file_path = dest_file_path;
    194   params.preserve_last_modified = preserve_last_modified;
    195   params.callback = callback;
    196 
    197   ResourceEntry* src_entry = new ResourceEntry;
    198   std::string* parent_resource_id = new std::string;
    199   base::PostTaskAndReplyWithResult(
    200       blocking_task_runner_.get(),
    201       FROM_HERE,
    202       base::Bind(&PrepareCopy,
    203                  metadata_, src_file_path, dest_file_path,
    204                  src_entry, parent_resource_id),
    205       base::Bind(&CopyOperation::CopyAfterPrepare,
    206                  weak_ptr_factory_.GetWeakPtr(), params,
    207                  base::Owned(src_entry), base::Owned(parent_resource_id)));
    208 }
    209 
    210 void CopyOperation::CopyAfterPrepare(const CopyParams& params,
    211                                      ResourceEntry* src_entry,
    212                                      std::string* parent_resource_id,
    213                                      FileError error) {
    214   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    215   DCHECK(!params.callback.is_null());
    216 
    217   if (error != FILE_ERROR_OK) {
    218     params.callback.Run(error);
    219     return;
    220   }
    221 
    222   base::FilePath new_title = params.dest_file_path.BaseName();
    223   if (src_entry->file_specific_info().is_hosted_document()) {
    224     // Drop the document extension, which should not be in the title.
    225     // TODO(yoshiki): Remove this code with crbug.com/223304.
    226     new_title = new_title.RemoveExtension();
    227   }
    228 
    229   base::Time last_modified =
    230       params.preserve_last_modified ?
    231       base::Time::FromInternalValue(src_entry->file_info().last_modified()) :
    232       base::Time();
    233 
    234   CopyResourceOnServer(
    235       src_entry->resource_id(), *parent_resource_id,
    236       new_title.AsUTF8Unsafe(), last_modified, params.callback);
    237 }
    238 
    239 void CopyOperation::TransferFileFromLocalToRemote(
    240     const base::FilePath& local_src_path,
    241     const base::FilePath& remote_dest_path,
    242     const FileOperationCallback& callback) {
    243   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    244   DCHECK(!callback.is_null());
    245 
    246   std::string* gdoc_resource_id = new std::string;
    247   std::string* parent_resource_id = new std::string;
    248   base::PostTaskAndReplyWithResult(
    249       blocking_task_runner_.get(),
    250       FROM_HERE,
    251       base::Bind(
    252           &PrepareTransferFileFromLocalToRemote,
    253           metadata_, local_src_path, remote_dest_path,
    254           gdoc_resource_id, parent_resource_id),
    255       base::Bind(
    256           &CopyOperation::TransferFileFromLocalToRemoteAfterPrepare,
    257           weak_ptr_factory_.GetWeakPtr(),
    258           local_src_path, remote_dest_path, callback,
    259           base::Owned(gdoc_resource_id), base::Owned(parent_resource_id)));
    260 }
    261 
    262 void CopyOperation::TransferFileFromLocalToRemoteAfterPrepare(
    263     const base::FilePath& local_src_path,
    264     const base::FilePath& remote_dest_path,
    265     const FileOperationCallback& callback,
    266     std::string* gdoc_resource_id,
    267     std::string* parent_resource_id,
    268     FileError error) {
    269   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    270   DCHECK(!callback.is_null());
    271 
    272   if (error != FILE_ERROR_OK) {
    273     callback.Run(error);
    274     return;
    275   }
    276 
    277   // For regular files, schedule the transfer.
    278   if (gdoc_resource_id->empty()) {
    279     ScheduleTransferRegularFile(local_src_path, remote_dest_path, callback);
    280     return;
    281   }
    282 
    283   // This is uploading a JSON file representing a hosted document.
    284   // Copy the document on the Drive server.
    285 
    286   // GDoc file may contain a resource ID in the old format.
    287   const std::string canonicalized_resource_id =
    288       id_canonicalizer_.Run(*gdoc_resource_id);
    289 
    290   CopyResourceOnServer(
    291       canonicalized_resource_id, *parent_resource_id,
    292       // Drop the document extension, which should not be in the title.
    293       // TODO(yoshiki): Remove this code with crbug.com/223304.
    294       remote_dest_path.BaseName().RemoveExtension().AsUTF8Unsafe(),
    295       base::Time(),
    296       callback);
    297 }
    298 
    299 void CopyOperation::CopyResourceOnServer(
    300     const std::string& resource_id,
    301     const std::string& parent_resource_id,
    302     const std::string& new_title,
    303     const base::Time& last_modified,
    304     const FileOperationCallback& callback) {
    305   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    306   DCHECK(!callback.is_null());
    307 
    308   scheduler_->CopyResource(
    309       resource_id, parent_resource_id, new_title, last_modified,
    310       base::Bind(&CopyOperation::CopyResourceOnServerAfterServerSideCopy,
    311                  weak_ptr_factory_.GetWeakPtr(),
    312                  callback));
    313 }
    314 
    315 void CopyOperation::CopyResourceOnServerAfterServerSideCopy(
    316     const FileOperationCallback& callback,
    317     google_apis::GDataErrorCode status,
    318     scoped_ptr<google_apis::ResourceEntry> resource_entry) {
    319   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    320   DCHECK(!callback.is_null());
    321 
    322   FileError error = GDataToFileError(status);
    323   if (error != FILE_ERROR_OK) {
    324     callback.Run(error);
    325     return;
    326   }
    327 
    328   // The copy on the server side is completed successfully. Update the local
    329   // metadata.
    330   base::FilePath* file_path = new base::FilePath;
    331   base::PostTaskAndReplyWithResult(
    332       blocking_task_runner_.get(),
    333       FROM_HERE,
    334       base::Bind(&UpdateLocalStateForServerSideCopy,
    335                  metadata_, base::Passed(&resource_entry), file_path),
    336       base::Bind(&CopyOperation::CopyResourceOnServerAfterUpdateLocalState,
    337                  weak_ptr_factory_.GetWeakPtr(),
    338                  callback, base::Owned(file_path)));
    339 }
    340 
    341 void CopyOperation::CopyResourceOnServerAfterUpdateLocalState(
    342     const FileOperationCallback& callback,
    343     base::FilePath* file_path,
    344     FileError error) {
    345   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    346   DCHECK(!callback.is_null());
    347 
    348   if (error == FILE_ERROR_OK)
    349     observer_->OnDirectoryChangedByOperation(file_path->DirName());
    350   callback.Run(error);
    351 }
    352 
    353 void CopyOperation::ScheduleTransferRegularFile(
    354     const base::FilePath& local_src_path,
    355     const base::FilePath& remote_dest_path,
    356     const FileOperationCallback& callback) {
    357   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    358   DCHECK(!callback.is_null());
    359 
    360   base::PostTaskAndReplyWithResult(
    361       blocking_task_runner_.get(),
    362       FROM_HERE,
    363       base::Bind(&GetFileSize, local_src_path),
    364       base::Bind(&CopyOperation::ScheduleTransferRegularFileAfterGetFileSize,
    365                  weak_ptr_factory_.GetWeakPtr(),
    366                  local_src_path, remote_dest_path, callback));
    367 }
    368 
    369 void CopyOperation::ScheduleTransferRegularFileAfterGetFileSize(
    370     const base::FilePath& local_src_path,
    371     const base::FilePath& remote_dest_path,
    372     const FileOperationCallback& callback,
    373     int64 local_file_size) {
    374   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    375   DCHECK(!callback.is_null());
    376 
    377   if (local_file_size < 0) {
    378     callback.Run(FILE_ERROR_NOT_FOUND);
    379     return;
    380   }
    381 
    382   // For regular files, check the server-side quota whether sufficient space
    383   // is available for the file to be uploaded.
    384   scheduler_->GetAboutResource(
    385       base::Bind(
    386           &CopyOperation::ScheduleTransferRegularFileAfterGetAboutResource,
    387           weak_ptr_factory_.GetWeakPtr(),
    388           local_src_path, remote_dest_path, callback, local_file_size));
    389 }
    390 
    391 void CopyOperation::ScheduleTransferRegularFileAfterGetAboutResource(
    392     const base::FilePath& local_src_path,
    393     const base::FilePath& remote_dest_path,
    394     const FileOperationCallback& callback,
    395     int64 local_file_size,
    396     google_apis::GDataErrorCode status,
    397     scoped_ptr<google_apis::AboutResource> about_resource) {
    398   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    399   DCHECK(!callback.is_null());
    400 
    401   FileError error = GDataToFileError(status);
    402   if (error != FILE_ERROR_OK) {
    403     callback.Run(error);
    404     return;
    405   }
    406 
    407   DCHECK(about_resource);
    408   const int64 space =
    409       about_resource->quota_bytes_total() - about_resource->quota_bytes_used();
    410   if (space < local_file_size) {
    411     callback.Run(FILE_ERROR_NO_SERVER_SPACE);
    412     return;
    413   }
    414 
    415   create_file_operation_->CreateFile(
    416       remote_dest_path,
    417       true,  // Exclusive (i.e. fail if a file already exists).
    418       std::string(),  // no specific mime type; CreateFile should guess it.
    419       base::Bind(&CopyOperation::ScheduleTransferRegularFileAfterCreate,
    420                  weak_ptr_factory_.GetWeakPtr(),
    421                  local_src_path, remote_dest_path, callback));
    422 }
    423 
    424 void CopyOperation::ScheduleTransferRegularFileAfterCreate(
    425     const base::FilePath& local_src_path,
    426     const base::FilePath& remote_dest_path,
    427     const FileOperationCallback& callback,
    428     FileError error) {
    429   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    430   DCHECK(!callback.is_null());
    431 
    432   if (error != FILE_ERROR_OK) {
    433     callback.Run(error);
    434     return;
    435   }
    436 
    437   std::string* local_id = new std::string;
    438   base::PostTaskAndReplyWithResult(
    439       blocking_task_runner_.get(),
    440       FROM_HERE,
    441       base::Bind(
    442           &UpdateLocalStateForScheduleTransfer,
    443           metadata_, cache_, local_src_path, remote_dest_path, local_id),
    444       base::Bind(
    445           &CopyOperation::ScheduleTransferRegularFileAfterUpdateLocalState,
    446           weak_ptr_factory_.GetWeakPtr(), callback, base::Owned(local_id)));
    447 }
    448 
    449 void CopyOperation::ScheduleTransferRegularFileAfterUpdateLocalState(
    450     const FileOperationCallback& callback,
    451     std::string* local_id,
    452     FileError error) {
    453   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    454   DCHECK(!callback.is_null());
    455 
    456   if (error == FILE_ERROR_OK)
    457     observer_->OnCacheFileUploadNeededByOperation(*local_id);
    458   callback.Run(error);
    459 }
    460 
    461 }  // namespace file_system
    462 }  // namespace drive
    463