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 16 using content::BrowserThread; 17 18 namespace drive { 19 namespace { 20 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(); 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. 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); 37 } 38 39 // Runs |callback| with arguments. 40 void 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. 51 void 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 63 namespace internal { 64 65 ResourceMetadata::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 74 FileError 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 86 void 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 96 FileError 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 110 ResourceMetadata::~ResourceMetadata() { 111 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread()); 112 } 113 114 bool 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 153 void ResourceMetadata::DestroyOnBlockingPool() { 154 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread()); 155 delete this; 156 } 157 158 int64 ResourceMetadata::GetLargestChangestamp() { 159 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread()); 160 return storage_->GetLargestChangestamp(); 161 } 162 163 FileError 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 173 FileError 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 208 FileError 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 229 FileError 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 239 void 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 257 FileError 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 270 void 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 288 FileError 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 319 FileError 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 350 void 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 367 std::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 373 scoped_ptr<ResourceMetadata::Iterator> ResourceMetadata::GetIterator() { 374 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread()); 375 376 return storage_->GetIterator(); 377 } 378 379 base::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 392 FileError 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 414 FileError 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 422 bool 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 458 bool 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