Home | History | Annotate | Download | only in drive
      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/resource_metadata.h"
      6 
      7 #include "base/guid.h"
      8 #include "base/rand_util.h"
      9 #include "base/strings/string_number_conversions.h"
     10 #include "base/strings/stringprintf.h"
     11 #include "base/sys_info.h"
     12 #include "chrome/browser/chromeos/drive/drive.pb.h"
     13 #include "chrome/browser/chromeos/drive/file_cache.h"
     14 #include "chrome/browser/chromeos/drive/file_system_util.h"
     15 #include "chrome/browser/chromeos/drive/resource_metadata_storage.h"
     16 #include "content/public/browser/browser_thread.h"
     17 
     18 using content::BrowserThread;
     19 
     20 namespace drive {
     21 namespace internal {
     22 namespace {
     23 
     24 // Returns true if enough disk space is available for DB operation.
     25 // TODO(hashimoto): Merge this with FileCache's FreeDiskSpaceGetterInterface.
     26 bool EnoughDiskSpaceIsAvailableForDBOperation(const base::FilePath& path) {
     27   const int64 kRequiredDiskSpaceInMB = 128;  // 128 MB seems to be large enough.
     28   return base::SysInfo::AmountOfFreeDiskSpace(path) >=
     29       kRequiredDiskSpaceInMB * (1 << 20);
     30 }
     31 
     32 // Returns a file name with a uniquifier appended. (e.g. "File (1).txt")
     33 std::string GetUniquifiedName(const std::string& name, int uniquifier) {
     34   base::FilePath name_path = base::FilePath::FromUTF8Unsafe(name);
     35   name_path = name_path.InsertBeforeExtension(
     36       base::StringPrintf(" (%d)", uniquifier));
     37   return name_path.AsUTF8Unsafe();
     38 }
     39 
     40 // Returns true when there is no entry with the specified name under the parent
     41 // other than the specified entry.
     42 FileError EntryCanUseName(ResourceMetadataStorage* storage,
     43                           const std::string& parent_local_id,
     44                           const std::string& local_id,
     45                           const std::string& base_name,
     46                           bool* result) {
     47   std::string existing_entry_id;
     48   FileError error = storage->GetChild(parent_local_id, base_name,
     49                                       &existing_entry_id);
     50   if (error == FILE_ERROR_OK)
     51     *result = existing_entry_id == local_id;
     52   else if (error == FILE_ERROR_NOT_FOUND)
     53     *result = true;
     54   else
     55     return error;
     56   return FILE_ERROR_OK;
     57 }
     58 
     59 // Returns true when the ID is used by an immutable entry.
     60 bool IsImmutableEntry(const std::string& id) {
     61   return id == util::kDriveGrandRootLocalId ||
     62       id == util::kDriveOtherDirLocalId ||
     63       id == util::kDriveTrashDirLocalId;
     64 }
     65 
     66 }  // namespace
     67 
     68 ResourceMetadata::ResourceMetadata(
     69     ResourceMetadataStorage* storage,
     70     FileCache* cache,
     71     scoped_refptr<base::SequencedTaskRunner> blocking_task_runner)
     72     : blocking_task_runner_(blocking_task_runner),
     73       storage_(storage),
     74       cache_(cache) {
     75   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
     76 }
     77 
     78 FileError ResourceMetadata::Initialize() {
     79   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
     80   return SetUpDefaultEntries();
     81 }
     82 
     83 void ResourceMetadata::Destroy() {
     84   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
     85 
     86   blocking_task_runner_->PostTask(
     87       FROM_HERE,
     88       base::Bind(&ResourceMetadata::DestroyOnBlockingPool,
     89                  base::Unretained(this)));
     90 }
     91 
     92 FileError ResourceMetadata::Reset() {
     93   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
     94 
     95   if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_->directory_path()))
     96     return FILE_ERROR_NO_LOCAL_SPACE;
     97 
     98   FileError error = storage_->SetLargestChangestamp(0);
     99   if (error != FILE_ERROR_OK)
    100     return error;
    101 
    102   // Remove all root entries.
    103   scoped_ptr<Iterator> it = GetIterator();
    104   for (; !it->IsAtEnd(); it->Advance()) {
    105     if (it->GetValue().parent_local_id().empty()) {
    106       error = RemoveEntryRecursively(it->GetID());
    107       if (error != FILE_ERROR_OK)
    108         return error;
    109     }
    110   }
    111   if (it->HasError())
    112     return FILE_ERROR_FAILED;
    113 
    114   return SetUpDefaultEntries();
    115 }
    116 
    117 ResourceMetadata::~ResourceMetadata() {
    118   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
    119 }
    120 
    121 FileError ResourceMetadata::SetUpDefaultEntries() {
    122   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
    123 
    124   // Initialize "/drive".
    125   ResourceEntry entry;
    126   FileError error = storage_->GetEntry(util::kDriveGrandRootLocalId, &entry);
    127   if (error == FILE_ERROR_NOT_FOUND) {
    128     ResourceEntry root;
    129     root.mutable_file_info()->set_is_directory(true);
    130     root.set_local_id(util::kDriveGrandRootLocalId);
    131     root.set_title(util::kDriveGrandRootDirName);
    132     root.set_base_name(util::kDriveGrandRootDirName);
    133     error = storage_->PutEntry(root);
    134     if (error != FILE_ERROR_OK)
    135       return error;
    136   } else if (error == FILE_ERROR_OK) {
    137     if (!entry.resource_id().empty()) {
    138       // Old implementations used kDriveGrandRootLocalId as a resource ID.
    139       entry.clear_resource_id();
    140       error = storage_->PutEntry(entry);
    141       if (error != FILE_ERROR_OK)
    142         return error;
    143     }
    144   } else {
    145     return error;
    146   }
    147 
    148   // Initialize "/drive/other".
    149   error = storage_->GetEntry(util::kDriveOtherDirLocalId, &entry);
    150   if (error == FILE_ERROR_NOT_FOUND) {
    151     ResourceEntry other_dir;
    152     other_dir.mutable_file_info()->set_is_directory(true);
    153     other_dir.set_local_id(util::kDriveOtherDirLocalId);
    154     other_dir.set_parent_local_id(util::kDriveGrandRootLocalId);
    155     other_dir.set_title(util::kDriveOtherDirName);
    156     error = PutEntryUnderDirectory(other_dir);
    157     if (error != FILE_ERROR_OK)
    158       return error;
    159   } else if (error == FILE_ERROR_OK) {
    160     if (!entry.resource_id().empty()) {
    161       // Old implementations used kDriveOtherDirLocalId as a resource ID.
    162       entry.clear_resource_id();
    163       error = storage_->PutEntry(entry);
    164       if (error != FILE_ERROR_OK)
    165         return error;
    166     }
    167   } else {
    168     return error;
    169   }
    170 
    171   // Initialize "drive/trash".
    172   error = storage_->GetEntry(util::kDriveTrashDirLocalId, &entry);
    173   if (error == FILE_ERROR_NOT_FOUND) {
    174     ResourceEntry trash_dir;
    175     trash_dir.mutable_file_info()->set_is_directory(true);
    176     trash_dir.set_local_id(util::kDriveTrashDirLocalId);
    177     trash_dir.set_parent_local_id(util::kDriveGrandRootLocalId);
    178     trash_dir.set_title(util::kDriveTrashDirName);
    179     error = PutEntryUnderDirectory(trash_dir);
    180     if (error != FILE_ERROR_OK)
    181       return error;
    182   } else if (error != FILE_ERROR_OK) {
    183     return error;
    184   }
    185 
    186   // Initialize "drive/root".
    187   std::string child_id;
    188   error = storage_->GetChild(
    189       util::kDriveGrandRootLocalId, util::kDriveMyDriveRootDirName, &child_id);
    190   if (error == FILE_ERROR_NOT_FOUND) {
    191     ResourceEntry mydrive;
    192     mydrive.mutable_file_info()->set_is_directory(true);
    193     mydrive.set_parent_local_id(util::kDriveGrandRootLocalId);
    194     mydrive.set_title(util::kDriveMyDriveRootDirName);
    195 
    196     std::string local_id;
    197     error = AddEntry(mydrive, &local_id);
    198     if (error != FILE_ERROR_OK)
    199       return error;
    200   } else if (error != FILE_ERROR_OK) {
    201     return error;
    202   }
    203   return FILE_ERROR_OK;
    204 }
    205 
    206 void ResourceMetadata::DestroyOnBlockingPool() {
    207   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
    208   delete this;
    209 }
    210 
    211 FileError ResourceMetadata::GetLargestChangestamp(int64* out_value) {
    212   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
    213   return storage_->GetLargestChangestamp(out_value);
    214 }
    215 
    216 FileError ResourceMetadata::SetLargestChangestamp(int64 value) {
    217   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
    218 
    219   if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_->directory_path()))
    220     return FILE_ERROR_NO_LOCAL_SPACE;
    221 
    222   return storage_->SetLargestChangestamp(value);
    223 }
    224 
    225 FileError ResourceMetadata::AddEntry(const ResourceEntry& entry,
    226                                      std::string* out_id) {
    227   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
    228   DCHECK(entry.local_id().empty());
    229 
    230   if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_->directory_path()))
    231     return FILE_ERROR_NO_LOCAL_SPACE;
    232 
    233   ResourceEntry parent;
    234   FileError error = storage_->GetEntry(entry.parent_local_id(), &parent);
    235   if (error != FILE_ERROR_OK)
    236     return error;
    237   if (!parent.file_info().is_directory())
    238     return FILE_ERROR_NOT_A_DIRECTORY;
    239 
    240   // Multiple entries with the same resource ID should not be present.
    241   std::string local_id;
    242   ResourceEntry existing_entry;
    243   if (!entry.resource_id().empty()) {
    244     error = storage_->GetIdByResourceId(entry.resource_id(), &local_id);
    245     if (error == FILE_ERROR_OK)
    246       error = storage_->GetEntry(local_id, &existing_entry);
    247 
    248     if (error == FILE_ERROR_OK)
    249       return FILE_ERROR_EXISTS;
    250     else if (error != FILE_ERROR_NOT_FOUND)
    251       return error;
    252   }
    253 
    254   // Generate unique local ID when needed.
    255   // We don't check for ID collisions as its probability is extremely low.
    256   if (local_id.empty())
    257     local_id = base::GenerateGUID();
    258 
    259   ResourceEntry new_entry(entry);
    260   new_entry.set_local_id(local_id);
    261 
    262   error = PutEntryUnderDirectory(new_entry);
    263   if (error != FILE_ERROR_OK)
    264     return error;
    265 
    266   *out_id = local_id;
    267   return FILE_ERROR_OK;
    268 }
    269 
    270 FileError ResourceMetadata::RemoveEntry(const std::string& id) {
    271   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
    272 
    273   if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_->directory_path()))
    274     return FILE_ERROR_NO_LOCAL_SPACE;
    275 
    276   // Disallow deletion of default entries.
    277   if (IsImmutableEntry(id))
    278     return FILE_ERROR_ACCESS_DENIED;
    279 
    280   ResourceEntry entry;
    281   FileError error = storage_->GetEntry(id, &entry);
    282   if (error != FILE_ERROR_OK)
    283     return error;
    284 
    285   return RemoveEntryRecursively(id);
    286 }
    287 
    288 FileError ResourceMetadata::GetResourceEntryById(const std::string& id,
    289                                                  ResourceEntry* out_entry) {
    290   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
    291   DCHECK(!id.empty());
    292   DCHECK(out_entry);
    293 
    294   return storage_->GetEntry(id, out_entry);
    295 }
    296 
    297 FileError ResourceMetadata::GetResourceEntryByPath(const base::FilePath& path,
    298                                                    ResourceEntry* out_entry) {
    299   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
    300   DCHECK(out_entry);
    301 
    302   std::string id;
    303   FileError error = GetIdByPath(path, &id);
    304   if (error != FILE_ERROR_OK)
    305     return error;
    306 
    307   return GetResourceEntryById(id, out_entry);
    308 }
    309 
    310 FileError ResourceMetadata::ReadDirectoryByPath(
    311     const base::FilePath& path,
    312     ResourceEntryVector* out_entries) {
    313   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
    314   DCHECK(out_entries);
    315 
    316   std::string id;
    317   FileError error = GetIdByPath(path, &id);
    318   if (error != FILE_ERROR_OK)
    319     return error;
    320   return ReadDirectoryById(id, out_entries);
    321 }
    322 
    323 FileError ResourceMetadata::ReadDirectoryById(
    324     const std::string& id,
    325     ResourceEntryVector* out_entries) {
    326   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
    327   DCHECK(out_entries);
    328 
    329   ResourceEntry entry;
    330   FileError error = GetResourceEntryById(id, &entry);
    331   if (error != FILE_ERROR_OK)
    332     return error;
    333 
    334   if (!entry.file_info().is_directory())
    335     return FILE_ERROR_NOT_A_DIRECTORY;
    336 
    337   std::vector<std::string> children;
    338   error = storage_->GetChildren(id, &children);
    339   if (error != FILE_ERROR_OK)
    340     return error;
    341 
    342   ResourceEntryVector entries(children.size());
    343   for (size_t i = 0; i < children.size(); ++i) {
    344     error = storage_->GetEntry(children[i], &entries[i]);
    345     if (error != FILE_ERROR_OK)
    346       return error;
    347   }
    348   out_entries->swap(entries);
    349   return FILE_ERROR_OK;
    350 }
    351 
    352 FileError ResourceMetadata::RefreshEntry(const ResourceEntry& entry) {
    353   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
    354 
    355   if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_->directory_path()))
    356     return FILE_ERROR_NO_LOCAL_SPACE;
    357 
    358   ResourceEntry old_entry;
    359   FileError error = storage_->GetEntry(entry.local_id(), &old_entry);
    360   if (error != FILE_ERROR_OK)
    361     return error;
    362 
    363   if (IsImmutableEntry(entry.local_id()) ||
    364       old_entry.file_info().is_directory() !=  // Reject incompatible input.
    365       entry.file_info().is_directory())
    366     return FILE_ERROR_INVALID_OPERATION;
    367 
    368   if (!entry.resource_id().empty()) {
    369     // Multiple entries cannot share the same resource ID.
    370     std::string local_id;
    371     FileError error = GetIdByResourceId(entry.resource_id(), &local_id);
    372     switch (error) {
    373       case FILE_ERROR_OK:
    374         if (local_id != entry.local_id())
    375           return FILE_ERROR_INVALID_OPERATION;
    376         break;
    377 
    378       case FILE_ERROR_NOT_FOUND:
    379         break;
    380 
    381       default:
    382         return error;
    383     }
    384   }
    385 
    386   // Make sure that the new parent exists and it is a directory.
    387   ResourceEntry new_parent;
    388   error = storage_->GetEntry(entry.parent_local_id(), &new_parent);
    389   if (error != FILE_ERROR_OK)
    390     return error;
    391 
    392   if (!new_parent.file_info().is_directory())
    393     return FILE_ERROR_NOT_A_DIRECTORY;
    394 
    395   // Do not overwrite cache states.
    396   // Cache state should be changed via FileCache.
    397   ResourceEntry updated_entry(entry);
    398   if (old_entry.file_specific_info().has_cache_state()) {
    399     *updated_entry.mutable_file_specific_info()->mutable_cache_state() =
    400         old_entry.file_specific_info().cache_state();
    401   } else if (updated_entry.file_specific_info().has_cache_state()) {
    402     updated_entry.mutable_file_specific_info()->clear_cache_state();
    403   }
    404   // Remove from the old parent and add it to the new parent with the new data.
    405   return PutEntryUnderDirectory(updated_entry);
    406 }
    407 
    408 FileError ResourceMetadata::GetSubDirectoriesRecursively(
    409     const std::string& id,
    410     std::set<base::FilePath>* sub_directories) {
    411   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
    412 
    413   std::vector<std::string> children;
    414   FileError error = storage_->GetChildren(id, &children);
    415   if (error != FILE_ERROR_OK)
    416     return error;
    417   for (size_t i = 0; i < children.size(); ++i) {
    418     ResourceEntry entry;
    419     error = storage_->GetEntry(children[i], &entry);
    420     if (error != FILE_ERROR_OK)
    421       return error;
    422     if (entry.file_info().is_directory()) {
    423       base::FilePath path;
    424       error = GetFilePath(children[i], &path);
    425       if (error != FILE_ERROR_OK)
    426         return error;
    427       sub_directories->insert(path);
    428       GetSubDirectoriesRecursively(children[i], sub_directories);
    429     }
    430   }
    431   return FILE_ERROR_OK;
    432 }
    433 
    434 FileError ResourceMetadata::GetChildId(const std::string& parent_local_id,
    435                                        const std::string& base_name,
    436                                        std::string* out_child_id) {
    437   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
    438   return storage_->GetChild(parent_local_id, base_name, out_child_id);
    439 }
    440 
    441 scoped_ptr<ResourceMetadata::Iterator> ResourceMetadata::GetIterator() {
    442   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
    443 
    444   return storage_->GetIterator();
    445 }
    446 
    447 FileError ResourceMetadata::GetFilePath(const std::string& id,
    448                                         base::FilePath* out_file_path) {
    449   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
    450 
    451   ResourceEntry entry;
    452   FileError error = storage_->GetEntry(id, &entry);
    453   if (error != FILE_ERROR_OK)
    454     return error;
    455 
    456   base::FilePath path;
    457   if (!entry.parent_local_id().empty()) {
    458     error = GetFilePath(entry.parent_local_id(), &path);
    459     if (error != FILE_ERROR_OK)
    460       return error;
    461   } else if (entry.local_id() != util::kDriveGrandRootLocalId) {
    462     DVLOG(1) << "Entries not under the grand root don't have paths.";
    463     return FILE_ERROR_NOT_FOUND;
    464   }
    465   path = path.Append(base::FilePath::FromUTF8Unsafe(entry.base_name()));
    466   *out_file_path = path;
    467   return FILE_ERROR_OK;
    468 }
    469 
    470 FileError ResourceMetadata::GetIdByPath(const base::FilePath& file_path,
    471                                         std::string* out_id) {
    472   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
    473 
    474   // Start from the root.
    475   std::vector<base::FilePath::StringType> components;
    476   file_path.GetComponents(&components);
    477   if (components.empty() || components[0] != util::kDriveGrandRootDirName)
    478     return FILE_ERROR_NOT_FOUND;
    479 
    480   // Iterate over the remaining components.
    481   std::string id = util::kDriveGrandRootLocalId;
    482   for (size_t i = 1; i < components.size(); ++i) {
    483     const std::string component = base::FilePath(components[i]).AsUTF8Unsafe();
    484     std::string child_id;
    485     FileError error = storage_->GetChild(id, component, &child_id);
    486     if (error != FILE_ERROR_OK)
    487       return error;
    488     id = child_id;
    489   }
    490   *out_id = id;
    491   return FILE_ERROR_OK;
    492 }
    493 
    494 FileError ResourceMetadata::GetIdByResourceId(const std::string& resource_id,
    495                                               std::string* out_local_id) {
    496   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
    497   return storage_->GetIdByResourceId(resource_id, out_local_id);
    498 }
    499 
    500 FileError ResourceMetadata::PutEntryUnderDirectory(const ResourceEntry& entry) {
    501   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
    502   DCHECK(!entry.local_id().empty());
    503   DCHECK(!entry.parent_local_id().empty());
    504 
    505   std::string base_name;
    506   FileError error = GetDeduplicatedBaseName(entry, &base_name);
    507   if (error != FILE_ERROR_OK)
    508     return error;
    509   ResourceEntry updated_entry(entry);
    510   updated_entry.set_base_name(base_name);
    511   return storage_->PutEntry(updated_entry);
    512 }
    513 
    514 FileError ResourceMetadata::GetDeduplicatedBaseName(
    515     const ResourceEntry& entry,
    516     std::string* base_name) {
    517   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
    518   DCHECK(!entry.parent_local_id().empty());
    519   DCHECK(!entry.title().empty());
    520 
    521   // The entry name may have been changed due to prior name de-duplication.
    522   // We need to first restore the file name based on the title before going
    523   // through name de-duplication again when it is added to another directory.
    524   *base_name = entry.title();
    525   if (entry.has_file_specific_info() &&
    526       entry.file_specific_info().is_hosted_document()) {
    527     *base_name += entry.file_specific_info().document_extension();
    528   }
    529   *base_name = util::NormalizeFileName(*base_name);
    530 
    531   // If |base_name| is not used, just return it.
    532   bool can_use_name = false;
    533   FileError error = EntryCanUseName(storage_, entry.parent_local_id(),
    534                                     entry.local_id(), *base_name,
    535                                     &can_use_name);
    536   if (error != FILE_ERROR_OK || can_use_name)
    537     return error;
    538 
    539   // Find an unused number with binary search.
    540   int smallest_known_unused_modifier = 1;
    541   while (true) {
    542     error = EntryCanUseName(storage_, entry.parent_local_id(), entry.local_id(),
    543                             GetUniquifiedName(*base_name,
    544                                               smallest_known_unused_modifier),
    545                             &can_use_name);
    546     if (error != FILE_ERROR_OK)
    547       return error;
    548     if (can_use_name)
    549       break;
    550 
    551     const int delta = base::RandInt(1, smallest_known_unused_modifier);
    552     if (smallest_known_unused_modifier <= INT_MAX - delta) {
    553       smallest_known_unused_modifier += delta;
    554     } else {  // No luck finding an unused number. Try again.
    555       smallest_known_unused_modifier = 1;
    556     }
    557   }
    558 
    559   int largest_known_used_modifier = 1;
    560   while (smallest_known_unused_modifier - largest_known_used_modifier > 1) {
    561     const int modifier = largest_known_used_modifier +
    562         (smallest_known_unused_modifier - largest_known_used_modifier) / 2;
    563 
    564     error = EntryCanUseName(storage_, entry.parent_local_id(), entry.local_id(),
    565                             GetUniquifiedName(*base_name, modifier),
    566                             &can_use_name);
    567     if (error != FILE_ERROR_OK)
    568       return error;
    569     if (can_use_name) {
    570       smallest_known_unused_modifier = modifier;
    571     } else {
    572       largest_known_used_modifier = modifier;
    573     }
    574   }
    575   *base_name = GetUniquifiedName(*base_name, smallest_known_unused_modifier);
    576   return FILE_ERROR_OK;
    577 }
    578 
    579 FileError ResourceMetadata::RemoveEntryRecursively(const std::string& id) {
    580   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
    581 
    582   ResourceEntry entry;
    583   FileError error = storage_->GetEntry(id, &entry);
    584   if (error != FILE_ERROR_OK)
    585     return error;
    586 
    587   if (entry.file_info().is_directory()) {
    588     std::vector<std::string> children;
    589     error = storage_->GetChildren(id, &children);
    590     if (error != FILE_ERROR_OK)
    591       return error;
    592     for (size_t i = 0; i < children.size(); ++i) {
    593       error = RemoveEntryRecursively(children[i]);
    594       if (error != FILE_ERROR_OK)
    595         return error;
    596     }
    597   }
    598 
    599   error = cache_->Remove(id);
    600   if (error != FILE_ERROR_OK)
    601     return error;
    602 
    603   return storage_->RemoveEntry(id);
    604 }
    605 
    606 }  // namespace internal
    607 }  // namespace drive
    608