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/strings/string_number_conversions.h"
9 #include "base/strings/stringprintf.h"
10 #include "base/sys_info.h"
11 #include "chrome/browser/chromeos/drive/drive.pb.h"
12 #include "chrome/browser/chromeos/drive/file_system_util.h"
13 #include "chrome/browser/chromeos/drive/resource_metadata_storage.h"
14 #include "content/public/browser/browser_thread.h"
16 using content::BrowserThread
;
21 // Sets entry's base name from its title and other attributes.
22 void SetBaseNameFromTitle(ResourceEntry
* entry
) {
23 std::string base_name
= entry
->title();
24 if (entry
->has_file_specific_info() &&
25 entry
->file_specific_info().is_hosted_document()) {
26 base_name
+= entry
->file_specific_info().document_extension();
28 entry
->set_base_name(util::NormalizeFileName(base_name
));
31 // Returns true if enough disk space is available for DB operation.
32 // TODO(hashimoto): Merge this with FileCache's FreeDiskSpaceGetterInterface.
33 bool EnoughDiskSpaceIsAvailableForDBOperation(const base::FilePath
& path
) {
34 const int64 kRequiredDiskSpaceInMB
= 128; // 128 MB seems to be large enough.
35 return base::SysInfo::AmountOfFreeDiskSpace(path
) >=
36 kRequiredDiskSpaceInMB
* (1 << 20);
39 // Runs |callback| with arguments.
40 void RunGetResourceEntryCallback(const GetResourceEntryCallback
& callback
,
41 scoped_ptr
<ResourceEntry
> entry
,
43 DCHECK(!callback
.is_null());
45 if (error
!= FILE_ERROR_OK
)
47 callback
.Run(error
, entry
.Pass());
50 // Runs |callback| with arguments.
51 void RunReadDirectoryCallback(const ReadDirectoryCallback
& callback
,
52 scoped_ptr
<ResourceEntryVector
> entries
,
54 DCHECK(!callback
.is_null());
56 if (error
!= FILE_ERROR_OK
)
58 callback
.Run(error
, entries
.Pass());
65 ResourceMetadata::ResourceMetadata(
66 ResourceMetadataStorage
* storage
,
67 scoped_refptr
<base::SequencedTaskRunner
> blocking_task_runner
)
68 : blocking_task_runner_(blocking_task_runner
),
70 weak_ptr_factory_(this) {
71 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
74 FileError
ResourceMetadata::Initialize() {
75 DCHECK(blocking_task_runner_
->RunsTasksOnCurrentThread());
77 if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_
->directory_path()))
78 return FILE_ERROR_NO_LOCAL_SPACE
;
80 if (!SetUpDefaultEntries())
81 return FILE_ERROR_FAILED
;
86 void ResourceMetadata::Destroy() {
87 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
89 weak_ptr_factory_
.InvalidateWeakPtrs();
90 blocking_task_runner_
->PostTask(
92 base::Bind(&ResourceMetadata::DestroyOnBlockingPool
,
93 base::Unretained(this)));
96 FileError
ResourceMetadata::Reset() {
97 DCHECK(blocking_task_runner_
->RunsTasksOnCurrentThread());
99 if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_
->directory_path()))
100 return FILE_ERROR_NO_LOCAL_SPACE
;
102 if (!storage_
->SetLargestChangestamp(0) ||
103 !RemoveEntryRecursively(util::kDriveGrandRootLocalId
) ||
104 !SetUpDefaultEntries())
105 return FILE_ERROR_FAILED
;
107 return FILE_ERROR_OK
;
110 ResourceMetadata::~ResourceMetadata() {
111 DCHECK(blocking_task_runner_
->RunsTasksOnCurrentThread());
114 bool ResourceMetadata::SetUpDefaultEntries() {
115 DCHECK(blocking_task_runner_
->RunsTasksOnCurrentThread());
117 // Initialize "/drive", "/drive/other", "drive/trash" and "drive/root".
119 if (!storage_
->GetEntry(util::kDriveGrandRootLocalId
, &entry
)) {
121 root
.mutable_file_info()->set_is_directory(true);
122 root
.set_local_id(util::kDriveGrandRootLocalId
);
123 root
.set_title(util::kDriveGrandRootDirName
);
124 SetBaseNameFromTitle(&root
);
125 if (!storage_
->PutEntry(root
))
127 } else if (!entry
.resource_id().empty()) {
128 // Old implementations used kDriveGrandRootLocalId as a resource ID.
129 entry
.clear_resource_id();
130 if (!storage_
->PutEntry(entry
))
133 if (!storage_
->GetEntry(util::kDriveOtherDirLocalId
, &entry
)) {
134 ResourceEntry other_dir
;
135 other_dir
.mutable_file_info()->set_is_directory(true);
136 other_dir
.set_local_id(util::kDriveOtherDirLocalId
);
137 other_dir
.set_parent_local_id(util::kDriveGrandRootLocalId
);
138 other_dir
.set_title(util::kDriveOtherDirName
);
139 if (!PutEntryUnderDirectory(other_dir
))
141 } else if (!entry
.resource_id().empty()) {
142 // Old implementations used kDriveOtherDirLocalId as a resource ID.
143 entry
.clear_resource_id();
144 if (!storage_
->PutEntry(entry
))
147 if (!storage_
->GetEntry(util::kDriveTrashDirLocalId
, &entry
)) {
148 ResourceEntry trash_dir
;
149 trash_dir
.mutable_file_info()->set_is_directory(true);
150 trash_dir
.set_local_id(util::kDriveTrashDirLocalId
);
151 trash_dir
.set_parent_local_id(util::kDriveGrandRootLocalId
);
152 trash_dir
.set_title(util::kDriveTrashDirName
);
153 if (!PutEntryUnderDirectory(trash_dir
))
156 if (storage_
->GetChild(util::kDriveGrandRootLocalId
,
157 util::kDriveMyDriveRootDirName
).empty()) {
158 ResourceEntry mydrive
;
159 mydrive
.mutable_file_info()->set_is_directory(true);
160 mydrive
.set_parent_local_id(util::kDriveGrandRootLocalId
);
161 mydrive
.set_title(util::kDriveMyDriveRootDirName
);
163 std::string local_id
;
164 if (AddEntry(mydrive
, &local_id
) != FILE_ERROR_OK
)
170 void ResourceMetadata::DestroyOnBlockingPool() {
171 DCHECK(blocking_task_runner_
->RunsTasksOnCurrentThread());
175 int64
ResourceMetadata::GetLargestChangestamp() {
176 DCHECK(blocking_task_runner_
->RunsTasksOnCurrentThread());
177 return storage_
->GetLargestChangestamp();
180 FileError
ResourceMetadata::SetLargestChangestamp(int64 value
) {
181 DCHECK(blocking_task_runner_
->RunsTasksOnCurrentThread());
183 if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_
->directory_path()))
184 return FILE_ERROR_NO_LOCAL_SPACE
;
186 return storage_
->SetLargestChangestamp(value
) ?
187 FILE_ERROR_OK
: FILE_ERROR_FAILED
;
190 FileError
ResourceMetadata::AddEntry(const ResourceEntry
& entry
,
191 std::string
* out_id
) {
192 DCHECK(blocking_task_runner_
->RunsTasksOnCurrentThread());
193 DCHECK(entry
.local_id().empty());
195 if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_
->directory_path()))
196 return FILE_ERROR_NO_LOCAL_SPACE
;
198 ResourceEntry parent
;
199 if (!storage_
->GetEntry(entry
.parent_local_id(), &parent
) ||
200 !parent
.file_info().is_directory())
201 return FILE_ERROR_NOT_FOUND
;
203 // Multiple entries with the same resource ID should not be present.
204 std::string local_id
;
205 ResourceEntry existing_entry
;
206 if (!entry
.resource_id().empty() &&
207 storage_
->GetIdByResourceId(entry
.resource_id(), &local_id
) &&
208 storage_
->GetEntry(local_id
, &existing_entry
))
209 return FILE_ERROR_EXISTS
;
211 // Generate unique local ID when needed.
212 while (local_id
.empty() || storage_
->GetEntry(local_id
, &existing_entry
))
213 local_id
= base::GenerateGUID();
215 ResourceEntry
new_entry(entry
);
216 new_entry
.set_local_id(local_id
);
218 if (!PutEntryUnderDirectory(new_entry
))
219 return FILE_ERROR_FAILED
;
222 return FILE_ERROR_OK
;
225 FileError
ResourceMetadata::RemoveEntry(const std::string
& id
) {
226 DCHECK(blocking_task_runner_
->RunsTasksOnCurrentThread());
228 if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_
->directory_path()))
229 return FILE_ERROR_NO_LOCAL_SPACE
;
231 // Disallow deletion of default entries.
232 if (id
== util::kDriveGrandRootLocalId
||
233 id
== util::kDriveOtherDirLocalId
||
234 id
== util::kDriveTrashDirLocalId
)
235 return FILE_ERROR_ACCESS_DENIED
;
238 if (!storage_
->GetEntry(id
, &entry
))
239 return FILE_ERROR_NOT_FOUND
;
241 if (!RemoveEntryRecursively(id
))
242 return FILE_ERROR_FAILED
;
243 return FILE_ERROR_OK
;
246 FileError
ResourceMetadata::GetResourceEntryById(const std::string
& id
,
247 ResourceEntry
* out_entry
) {
248 DCHECK(blocking_task_runner_
->RunsTasksOnCurrentThread());
252 return storage_
->GetEntry(id
, out_entry
) ?
253 FILE_ERROR_OK
: FILE_ERROR_NOT_FOUND
;
256 void ResourceMetadata::GetResourceEntryByPathOnUIThread(
257 const base::FilePath
& file_path
,
258 const GetResourceEntryCallback
& callback
) {
259 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
260 DCHECK(!callback
.is_null());
262 scoped_ptr
<ResourceEntry
> entry(new ResourceEntry
);
263 ResourceEntry
* entry_ptr
= entry
.get();
264 base::PostTaskAndReplyWithResult(
265 blocking_task_runner_
.get(),
267 base::Bind(&ResourceMetadata::GetResourceEntryByPath
,
268 base::Unretained(this),
271 base::Bind(&RunGetResourceEntryCallback
, callback
, base::Passed(&entry
)));
274 FileError
ResourceMetadata::GetResourceEntryByPath(const base::FilePath
& path
,
275 ResourceEntry
* out_entry
) {
276 DCHECK(blocking_task_runner_
->RunsTasksOnCurrentThread());
280 FileError error
= GetIdByPath(path
, &id
);
281 if (error
!= FILE_ERROR_OK
)
284 return GetResourceEntryById(id
, out_entry
);
287 void ResourceMetadata::ReadDirectoryByPathOnUIThread(
288 const base::FilePath
& file_path
,
289 const ReadDirectoryCallback
& callback
) {
290 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
291 DCHECK(!callback
.is_null());
293 scoped_ptr
<ResourceEntryVector
> entries(new ResourceEntryVector
);
294 ResourceEntryVector
* entries_ptr
= entries
.get();
295 base::PostTaskAndReplyWithResult(
296 blocking_task_runner_
.get(),
298 base::Bind(&ResourceMetadata::ReadDirectoryByPath
,
299 base::Unretained(this),
302 base::Bind(&RunReadDirectoryCallback
, callback
, base::Passed(&entries
)));
305 FileError
ResourceMetadata::ReadDirectoryByPath(
306 const base::FilePath
& path
,
307 ResourceEntryVector
* out_entries
) {
308 DCHECK(blocking_task_runner_
->RunsTasksOnCurrentThread());
312 FileError error
= GetIdByPath(path
, &id
);
313 if (error
!= FILE_ERROR_OK
)
317 error
= GetResourceEntryById(id
, &entry
);
318 if (error
!= FILE_ERROR_OK
)
321 if (!entry
.file_info().is_directory())
322 return FILE_ERROR_NOT_A_DIRECTORY
;
324 std::vector
<std::string
> children
;
325 storage_
->GetChildren(id
, &children
);
327 ResourceEntryVector
entries(children
.size());
328 for (size_t i
= 0; i
< children
.size(); ++i
) {
329 if (!storage_
->GetEntry(children
[i
], &entries
[i
]))
330 return FILE_ERROR_FAILED
;
332 out_entries
->swap(entries
);
333 return FILE_ERROR_OK
;
336 FileError
ResourceMetadata::RefreshEntry(const ResourceEntry
& entry
) {
337 DCHECK(blocking_task_runner_
->RunsTasksOnCurrentThread());
338 // TODO(hashimoto): Return an error if the operation will result in having
339 // multiple entries with the same resource ID.
341 if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_
->directory_path()))
342 return FILE_ERROR_NO_LOCAL_SPACE
;
344 ResourceEntry old_entry
;
345 if (!storage_
->GetEntry(entry
.local_id(), &old_entry
))
346 return FILE_ERROR_NOT_FOUND
;
348 if (old_entry
.parent_local_id().empty() || // Reject root.
349 old_entry
.file_info().is_directory() != // Reject incompatible input.
350 entry
.file_info().is_directory())
351 return FILE_ERROR_INVALID_OPERATION
;
353 // Make sure that the new parent exists and it is a directory.
354 ResourceEntry new_parent
;
355 if (!storage_
->GetEntry(entry
.parent_local_id(), &new_parent
))
356 return FILE_ERROR_NOT_FOUND
;
358 if (!new_parent
.file_info().is_directory())
359 return FILE_ERROR_NOT_A_DIRECTORY
;
361 // Remove from the old parent and add it to the new parent with the new data.
362 if (!PutEntryUnderDirectory(entry
))
363 return FILE_ERROR_FAILED
;
364 return FILE_ERROR_OK
;
367 void ResourceMetadata::GetSubDirectoriesRecursively(
368 const std::string
& id
,
369 std::set
<base::FilePath
>* sub_directories
) {
370 DCHECK(blocking_task_runner_
->RunsTasksOnCurrentThread());
372 std::vector
<std::string
> children
;
373 storage_
->GetChildren(id
, &children
);
374 for (size_t i
= 0; i
< children
.size(); ++i
) {
376 if (storage_
->GetEntry(children
[i
], &entry
) &&
377 entry
.file_info().is_directory()) {
378 sub_directories
->insert(GetFilePath(children
[i
]));
379 GetSubDirectoriesRecursively(children
[i
], sub_directories
);
384 std::string
ResourceMetadata::GetChildId(const std::string
& parent_local_id
,
385 const std::string
& base_name
) {
386 DCHECK(blocking_task_runner_
->RunsTasksOnCurrentThread());
387 return storage_
->GetChild(parent_local_id
, base_name
);
390 scoped_ptr
<ResourceMetadata::Iterator
> ResourceMetadata::GetIterator() {
391 DCHECK(blocking_task_runner_
->RunsTasksOnCurrentThread());
393 return storage_
->GetIterator();
396 base::FilePath
ResourceMetadata::GetFilePath(const std::string
& id
) {
397 DCHECK(blocking_task_runner_
->RunsTasksOnCurrentThread());
401 if (storage_
->GetEntry(id
, &entry
)) {
402 if (!entry
.parent_local_id().empty())
403 path
= GetFilePath(entry
.parent_local_id());
404 path
= path
.Append(base::FilePath::FromUTF8Unsafe(entry
.base_name()));
409 FileError
ResourceMetadata::GetIdByPath(const base::FilePath
& file_path
,
410 std::string
* out_id
) {
411 DCHECK(blocking_task_runner_
->RunsTasksOnCurrentThread());
413 // Start from the root.
414 std::vector
<base::FilePath::StringType
> components
;
415 file_path
.GetComponents(&components
);
416 if (components
.empty() || components
[0] != util::kDriveGrandRootDirName
)
417 return FILE_ERROR_NOT_FOUND
;
419 // Iterate over the remaining components.
420 std::string id
= util::kDriveGrandRootLocalId
;
421 for (size_t i
= 1; i
< components
.size(); ++i
) {
422 const std::string component
= base::FilePath(components
[i
]).AsUTF8Unsafe();
423 id
= storage_
->GetChild(id
, component
);
425 return FILE_ERROR_NOT_FOUND
;
428 return FILE_ERROR_OK
;
431 FileError
ResourceMetadata::GetIdByResourceId(const std::string
& resource_id
,
432 std::string
* out_local_id
) {
433 DCHECK(blocking_task_runner_
->RunsTasksOnCurrentThread());
435 return storage_
->GetIdByResourceId(resource_id
, out_local_id
) ?
436 FILE_ERROR_OK
: FILE_ERROR_NOT_FOUND
;
439 bool ResourceMetadata::PutEntryUnderDirectory(const ResourceEntry
& entry
) {
440 DCHECK(blocking_task_runner_
->RunsTasksOnCurrentThread());
441 DCHECK(!entry
.local_id().empty());
442 DCHECK(!entry
.parent_local_id().empty());
444 ResourceEntry
updated_entry(entry
);
446 // The entry name may have been changed due to prior name de-duplication.
447 // We need to first restore the file name based on the title before going
448 // through name de-duplication again when it is added to another directory.
449 SetBaseNameFromTitle(&updated_entry
);
451 // Do file name de-duplication - Keep changing |entry|'s name until there is
452 // no other entry with the same name under the parent.
454 std::string new_base_name
= updated_entry
.base_name();
456 const std::string existing_entry_id
=
457 storage_
->GetChild(entry
.parent_local_id(), new_base_name
);
458 if (existing_entry_id
.empty() || existing_entry_id
== entry
.local_id())
461 base::FilePath new_path
=
462 base::FilePath::FromUTF8Unsafe(updated_entry
.base_name());
464 new_path
.InsertBeforeExtension(base::StringPrintf(" (%d)", ++modifier
));
465 // The new filename must be different from the previous one.
466 DCHECK_NE(new_base_name
, new_path
.AsUTF8Unsafe());
467 new_base_name
= new_path
.AsUTF8Unsafe();
469 updated_entry
.set_base_name(new_base_name
);
471 // Add the entry to resource map.
472 return storage_
->PutEntry(updated_entry
);
475 bool ResourceMetadata::RemoveEntryRecursively(const std::string
& id
) {
476 DCHECK(blocking_task_runner_
->RunsTasksOnCurrentThread());
479 if (!storage_
->GetEntry(id
, &entry
))
482 if (entry
.file_info().is_directory()) {
483 std::vector
<std::string
> children
;
484 storage_
->GetChildren(id
, &children
);
485 for (size_t i
= 0; i
< children
.size(); ++i
) {
486 if (!RemoveEntryRecursively(children
[i
]))
490 return storage_
->RemoveEntry(id
);
493 } // namespace internal