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 "chrome/browser/chromeos/drive/resource_metadata.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"
18 using content::BrowserThread
;
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
.InsertBeforeExtension(
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
,
47 std::string existing_entry_id
;
48 FileError error
= storage
->GetChild(parent_local_id
, base_name
,
50 if (error
== FILE_ERROR_OK
)
51 *result
= existing_entry_id
== local_id
;
52 else if (error
== FILE_ERROR_NOT_FOUND
)
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
;
68 ResourceMetadata::ResourceMetadata(
69 ResourceMetadataStorage
* storage
,
71 scoped_refptr
<base::SequencedTaskRunner
> blocking_task_runner
)
72 : blocking_task_runner_(blocking_task_runner
),
75 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
78 FileError
ResourceMetadata::Initialize() {
79 DCHECK(blocking_task_runner_
->RunsTasksOnCurrentThread());
80 return SetUpDefaultEntries();
83 void ResourceMetadata::Destroy() {
84 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
86 blocking_task_runner_
->PostTask(
88 base::Bind(&ResourceMetadata::DestroyOnBlockingPool
,
89 base::Unretained(this)));
92 FileError
ResourceMetadata::Reset() {
93 DCHECK(blocking_task_runner_
->RunsTasksOnCurrentThread());
95 if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_
->directory_path()))
96 return FILE_ERROR_NO_LOCAL_SPACE
;
98 FileError error
= storage_
->SetLargestChangestamp(0);
99 if (error
!= FILE_ERROR_OK
)
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
)
112 return FILE_ERROR_FAILED
;
114 return SetUpDefaultEntries();
117 ResourceMetadata::~ResourceMetadata() {
118 DCHECK(blocking_task_runner_
->RunsTasksOnCurrentThread());
121 FileError
ResourceMetadata::SetUpDefaultEntries() {
122 DCHECK(blocking_task_runner_
->RunsTasksOnCurrentThread());
124 // Initialize "/drive".
126 FileError error
= storage_
->GetEntry(util::kDriveGrandRootLocalId
, &entry
);
127 if (error
== FILE_ERROR_NOT_FOUND
) {
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
)
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
)
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
)
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
)
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
)
182 } else if (error
!= FILE_ERROR_OK
) {
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
);
196 std::string local_id
;
197 error
= AddEntry(mydrive
, &local_id
);
198 if (error
!= FILE_ERROR_OK
)
200 } else if (error
!= FILE_ERROR_OK
) {
203 return FILE_ERROR_OK
;
206 void ResourceMetadata::DestroyOnBlockingPool() {
207 DCHECK(blocking_task_runner_
->RunsTasksOnCurrentThread());
211 FileError
ResourceMetadata::GetLargestChangestamp(int64
* out_value
) {
212 DCHECK(blocking_task_runner_
->RunsTasksOnCurrentThread());
213 return storage_
->GetLargestChangestamp(out_value
);
216 FileError
ResourceMetadata::SetLargestChangestamp(int64 value
) {
217 DCHECK(blocking_task_runner_
->RunsTasksOnCurrentThread());
219 if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_
->directory_path()))
220 return FILE_ERROR_NO_LOCAL_SPACE
;
222 return storage_
->SetLargestChangestamp(value
);
225 FileError
ResourceMetadata::AddEntry(const ResourceEntry
& entry
,
226 std::string
* out_id
) {
227 DCHECK(blocking_task_runner_
->RunsTasksOnCurrentThread());
228 DCHECK(entry
.local_id().empty());
230 if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_
->directory_path()))
231 return FILE_ERROR_NO_LOCAL_SPACE
;
233 ResourceEntry parent
;
234 FileError error
= storage_
->GetEntry(entry
.parent_local_id(), &parent
);
235 if (error
!= FILE_ERROR_OK
)
237 if (!parent
.file_info().is_directory())
238 return FILE_ERROR_NOT_A_DIRECTORY
;
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
);
248 if (error
== FILE_ERROR_OK
)
249 return FILE_ERROR_EXISTS
;
250 else if (error
!= FILE_ERROR_NOT_FOUND
)
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();
259 ResourceEntry
new_entry(entry
);
260 new_entry
.set_local_id(local_id
);
262 error
= PutEntryUnderDirectory(new_entry
);
263 if (error
!= FILE_ERROR_OK
)
267 return FILE_ERROR_OK
;
270 FileError
ResourceMetadata::RemoveEntry(const std::string
& id
) {
271 DCHECK(blocking_task_runner_
->RunsTasksOnCurrentThread());
273 if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_
->directory_path()))
274 return FILE_ERROR_NO_LOCAL_SPACE
;
276 // Disallow deletion of default entries.
277 if (IsImmutableEntry(id
))
278 return FILE_ERROR_ACCESS_DENIED
;
281 FileError error
= storage_
->GetEntry(id
, &entry
);
282 if (error
!= FILE_ERROR_OK
)
285 return RemoveEntryRecursively(id
);
288 FileError
ResourceMetadata::GetResourceEntryById(const std::string
& id
,
289 ResourceEntry
* out_entry
) {
290 DCHECK(blocking_task_runner_
->RunsTasksOnCurrentThread());
294 return storage_
->GetEntry(id
, out_entry
);
297 FileError
ResourceMetadata::GetResourceEntryByPath(const base::FilePath
& path
,
298 ResourceEntry
* out_entry
) {
299 DCHECK(blocking_task_runner_
->RunsTasksOnCurrentThread());
303 FileError error
= GetIdByPath(path
, &id
);
304 if (error
!= FILE_ERROR_OK
)
307 return GetResourceEntryById(id
, out_entry
);
310 FileError
ResourceMetadata::ReadDirectoryByPath(
311 const base::FilePath
& path
,
312 ResourceEntryVector
* out_entries
) {
313 DCHECK(blocking_task_runner_
->RunsTasksOnCurrentThread());
317 FileError error
= GetIdByPath(path
, &id
);
318 if (error
!= FILE_ERROR_OK
)
320 return ReadDirectoryById(id
, out_entries
);
323 FileError
ResourceMetadata::ReadDirectoryById(
324 const std::string
& id
,
325 ResourceEntryVector
* out_entries
) {
326 DCHECK(blocking_task_runner_
->RunsTasksOnCurrentThread());
330 FileError error
= GetResourceEntryById(id
, &entry
);
331 if (error
!= FILE_ERROR_OK
)
334 if (!entry
.file_info().is_directory())
335 return FILE_ERROR_NOT_A_DIRECTORY
;
337 std::vector
<std::string
> children
;
338 error
= storage_
->GetChildren(id
, &children
);
339 if (error
!= FILE_ERROR_OK
)
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
)
348 out_entries
->swap(entries
);
349 return FILE_ERROR_OK
;
352 FileError
ResourceMetadata::RefreshEntry(const ResourceEntry
& entry
) {
353 DCHECK(blocking_task_runner_
->RunsTasksOnCurrentThread());
355 if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_
->directory_path()))
356 return FILE_ERROR_NO_LOCAL_SPACE
;
358 ResourceEntry old_entry
;
359 FileError error
= storage_
->GetEntry(entry
.local_id(), &old_entry
);
360 if (error
!= FILE_ERROR_OK
)
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
;
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
);
374 if (local_id
!= entry
.local_id())
375 return FILE_ERROR_INVALID_OPERATION
;
378 case FILE_ERROR_NOT_FOUND
:
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
)
392 if (!new_parent
.file_info().is_directory())
393 return FILE_ERROR_NOT_A_DIRECTORY
;
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();
404 // Remove from the old parent and add it to the new parent with the new data.
405 return PutEntryUnderDirectory(updated_entry
);
408 FileError
ResourceMetadata::GetSubDirectoriesRecursively(
409 const std::string
& id
,
410 std::set
<base::FilePath
>* sub_directories
) {
411 DCHECK(blocking_task_runner_
->RunsTasksOnCurrentThread());
413 std::vector
<std::string
> children
;
414 FileError error
= storage_
->GetChildren(id
, &children
);
415 if (error
!= FILE_ERROR_OK
)
417 for (size_t i
= 0; i
< children
.size(); ++i
) {
419 error
= storage_
->GetEntry(children
[i
], &entry
);
420 if (error
!= FILE_ERROR_OK
)
422 if (entry
.file_info().is_directory()) {
424 error
= GetFilePath(children
[i
], &path
);
425 if (error
!= FILE_ERROR_OK
)
427 sub_directories
->insert(path
);
428 GetSubDirectoriesRecursively(children
[i
], sub_directories
);
431 return FILE_ERROR_OK
;
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
);
441 scoped_ptr
<ResourceMetadata::Iterator
> ResourceMetadata::GetIterator() {
442 DCHECK(blocking_task_runner_
->RunsTasksOnCurrentThread());
444 return storage_
->GetIterator();
447 FileError
ResourceMetadata::GetFilePath(const std::string
& id
,
448 base::FilePath
* out_file_path
) {
449 DCHECK(blocking_task_runner_
->RunsTasksOnCurrentThread());
452 FileError error
= storage_
->GetEntry(id
, &entry
);
453 if (error
!= FILE_ERROR_OK
)
457 if (!entry
.parent_local_id().empty()) {
458 error
= GetFilePath(entry
.parent_local_id(), &path
);
459 if (error
!= FILE_ERROR_OK
)
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
;
465 path
= path
.Append(base::FilePath::FromUTF8Unsafe(entry
.base_name()));
466 *out_file_path
= path
;
467 return FILE_ERROR_OK
;
470 FileError
ResourceMetadata::GetIdByPath(const base::FilePath
& file_path
,
471 std::string
* out_id
) {
472 DCHECK(blocking_task_runner_
->RunsTasksOnCurrentThread());
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
;
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
)
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
)
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
,
536 if (error
!= FILE_ERROR_OK
|| can_use_name
)
539 // Find an unused number with binary search.
540 int smallest_known_unused_modifier
= 1;
542 error
= EntryCanUseName(storage_
, entry
.parent_local_id(), entry
.local_id(),
543 GetUniquifiedName(*base_name
,
544 smallest_known_unused_modifier
),
546 if (error
!= FILE_ERROR_OK
)
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
),
567 if (error
!= FILE_ERROR_OK
)
570 smallest_known_unused_modifier
= modifier
;
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());
583 FileError error
= storage_
->GetEntry(id
, &entry
);
584 if (error
!= FILE_ERROR_OK
)
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
)
592 for (size_t i
= 0; i
< children
.size(); ++i
) {
593 error
= RemoveEntryRecursively(children
[i
]);
594 if (error
!= FILE_ERROR_OK
)
599 error
= cache_
->Remove(id
);
600 if (error
!= FILE_ERROR_OK
)
603 return storage_
->RemoveEntry(id
);
606 } // namespace internal