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