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/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" 15 16using content::BrowserThread; 17 18namespace drive { 19namespace { 20 21// Sets entry's base name from its title and other attributes. 22void 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(); 27 } 28 entry->set_base_name(util::NormalizeFileName(base_name)); 29} 30 31// Returns true if enough disk space is available for DB operation. 32// TODO(hashimoto): Merge this with FileCache's FreeDiskSpaceGetterInterface. 33bool 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); 37} 38 39// Runs |callback| with arguments. 40void RunGetResourceEntryCallback(const GetResourceEntryCallback& callback, 41 scoped_ptr<ResourceEntry> entry, 42 FileError error) { 43 DCHECK(!callback.is_null()); 44 45 if (error != FILE_ERROR_OK) 46 entry.reset(); 47 callback.Run(error, entry.Pass()); 48} 49 50// Runs |callback| with arguments. 51void RunReadDirectoryCallback(const ReadDirectoryCallback& callback, 52 scoped_ptr<ResourceEntryVector> entries, 53 FileError error) { 54 DCHECK(!callback.is_null()); 55 56 if (error != FILE_ERROR_OK) 57 entries.reset(); 58 callback.Run(error, entries.Pass()); 59} 60 61} // namespace 62 63namespace internal { 64 65ResourceMetadata::ResourceMetadata( 66 ResourceMetadataStorage* storage, 67 scoped_refptr<base::SequencedTaskRunner> blocking_task_runner) 68 : blocking_task_runner_(blocking_task_runner), 69 storage_(storage), 70 weak_ptr_factory_(this) { 71 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 72} 73 74FileError ResourceMetadata::Initialize() { 75 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread()); 76 77 if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_->directory_path())) 78 return FILE_ERROR_NO_LOCAL_SPACE; 79 80 if (!SetUpDefaultEntries()) 81 return FILE_ERROR_FAILED; 82 83 return FILE_ERROR_OK; 84} 85 86void ResourceMetadata::Destroy() { 87 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 88 89 weak_ptr_factory_.InvalidateWeakPtrs(); 90 blocking_task_runner_->PostTask( 91 FROM_HERE, 92 base::Bind(&ResourceMetadata::DestroyOnBlockingPool, 93 base::Unretained(this))); 94} 95 96FileError ResourceMetadata::Reset() { 97 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread()); 98 99 if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_->directory_path())) 100 return FILE_ERROR_NO_LOCAL_SPACE; 101 102 if (!storage_->SetLargestChangestamp(0) || 103 !RemoveEntryRecursively(util::kDriveGrandRootLocalId) || 104 !SetUpDefaultEntries()) 105 return FILE_ERROR_FAILED; 106 107 return FILE_ERROR_OK; 108} 109 110ResourceMetadata::~ResourceMetadata() { 111 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread()); 112} 113 114bool ResourceMetadata::SetUpDefaultEntries() { 115 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread()); 116 117 // Initialize "/drive", "/drive/other" and "drive/trash". 118 ResourceEntry entry; 119 if (!storage_->GetEntry(util::kDriveGrandRootLocalId, &entry)) { 120 ResourceEntry root; 121 root.mutable_file_info()->set_is_directory(true); 122 // TODO(hashimoto): Stop setting dummy resource ID here. 123 root.set_resource_id(util::kDriveGrandRootLocalId); 124 root.set_local_id(util::kDriveGrandRootLocalId); 125 root.set_title(util::kDriveGrandRootDirName); 126 SetBaseNameFromTitle(&root); 127 if (!storage_->PutEntry(root)) 128 return false; 129 } 130 if (!storage_->GetEntry(util::kDriveOtherDirLocalId, &entry)) { 131 ResourceEntry other_dir; 132 other_dir.mutable_file_info()->set_is_directory(true); 133 // TODO(hashimoto): Stop setting dummy resource ID here. 134 other_dir.set_resource_id(util::kDriveOtherDirLocalId); 135 other_dir.set_local_id(util::kDriveOtherDirLocalId); 136 other_dir.set_parent_local_id(util::kDriveGrandRootLocalId); 137 other_dir.set_title(util::kDriveOtherDirName); 138 if (!PutEntryUnderDirectory(other_dir)) 139 return false; 140 } 141 if (!storage_->GetEntry(util::kDriveTrashDirLocalId, &entry)) { 142 ResourceEntry trash_dir; 143 trash_dir.mutable_file_info()->set_is_directory(true); 144 trash_dir.set_local_id(util::kDriveTrashDirLocalId); 145 trash_dir.set_parent_local_id(util::kDriveGrandRootLocalId); 146 trash_dir.set_title(util::kDriveTrashDirName); 147 if (!PutEntryUnderDirectory(trash_dir)) 148 return false; 149 } 150 return true; 151} 152 153void ResourceMetadata::DestroyOnBlockingPool() { 154 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread()); 155 delete this; 156} 157 158int64 ResourceMetadata::GetLargestChangestamp() { 159 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread()); 160 return storage_->GetLargestChangestamp(); 161} 162 163FileError ResourceMetadata::SetLargestChangestamp(int64 value) { 164 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread()); 165 166 if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_->directory_path())) 167 return FILE_ERROR_NO_LOCAL_SPACE; 168 169 return storage_->SetLargestChangestamp(value) ? 170 FILE_ERROR_OK : FILE_ERROR_FAILED; 171} 172 173FileError ResourceMetadata::AddEntry(const ResourceEntry& entry, 174 std::string* out_id) { 175 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread()); 176 DCHECK(entry.local_id().empty()); 177 178 if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_->directory_path())) 179 return FILE_ERROR_NO_LOCAL_SPACE; 180 181 ResourceEntry parent; 182 if (!storage_->GetEntry(entry.parent_local_id(), &parent) || 183 !parent.file_info().is_directory()) 184 return FILE_ERROR_NOT_FOUND; 185 186 // Multiple entries with the same resource ID should not be present. 187 std::string local_id; 188 ResourceEntry existing_entry; 189 if (!entry.resource_id().empty() && 190 storage_->GetIdByResourceId(entry.resource_id(), &local_id) && 191 storage_->GetEntry(local_id, &existing_entry)) 192 return FILE_ERROR_EXISTS; 193 194 // Generate unique local ID when needed. 195 while (local_id.empty() || storage_->GetEntry(local_id, &existing_entry)) 196 local_id = base::GenerateGUID(); 197 198 ResourceEntry new_entry(entry); 199 new_entry.set_local_id(local_id); 200 201 if (!PutEntryUnderDirectory(new_entry)) 202 return FILE_ERROR_FAILED; 203 204 *out_id = local_id; 205 return FILE_ERROR_OK; 206} 207 208FileError ResourceMetadata::RemoveEntry(const std::string& id) { 209 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread()); 210 211 if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_->directory_path())) 212 return FILE_ERROR_NO_LOCAL_SPACE; 213 214 // Disallow deletion of default entries. 215 if (id == util::kDriveGrandRootLocalId || 216 id == util::kDriveOtherDirLocalId || 217 id == util::kDriveTrashDirLocalId) 218 return FILE_ERROR_ACCESS_DENIED; 219 220 ResourceEntry entry; 221 if (!storage_->GetEntry(id, &entry)) 222 return FILE_ERROR_NOT_FOUND; 223 224 if (!RemoveEntryRecursively(id)) 225 return FILE_ERROR_FAILED; 226 return FILE_ERROR_OK; 227} 228 229FileError ResourceMetadata::GetResourceEntryById(const std::string& id, 230 ResourceEntry* out_entry) { 231 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread()); 232 DCHECK(!id.empty()); 233 DCHECK(out_entry); 234 235 return storage_->GetEntry(id, out_entry) ? 236 FILE_ERROR_OK : FILE_ERROR_NOT_FOUND; 237} 238 239void ResourceMetadata::GetResourceEntryByPathOnUIThread( 240 const base::FilePath& file_path, 241 const GetResourceEntryCallback& callback) { 242 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 243 DCHECK(!callback.is_null()); 244 245 scoped_ptr<ResourceEntry> entry(new ResourceEntry); 246 ResourceEntry* entry_ptr = entry.get(); 247 base::PostTaskAndReplyWithResult( 248 blocking_task_runner_.get(), 249 FROM_HERE, 250 base::Bind(&ResourceMetadata::GetResourceEntryByPath, 251 base::Unretained(this), 252 file_path, 253 entry_ptr), 254 base::Bind(&RunGetResourceEntryCallback, callback, base::Passed(&entry))); 255} 256 257FileError ResourceMetadata::GetResourceEntryByPath(const base::FilePath& path, 258 ResourceEntry* out_entry) { 259 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread()); 260 DCHECK(out_entry); 261 262 std::string id; 263 FileError error = GetIdByPath(path, &id); 264 if (error != FILE_ERROR_OK) 265 return error; 266 267 return GetResourceEntryById(id, out_entry); 268} 269 270void ResourceMetadata::ReadDirectoryByPathOnUIThread( 271 const base::FilePath& file_path, 272 const ReadDirectoryCallback& callback) { 273 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 274 DCHECK(!callback.is_null()); 275 276 scoped_ptr<ResourceEntryVector> entries(new ResourceEntryVector); 277 ResourceEntryVector* entries_ptr = entries.get(); 278 base::PostTaskAndReplyWithResult( 279 blocking_task_runner_.get(), 280 FROM_HERE, 281 base::Bind(&ResourceMetadata::ReadDirectoryByPath, 282 base::Unretained(this), 283 file_path, 284 entries_ptr), 285 base::Bind(&RunReadDirectoryCallback, callback, base::Passed(&entries))); 286} 287 288FileError ResourceMetadata::ReadDirectoryByPath( 289 const base::FilePath& path, 290 ResourceEntryVector* out_entries) { 291 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread()); 292 DCHECK(out_entries); 293 294 std::string id; 295 FileError error = GetIdByPath(path, &id); 296 if (error != FILE_ERROR_OK) 297 return error; 298 299 ResourceEntry entry; 300 error = GetResourceEntryById(id, &entry); 301 if (error != FILE_ERROR_OK) 302 return error; 303 304 if (!entry.file_info().is_directory()) 305 return FILE_ERROR_NOT_A_DIRECTORY; 306 307 std::vector<std::string> children; 308 storage_->GetChildren(id, &children); 309 310 ResourceEntryVector entries(children.size()); 311 for (size_t i = 0; i < children.size(); ++i) { 312 if (!storage_->GetEntry(children[i], &entries[i])) 313 return FILE_ERROR_FAILED; 314 } 315 out_entries->swap(entries); 316 return FILE_ERROR_OK; 317} 318 319FileError ResourceMetadata::RefreshEntry(const ResourceEntry& entry) { 320 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread()); 321 // TODO(hashimoto): Return an error if the operation will result in having 322 // multiple entries with the same resource ID. 323 324 if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_->directory_path())) 325 return FILE_ERROR_NO_LOCAL_SPACE; 326 327 ResourceEntry old_entry; 328 if (!storage_->GetEntry(entry.local_id(), &old_entry)) 329 return FILE_ERROR_NOT_FOUND; 330 331 if (old_entry.parent_local_id().empty() || // Reject root. 332 old_entry.file_info().is_directory() != // Reject incompatible input. 333 entry.file_info().is_directory()) 334 return FILE_ERROR_INVALID_OPERATION; 335 336 // Make sure that the new parent exists and it is a directory. 337 ResourceEntry new_parent; 338 if (!storage_->GetEntry(entry.parent_local_id(), &new_parent)) 339 return FILE_ERROR_NOT_FOUND; 340 341 if (!new_parent.file_info().is_directory()) 342 return FILE_ERROR_NOT_A_DIRECTORY; 343 344 // Remove from the old parent and add it to the new parent with the new data. 345 if (!PutEntryUnderDirectory(entry)) 346 return FILE_ERROR_FAILED; 347 return FILE_ERROR_OK; 348} 349 350void ResourceMetadata::GetSubDirectoriesRecursively( 351 const std::string& id, 352 std::set<base::FilePath>* sub_directories) { 353 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread()); 354 355 std::vector<std::string> children; 356 storage_->GetChildren(id, &children); 357 for (size_t i = 0; i < children.size(); ++i) { 358 ResourceEntry entry; 359 if (storage_->GetEntry(children[i], &entry) && 360 entry.file_info().is_directory()) { 361 sub_directories->insert(GetFilePath(children[i])); 362 GetSubDirectoriesRecursively(children[i], sub_directories); 363 } 364 } 365} 366 367std::string ResourceMetadata::GetChildId(const std::string& parent_local_id, 368 const std::string& base_name) { 369 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread()); 370 return storage_->GetChild(parent_local_id, base_name); 371} 372 373scoped_ptr<ResourceMetadata::Iterator> ResourceMetadata::GetIterator() { 374 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread()); 375 376 return storage_->GetIterator(); 377} 378 379base::FilePath ResourceMetadata::GetFilePath(const std::string& id) { 380 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread()); 381 382 base::FilePath path; 383 ResourceEntry entry; 384 if (storage_->GetEntry(id, &entry)) { 385 if (!entry.parent_local_id().empty()) 386 path = GetFilePath(entry.parent_local_id()); 387 path = path.Append(base::FilePath::FromUTF8Unsafe(entry.base_name())); 388 } 389 return path; 390} 391 392FileError ResourceMetadata::GetIdByPath(const base::FilePath& file_path, 393 std::string* out_id) { 394 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread()); 395 396 // Start from the root. 397 std::vector<base::FilePath::StringType> components; 398 file_path.GetComponents(&components); 399 if (components.empty() || components[0] != util::kDriveGrandRootDirName) 400 return FILE_ERROR_NOT_FOUND; 401 402 // Iterate over the remaining components. 403 std::string id = util::kDriveGrandRootLocalId; 404 for (size_t i = 1; i < components.size(); ++i) { 405 const std::string component = base::FilePath(components[i]).AsUTF8Unsafe(); 406 id = storage_->GetChild(id, component); 407 if (id.empty()) 408 return FILE_ERROR_NOT_FOUND; 409 } 410 *out_id = id; 411 return FILE_ERROR_OK; 412} 413 414FileError ResourceMetadata::GetIdByResourceId(const std::string& resource_id, 415 std::string* out_local_id) { 416 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread()); 417 418 return storage_->GetIdByResourceId(resource_id, out_local_id) ? 419 FILE_ERROR_OK : FILE_ERROR_NOT_FOUND; 420} 421 422bool ResourceMetadata::PutEntryUnderDirectory(const ResourceEntry& entry) { 423 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread()); 424 DCHECK(!entry.local_id().empty()); 425 DCHECK(!entry.parent_local_id().empty()); 426 427 ResourceEntry updated_entry(entry); 428 429 // The entry name may have been changed due to prior name de-duplication. 430 // We need to first restore the file name based on the title before going 431 // through name de-duplication again when it is added to another directory. 432 SetBaseNameFromTitle(&updated_entry); 433 434 // Do file name de-duplication - Keep changing |entry|'s name until there is 435 // no other entry with the same name under the parent. 436 int modifier = 0; 437 std::string new_base_name = updated_entry.base_name(); 438 while (true) { 439 const std::string existing_entry_id = 440 storage_->GetChild(entry.parent_local_id(), new_base_name); 441 if (existing_entry_id.empty() || existing_entry_id == entry.local_id()) 442 break; 443 444 base::FilePath new_path = 445 base::FilePath::FromUTF8Unsafe(updated_entry.base_name()); 446 new_path = 447 new_path.InsertBeforeExtension(base::StringPrintf(" (%d)", ++modifier)); 448 // The new filename must be different from the previous one. 449 DCHECK_NE(new_base_name, new_path.AsUTF8Unsafe()); 450 new_base_name = new_path.AsUTF8Unsafe(); 451 } 452 updated_entry.set_base_name(new_base_name); 453 454 // Add the entry to resource map. 455 return storage_->PutEntry(updated_entry); 456} 457 458bool ResourceMetadata::RemoveEntryRecursively(const std::string& id) { 459 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread()); 460 461 ResourceEntry entry; 462 if (!storage_->GetEntry(id, &entry)) 463 return false; 464 465 if (entry.file_info().is_directory()) { 466 std::vector<std::string> children; 467 storage_->GetChildren(id, &children); 468 for (size_t i = 0; i < children.size(); ++i) { 469 if (!RemoveEntryRecursively(children[i])) 470 return false; 471 } 472 } 473 return storage_->RemoveEntry(id); 474} 475 476} // namespace internal 477} // namespace drive 478