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