| OLD | NEW |
| (Empty) |
| 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 "webkit/fileapi/syncable/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 "third_party/leveldatabase/src/include/leveldb/db.h" | |
| 14 #include "webkit/fileapi/file_system_context.h" | |
| 15 #include "webkit/fileapi/file_system_file_util.h" | |
| 16 #include "webkit/fileapi/file_system_operation_context.h" | |
| 17 #include "webkit/fileapi/file_system_util.h" | |
| 18 #include "webkit/fileapi/syncable/local_file_sync_status.h" | |
| 19 #include "webkit/fileapi/syncable/syncable_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(), url.filesystem_id(), 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.create_if_missing = true; | |
| 310 leveldb::DB* db; | |
| 311 leveldb::Status status = leveldb::DB::Open(options, path, &db); | |
| 312 if (status.ok()) { | |
| 313 db_.reset(db); | |
| 314 return SYNC_STATUS_OK; | |
| 315 } | |
| 316 | |
| 317 HandleError(FROM_HERE, status); | |
| 318 if (!status.IsCorruption()) | |
| 319 return LevelDBStatusToSyncStatusCode(status); | |
| 320 | |
| 321 // Try to repair the corrupted DB. | |
| 322 switch (recovery_option) { | |
| 323 case FAIL_ON_CORRUPTION: | |
| 324 return SYNC_DATABASE_ERROR_CORRUPTION; | |
| 325 case REPAIR_ON_CORRUPTION: | |
| 326 return Repair(path); | |
| 327 } | |
| 328 NOTREACHED(); | |
| 329 return SYNC_DATABASE_ERROR_FAILED; | |
| 330 } | |
| 331 | |
| 332 SyncStatusCode LocalFileChangeTracker::TrackerDB::Repair( | |
| 333 const std::string& db_path) { | |
| 334 DCHECK(!db_.get()); | |
| 335 LOG(WARNING) << "Attempting to repair TrackerDB."; | |
| 336 | |
| 337 if (leveldb::RepairDB(db_path, leveldb::Options()).ok() && | |
| 338 Init(FAIL_ON_CORRUPTION) == SYNC_STATUS_OK) { | |
| 339 // TODO(nhiroki): perform some consistency checks between TrackerDB and | |
| 340 // syncable file system. | |
| 341 LOG(WARNING) << "Repairing TrackerDB completed."; | |
| 342 return SYNC_STATUS_OK; | |
| 343 } | |
| 344 | |
| 345 LOG(WARNING) << "Failed to repair TrackerDB."; | |
| 346 return SYNC_DATABASE_ERROR_CORRUPTION; | |
| 347 } | |
| 348 | |
| 349 // TODO(nhiroki): factor out the common methods into somewhere else. | |
| 350 void LocalFileChangeTracker::TrackerDB::HandleError( | |
| 351 const tracked_objects::Location& from_here, | |
| 352 const leveldb::Status& status) { | |
| 353 LOG(ERROR) << "LocalFileChangeTracker::TrackerDB failed at: " | |
| 354 << from_here.ToString() << " with error: " << status.ToString(); | |
| 355 } | |
| 356 | |
| 357 SyncStatusCode LocalFileChangeTracker::TrackerDB::MarkDirty( | |
| 358 const std::string& url) { | |
| 359 if (db_status_ != SYNC_STATUS_OK) | |
| 360 return db_status_; | |
| 361 | |
| 362 db_status_ = Init(REPAIR_ON_CORRUPTION); | |
| 363 if (db_status_ != SYNC_STATUS_OK) { | |
| 364 db_.reset(); | |
| 365 return db_status_; | |
| 366 } | |
| 367 | |
| 368 leveldb::Status status = db_->Put(leveldb::WriteOptions(), url, kMark); | |
| 369 if (!status.ok()) { | |
| 370 HandleError(FROM_HERE, status); | |
| 371 db_status_ = LevelDBStatusToSyncStatusCode(status); | |
| 372 db_.reset(); | |
| 373 return db_status_; | |
| 374 } | |
| 375 return SYNC_STATUS_OK; | |
| 376 } | |
| 377 | |
| 378 SyncStatusCode LocalFileChangeTracker::TrackerDB::ClearDirty( | |
| 379 const std::string& url) { | |
| 380 if (db_status_ != SYNC_STATUS_OK) | |
| 381 return db_status_; | |
| 382 | |
| 383 // Should not reach here before initializing the database. The database should | |
| 384 // be cleared after read, and should be initialized during read if | |
| 385 // uninitialized. | |
| 386 DCHECK(db_.get()); | |
| 387 | |
| 388 leveldb::Status status = db_->Delete(leveldb::WriteOptions(), url); | |
| 389 if (!status.ok() && !status.IsNotFound()) { | |
| 390 HandleError(FROM_HERE, status); | |
| 391 db_status_ = LevelDBStatusToSyncStatusCode(status); | |
| 392 db_.reset(); | |
| 393 return db_status_; | |
| 394 } | |
| 395 return SYNC_STATUS_OK; | |
| 396 } | |
| 397 | |
| 398 SyncStatusCode LocalFileChangeTracker::TrackerDB::GetDirtyEntries( | |
| 399 std::queue<FileSystemURL>* dirty_files) { | |
| 400 if (db_status_ != SYNC_STATUS_OK) | |
| 401 return db_status_; | |
| 402 | |
| 403 db_status_ = Init(REPAIR_ON_CORRUPTION); | |
| 404 if (db_status_ != SYNC_STATUS_OK) { | |
| 405 db_.reset(); | |
| 406 return db_status_; | |
| 407 } | |
| 408 | |
| 409 scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions())); | |
| 410 iter->SeekToFirst(); | |
| 411 FileSystemURL url; | |
| 412 while (iter->Valid()) { | |
| 413 if (!DeserializeSyncableFileSystemURL(iter->key().ToString(), &url)) { | |
| 414 LOG(WARNING) << "Failed to deserialize an URL. " | |
| 415 << "TrackerDB might be corrupted."; | |
| 416 db_status_ = SYNC_DATABASE_ERROR_CORRUPTION; | |
| 417 db_.reset(); | |
| 418 return db_status_; | |
| 419 } | |
| 420 dirty_files->push(url); | |
| 421 iter->Next(); | |
| 422 } | |
| 423 return SYNC_STATUS_OK; | |
| 424 } | |
| 425 | |
| 426 } // namespace sync_file_system | |
| OLD | NEW |