metadata_database.cc revision bb1529ce867d8845a77ec7cdf3e3003ef1771a40
1// Copyright 2013 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/sync_file_system/drive_backend/metadata_database.h" 6 7#include <stack> 8 9#include "base/bind.h" 10#include "base/callback.h" 11#include "base/files/file_path.h" 12#include "base/location.h" 13#include "base/memory/scoped_vector.h" 14#include "base/message_loop/message_loop_proxy.h" 15#include "base/sequenced_task_runner.h" 16#include "base/stl_util.h" 17#include "base/strings/string_number_conversions.h" 18#include "base/strings/string_util.h" 19#include "base/strings/stringprintf.h" 20#include "base/task_runner_util.h" 21#include "base/threading/thread_restrictions.h" 22#include "chrome/browser/google_apis/drive_api_parser.h" 23#include "chrome/browser/sync_file_system/drive_backend/metadata_database.pb.h" 24#include "chrome/browser/sync_file_system/drive_backend/metadata_db_migration_util.h" 25#include "chrome/browser/sync_file_system/logger.h" 26#include "chrome/browser/sync_file_system/syncable_file_system_util.h" 27#include "third_party/leveldatabase/src/include/leveldb/db.h" 28#include "third_party/leveldatabase/src/include/leveldb/write_batch.h" 29#include "webkit/common/fileapi/file_system_util.h" 30 31namespace sync_file_system { 32namespace drive_backend { 33 34const char kDatabaseVersionKey[] = "VERSION"; 35const int64 kCurrentDatabaseVersion = 3; 36const char kServiceMetadataKey[] = "SERVICE"; 37const char kFileMetadataKeyPrefix[] = "FILE: "; 38const char kFileTrackerKeyPrefix[] = "TRACKER: "; 39 40struct DatabaseContents { 41 scoped_ptr<ServiceMetadata> service_metadata; 42 ScopedVector<FileMetadata> file_metadata; 43 ScopedVector<FileTracker> file_trackers; 44}; 45 46namespace { 47 48typedef MetadataDatabase::FileByID FileByID; 49typedef MetadataDatabase::TrackerByID TrackerByID; 50typedef MetadataDatabase::TrackersByParentAndTitle TrackersByParentAndTitle; 51typedef MetadataDatabase::TrackersByTitle TrackersByTitle; 52 53std::string RemovePrefix(const std::string& str, const std::string& prefix) { 54 if (StartsWithASCII(str, prefix, true)) 55 return str.substr(prefix.size()); 56 return str; 57} 58 59base::FilePath ReverseConcatPathComponents( 60 const std::vector<base::FilePath>& components) { 61 if (components.empty()) 62 return base::FilePath(FILE_PATH_LITERAL("/")).NormalizePathSeparators(); 63 64 size_t total_size = 0; 65 typedef std::vector<base::FilePath> PathComponents; 66 for (PathComponents::const_iterator itr = components.begin(); 67 itr != components.end(); ++itr) 68 total_size += itr->value().size() + 1; 69 70 base::FilePath::StringType result; 71 result.reserve(total_size); 72 for (PathComponents::const_reverse_iterator itr = components.rbegin(); 73 itr != components.rend(); ++itr) { 74 result.append(1, base::FilePath::kSeparators[0]); 75 result.append(itr->value()); 76 } 77 78 return base::FilePath(result).NormalizePathSeparators(); 79} 80 81void AdaptLevelDBStatusToSyncStatusCode(const SyncStatusCallback& callback, 82 const leveldb::Status& status) { 83 callback.Run(LevelDBStatusToSyncStatusCode(status)); 84} 85 86void PutServiceMetadataToBatch(const ServiceMetadata& service_metadata, 87 leveldb::WriteBatch* batch) { 88 std::string value; 89 bool success = service_metadata.SerializeToString(&value); 90 DCHECK(success); 91 batch->Put(kServiceMetadataKey, value); 92} 93 94void PutFileToBatch(const FileMetadata& file, leveldb::WriteBatch* batch) { 95 std::string value; 96 bool success = file.SerializeToString(&value); 97 DCHECK(success); 98 batch->Put(kFileMetadataKeyPrefix + file.file_id(), value); 99} 100 101void PutTrackerToBatch(const FileTracker& tracker, leveldb::WriteBatch* batch) { 102 std::string value; 103 bool success = tracker.SerializeToString(&value); 104 DCHECK(success); 105 batch->Put(kFileTrackerKeyPrefix + base::Int64ToString(tracker.tracker_id()), 106 value); 107} 108 109void PutFileDeletionToBatch(const std::string& file_id, 110 leveldb::WriteBatch* batch) { 111 batch->Delete(kFileMetadataKeyPrefix + file_id); 112} 113 114void PutTrackerDeletionToBatch(int64 tracker_id, leveldb::WriteBatch* batch) { 115 batch->Delete(kFileTrackerKeyPrefix + base::Int64ToString(tracker_id)); 116} 117 118std::string GetTrackerTitle(const FileTracker& tracker) { 119 if (tracker.has_synced_details()) 120 return tracker.synced_details().title(); 121 return std::string(); 122} 123 124// Returns true if |db| has no content. 125bool IsDatabaseEmpty(leveldb::DB* db) { 126 DCHECK(db); 127 scoped_ptr<leveldb::Iterator> itr(db->NewIterator(leveldb::ReadOptions())); 128 itr->SeekToFirst(); 129 return !itr->Valid(); 130} 131 132SyncStatusCode OpenDatabase(const base::FilePath& path, 133 scoped_ptr<leveldb::DB>* db_out, 134 bool* created) { 135 base::ThreadRestrictions::AssertIOAllowed(); 136 DCHECK(db_out); 137 DCHECK(created); 138 139 leveldb::Options options; 140 options.max_open_files = 0; // Use minimum. 141 options.create_if_missing = true; 142 leveldb::DB* db = NULL; 143 leveldb::Status db_status = 144 leveldb::DB::Open(options, path.AsUTF8Unsafe(), &db); 145 SyncStatusCode status = LevelDBStatusToSyncStatusCode(db_status); 146 if (status != SYNC_STATUS_OK) { 147 delete db; 148 return status; 149 } 150 151 *created = IsDatabaseEmpty(db); 152 db_out->reset(db); 153 return status; 154} 155 156SyncStatusCode MigrateDatabaseIfNeeded(leveldb::DB* db) { 157 base::ThreadRestrictions::AssertIOAllowed(); 158 DCHECK(db); 159 std::string value; 160 leveldb::Status status = 161 db->Get(leveldb::ReadOptions(), kDatabaseVersionKey, &value); 162 int64 version = 0; 163 if (status.ok()) { 164 if (!base::StringToInt64(value, &version)) 165 return SYNC_DATABASE_ERROR_FAILED; 166 } else { 167 if (!status.IsNotFound()) 168 return SYNC_DATABASE_ERROR_FAILED; 169 } 170 171 switch (version) { 172 case 0: 173 drive_backend::MigrateDatabaseFromV0ToV1(db); 174 // fall-through 175 case 1: 176 drive_backend::MigrateDatabaseFromV1ToV2(db); 177 // fall-through 178 case 2: 179 // TODO(tzik): Migrate database from version 2 to 3. 180 // * Add sync-root folder as active, dirty and needs_folder_listing 181 // folder. 182 // * Add app-root folders for each origins. Each app-root folder for 183 // an enabled origin should be a active, dirty and 184 // needs_folder_listing folder. And Each app-root folder for a 185 // disabled origin should be an inactive, dirty and 186 // non-needs_folder_listing folder. 187 // * Add a file metadata for each file in previous version. 188 NOTIMPLEMENTED(); 189 return SYNC_DATABASE_ERROR_FAILED; 190 // fall-through 191 case 3: 192 DCHECK_EQ(3, kCurrentDatabaseVersion); 193 return SYNC_STATUS_OK; 194 default: 195 return SYNC_DATABASE_ERROR_FAILED; 196 } 197} 198 199SyncStatusCode WriteVersionInfo(leveldb::DB* db) { 200 base::ThreadRestrictions::AssertIOAllowed(); 201 DCHECK(db); 202 return LevelDBStatusToSyncStatusCode( 203 db->Put(leveldb::WriteOptions(), 204 kDatabaseVersionKey, 205 base::Int64ToString(kCurrentDatabaseVersion))); 206} 207 208SyncStatusCode ReadDatabaseContents(leveldb::DB* db, 209 DatabaseContents* contents) { 210 base::ThreadRestrictions::AssertIOAllowed(); 211 DCHECK(db); 212 DCHECK(contents); 213 214 scoped_ptr<leveldb::Iterator> itr(db->NewIterator(leveldb::ReadOptions())); 215 for (itr->SeekToFirst(); itr->Valid(); itr->Next()) { 216 std::string key = itr->key().ToString(); 217 std::string value = itr->value().ToString(); 218 if (key == kServiceMetadataKey) { 219 scoped_ptr<ServiceMetadata> service_metadata(new ServiceMetadata); 220 if (!service_metadata->ParseFromString(value)) { 221 util::Log(logging::LOG_WARNING, FROM_HERE, 222 "Failed to parse SyncServiceMetadata"); 223 continue; 224 } 225 226 contents->service_metadata = service_metadata.Pass(); 227 continue; 228 } 229 230 if (StartsWithASCII(key, kFileMetadataKeyPrefix, true)) { 231 std::string file_id = RemovePrefix(key, kFileMetadataKeyPrefix); 232 233 scoped_ptr<FileMetadata> file(new FileMetadata); 234 if (!file->ParseFromString(itr->value().ToString())) { 235 util::Log(logging::LOG_WARNING, FROM_HERE, 236 "Failed to parse a FileMetadata"); 237 continue; 238 } 239 240 contents->file_metadata.push_back(file.release()); 241 continue; 242 } 243 244 if (StartsWithASCII(key, kFileTrackerKeyPrefix, true)) { 245 int64 tracker_id = 0; 246 if (!base::StringToInt64(RemovePrefix(key, kFileTrackerKeyPrefix), 247 &tracker_id)) { 248 util::Log(logging::LOG_WARNING, FROM_HERE, 249 "Failed to parse TrackerID"); 250 continue; 251 } 252 253 scoped_ptr<FileTracker> tracker(new FileTracker); 254 if (!tracker->ParseFromString(itr->value().ToString())) { 255 util::Log(logging::LOG_WARNING, FROM_HERE, 256 "Failed to parse a Tracker"); 257 continue; 258 } 259 contents->file_trackers.push_back(tracker.release()); 260 continue; 261 } 262 } 263 264 return SYNC_STATUS_OK; 265} 266 267SyncStatusCode InitializeServiceMetadata(DatabaseContents* contents, 268 leveldb::WriteBatch* batch) { 269 270 if (!contents->service_metadata) { 271 contents->service_metadata.reset(new ServiceMetadata); 272 contents->service_metadata->set_next_tracker_id(1); 273 274 std::string value; 275 contents->service_metadata->SerializeToString(&value); 276 batch->Put(kServiceMetadataKey, value); 277 } 278 return SYNC_STATUS_OK; 279} 280 281SyncStatusCode RemoveUnreachableItems(DatabaseContents* contents, 282 leveldb::WriteBatch* batch) { 283 TrackerByID unvisited_trackers; 284 typedef std::map<int64, std::set<FileTracker*> > TrackersByParent; 285 TrackersByParent trackers_by_parent; 286 287 for (ScopedVector<FileTracker>::iterator itr = 288 contents->file_trackers.begin(); 289 itr != contents->file_trackers.end(); 290 ++itr) { 291 FileTracker* tracker = *itr; 292 DCHECK(!ContainsKey(unvisited_trackers, tracker->tracker_id())); 293 unvisited_trackers[tracker->tracker_id()] = tracker; 294 if (tracker->parent_tracker_id()) 295 trackers_by_parent[tracker->parent_tracker_id()].insert(tracker); 296 } 297 298 // Traverse synced tracker tree. Take only active items, app-root and their 299 // children. Drop unreachable items. 300 ScopedVector<FileTracker> reachable_trackers; 301 std::stack<int64> pending; 302 if (contents->service_metadata->sync_root_tracker_id()) 303 pending.push(contents->service_metadata->sync_root_tracker_id()); 304 305 while (!pending.empty()) { 306 int64 tracker_id = pending.top(); 307 pending.pop(); 308 309 { 310 TrackerByID::iterator found = unvisited_trackers.find(tracker_id); 311 if (found == unvisited_trackers.end()) 312 continue; 313 314 FileTracker* tracker = found->second; 315 unvisited_trackers.erase(found); 316 reachable_trackers.push_back(tracker); 317 318 if (!tracker->active() && !tracker->is_app_root()) 319 continue; 320 } 321 322 TrackersByParent::iterator found = trackers_by_parent.find(tracker_id); 323 if (found == trackers_by_parent.end()) 324 continue; 325 326 for (std::set<FileTracker*>::const_iterator itr = 327 found->second.begin(); 328 itr != found->second.end(); 329 ++itr) 330 pending.push((*itr)->tracker_id()); 331 } 332 333 // Delete all unreachable trackers. 334 for (TrackerByID::iterator itr = unvisited_trackers.begin(); 335 itr != unvisited_trackers.end(); ++itr) { 336 FileTracker* tracker = itr->second; 337 PutTrackerDeletionToBatch(tracker->tracker_id(), batch); 338 delete tracker; 339 } 340 unvisited_trackers.clear(); 341 342 // |reachable_trackers| contains all files/folders reachable from sync-root 343 // folder via active folders and app-root folders. 344 // Update the tracker set in database contents with the reachable tracker set. 345 contents->file_trackers.weak_clear(); 346 contents->file_trackers.swap(reachable_trackers); 347 348 // Do the similar traverse for FileMetadata and remove FileMetadata that don't 349 // have reachable trackers. 350 FileByID unreferred_files; 351 for (ScopedVector<FileMetadata>::const_iterator itr = 352 contents->file_metadata.begin(); 353 itr != contents->file_metadata.end(); 354 ++itr) { 355 unreferred_files.insert(std::make_pair((*itr)->file_id(), *itr)); 356 } 357 358 ScopedVector<FileMetadata> referred_files; 359 for (ScopedVector<FileTracker>::const_iterator itr = 360 contents->file_trackers.begin(); 361 itr != contents->file_trackers.end(); 362 ++itr) { 363 FileByID::iterator found = unreferred_files.find((*itr)->file_id()); 364 if (found != unreferred_files.end()) { 365 referred_files.push_back(found->second); 366 unreferred_files.erase(found); 367 } 368 } 369 370 for (FileByID::iterator itr = unreferred_files.begin(); 371 itr != unreferred_files.end(); ++itr) { 372 FileMetadata* file = itr->second; 373 PutFileDeletionToBatch(file->file_id(), batch); 374 delete file; 375 } 376 unreferred_files.clear(); 377 378 contents->file_metadata.weak_clear(); 379 contents->file_metadata.swap(referred_files); 380 381 return SYNC_STATUS_OK; 382} 383 384template <typename Container, typename Key, typename Value> 385bool FindItem(const Container& container, const Key& key, Value* value) { 386 typename Container::const_iterator found = container.find(key); 387 if (found == container.end()) 388 return false; 389 if (value) 390 *value = *found->second; 391 return true; 392} 393 394void RunSoon(const tracked_objects::Location& from_here, 395 const base::Closure& closure) { 396 base::MessageLoopProxy::current()->PostTask(from_here, closure); 397} 398 399} // namespace 400 401bool MetadataDatabase::DirtyTrackerComparator::operator()( 402 const FileTracker* left, 403 const FileTracker* right) const { 404 return left->tracker_id() < right->tracker_id(); 405} 406 407// static 408void MetadataDatabase::Create(base::SequencedTaskRunner* task_runner, 409 const base::FilePath& database_path, 410 const CreateCallback& callback) { 411 task_runner->PostTask(FROM_HERE, base::Bind( 412 &CreateOnTaskRunner, 413 base::MessageLoopProxy::current(), 414 make_scoped_refptr(task_runner), 415 database_path, callback)); 416} 417 418MetadataDatabase::~MetadataDatabase() { 419 task_runner_->DeleteSoon(FROM_HERE, db_.release()); 420 STLDeleteContainerPairSecondPointers( 421 file_by_id_.begin(), file_by_id_.end()); 422 STLDeleteContainerPairSecondPointers( 423 tracker_by_id_.begin(), tracker_by_id_.end()); 424} 425 426int64 MetadataDatabase::GetLargestChangeID() const { 427 return service_metadata_->largest_change_id(); 428} 429 430void MetadataDatabase::RegisterApp(const std::string& app_id, 431 const std::string& folder_id, 432 const SyncStatusCallback& callback) { 433 NOTIMPLEMENTED(); 434} 435 436void MetadataDatabase::DisableApp(const std::string& app_id, 437 const SyncStatusCallback& callback) { 438 NOTIMPLEMENTED(); 439} 440 441void MetadataDatabase::EnableApp(const std::string& app_id, 442 const SyncStatusCallback& callback) { 443 NOTIMPLEMENTED(); 444} 445 446void MetadataDatabase::UnregisterApp(const std::string& app_id, 447 const SyncStatusCallback& callback) { 448 NOTIMPLEMENTED(); 449} 450 451void MetadataDatabase::UpdateByChangeList( 452 ScopedVector<google_apis::ChangeResource> changes, 453 const SyncStatusCallback& callback) { 454 NOTIMPLEMENTED(); 455} 456 457MetadataDatabase::MetadataDatabase(base::SequencedTaskRunner* task_runner) 458 : task_runner_(task_runner), weak_ptr_factory_(this) { 459 DCHECK(task_runner); 460} 461 462// static 463void MetadataDatabase::CreateOnTaskRunner( 464 base::SingleThreadTaskRunner* callback_runner, 465 base::SequencedTaskRunner* task_runner, 466 const base::FilePath& database_path, 467 const CreateCallback& callback) { 468 scoped_ptr<MetadataDatabase> metadata_database( 469 new MetadataDatabase(task_runner)); 470 SyncStatusCode status = 471 metadata_database->InitializeOnTaskRunner(database_path); 472 if (status != SYNC_STATUS_OK) 473 metadata_database.reset(); 474 475 callback_runner->PostTask(FROM_HERE, base::Bind( 476 callback, status, base::Passed(&metadata_database))); 477} 478 479// static 480SyncStatusCode MetadataDatabase::CreateForTesting( 481 scoped_ptr<leveldb::DB> db, 482 scoped_ptr<MetadataDatabase>* metadata_database_out) { 483 scoped_ptr<MetadataDatabase> metadata_database( 484 new MetadataDatabase(base::MessageLoopProxy::current())); 485 metadata_database->db_ = db.Pass(); 486 SyncStatusCode status = 487 metadata_database->InitializeOnTaskRunner(base::FilePath()); 488 if (status == SYNC_STATUS_OK) 489 *metadata_database_out = metadata_database.Pass(); 490 return status; 491} 492 493SyncStatusCode MetadataDatabase::InitializeOnTaskRunner( 494 const base::FilePath& database_path) { 495 base::ThreadRestrictions::AssertIOAllowed(); 496 DCHECK(task_runner_->RunsTasksOnCurrentThread()); 497 498 SyncStatusCode status = SYNC_STATUS_UNKNOWN; 499 bool created = false; 500 // Open database unless |db_| is overridden for testing. 501 if (!db_) { 502 status = OpenDatabase(database_path, &db_, &created); 503 if (status != SYNC_STATUS_OK) 504 return status; 505 } 506 507 if (created) { 508 status = WriteVersionInfo(db_.get()); 509 if (status != SYNC_STATUS_OK) 510 return status; 511 } else { 512 status = MigrateDatabaseIfNeeded(db_.get()); 513 if (status != SYNC_STATUS_OK) 514 return status; 515 } 516 517 DatabaseContents contents; 518 status = ReadDatabaseContents(db_.get(), &contents); 519 if (status != SYNC_STATUS_OK) 520 return status; 521 522 leveldb::WriteBatch batch; 523 status = InitializeServiceMetadata(&contents, &batch); 524 if (status != SYNC_STATUS_OK) 525 return status; 526 527 status = RemoveUnreachableItems(&contents, &batch); 528 if (status != SYNC_STATUS_OK) 529 return status; 530 531 status = LevelDBStatusToSyncStatusCode( 532 db_->Write(leveldb::WriteOptions(), &batch)); 533 if (status != SYNC_STATUS_OK) 534 return status; 535 536 BuildIndexes(&contents); 537 return status; 538} 539 540void MetadataDatabase::BuildIndexes(DatabaseContents* contents) { 541 service_metadata_ = contents->service_metadata.Pass(); 542 543 for (ScopedVector<FileMetadata>::const_iterator itr = 544 contents->file_metadata.begin(); 545 itr != contents->file_metadata.end(); 546 ++itr) { 547 file_by_id_[(*itr)->file_id()] = *itr; 548 } 549 contents->file_metadata.weak_clear(); 550 551 for (ScopedVector<FileTracker>::const_iterator itr = 552 contents->file_trackers.begin(); 553 itr != contents->file_trackers.end(); 554 ++itr) { 555 FileTracker* tracker = *itr; 556 tracker_by_id_[tracker->tracker_id()] = tracker; 557 trackers_by_file_id_[tracker->file_id()].Insert(tracker); 558 559 if (tracker->is_app_root()) 560 app_root_by_app_id_[tracker->app_id()] = tracker; 561 562 if (tracker->parent_tracker_id()) { 563 std::string title = GetTrackerTitle(*tracker); 564 TrackerSet* trackers = 565 &trackers_by_parent_and_title_[tracker->parent_tracker_id()][title]; 566 trackers->Insert(tracker); 567 } 568 569 if (tracker->dirty()) 570 dirty_trackers_.insert(tracker); 571 } 572 contents->file_trackers.weak_clear(); 573} 574 575void MetadataDatabase::WriteToDatabase(scoped_ptr<leveldb::WriteBatch> batch, 576 const SyncStatusCallback& callback) { 577 base::PostTaskAndReplyWithResult( 578 task_runner_.get(), 579 FROM_HERE, 580 base::Bind(&leveldb::DB::Write, 581 base::Unretained(db_.get()), 582 leveldb::WriteOptions(), 583 base::Owned(batch.release())), 584 base::Bind(&AdaptLevelDBStatusToSyncStatusCode, callback)); 585} 586 587} // namespace drive_backend 588} // namespace sync_file_system 589