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 struct CopyOperation::CopyParams {
     29   base::FilePath src_file_path;
     30   base::FilePath dest_file_path;
     31   bool preserve_last_modified;
     32   FileOperationCallback callback;
     33   ResourceEntry src_entry;
     34   ResourceEntry parent_entry;
     35 };
     36 
     37 // Enum for categorizing where a gdoc represented by a JSON file exists.
     38 enum JsonGdocLocationType {
     39   NOT_IN_METADATA,
     40   IS_ORPHAN,
     41   HAS_PARENT,
     42 };
     43 
     44 struct CopyOperation::TransferJsonGdocParams {
     45   TransferJsonGdocParams(const FileOperationCallback& callback,
     46                          const std::string& resource_id,
     47                          const ResourceEntry& parent_entry,
     48                          const std::string& new_title)
     49       : callback(callback),
     50         resource_id(resource_id),
     51         parent_resource_id(parent_entry.resource_id()),
     52         parent_local_id(parent_entry.local_id()),
     53         new_title(new_title),
     54         location_type(NOT_IN_METADATA) {
     55   }
     56   // Parameters supplied or calculated from operation arguments.
     57   const FileOperationCallback callback;
     58   const std::string resource_id;
     59   const std::string parent_resource_id;
     60   const std::string parent_local_id;
     61   const std::string new_title;
     62 
     63   // Values computed during operation.
     64   JsonGdocLocationType location_type;  // types where the gdoc file is located.
     65   std::string local_id;  // the local_id of the file (if exists in metadata.)
     66   base::FilePath changed_path;
     67 };
     68 
     69 namespace {
     70 
     71 FileError TryToCopyLocally(internal::ResourceMetadata* metadata,
     72                            internal::FileCache* cache,
     73                            CopyOperation::CopyParams* params,
     74                            std::vector<std::string>* updated_local_ids,
     75                            bool* directory_changed,
     76                            bool* should_copy_on_server) {
     77   FileError error = metadata->GetResourceEntryByPath(params->src_file_path,
     78                                                      &params->src_entry);
     79   if (error != FILE_ERROR_OK)
     80     return error;
     81 
     82   error = metadata->GetResourceEntryByPath(params->dest_file_path.DirName(),
     83                                            &params->parent_entry);
     84   if (error != FILE_ERROR_OK)
     85     return error;
     86 
     87   if (!params->parent_entry.file_info().is_directory())
     88     return FILE_ERROR_NOT_A_DIRECTORY;
     89 
     90   // Drive File System doesn't support recursive copy.
     91   if (params->src_entry.file_info().is_directory())
     92     return FILE_ERROR_NOT_A_FILE;
     93 
     94   // Check destination.
     95   ResourceEntry dest_entry;
     96   error = metadata->GetResourceEntryByPath(params->dest_file_path, &dest_entry);
     97   switch (error) {
     98     case FILE_ERROR_OK:
     99       // File API spec says it is an error to try to "copy a file to a path
    100       // occupied by a directory".
    101       if (dest_entry.file_info().is_directory())
    102         return FILE_ERROR_INVALID_OPERATION;
    103 
    104       // Move the existing entry to the trash.
    105       dest_entry.set_parent_local_id(util::kDriveTrashDirLocalId);
    106       error = metadata->RefreshEntry(dest_entry);
    107       if (error != FILE_ERROR_OK)
    108         return error;
    109       updated_local_ids->push_back(dest_entry.local_id());
    110       *directory_changed = true;
    111       break;
    112     case FILE_ERROR_NOT_FOUND:
    113       break;
    114     default:
    115       return error;
    116   }
    117 
    118   // If the cache file is not present and the entry exists on the server,
    119   // server side copy should be used.
    120   if (!params->src_entry.file_specific_info().cache_state().is_present() &&
    121       !params->src_entry.resource_id().empty()) {
    122     *should_copy_on_server = true;
    123     return FILE_ERROR_OK;
    124   }
    125 
    126   // Copy locally.
    127   ResourceEntry entry;
    128   const int64 now = base::Time::Now().ToInternalValue();
    129   entry.set_title(params->dest_file_path.BaseName().AsUTF8Unsafe());
    130   entry.set_parent_local_id(params->parent_entry.local_id());
    131   entry.mutable_file_specific_info()->set_content_mime_type(
    132       params->src_entry.file_specific_info().content_mime_type());
    133   entry.set_metadata_edit_state(ResourceEntry::DIRTY);
    134   entry.set_modification_date(base::Time::Now().ToInternalValue());
    135   entry.mutable_file_info()->set_last_modified(
    136       params->preserve_last_modified ?
    137       params->src_entry.file_info().last_modified() : now);
    138   entry.mutable_file_info()->set_last_accessed(now);
    139 
    140   std::string local_id;
    141   error = metadata->AddEntry(entry, &local_id);
    142   if (error != FILE_ERROR_OK)
    143     return error;
    144   updated_local_ids->push_back(local_id);
    145   *directory_changed = true;
    146 
    147   if (!params->src_entry.file_specific_info().cache_state().is_present()) {
    148     DCHECK(params->src_entry.resource_id().empty());
    149     // Locally created empty file may have no cache file.
    150     return FILE_ERROR_OK;
    151   }
    152 
    153   base::FilePath cache_file_path;
    154   error = cache->GetFile(params->src_entry.local_id(), &cache_file_path);
    155   if (error != FILE_ERROR_OK)
    156     return error;
    157 
    158   return cache->Store(local_id, std::string(), cache_file_path,
    159                       internal::FileCache::FILE_OPERATION_COPY);
    160 }
    161 
    162 // Stores the entry returned from the server and returns its path.
    163 FileError UpdateLocalStateForServerSideOperation(
    164     internal::ResourceMetadata* metadata,
    165     scoped_ptr<google_apis::FileResource> file_resource,
    166     base::FilePath* file_path) {
    167   DCHECK(file_resource);
    168 
    169   ResourceEntry entry;
    170   std::string parent_resource_id;
    171   if (!ConvertFileResourceToResourceEntry(*file_resource, &entry,
    172                                           &parent_resource_id) ||
    173       parent_resource_id.empty())
    174     return FILE_ERROR_NOT_A_FILE;
    175 
    176   std::string parent_local_id;
    177   FileError error = metadata->GetIdByResourceId(parent_resource_id,
    178                                                 &parent_local_id);
    179   if (error != FILE_ERROR_OK)
    180     return error;
    181   entry.set_parent_local_id(parent_local_id);
    182 
    183   std::string local_id;
    184   error = metadata->AddEntry(entry, &local_id);
    185   // Depending on timing, the metadata may have inserted via change list
    186   // already. So, FILE_ERROR_EXISTS is not an error.
    187   if (error == FILE_ERROR_EXISTS)
    188     error = metadata->GetIdByResourceId(entry.resource_id(), &local_id);
    189 
    190   if (error != FILE_ERROR_OK)
    191     return error;
    192 
    193   return metadata->GetFilePath(local_id, file_path);
    194 }
    195 
    196 // Stores the file at |local_file_path| to the cache as a content of entry at
    197 // |remote_dest_path|, and marks it dirty.
    198 FileError UpdateLocalStateForScheduleTransfer(
    199     internal::ResourceMetadata* metadata,
    200     internal::FileCache* cache,
    201     const base::FilePath& local_src_path,
    202     const base::FilePath& remote_dest_path,
    203     std::string* local_id) {
    204   FileError error = metadata->GetIdByPath(remote_dest_path, local_id);
    205   if (error != FILE_ERROR_OK)
    206     return error;
    207 
    208   ResourceEntry entry;
    209   error = metadata->GetResourceEntryById(*local_id, &entry);
    210   if (error != FILE_ERROR_OK)
    211     return error;
    212 
    213   return cache->Store(*local_id, std::string(), local_src_path,
    214                       internal::FileCache::FILE_OPERATION_COPY);
    215 }
    216 
    217 // Gets the file size of the |local_path|, and the ResourceEntry for the parent
    218 // of |remote_path| to prepare the necessary information for transfer.
    219 FileError PrepareTransferFileFromLocalToRemote(
    220     internal::ResourceMetadata* metadata,
    221     const base::FilePath& local_src_path,
    222     const base::FilePath& remote_dest_path,
    223     std::string* gdoc_resource_id,
    224     ResourceEntry* parent_entry) {
    225   FileError error = metadata->GetResourceEntryByPath(
    226       remote_dest_path.DirName(), parent_entry);
    227   if (error != FILE_ERROR_OK)
    228     return error;
    229 
    230   // The destination's parent must be a directory.
    231   if (!parent_entry->file_info().is_directory())
    232     return FILE_ERROR_NOT_A_DIRECTORY;
    233 
    234   // Try to parse GDoc File and extract the resource id, if necessary.
    235   // Failing isn't problem. It'd be handled as a regular file, then.
    236   if (util::HasGDocFileExtension(local_src_path))
    237     *gdoc_resource_id = util::ReadResourceIdFromGDocFile(local_src_path);
    238   return FILE_ERROR_OK;
    239 }
    240 
    241 // Performs local work before server-side work for transferring JSON-represented
    242 // gdoc files.
    243 FileError LocalWorkForTransferJsonGdocFile(
    244     internal::ResourceMetadata* metadata,
    245     CopyOperation::TransferJsonGdocParams* params) {
    246   std::string local_id;
    247   FileError error = metadata->GetIdByResourceId(params->resource_id, &local_id);
    248   if (error != FILE_ERROR_OK) {
    249     params->location_type = NOT_IN_METADATA;
    250     return error == FILE_ERROR_NOT_FOUND ? FILE_ERROR_OK : error;
    251   }
    252 
    253   ResourceEntry entry;
    254   error = metadata->GetResourceEntryById(local_id, &entry);
    255   if (error != FILE_ERROR_OK)
    256     return error;
    257   params->local_id = entry.local_id();
    258 
    259   if (entry.parent_local_id() == util::kDriveOtherDirLocalId) {
    260     params->location_type = IS_ORPHAN;
    261     entry.set_title(params->new_title);
    262     entry.set_parent_local_id(params->parent_local_id);
    263     entry.set_metadata_edit_state(ResourceEntry::DIRTY);
    264     entry.set_modification_date(base::Time::Now().ToInternalValue());
    265     error = metadata->RefreshEntry(entry);
    266     if (error != FILE_ERROR_OK)
    267       return error;
    268     return metadata->GetFilePath(local_id, &params->changed_path);
    269   }
    270 
    271   params->location_type = HAS_PARENT;
    272   return FILE_ERROR_OK;
    273 }
    274 
    275 }  // namespace
    276 
    277 CopyOperation::CopyOperation(base::SequencedTaskRunner* blocking_task_runner,
    278                              OperationObserver* observer,
    279                              JobScheduler* scheduler,
    280                              internal::ResourceMetadata* metadata,
    281                              internal::FileCache* cache,
    282                              const ResourceIdCanonicalizer& id_canonicalizer)
    283   : blocking_task_runner_(blocking_task_runner),
    284     observer_(observer),
    285     scheduler_(scheduler),
    286     metadata_(metadata),
    287     cache_(cache),
    288     id_canonicalizer_(id_canonicalizer),
    289     create_file_operation_(new CreateFileOperation(blocking_task_runner,
    290                                                    observer,
    291                                                    metadata)),
    292     weak_ptr_factory_(this) {
    293   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    294 }
    295 
    296 CopyOperation::~CopyOperation() {
    297   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    298 }
    299 
    300 void CopyOperation::Copy(const base::FilePath& src_file_path,
    301                          const base::FilePath& dest_file_path,
    302                          bool preserve_last_modified,
    303                          const FileOperationCallback& callback) {
    304   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    305   DCHECK(!callback.is_null());
    306 
    307   CopyParams* params = new CopyParams;
    308   params->src_file_path = src_file_path;
    309   params->dest_file_path = dest_file_path;
    310   params->preserve_last_modified = preserve_last_modified;
    311   params->callback = callback;
    312 
    313   std::vector<std::string>* updated_local_ids = new std::vector<std::string>;
    314   bool* directory_changed = new bool(false);
    315   bool* should_copy_on_server = new bool(false);
    316   base::PostTaskAndReplyWithResult(
    317       blocking_task_runner_.get(),
    318       FROM_HERE,
    319       base::Bind(&TryToCopyLocally, metadata_, cache_, params,
    320                  updated_local_ids, directory_changed, should_copy_on_server),
    321       base::Bind(&CopyOperation::CopyAfterTryToCopyLocally,
    322                  weak_ptr_factory_.GetWeakPtr(), base::Owned(params),
    323                  base::Owned(updated_local_ids), base::Owned(directory_changed),
    324                  base::Owned(should_copy_on_server)));
    325 }
    326 
    327 void CopyOperation::CopyAfterTryToCopyLocally(
    328     const CopyParams* params,
    329     const std::vector<std::string>* updated_local_ids,
    330     const bool* directory_changed,
    331     const bool* should_copy_on_server,
    332     FileError error) {
    333   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    334   DCHECK(!params->callback.is_null());
    335 
    336   for (size_t i = 0; i < updated_local_ids->size(); ++i)
    337     observer_->OnEntryUpdatedByOperation((*updated_local_ids)[i]);
    338 
    339   if (*directory_changed)
    340     observer_->OnDirectoryChangedByOperation(params->dest_file_path.DirName());
    341 
    342   if (error != FILE_ERROR_OK || !*should_copy_on_server) {
    343     params->callback.Run(error);
    344     return;
    345   }
    346 
    347   base::FilePath new_title = params->dest_file_path.BaseName();
    348   if (params->src_entry.file_specific_info().is_hosted_document()) {
    349     // Drop the document extension, which should not be in the title.
    350     // TODO(yoshiki): Remove this code with crbug.com/223304.
    351     new_title = new_title.RemoveExtension();
    352   }
    353 
    354   base::Time last_modified =
    355       params->preserve_last_modified ?
    356       base::Time::FromInternalValue(
    357           params->src_entry.file_info().last_modified()) : base::Time();
    358 
    359   CopyResourceOnServer(
    360       params->src_entry.resource_id(), params->parent_entry.resource_id(),
    361       new_title.AsUTF8Unsafe(), last_modified, params->callback);
    362 }
    363 
    364 void CopyOperation::TransferFileFromLocalToRemote(
    365     const base::FilePath& local_src_path,
    366     const base::FilePath& remote_dest_path,
    367     const FileOperationCallback& callback) {
    368   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    369   DCHECK(!callback.is_null());
    370 
    371   std::string* gdoc_resource_id = new std::string;
    372   ResourceEntry* parent_entry = new ResourceEntry;
    373   base::PostTaskAndReplyWithResult(
    374       blocking_task_runner_.get(),
    375       FROM_HERE,
    376       base::Bind(
    377           &PrepareTransferFileFromLocalToRemote,
    378           metadata_, local_src_path, remote_dest_path,
    379           gdoc_resource_id, parent_entry),
    380       base::Bind(
    381           &CopyOperation::TransferFileFromLocalToRemoteAfterPrepare,
    382           weak_ptr_factory_.GetWeakPtr(),
    383           local_src_path, remote_dest_path, callback,
    384           base::Owned(gdoc_resource_id), base::Owned(parent_entry)));
    385 }
    386 
    387 void CopyOperation::TransferFileFromLocalToRemoteAfterPrepare(
    388     const base::FilePath& local_src_path,
    389     const base::FilePath& remote_dest_path,
    390     const FileOperationCallback& callback,
    391     std::string* gdoc_resource_id,
    392     ResourceEntry* parent_entry,
    393     FileError error) {
    394   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    395   DCHECK(!callback.is_null());
    396 
    397   if (error != FILE_ERROR_OK) {
    398     callback.Run(error);
    399     return;
    400   }
    401 
    402   // For regular files, schedule the transfer.
    403   if (gdoc_resource_id->empty()) {
    404     ScheduleTransferRegularFile(local_src_path, remote_dest_path, callback);
    405     return;
    406   }
    407 
    408   // GDoc file may contain a resource ID in the old format.
    409   const std::string canonicalized_resource_id =
    410       id_canonicalizer_.Run(*gdoc_resource_id);
    411 
    412   // Drop the document extension, which should not be in the title.
    413   // TODO(yoshiki): Remove this code with crbug.com/223304.
    414   const std::string new_title =
    415       remote_dest_path.BaseName().RemoveExtension().AsUTF8Unsafe();
    416 
    417   // This is uploading a JSON file representing a hosted document.
    418   TransferJsonGdocParams* params = new TransferJsonGdocParams(
    419       callback, canonicalized_resource_id, *parent_entry, new_title);
    420   base::PostTaskAndReplyWithResult(
    421       blocking_task_runner_.get(),
    422       FROM_HERE,
    423       base::Bind(&LocalWorkForTransferJsonGdocFile, metadata_, params),
    424       base::Bind(&CopyOperation::TransferJsonGdocFileAfterLocalWork,
    425                  weak_ptr_factory_.GetWeakPtr(), base::Owned(params)));
    426 }
    427 
    428 void CopyOperation::TransferJsonGdocFileAfterLocalWork(
    429     TransferJsonGdocParams* params,
    430     FileError error) {
    431   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    432 
    433   if (error != FILE_ERROR_OK) {
    434     params->callback.Run(error);
    435     return;
    436   }
    437 
    438   switch (params->location_type) {
    439     // When |resource_id| is found in the local metadata and it has a specific
    440     // parent folder, we assume the user's intention is to copy the document and
    441     // thus perform the server-side copy operation.
    442     case HAS_PARENT:
    443       CopyResourceOnServer(params->resource_id,
    444                            params->parent_resource_id,
    445                            params->new_title,
    446                            base::Time(),
    447                            params->callback);
    448       break;
    449     // When |resource_id| has no parent, we just set the new destination folder
    450     // as the parent, for sharing the document between the original source.
    451     // This reparenting is already done in LocalWorkForTransferJsonGdocFile().
    452     case IS_ORPHAN:
    453       DCHECK(!params->changed_path.empty());
    454       observer_->OnEntryUpdatedByOperation(params->local_id);
    455       observer_->OnDirectoryChangedByOperation(params->changed_path.DirName());
    456       params->callback.Run(error);
    457       break;
    458     // When the |resource_id| is not in the local metadata, assume it to be a
    459     // document just now shared on the server but not synced locally.
    460     // Same as the IS_ORPHAN case, we want to deal the case by setting parent,
    461     // but this time we need to resort to server side operation.
    462     case NOT_IN_METADATA:
    463       scheduler_->UpdateResource(
    464           params->resource_id,
    465           params->parent_resource_id,
    466           params->new_title,
    467           base::Time(),
    468           base::Time(),
    469           ClientContext(USER_INITIATED),
    470           base::Bind(&CopyOperation::UpdateAfterServerSideOperation,
    471                      weak_ptr_factory_.GetWeakPtr(),
    472                      params->callback));
    473       break;
    474   }
    475 }
    476 
    477 void CopyOperation::CopyResourceOnServer(
    478     const std::string& resource_id,
    479     const std::string& parent_resource_id,
    480     const std::string& new_title,
    481     const base::Time& last_modified,
    482     const FileOperationCallback& callback) {
    483   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    484   DCHECK(!callback.is_null());
    485 
    486   scheduler_->CopyResource(
    487       resource_id, parent_resource_id, new_title, last_modified,
    488       base::Bind(&CopyOperation::UpdateAfterServerSideOperation,
    489                  weak_ptr_factory_.GetWeakPtr(),
    490                  callback));
    491 }
    492 
    493 void CopyOperation::UpdateAfterServerSideOperation(
    494     const FileOperationCallback& callback,
    495     google_apis::GDataErrorCode status,
    496     scoped_ptr<google_apis::FileResource> entry) {
    497   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    498   DCHECK(!callback.is_null());
    499 
    500   FileError error = GDataToFileError(status);
    501   if (error != FILE_ERROR_OK) {
    502     callback.Run(error);
    503     return;
    504   }
    505 
    506   // The copy on the server side is completed successfully. Update the local
    507   // metadata.
    508   base::FilePath* file_path = new base::FilePath;
    509   base::PostTaskAndReplyWithResult(
    510       blocking_task_runner_.get(),
    511       FROM_HERE,
    512       base::Bind(&UpdateLocalStateForServerSideOperation,
    513                  metadata_, base::Passed(&entry), file_path),
    514       base::Bind(&CopyOperation::UpdateAfterLocalStateUpdate,
    515                  weak_ptr_factory_.GetWeakPtr(),
    516                  callback, base::Owned(file_path)));
    517 }
    518 
    519 void CopyOperation::UpdateAfterLocalStateUpdate(
    520     const FileOperationCallback& callback,
    521     base::FilePath* file_path,
    522     FileError error) {
    523   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    524   DCHECK(!callback.is_null());
    525 
    526   if (error == FILE_ERROR_OK)
    527     observer_->OnDirectoryChangedByOperation(file_path->DirName());
    528   callback.Run(error);
    529 }
    530 
    531 void CopyOperation::ScheduleTransferRegularFile(
    532     const base::FilePath& local_src_path,
    533     const base::FilePath& remote_dest_path,
    534     const FileOperationCallback& callback) {
    535   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    536   DCHECK(!callback.is_null());
    537 
    538   create_file_operation_->CreateFile(
    539       remote_dest_path,
    540       false,  // Not exclusive (OK even if a file already exists).
    541       std::string(),  // no specific mime type; CreateFile should guess it.
    542       base::Bind(&CopyOperation::ScheduleTransferRegularFileAfterCreate,
    543                  weak_ptr_factory_.GetWeakPtr(),
    544                  local_src_path, remote_dest_path, callback));
    545 }
    546 
    547 void CopyOperation::ScheduleTransferRegularFileAfterCreate(
    548     const base::FilePath& local_src_path,
    549     const base::FilePath& remote_dest_path,
    550     const FileOperationCallback& callback,
    551     FileError error) {
    552   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    553   DCHECK(!callback.is_null());
    554 
    555   if (error != FILE_ERROR_OK) {
    556     callback.Run(error);
    557     return;
    558   }
    559 
    560   std::string* local_id = new std::string;
    561   base::PostTaskAndReplyWithResult(
    562       blocking_task_runner_.get(),
    563       FROM_HERE,
    564       base::Bind(
    565           &UpdateLocalStateForScheduleTransfer,
    566           metadata_, cache_, local_src_path, remote_dest_path, local_id),
    567       base::Bind(
    568           &CopyOperation::ScheduleTransferRegularFileAfterUpdateLocalState,
    569           weak_ptr_factory_.GetWeakPtr(), callback, remote_dest_path,
    570           base::Owned(local_id)));
    571 }
    572 
    573 void CopyOperation::ScheduleTransferRegularFileAfterUpdateLocalState(
    574     const FileOperationCallback& callback,
    575     const base::FilePath& remote_dest_path,
    576     std::string* local_id,
    577     FileError error) {
    578   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    579   DCHECK(!callback.is_null());
    580 
    581   if (error == FILE_ERROR_OK) {
    582     observer_->OnDirectoryChangedByOperation(remote_dest_path.DirName());
    583     observer_->OnEntryUpdatedByOperation(*local_id);
    584   }
    585   callback.Run(error);
    586 }
    587 
    588 }  // namespace file_system
    589 }  // namespace drive
    590