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. 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") 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. 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. 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 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 78 FileError ResourceMetadata::Initialize() { 79 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread()); 80 return SetUpDefaultEntries(); 81 } 82 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 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 117 ResourceMetadata::~ResourceMetadata() { 118 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread()); 119 } 120 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 206 void ResourceMetadata::DestroyOnBlockingPool() { 207 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread()); 208 delete this; 209 } 210 211 FileError ResourceMetadata::GetLargestChangestamp(int64* out_value) { 212 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread()); 213 return storage_->GetLargestChangestamp(out_value); 214 } 215 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 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 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 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 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 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 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 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 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 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 441 scoped_ptr<ResourceMetadata::Iterator> ResourceMetadata::GetIterator() { 442 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread()); 443 444 return storage_->GetIterator(); 445 } 446 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 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 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 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 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 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