Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / components / drive / resource_metadata.cc
blob25250ffd90f6b5b87052c89043fce21b94cdae86
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.
5 #include "components/drive/resource_metadata.h"
7 #include "base/bind.h"
8 #include "base/bind_helpers.h"
9 #include "base/guid.h"
10 #include "base/location.h"
11 #include "base/rand_util.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/strings/stringprintf.h"
14 #include "base/sys_info.h"
15 #include "components/drive/drive.pb.h"
16 #include "components/drive/file_cache.h"
17 #include "components/drive/file_system_core_util.h"
18 #include "components/drive/resource_metadata_storage.h"
20 namespace drive {
21 namespace internal {
22 namespace {
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);
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.InsertBeforeExtensionASCII(
36 base::StringPrintf(" (%d)", uniquifier));
37 return name_path.AsUTF8Unsafe();
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;
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;
66 } // namespace
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) {
77 FileError ResourceMetadata::Initialize() {
78 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
79 return SetUpDefaultEntries();
82 void ResourceMetadata::Destroy() {
83 DCHECK(thread_checker_.CalledOnValidThread());
85 blocking_task_runner_->PostTask(
86 FROM_HERE,
87 base::Bind(&ResourceMetadata::DestroyOnBlockingPool,
88 base::Unretained(this)));
91 FileError ResourceMetadata::Reset() {
92 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
94 if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_->directory_path()))
95 return FILE_ERROR_NO_LOCAL_SPACE;
97 FileError error = storage_->SetLargestChangestamp(0);
98 if (error != FILE_ERROR_OK)
99 return error;
101 // Remove all root entries.
102 scoped_ptr<Iterator> it = GetIterator();
103 for (; !it->IsAtEnd(); it->Advance()) {
104 if (it->GetValue().parent_local_id().empty()) {
105 error = RemoveEntryRecursively(it->GetID());
106 if (error != FILE_ERROR_OK)
107 return error;
110 if (it->HasError())
111 return FILE_ERROR_FAILED;
113 return SetUpDefaultEntries();
116 ResourceMetadata::~ResourceMetadata() {
117 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
120 FileError ResourceMetadata::SetUpDefaultEntries() {
121 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
123 // Initialize "/drive".
124 ResourceEntry entry;
125 FileError error = storage_->GetEntry(util::kDriveGrandRootLocalId, &entry);
126 if (error == FILE_ERROR_NOT_FOUND) {
127 ResourceEntry root;
128 root.mutable_file_info()->set_is_directory(true);
129 root.set_local_id(util::kDriveGrandRootLocalId);
130 root.set_title(util::kDriveGrandRootDirName);
131 root.set_base_name(util::kDriveGrandRootDirName);
132 error = storage_->PutEntry(root);
133 if (error != FILE_ERROR_OK)
134 return error;
135 } else if (error == FILE_ERROR_OK) {
136 if (!entry.resource_id().empty()) {
137 // Old implementations used kDriveGrandRootLocalId as a resource ID.
138 entry.clear_resource_id();
139 error = storage_->PutEntry(entry);
140 if (error != FILE_ERROR_OK)
141 return error;
143 } else {
144 return error;
147 // Initialize "/drive/other".
148 error = storage_->GetEntry(util::kDriveOtherDirLocalId, &entry);
149 if (error == FILE_ERROR_NOT_FOUND) {
150 ResourceEntry other_dir;
151 other_dir.mutable_file_info()->set_is_directory(true);
152 other_dir.set_local_id(util::kDriveOtherDirLocalId);
153 other_dir.set_parent_local_id(util::kDriveGrandRootLocalId);
154 other_dir.set_title(util::kDriveOtherDirName);
155 error = PutEntryUnderDirectory(other_dir);
156 if (error != FILE_ERROR_OK)
157 return error;
158 } else if (error == FILE_ERROR_OK) {
159 if (!entry.resource_id().empty()) {
160 // Old implementations used kDriveOtherDirLocalId as a resource ID.
161 entry.clear_resource_id();
162 error = storage_->PutEntry(entry);
163 if (error != FILE_ERROR_OK)
164 return error;
166 } else {
167 return error;
170 // Initialize "drive/trash".
171 error = storage_->GetEntry(util::kDriveTrashDirLocalId, &entry);
172 if (error == FILE_ERROR_NOT_FOUND) {
173 ResourceEntry trash_dir;
174 trash_dir.mutable_file_info()->set_is_directory(true);
175 trash_dir.set_local_id(util::kDriveTrashDirLocalId);
176 trash_dir.set_parent_local_id(util::kDriveGrandRootLocalId);
177 trash_dir.set_title(util::kDriveTrashDirName);
178 error = PutEntryUnderDirectory(trash_dir);
179 if (error != FILE_ERROR_OK)
180 return error;
181 } else if (error != FILE_ERROR_OK) {
182 return error;
185 // Initialize "drive/root".
186 std::string child_id;
187 error = storage_->GetChild(
188 util::kDriveGrandRootLocalId, util::kDriveMyDriveRootDirName, &child_id);
189 if (error == FILE_ERROR_NOT_FOUND) {
190 ResourceEntry mydrive;
191 mydrive.mutable_file_info()->set_is_directory(true);
192 mydrive.set_parent_local_id(util::kDriveGrandRootLocalId);
193 mydrive.set_title(util::kDriveMyDriveRootDirName);
195 std::string local_id;
196 error = AddEntry(mydrive, &local_id);
197 if (error != FILE_ERROR_OK)
198 return error;
199 } else if (error != FILE_ERROR_OK) {
200 return error;
202 return FILE_ERROR_OK;
205 void ResourceMetadata::DestroyOnBlockingPool() {
206 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
207 delete this;
210 FileError ResourceMetadata::GetLargestChangestamp(int64* out_value) {
211 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
212 return storage_->GetLargestChangestamp(out_value);
215 FileError ResourceMetadata::SetLargestChangestamp(int64 value) {
216 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
218 if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_->directory_path()))
219 return FILE_ERROR_NO_LOCAL_SPACE;
221 return storage_->SetLargestChangestamp(value);
224 FileError ResourceMetadata::AddEntry(const ResourceEntry& entry,
225 std::string* out_id) {
226 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
227 DCHECK(entry.local_id().empty());
229 if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_->directory_path()))
230 return FILE_ERROR_NO_LOCAL_SPACE;
232 ResourceEntry parent;
233 FileError error = storage_->GetEntry(entry.parent_local_id(), &parent);
234 if (error != FILE_ERROR_OK)
235 return error;
236 if (!parent.file_info().is_directory())
237 return FILE_ERROR_NOT_A_DIRECTORY;
239 // Multiple entries with the same resource ID should not be present.
240 std::string local_id;
241 ResourceEntry existing_entry;
242 if (!entry.resource_id().empty()) {
243 error = storage_->GetIdByResourceId(entry.resource_id(), &local_id);
244 if (error == FILE_ERROR_OK)
245 error = storage_->GetEntry(local_id, &existing_entry);
247 if (error == FILE_ERROR_OK)
248 return FILE_ERROR_EXISTS;
249 else if (error != FILE_ERROR_NOT_FOUND)
250 return error;
253 // Generate unique local ID when needed.
254 // We don't check for ID collisions as its probability is extremely low.
255 if (local_id.empty())
256 local_id = base::GenerateGUID();
258 ResourceEntry new_entry(entry);
259 new_entry.set_local_id(local_id);
261 error = PutEntryUnderDirectory(new_entry);
262 if (error != FILE_ERROR_OK)
263 return error;
265 *out_id = local_id;
266 return FILE_ERROR_OK;
269 FileError ResourceMetadata::RemoveEntry(const std::string& id) {
270 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
272 if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_->directory_path()))
273 return FILE_ERROR_NO_LOCAL_SPACE;
275 // Disallow deletion of default entries.
276 if (IsImmutableEntry(id))
277 return FILE_ERROR_ACCESS_DENIED;
279 ResourceEntry entry;
280 FileError error = storage_->GetEntry(id, &entry);
281 if (error != FILE_ERROR_OK)
282 return error;
284 return RemoveEntryRecursively(id);
287 FileError ResourceMetadata::GetResourceEntryById(const std::string& id,
288 ResourceEntry* out_entry) {
289 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
290 DCHECK(!id.empty());
291 DCHECK(out_entry);
293 return storage_->GetEntry(id, out_entry);
296 FileError ResourceMetadata::GetResourceEntryByPath(const base::FilePath& path,
297 ResourceEntry* out_entry) {
298 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
299 DCHECK(out_entry);
301 std::string id;
302 FileError error = GetIdByPath(path, &id);
303 if (error != FILE_ERROR_OK)
304 return error;
306 return GetResourceEntryById(id, out_entry);
309 FileError ResourceMetadata::ReadDirectoryByPath(
310 const base::FilePath& path,
311 ResourceEntryVector* out_entries) {
312 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
313 DCHECK(out_entries);
315 std::string id;
316 FileError error = GetIdByPath(path, &id);
317 if (error != FILE_ERROR_OK)
318 return error;
319 return ReadDirectoryById(id, out_entries);
322 FileError ResourceMetadata::ReadDirectoryById(
323 const std::string& id,
324 ResourceEntryVector* out_entries) {
325 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
326 DCHECK(out_entries);
328 ResourceEntry entry;
329 FileError error = GetResourceEntryById(id, &entry);
330 if (error != FILE_ERROR_OK)
331 return error;
333 if (!entry.file_info().is_directory())
334 return FILE_ERROR_NOT_A_DIRECTORY;
336 std::vector<std::string> children;
337 error = storage_->GetChildren(id, &children);
338 if (error != FILE_ERROR_OK)
339 return error;
341 ResourceEntryVector entries(children.size());
342 for (size_t i = 0; i < children.size(); ++i) {
343 error = storage_->GetEntry(children[i], &entries[i]);
344 if (error != FILE_ERROR_OK)
345 return error;
347 out_entries->swap(entries);
348 return FILE_ERROR_OK;
351 FileError ResourceMetadata::RefreshEntry(const ResourceEntry& entry) {
352 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
354 if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_->directory_path()))
355 return FILE_ERROR_NO_LOCAL_SPACE;
357 ResourceEntry old_entry;
358 FileError error = storage_->GetEntry(entry.local_id(), &old_entry);
359 if (error != FILE_ERROR_OK)
360 return error;
362 if (IsImmutableEntry(entry.local_id()) ||
363 old_entry.file_info().is_directory() != // Reject incompatible input.
364 entry.file_info().is_directory())
365 return FILE_ERROR_INVALID_OPERATION;
367 if (!entry.resource_id().empty()) {
368 // Multiple entries cannot share the same resource ID.
369 std::string local_id;
370 FileError error = GetIdByResourceId(entry.resource_id(), &local_id);
371 switch (error) {
372 case FILE_ERROR_OK:
373 if (local_id != entry.local_id())
374 return FILE_ERROR_INVALID_OPERATION;
375 break;
377 case FILE_ERROR_NOT_FOUND:
378 break;
380 default:
381 return error;
385 // Make sure that the new parent exists and it is a directory.
386 ResourceEntry new_parent;
387 error = storage_->GetEntry(entry.parent_local_id(), &new_parent);
388 if (error != FILE_ERROR_OK)
389 return error;
391 if (!new_parent.file_info().is_directory())
392 return FILE_ERROR_NOT_A_DIRECTORY;
394 // Do not overwrite cache states.
395 // Cache state should be changed via FileCache.
396 ResourceEntry updated_entry(entry);
397 if (old_entry.file_specific_info().has_cache_state()) {
398 *updated_entry.mutable_file_specific_info()->mutable_cache_state() =
399 old_entry.file_specific_info().cache_state();
400 } else if (updated_entry.file_specific_info().has_cache_state()) {
401 updated_entry.mutable_file_specific_info()->clear_cache_state();
403 // Remove from the old parent and add it to the new parent with the new data.
404 return PutEntryUnderDirectory(updated_entry);
407 FileError ResourceMetadata::GetSubDirectoriesRecursively(
408 const std::string& id,
409 std::set<base::FilePath>* sub_directories) {
410 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
412 std::vector<std::string> children;
413 FileError error = storage_->GetChildren(id, &children);
414 if (error != FILE_ERROR_OK)
415 return error;
416 for (size_t i = 0; i < children.size(); ++i) {
417 ResourceEntry entry;
418 error = storage_->GetEntry(children[i], &entry);
419 if (error != FILE_ERROR_OK)
420 return error;
421 if (entry.file_info().is_directory()) {
422 base::FilePath path;
423 error = GetFilePath(children[i], &path);
424 if (error != FILE_ERROR_OK)
425 return error;
426 sub_directories->insert(path);
427 GetSubDirectoriesRecursively(children[i], sub_directories);
430 return FILE_ERROR_OK;
433 FileError ResourceMetadata::GetChildId(const std::string& parent_local_id,
434 const std::string& base_name,
435 std::string* out_child_id) {
436 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
437 return storage_->GetChild(parent_local_id, base_name, out_child_id);
440 scoped_ptr<ResourceMetadata::Iterator> ResourceMetadata::GetIterator() {
441 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
443 return storage_->GetIterator();
446 FileError ResourceMetadata::GetFilePath(const std::string& id,
447 base::FilePath* out_file_path) {
448 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
450 ResourceEntry entry;
451 FileError error = storage_->GetEntry(id, &entry);
452 if (error != FILE_ERROR_OK)
453 return error;
455 base::FilePath path;
456 if (!entry.parent_local_id().empty()) {
457 error = GetFilePath(entry.parent_local_id(), &path);
458 if (error != FILE_ERROR_OK)
459 return error;
460 } else if (entry.local_id() != util::kDriveGrandRootLocalId) {
461 DVLOG(1) << "Entries not under the grand root don't have paths.";
462 return FILE_ERROR_NOT_FOUND;
464 path = path.Append(base::FilePath::FromUTF8Unsafe(entry.base_name()));
465 *out_file_path = path;
466 return FILE_ERROR_OK;
469 FileError ResourceMetadata::GetIdByPath(const base::FilePath& file_path,
470 std::string* out_id) {
471 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
473 // Start from the root.
474 std::vector<base::FilePath::StringType> components;
475 file_path.GetComponents(&components);
476 if (components.empty() ||
477 components[0] != util::GetDriveGrandRootPath().value())
478 return FILE_ERROR_NOT_FOUND;
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;
490 *out_id = id;
491 return FILE_ERROR_OK;
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);
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());
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);
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());
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();
529 *base_name = util::NormalizeFileName(*base_name);
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;
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;
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;
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;
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;
575 *base_name = GetUniquifiedName(*base_name, smallest_known_unused_modifier);
576 return FILE_ERROR_OK;
579 FileError ResourceMetadata::RemoveEntryRecursively(const std::string& id) {
580 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
582 ResourceEntry entry;
583 FileError error = storage_->GetEntry(id, &entry);
584 if (error != FILE_ERROR_OK)
585 return error;
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;
599 error = cache_->Remove(id);
600 if (error != FILE_ERROR_OK)
601 return error;
603 return storage_->RemoveEntry(id);
606 } // namespace internal
607 } // namespace drive