• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
EnoughDiskSpaceIsAvailableForDBOperation(const base::FilePath & path)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")
GetUniquifiedName(const std::string & name,int uniquifier)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.
EntryCanUseName(ResourceMetadataStorage * storage,const std::string & parent_local_id,const std::string & local_id,const std::string & base_name,bool * result)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.
IsImmutableEntry(const std::string & id)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 
ResourceMetadata(ResourceMetadataStorage * storage,FileCache * cache,scoped_refptr<base::SequencedTaskRunner> blocking_task_runner)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 
Initialize()78 FileError ResourceMetadata::Initialize() {
79   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
80   return SetUpDefaultEntries();
81 }
82 
Destroy()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 
Reset()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 
~ResourceMetadata()117 ResourceMetadata::~ResourceMetadata() {
118   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
119 }
120 
SetUpDefaultEntries()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 
DestroyOnBlockingPool()206 void ResourceMetadata::DestroyOnBlockingPool() {
207   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
208   delete this;
209 }
210 
GetLargestChangestamp(int64 * out_value)211 FileError ResourceMetadata::GetLargestChangestamp(int64* out_value) {
212   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
213   return storage_->GetLargestChangestamp(out_value);
214 }
215 
SetLargestChangestamp(int64 value)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 
AddEntry(const ResourceEntry & entry,std::string * out_id)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 
RemoveEntry(const std::string & id)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 
GetResourceEntryById(const std::string & id,ResourceEntry * out_entry)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 
GetResourceEntryByPath(const base::FilePath & path,ResourceEntry * out_entry)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 
ReadDirectoryByPath(const base::FilePath & path,ResourceEntryVector * out_entries)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 
ReadDirectoryById(const std::string & id,ResourceEntryVector * out_entries)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 
RefreshEntry(const ResourceEntry & entry)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 
GetSubDirectoriesRecursively(const std::string & id,std::set<base::FilePath> * sub_directories)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 
GetChildId(const std::string & parent_local_id,const std::string & base_name,std::string * out_child_id)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 
GetIterator()441 scoped_ptr<ResourceMetadata::Iterator> ResourceMetadata::GetIterator() {
442   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
443 
444   return storage_->GetIterator();
445 }
446 
GetFilePath(const std::string & id,base::FilePath * out_file_path)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 
GetIdByPath(const base::FilePath & file_path,std::string * out_id)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 
GetIdByResourceId(const std::string & resource_id,std::string * out_local_id)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 
PutEntryUnderDirectory(const ResourceEntry & entry)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 
GetDeduplicatedBaseName(const ResourceEntry & entry,std::string * base_name)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 
RemoveEntryRecursively(const std::string & id)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