local_file_change_tracker.cc revision 116680a4aac90f2aa7413d9095a592090648e557
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/local/local_file_change_tracker.h" 6 7#include <queue> 8 9#include "base/location.h" 10#include "base/logging.h" 11#include "base/sequenced_task_runner.h" 12#include "base/stl_util.h" 13#include "chrome/browser/sync_file_system/local/local_file_sync_status.h" 14#include "chrome/browser/sync_file_system/syncable_file_system_util.h" 15#include "third_party/leveldatabase/src/helpers/memenv/memenv.h" 16#include "third_party/leveldatabase/src/include/leveldb/db.h" 17#include "third_party/leveldatabase/src/include/leveldb/env.h" 18#include "third_party/leveldatabase/src/include/leveldb/write_batch.h" 19#include "webkit/browser/fileapi/file_system_context.h" 20#include "webkit/browser/fileapi/file_system_file_util.h" 21#include "webkit/browser/fileapi/file_system_operation_context.h" 22#include "webkit/common/fileapi/file_system_util.h" 23 24using fileapi::FileSystemContext; 25using fileapi::FileSystemFileUtil; 26using fileapi::FileSystemOperationContext; 27using fileapi::FileSystemURL; 28using fileapi::FileSystemURLSet; 29 30namespace sync_file_system { 31 32namespace { 33const base::FilePath::CharType kDatabaseName[] = 34 FILE_PATH_LITERAL("LocalFileChangeTracker"); 35const char kMark[] = "d"; 36} // namespace 37 38// A database class that stores local file changes in a local database. This 39// object must be destructed on file_task_runner. 40class LocalFileChangeTracker::TrackerDB { 41 public: 42 TrackerDB(const base::FilePath& base_path, 43 leveldb::Env* env_override); 44 45 SyncStatusCode MarkDirty(const std::string& url); 46 SyncStatusCode ClearDirty(const std::string& url); 47 SyncStatusCode GetDirtyEntries( 48 std::queue<FileSystemURL>* dirty_files); 49 SyncStatusCode WriteBatch(scoped_ptr<leveldb::WriteBatch> batch); 50 51 private: 52 enum RecoveryOption { 53 REPAIR_ON_CORRUPTION, 54 FAIL_ON_CORRUPTION, 55 }; 56 57 SyncStatusCode Init(RecoveryOption recovery_option); 58 SyncStatusCode Repair(const std::string& db_path); 59 void HandleError(const tracked_objects::Location& from_here, 60 const leveldb::Status& status); 61 62 const base::FilePath base_path_; 63 leveldb::Env* env_override_; 64 scoped_ptr<leveldb::DB> db_; 65 SyncStatusCode db_status_; 66 67 DISALLOW_COPY_AND_ASSIGN(TrackerDB); 68}; 69 70LocalFileChangeTracker::ChangeInfo::ChangeInfo() : change_seq(-1) {} 71LocalFileChangeTracker::ChangeInfo::~ChangeInfo() {} 72 73// LocalFileChangeTracker ------------------------------------------------------ 74 75LocalFileChangeTracker::LocalFileChangeTracker( 76 const base::FilePath& base_path, 77 leveldb::Env* env_override, 78 base::SequencedTaskRunner* file_task_runner) 79 : initialized_(false), 80 file_task_runner_(file_task_runner), 81 tracker_db_(new TrackerDB(base_path, env_override)), 82 current_change_seq_(0), 83 num_changes_(0) { 84} 85 86LocalFileChangeTracker::~LocalFileChangeTracker() { 87 DCHECK(file_task_runner_->RunsTasksOnCurrentThread()); 88 tracker_db_.reset(); 89} 90 91void LocalFileChangeTracker::OnStartUpdate(const FileSystemURL& url) { 92 DCHECK(file_task_runner_->RunsTasksOnCurrentThread()); 93 if (ContainsKey(changes_, url)) 94 return; 95 // TODO(nhiroki): propagate the error code (see http://crbug.com/152127). 96 MarkDirtyOnDatabase(url); 97} 98 99void LocalFileChangeTracker::OnEndUpdate(const FileSystemURL& url) {} 100 101void LocalFileChangeTracker::OnCreateFile(const FileSystemURL& url) { 102 RecordChange(url, FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE, 103 SYNC_FILE_TYPE_FILE)); 104} 105 106void LocalFileChangeTracker::OnCreateFileFrom(const FileSystemURL& url, 107 const FileSystemURL& src) { 108 RecordChange(url, FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE, 109 SYNC_FILE_TYPE_FILE)); 110} 111 112void LocalFileChangeTracker::OnRemoveFile(const FileSystemURL& url) { 113 RecordChange(url, FileChange(FileChange::FILE_CHANGE_DELETE, 114 SYNC_FILE_TYPE_FILE)); 115} 116 117void LocalFileChangeTracker::OnModifyFile(const FileSystemURL& url) { 118 RecordChange(url, FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE, 119 SYNC_FILE_TYPE_FILE)); 120} 121 122void LocalFileChangeTracker::OnCreateDirectory(const FileSystemURL& url) { 123 RecordChange(url, FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE, 124 SYNC_FILE_TYPE_DIRECTORY)); 125} 126 127void LocalFileChangeTracker::OnRemoveDirectory(const FileSystemURL& url) { 128 RecordChange(url, FileChange(FileChange::FILE_CHANGE_DELETE, 129 SYNC_FILE_TYPE_DIRECTORY)); 130} 131 132void LocalFileChangeTracker::GetNextChangedURLs( 133 std::deque<FileSystemURL>* urls, int max_urls) { 134 DCHECK(file_task_runner_->RunsTasksOnCurrentThread()); 135 DCHECK(urls); 136 urls->clear(); 137 // Mildly prioritizes the URLs that older changes and have not been updated 138 // for a while. 139 for (ChangeSeqMap::iterator iter = change_seqs_.begin(); 140 iter != change_seqs_.end() && 141 (max_urls == 0 || urls->size() < static_cast<size_t>(max_urls)); 142 ++iter) { 143 urls->push_back(iter->second); 144 } 145} 146 147void LocalFileChangeTracker::GetChangesForURL( 148 const FileSystemURL& url, FileChangeList* changes) { 149 DCHECK(file_task_runner_->RunsTasksOnCurrentThread()); 150 DCHECK(changes); 151 changes->clear(); 152 FileChangeMap::iterator found = changes_.find(url); 153 if (found == changes_.end()) { 154 found = demoted_changes_.find(url); 155 if (found == demoted_changes_.end()) 156 return; 157 } 158 *changes = found->second.change_list; 159} 160 161void LocalFileChangeTracker::ClearChangesForURL(const FileSystemURL& url) { 162 DCHECK(file_task_runner_->RunsTasksOnCurrentThread()); 163 ClearDirtyOnDatabase(url); 164 mirror_changes_.erase(url); 165 demoted_changes_.erase(url); 166 FileChangeMap::iterator found = changes_.find(url); 167 if (found == changes_.end()) 168 return; 169 change_seqs_.erase(found->second.change_seq); 170 changes_.erase(found); 171 UpdateNumChanges(); 172} 173 174void LocalFileChangeTracker::CreateFreshMirrorForURL( 175 const fileapi::FileSystemURL& url) { 176 DCHECK(file_task_runner_->RunsTasksOnCurrentThread()); 177 DCHECK(!ContainsKey(mirror_changes_, url)); 178 mirror_changes_[url] = ChangeInfo(); 179} 180 181void LocalFileChangeTracker::RemoveMirrorAndCommitChangesForURL( 182 const fileapi::FileSystemURL& url) { 183 DCHECK(file_task_runner_->RunsTasksOnCurrentThread()); 184 FileChangeMap::iterator found = mirror_changes_.find(url); 185 if (found == mirror_changes_.end()) 186 return; 187 mirror_changes_.erase(found); 188 189 if (ContainsKey(changes_, url)) 190 MarkDirtyOnDatabase(url); 191 else 192 ClearDirtyOnDatabase(url); 193 UpdateNumChanges(); 194} 195 196void LocalFileChangeTracker::ResetToMirrorAndCommitChangesForURL( 197 const fileapi::FileSystemURL& url) { 198 DCHECK(file_task_runner_->RunsTasksOnCurrentThread()); 199 FileChangeMap::iterator found = mirror_changes_.find(url); 200 if (found == mirror_changes_.end() || found->second.change_list.empty()) { 201 ClearChangesForURL(url); 202 return; 203 } 204 const ChangeInfo& info = found->second; 205 change_seqs_[info.change_seq] = url; 206 changes_[url] = info; 207 RemoveMirrorAndCommitChangesForURL(url); 208} 209 210void LocalFileChangeTracker::DemoteChangesForURL( 211 const fileapi::FileSystemURL& url) { 212 DCHECK(file_task_runner_->RunsTasksOnCurrentThread()); 213 FileChangeMap::iterator found = changes_.find(url); 214 if (found == changes_.end()) 215 return; 216 FileChangeList changes = found->second.change_list; 217 218 mirror_changes_.erase(url); 219 change_seqs_.erase(found->second.change_seq); 220 changes_.erase(found); 221 222 FileChangeList::List change_list = changes.list(); 223 while (!change_list.empty()) { 224 RecordChangeToChangeMaps(url, change_list.front(), 0, 225 &demoted_changes_, NULL); 226 change_list.pop_front(); 227 } 228} 229 230void LocalFileChangeTracker::PromoteDemotedChangesForURL( 231 const fileapi::FileSystemURL& url) { 232 DCHECK(file_task_runner_->RunsTasksOnCurrentThread()); 233 234 FileChangeMap::iterator iter = demoted_changes_.find(url); 235 if (iter == demoted_changes_.end()) 236 return; 237 238 FileChangeList::List change_list = iter->second.change_list.list(); 239 // Make sure that this URL is in no queues. 240 DCHECK(!ContainsKey(changes_, url)); 241 DCHECK(!ContainsKey(mirror_changes_, url)); 242 243 demoted_changes_.erase(iter); 244 245 while (!change_list.empty()) { 246 RecordChange(url, change_list.front()); 247 change_list.pop_front(); 248 } 249} 250 251bool LocalFileChangeTracker::PromoteDemotedChanges() { 252 DCHECK(file_task_runner_->RunsTasksOnCurrentThread()); 253 if (demoted_changes_.empty()) 254 return false; 255 while (!demoted_changes_.empty()) { 256 fileapi::FileSystemURL url = demoted_changes_.begin()->first; 257 PromoteDemotedChangesForURL(url); 258 } 259 return true; 260} 261 262SyncStatusCode LocalFileChangeTracker::Initialize( 263 FileSystemContext* file_system_context) { 264 DCHECK(file_task_runner_->RunsTasksOnCurrentThread()); 265 DCHECK(!initialized_); 266 DCHECK(file_system_context); 267 268 SyncStatusCode status = CollectLastDirtyChanges(file_system_context); 269 if (status == SYNC_STATUS_OK) 270 initialized_ = true; 271 return status; 272} 273 274void LocalFileChangeTracker::ResetForFileSystem( 275 const GURL& origin, 276 fileapi::FileSystemType type) { 277 DCHECK(file_task_runner_->RunsTasksOnCurrentThread()); 278 scoped_ptr<leveldb::WriteBatch> batch(new leveldb::WriteBatch); 279 for (FileChangeMap::iterator iter = changes_.begin(); 280 iter != changes_.end();) { 281 fileapi::FileSystemURL url = iter->first; 282 if (url.origin() != origin || url.type() != type) { 283 ++iter; 284 continue; 285 } 286 mirror_changes_.erase(url); 287 demoted_changes_.erase(url); 288 change_seqs_.erase(iter->second.change_seq); 289 changes_.erase(iter++); 290 291 std::string serialized_url; 292 const bool should_success = 293 SerializeSyncableFileSystemURL(url, &serialized_url); 294 if (!should_success) { 295 NOTREACHED() << "Failed to serialize: " << url.DebugString(); 296 continue; 297 } 298 batch->Delete(serialized_url); 299 } 300 // Fail to apply batch to database wouldn't have critical effect, they'll be 301 // just marked deleted on next relaunch. 302 tracker_db_->WriteBatch(batch.Pass()); 303 UpdateNumChanges(); 304} 305 306void LocalFileChangeTracker::UpdateNumChanges() { 307 base::AutoLock lock(num_changes_lock_); 308 num_changes_ = static_cast<int64>(change_seqs_.size()); 309} 310 311void LocalFileChangeTracker::GetAllChangedURLs(FileSystemURLSet* urls) { 312 DCHECK(file_task_runner_->RunsTasksOnCurrentThread()); 313 std::deque<FileSystemURL> url_deque; 314 GetNextChangedURLs(&url_deque, 0); 315 urls->clear(); 316 urls->insert(url_deque.begin(), url_deque.end()); 317} 318 319void LocalFileChangeTracker::DropAllChanges() { 320 DCHECK(file_task_runner_->RunsTasksOnCurrentThread()); 321 changes_.clear(); 322 change_seqs_.clear(); 323 mirror_changes_.clear(); 324} 325 326SyncStatusCode LocalFileChangeTracker::MarkDirtyOnDatabase( 327 const FileSystemURL& url) { 328 std::string serialized_url; 329 if (!SerializeSyncableFileSystemURL(url, &serialized_url)) 330 return SYNC_FILE_ERROR_INVALID_URL; 331 332 return tracker_db_->MarkDirty(serialized_url); 333} 334 335SyncStatusCode LocalFileChangeTracker::ClearDirtyOnDatabase( 336 const FileSystemURL& url) { 337 DCHECK(file_task_runner_->RunsTasksOnCurrentThread()); 338 std::string serialized_url; 339 if (!SerializeSyncableFileSystemURL(url, &serialized_url)) 340 return SYNC_FILE_ERROR_INVALID_URL; 341 342 return tracker_db_->ClearDirty(serialized_url); 343} 344 345SyncStatusCode LocalFileChangeTracker::CollectLastDirtyChanges( 346 FileSystemContext* file_system_context) { 347 DCHECK(file_task_runner_->RunsTasksOnCurrentThread()); 348 349 std::queue<FileSystemURL> dirty_files; 350 const SyncStatusCode status = tracker_db_->GetDirtyEntries(&dirty_files); 351 if (status != SYNC_STATUS_OK) 352 return status; 353 354 FileSystemFileUtil* file_util = 355 file_system_context->sandbox_delegate()->sync_file_util(); 356 DCHECK(file_util); 357 scoped_ptr<FileSystemOperationContext> context( 358 new FileSystemOperationContext(file_system_context)); 359 360 base::File::Info file_info; 361 base::FilePath platform_path; 362 363 while (!dirty_files.empty()) { 364 const FileSystemURL url = dirty_files.front(); 365 dirty_files.pop(); 366 DCHECK_EQ(url.type(), fileapi::kFileSystemTypeSyncable); 367 368 switch (file_util->GetFileInfo(context.get(), url, 369 &file_info, &platform_path)) { 370 case base::File::FILE_OK: { 371 if (!file_info.is_directory) { 372 RecordChange(url, FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE, 373 SYNC_FILE_TYPE_FILE)); 374 break; 375 } 376 377 RecordChange(url, FileChange( 378 FileChange::FILE_CHANGE_ADD_OR_UPDATE, 379 SYNC_FILE_TYPE_DIRECTORY)); 380 381 // Push files and directories in this directory into |dirty_files|. 382 scoped_ptr<FileSystemFileUtil::AbstractFileEnumerator> enumerator( 383 file_util->CreateFileEnumerator(context.get(), url)); 384 base::FilePath path_each; 385 while (!(path_each = enumerator->Next()).empty()) { 386 dirty_files.push(CreateSyncableFileSystemURL( 387 url.origin(), path_each)); 388 } 389 break; 390 } 391 case base::File::FILE_ERROR_NOT_FOUND: { 392 // File represented by |url| has already been deleted. Since we cannot 393 // figure out if this file was directory or not from the URL, file 394 // type is treated as SYNC_FILE_TYPE_UNKNOWN. 395 // 396 // NOTE: Directory to have been reverted (that is, ADD -> DELETE) is 397 // also treated as FILE_CHANGE_DELETE. 398 RecordChange(url, FileChange(FileChange::FILE_CHANGE_DELETE, 399 SYNC_FILE_TYPE_UNKNOWN)); 400 break; 401 } 402 case base::File::FILE_ERROR_FAILED: 403 default: 404 // TODO(nhiroki): handle file access error (http://crbug.com/155251). 405 LOG(WARNING) << "Failed to access local file."; 406 break; 407 } 408 } 409 return SYNC_STATUS_OK; 410} 411 412void LocalFileChangeTracker::RecordChange( 413 const FileSystemURL& url, const FileChange& change) { 414 DCHECK(file_task_runner_->RunsTasksOnCurrentThread()); 415 if (ContainsKey(demoted_changes_, url)) { 416 RecordChangeToChangeMaps(url, change, 0, &demoted_changes_, NULL); 417 return; 418 } 419 int change_seq = current_change_seq_++; 420 RecordChangeToChangeMaps(url, change, change_seq, &changes_, &change_seqs_); 421 if (ContainsKey(mirror_changes_, url)) 422 RecordChangeToChangeMaps(url, change, change_seq, &mirror_changes_, NULL); 423 UpdateNumChanges(); 424} 425 426// static 427void LocalFileChangeTracker::RecordChangeToChangeMaps( 428 const FileSystemURL& url, 429 const FileChange& change, 430 int new_change_seq, 431 FileChangeMap* changes, 432 ChangeSeqMap* change_seqs) { 433 ChangeInfo& info = (*changes)[url]; 434 if (info.change_seq >= 0 && change_seqs) 435 change_seqs->erase(info.change_seq); 436 info.change_list.Update(change); 437 if (info.change_list.empty()) { 438 changes->erase(url); 439 return; 440 } 441 info.change_seq = new_change_seq; 442 if (change_seqs) 443 (*change_seqs)[info.change_seq] = url; 444} 445 446// TrackerDB ------------------------------------------------------------------- 447 448LocalFileChangeTracker::TrackerDB::TrackerDB(const base::FilePath& base_path, 449 leveldb::Env* env_override) 450 : base_path_(base_path), 451 env_override_(env_override), 452 db_status_(SYNC_STATUS_OK) {} 453 454SyncStatusCode LocalFileChangeTracker::TrackerDB::Init( 455 RecoveryOption recovery_option) { 456 if (db_.get() && db_status_ == SYNC_STATUS_OK) 457 return SYNC_STATUS_OK; 458 459 std::string path = fileapi::FilePathToString( 460 base_path_.Append(kDatabaseName)); 461 leveldb::Options options; 462 options.max_open_files = 0; // Use minimum. 463 options.create_if_missing = true; 464 if (env_override_) 465 options.env = env_override_; 466 leveldb::DB* db; 467 leveldb::Status status = leveldb::DB::Open(options, path, &db); 468 if (status.ok()) { 469 db_.reset(db); 470 return SYNC_STATUS_OK; 471 } 472 473 HandleError(FROM_HERE, status); 474 if (!status.IsCorruption()) 475 return LevelDBStatusToSyncStatusCode(status); 476 477 // Try to repair the corrupted DB. 478 switch (recovery_option) { 479 case FAIL_ON_CORRUPTION: 480 return SYNC_DATABASE_ERROR_CORRUPTION; 481 case REPAIR_ON_CORRUPTION: 482 return Repair(path); 483 } 484 NOTREACHED(); 485 return SYNC_DATABASE_ERROR_FAILED; 486} 487 488SyncStatusCode LocalFileChangeTracker::TrackerDB::Repair( 489 const std::string& db_path) { 490 DCHECK(!db_.get()); 491 LOG(WARNING) << "Attempting to repair TrackerDB."; 492 493 leveldb::Options options; 494 options.max_open_files = 0; // Use minimum. 495 if (leveldb::RepairDB(db_path, options).ok() && 496 Init(FAIL_ON_CORRUPTION) == SYNC_STATUS_OK) { 497 // TODO(nhiroki): perform some consistency checks between TrackerDB and 498 // syncable file system. 499 LOG(WARNING) << "Repairing TrackerDB completed."; 500 return SYNC_STATUS_OK; 501 } 502 503 LOG(WARNING) << "Failed to repair TrackerDB."; 504 return SYNC_DATABASE_ERROR_CORRUPTION; 505} 506 507// TODO(nhiroki): factor out the common methods into somewhere else. 508void LocalFileChangeTracker::TrackerDB::HandleError( 509 const tracked_objects::Location& from_here, 510 const leveldb::Status& status) { 511 LOG(ERROR) << "LocalFileChangeTracker::TrackerDB failed at: " 512 << from_here.ToString() << " with error: " << status.ToString(); 513} 514 515SyncStatusCode LocalFileChangeTracker::TrackerDB::MarkDirty( 516 const std::string& url) { 517 if (db_status_ != SYNC_STATUS_OK) 518 return db_status_; 519 520 db_status_ = Init(REPAIR_ON_CORRUPTION); 521 if (db_status_ != SYNC_STATUS_OK) { 522 db_.reset(); 523 return db_status_; 524 } 525 526 leveldb::Status status = db_->Put(leveldb::WriteOptions(), url, kMark); 527 if (!status.ok()) { 528 HandleError(FROM_HERE, status); 529 db_status_ = LevelDBStatusToSyncStatusCode(status); 530 db_.reset(); 531 return db_status_; 532 } 533 return SYNC_STATUS_OK; 534} 535 536SyncStatusCode LocalFileChangeTracker::TrackerDB::ClearDirty( 537 const std::string& url) { 538 if (db_status_ != SYNC_STATUS_OK) 539 return db_status_; 540 541 // Should not reach here before initializing the database. The database should 542 // be cleared after read, and should be initialized during read if 543 // uninitialized. 544 DCHECK(db_.get()); 545 546 leveldb::Status status = db_->Delete(leveldb::WriteOptions(), url); 547 if (!status.ok() && !status.IsNotFound()) { 548 HandleError(FROM_HERE, status); 549 db_status_ = LevelDBStatusToSyncStatusCode(status); 550 db_.reset(); 551 return db_status_; 552 } 553 return SYNC_STATUS_OK; 554} 555 556SyncStatusCode LocalFileChangeTracker::TrackerDB::GetDirtyEntries( 557 std::queue<FileSystemURL>* dirty_files) { 558 if (db_status_ != SYNC_STATUS_OK) 559 return db_status_; 560 561 db_status_ = Init(REPAIR_ON_CORRUPTION); 562 if (db_status_ != SYNC_STATUS_OK) { 563 db_.reset(); 564 return db_status_; 565 } 566 567 scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions())); 568 iter->SeekToFirst(); 569 FileSystemURL url; 570 while (iter->Valid()) { 571 if (!DeserializeSyncableFileSystemURL(iter->key().ToString(), &url)) { 572 LOG(WARNING) << "Failed to deserialize an URL. " 573 << "TrackerDB might be corrupted."; 574 db_status_ = SYNC_DATABASE_ERROR_CORRUPTION; 575 db_.reset(); 576 return db_status_; 577 } 578 dirty_files->push(url); 579 iter->Next(); 580 } 581 return SYNC_STATUS_OK; 582} 583 584SyncStatusCode LocalFileChangeTracker::TrackerDB::WriteBatch( 585 scoped_ptr<leveldb::WriteBatch> batch) { 586 if (db_status_ != SYNC_STATUS_OK) 587 return db_status_; 588 589 leveldb::Status status = db_->Write(leveldb::WriteOptions(), batch.get()); 590 if (!status.ok() && !status.IsNotFound()) { 591 HandleError(FROM_HERE, status); 592 db_status_ = LevelDBStatusToSyncStatusCode(status); 593 db_.reset(); 594 return db_status_; 595 } 596 return SYNC_STATUS_OK; 597} 598 599} // namespace sync_file_system 600