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