Home | History | Annotate | Download | only in sync
      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/sync/entry_update_performer.h"
      6 
      7 #include "base/callback_helpers.h"
      8 #include "base/file_util.h"
      9 #include "chrome/browser/chromeos/drive/change_list_loader.h"
     10 #include "chrome/browser/chromeos/drive/drive.pb.h"
     11 #include "chrome/browser/chromeos/drive/file_cache.h"
     12 #include "chrome/browser/chromeos/drive/file_system/operation_observer.h"
     13 #include "chrome/browser/chromeos/drive/file_system_util.h"
     14 #include "chrome/browser/chromeos/drive/job_scheduler.h"
     15 #include "chrome/browser/chromeos/drive/resource_metadata.h"
     16 #include "chrome/browser/chromeos/drive/sync/entry_revert_performer.h"
     17 #include "chrome/browser/chromeos/drive/sync/remove_performer.h"
     18 #include "content/public/browser/browser_thread.h"
     19 #include "google_apis/drive/drive_api_parser.h"
     20 #include "google_apis/drive/gdata_wapi_parser.h"
     21 
     22 using content::BrowserThread;
     23 
     24 namespace drive {
     25 namespace internal {
     26 
     27 struct EntryUpdatePerformer::LocalState {
     28   LocalState() : should_content_update(false) {
     29   }
     30 
     31   ResourceEntry entry;
     32   ResourceEntry parent_entry;
     33   base::FilePath drive_file_path;
     34   base::FilePath cache_file_path;
     35   bool should_content_update;
     36 };
     37 
     38 namespace {
     39 
     40 // Looks up ResourceEntry for source entry and its parent.
     41 FileError PrepareUpdate(ResourceMetadata* metadata,
     42                         FileCache* cache,
     43                         const std::string& local_id,
     44                         EntryUpdatePerformer::LocalState* local_state) {
     45   FileError error = metadata->GetResourceEntryById(local_id,
     46                                                    &local_state->entry);
     47   if (error != FILE_ERROR_OK)
     48     return error;
     49 
     50   error = metadata->GetResourceEntryById(local_state->entry.parent_local_id(),
     51                                          &local_state->parent_entry);
     52   if (error != FILE_ERROR_OK)
     53     return error;
     54 
     55   error = metadata->GetFilePath(local_id, &local_state->drive_file_path);
     56   if (error != FILE_ERROR_OK)
     57     return error;
     58 
     59   if (!local_state->entry.file_info().is_directory() &&
     60       !local_state->entry.file_specific_info().cache_state().is_present() &&
     61       local_state->entry.resource_id().empty()) {
     62     // Locally created file with no cache file, store an empty file.
     63     base::FilePath empty_file;
     64     if (!base::CreateTemporaryFile(&empty_file))
     65       return FILE_ERROR_FAILED;
     66     error = cache->Store(local_id, std::string(), empty_file,
     67                          FileCache::FILE_OPERATION_MOVE);
     68     if (error != FILE_ERROR_OK)
     69       return error;
     70     error = metadata->GetResourceEntryById(local_id, &local_state->entry);
     71     if (error != FILE_ERROR_OK)
     72       return error;
     73   }
     74 
     75   // Check if content update is needed or not.
     76   if (local_state->entry.file_specific_info().cache_state().is_dirty() &&
     77       !cache->IsOpenedForWrite(local_id)) {
     78     // Update cache entry's MD5 if needed.
     79     if (local_state->entry.file_specific_info().cache_state().md5().empty()) {
     80       error = cache->UpdateMd5(local_id);
     81       if (error != FILE_ERROR_OK)
     82         return error;
     83       error = metadata->GetResourceEntryById(local_id, &local_state->entry);
     84       if (error != FILE_ERROR_OK)
     85         return error;
     86     }
     87 
     88     if (local_state->entry.file_specific_info().cache_state().md5() ==
     89         local_state->entry.file_specific_info().md5()) {
     90       error = cache->ClearDirty(local_id);
     91       if (error != FILE_ERROR_OK)
     92         return error;
     93     } else {
     94       error = cache->GetFile(local_id, &local_state->cache_file_path);
     95       if (error != FILE_ERROR_OK)
     96         return error;
     97 
     98       local_state->should_content_update = true;
     99     }
    100   }
    101 
    102   // Update metadata_edit_state.
    103   switch (local_state->entry.metadata_edit_state()) {
    104     case ResourceEntry::CLEAN:  // Nothing to do.
    105     case ResourceEntry::SYNCING:  // Error during the last update. Go ahead.
    106       break;
    107 
    108     case ResourceEntry::DIRTY:
    109       local_state->entry.set_metadata_edit_state(ResourceEntry::SYNCING);
    110       error = metadata->RefreshEntry(local_state->entry);
    111       if (error != FILE_ERROR_OK)
    112         return error;
    113       break;
    114   }
    115   return FILE_ERROR_OK;
    116 }
    117 
    118 FileError FinishUpdate(ResourceMetadata* metadata,
    119                        FileCache* cache,
    120                        const std::string& local_id,
    121                        scoped_ptr<google_apis::FileResource> file_resource,
    122                        base::FilePath* changed_directory) {
    123   // When creating new entries, update check may add a new entry with the same
    124   // resource ID before us. If such an entry exists, remove it.
    125   std::string existing_local_id;
    126   FileError error = metadata->GetIdByResourceId(
    127       file_resource->file_id(), &existing_local_id);
    128   switch (error) {
    129     case FILE_ERROR_OK:
    130       if (existing_local_id != local_id) {
    131         base::FilePath existing_entry_path;
    132         error = metadata->GetFilePath(existing_local_id, &existing_entry_path);
    133         if (error != FILE_ERROR_OK)
    134           return error;
    135         error = metadata->RemoveEntry(existing_local_id);
    136         if (error != FILE_ERROR_OK)
    137           return error;
    138         *changed_directory = existing_entry_path.DirName();
    139       }
    140       break;
    141     case FILE_ERROR_NOT_FOUND:
    142       break;
    143     default:
    144       return error;
    145   }
    146 
    147   ResourceEntry entry;
    148   error = metadata->GetResourceEntryById(local_id, &entry);
    149   if (error != FILE_ERROR_OK)
    150     return error;
    151 
    152   // Update metadata_edit_state and MD5.
    153   switch (entry.metadata_edit_state()) {
    154     case ResourceEntry::CLEAN:  // Nothing to do.
    155     case ResourceEntry::DIRTY:  // Entry was edited again during the update.
    156       break;
    157 
    158     case ResourceEntry::SYNCING:
    159       entry.set_metadata_edit_state(ResourceEntry::CLEAN);
    160       break;
    161   }
    162   if (!entry.file_info().is_directory())
    163     entry.mutable_file_specific_info()->set_md5(file_resource->md5_checksum());
    164   entry.set_resource_id(file_resource->file_id());
    165   error = metadata->RefreshEntry(entry);
    166   if (error != FILE_ERROR_OK)
    167     return error;
    168 
    169   // Clear dirty bit unless the file has been edited during update.
    170   if (entry.file_specific_info().cache_state().is_dirty() &&
    171       entry.file_specific_info().cache_state().md5() ==
    172       entry.file_specific_info().md5()) {
    173     error = cache->ClearDirty(local_id);
    174     if (error != FILE_ERROR_OK)
    175       return error;
    176   }
    177   return FILE_ERROR_OK;
    178 }
    179 
    180 }  // namespace
    181 
    182 EntryUpdatePerformer::EntryUpdatePerformer(
    183     base::SequencedTaskRunner* blocking_task_runner,
    184     file_system::OperationObserver* observer,
    185     JobScheduler* scheduler,
    186     ResourceMetadata* metadata,
    187     FileCache* cache,
    188     LoaderController* loader_controller)
    189     : blocking_task_runner_(blocking_task_runner),
    190       observer_(observer),
    191       scheduler_(scheduler),
    192       metadata_(metadata),
    193       cache_(cache),
    194       loader_controller_(loader_controller),
    195       remove_performer_(new RemovePerformer(blocking_task_runner,
    196                                             observer,
    197                                             scheduler,
    198                                             metadata)),
    199       entry_revert_performer_(new EntryRevertPerformer(blocking_task_runner,
    200                                                        observer,
    201                                                        scheduler,
    202                                                        metadata)),
    203       weak_ptr_factory_(this) {
    204   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    205 }
    206 
    207 EntryUpdatePerformer::~EntryUpdatePerformer() {
    208   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    209 }
    210 
    211 void EntryUpdatePerformer::UpdateEntry(const std::string& local_id,
    212                                        const ClientContext& context,
    213                                        const FileOperationCallback& callback) {
    214   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    215   DCHECK(!callback.is_null());
    216 
    217   scoped_ptr<LocalState> local_state(new LocalState);
    218   LocalState* local_state_ptr = local_state.get();
    219   base::PostTaskAndReplyWithResult(
    220       blocking_task_runner_.get(),
    221       FROM_HERE,
    222       base::Bind(&PrepareUpdate, metadata_, cache_, local_id, local_state_ptr),
    223       base::Bind(&EntryUpdatePerformer::UpdateEntryAfterPrepare,
    224                  weak_ptr_factory_.GetWeakPtr(), context, callback,
    225                  base::Passed(&local_state)));
    226 }
    227 
    228 void EntryUpdatePerformer::UpdateEntryAfterPrepare(
    229     const ClientContext& context,
    230     const FileOperationCallback& callback,
    231     scoped_ptr<LocalState> local_state,
    232     FileError error) {
    233   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    234   DCHECK(!callback.is_null());
    235 
    236   if (error != FILE_ERROR_OK) {
    237     callback.Run(error);
    238     return;
    239   }
    240 
    241   // Trashed entry should be removed.
    242   if (local_state->entry.parent_local_id() == util::kDriveTrashDirLocalId) {
    243     remove_performer_->Remove(local_state->entry.local_id(), context, callback);
    244     return;
    245   }
    246 
    247   // Parent was locally created and needs update. Just return for now.
    248   // This entry should be updated again after the parent update completes.
    249   if (local_state->parent_entry.resource_id().empty() &&
    250       local_state->parent_entry.metadata_edit_state() != ResourceEntry::CLEAN) {
    251     callback.Run(FILE_ERROR_OK);
    252     return;
    253   }
    254 
    255   base::Time last_modified = base::Time::FromInternalValue(
    256       local_state->entry.file_info().last_modified());
    257   base::Time last_accessed = base::Time::FromInternalValue(
    258       local_state->entry.file_info().last_accessed());
    259 
    260   // Perform content update.
    261   if (local_state->should_content_update) {
    262     if (local_state->entry.resource_id().empty()) {
    263       // Not locking the loader intentionally here to avoid making the UI
    264       // unresponsive while uploading large files.
    265       // FinishUpdate() is responsible to resolve conflicts caused by this.
    266       scoped_ptr<base::ScopedClosureRunner> null_loader_lock;
    267 
    268       DriveUploader::UploadNewFileOptions options;
    269       options.modified_date = last_modified;
    270       options.last_viewed_by_me_date = last_accessed;
    271       scheduler_->UploadNewFile(
    272           local_state->parent_entry.resource_id(),
    273           local_state->drive_file_path,
    274           local_state->cache_file_path,
    275           local_state->entry.title(),
    276           local_state->entry.file_specific_info().content_mime_type(),
    277           options,
    278           context,
    279           base::Bind(&EntryUpdatePerformer::UpdateEntryAfterUpdateResource,
    280                      weak_ptr_factory_.GetWeakPtr(),
    281                      context,
    282                      callback,
    283                      local_state->entry.local_id(),
    284                      base::Passed(&null_loader_lock)));
    285     } else {
    286       DriveUploader::UploadExistingFileOptions options;
    287       options.title = local_state->entry.title();
    288       options.parent_resource_id = local_state->parent_entry.resource_id();
    289       options.modified_date = last_modified;
    290       options.last_viewed_by_me_date = last_accessed;
    291       scheduler_->UploadExistingFile(
    292           local_state->entry.resource_id(),
    293           local_state->drive_file_path,
    294           local_state->cache_file_path,
    295           local_state->entry.file_specific_info().content_mime_type(),
    296           options,
    297           context,
    298           base::Bind(&EntryUpdatePerformer::UpdateEntryAfterUpdateResource,
    299                      weak_ptr_factory_.GetWeakPtr(),
    300                      context,
    301                      callback,
    302                      local_state->entry.local_id(),
    303                      base::Passed(scoped_ptr<base::ScopedClosureRunner>())));
    304     }
    305     return;
    306   }
    307 
    308   // Create directory.
    309   if (local_state->entry.file_info().is_directory() &&
    310       local_state->entry.resource_id().empty()) {
    311     // Lock the loader to avoid race conditions.
    312     scoped_ptr<base::ScopedClosureRunner> loader_lock =
    313         loader_controller_->GetLock();
    314 
    315     DriveServiceInterface::AddNewDirectoryOptions options;
    316     options.modified_date = last_modified;
    317     options.last_viewed_by_me_date = last_accessed;
    318     scheduler_->AddNewDirectory(
    319         local_state->parent_entry.resource_id(),
    320         local_state->entry.title(),
    321         options,
    322         context,
    323         base::Bind(&EntryUpdatePerformer::UpdateEntryAfterUpdateResource,
    324                    weak_ptr_factory_.GetWeakPtr(),
    325                    context,
    326                    callback,
    327                    local_state->entry.local_id(),
    328                    base::Passed(&loader_lock)));
    329     return;
    330   }
    331 
    332   // No need to perform update.
    333   if (local_state->entry.metadata_edit_state() == ResourceEntry::CLEAN ||
    334       local_state->entry.resource_id().empty()) {
    335     callback.Run(FILE_ERROR_OK);
    336     return;
    337   }
    338 
    339   // Perform metadata update.
    340   scheduler_->UpdateResource(
    341       local_state->entry.resource_id(), local_state->parent_entry.resource_id(),
    342       local_state->entry.title(), last_modified, last_accessed,
    343       context,
    344       base::Bind(&EntryUpdatePerformer::UpdateEntryAfterUpdateResource,
    345                  weak_ptr_factory_.GetWeakPtr(),
    346                  context, callback, local_state->entry.local_id(),
    347                  base::Passed(scoped_ptr<base::ScopedClosureRunner>())));
    348 }
    349 
    350 void EntryUpdatePerformer::UpdateEntryAfterUpdateResource(
    351     const ClientContext& context,
    352     const FileOperationCallback& callback,
    353     const std::string& local_id,
    354     scoped_ptr<base::ScopedClosureRunner> loader_lock,
    355     google_apis::GDataErrorCode status,
    356     scoped_ptr<google_apis::FileResource> entry) {
    357   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    358   DCHECK(!callback.is_null());
    359 
    360   if (status == google_apis::HTTP_FORBIDDEN) {
    361     // Editing this entry is not allowed, revert local changes.
    362     entry_revert_performer_->RevertEntry(local_id, context, callback);
    363     return;
    364   }
    365 
    366   FileError error = GDataToFileError(status);
    367   if (error != FILE_ERROR_OK) {
    368     callback.Run(error);
    369     return;
    370   }
    371 
    372   base::FilePath* changed_directory = new base::FilePath;
    373   base::PostTaskAndReplyWithResult(
    374       blocking_task_runner_.get(),
    375       FROM_HERE,
    376       base::Bind(&FinishUpdate,
    377                  metadata_, cache_, local_id, base::Passed(&entry),
    378                  changed_directory),
    379       base::Bind(&EntryUpdatePerformer::UpdateEntryAfterFinish,
    380                  weak_ptr_factory_.GetWeakPtr(), callback,
    381                  base::Owned(changed_directory)));
    382 }
    383 
    384 void EntryUpdatePerformer::UpdateEntryAfterFinish(
    385     const FileOperationCallback& callback,
    386     const base::FilePath* changed_directory,
    387     FileError error) {
    388   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    389   DCHECK(!callback.is_null());
    390 
    391   if (!changed_directory->empty())
    392     observer_->OnDirectoryChangedByOperation(*changed_directory);
    393   callback.Run(error);
    394 }
    395 
    396 }  // namespace internal
    397 }  // namespace drive
    398