Chromium Code Reviews| Index: webkit/fileapi/syncable/local_file_change_tracker.cc |
| diff --git a/webkit/fileapi/syncable/local_file_change_tracker.cc b/webkit/fileapi/syncable/local_file_change_tracker.cc |
| index 81e61a094b956a2dabd3a8c07f9b5ba2d0f31939..7ed60e09d550cf40c2956def852ff79bf51febca 100644 |
| --- a/webkit/fileapi/syncable/local_file_change_tracker.cc |
| +++ b/webkit/fileapi/syncable/local_file_change_tracker.cc |
| @@ -4,20 +4,77 @@ |
| #include "webkit/fileapi/syncable/local_file_change_tracker.h" |
| +#include "base/location.h" |
| +#include "base/logging.h" |
| +#include "base/metrics/histogram.h" |
| #include "base/sequenced_task_runner.h" |
| +#include "third_party/leveldatabase/src/include/leveldb/db.h" |
| #include "webkit/fileapi/syncable/local_file_sync_status.h" |
| +#include "webkit/fileapi/file_system_util.h" |
| + |
| +namespace { |
| + |
| +const FilePath::CharType kDatabaseName[] = |
| + FILE_PATH_LITERAL("LocalFileChangeTracker"); |
| +const int64 kMinimumReportIntervalHours = 1; |
| +const char kInitStatusHistogramLabel[] = |
| + "FileSystem.LocalFileChangeTracker.InitDatabase"; |
| +const char kMark[] = "d"; |
| + |
| +} |
| namespace fileapi { |
| +// A database class that stores local file changes in a local database. This |
| +// object must be destructed on file_task_runner. |
| +class LocalFileChangeTracker::TrackerDB { |
| + public: |
| + TrackerDB(const FilePath& profile_path); |
| + |
| + void MarkDirty(const FileSystemURL& url); |
| + void ClearDirty(const FileSystemURL& url); |
| + |
| + private: |
| + enum InitStatus { |
| + INIT_STATUS_OK = 0, |
| + INIT_STATUS_CORRUPTION, |
| + INIT_STATUS_IO_ERROR, |
| + INIT_STATUS_UNKNOWN_ERROR, |
| + INIT_STATUS_MAX, |
| + }; |
| + |
| + enum RecoveryOption { |
| + REPAIR_ON_CORRUPTION, |
| + FAIL_ON_CORRUPTION, |
| + }; |
| + |
| + bool Init(RecoveryOption recovery_option); |
| + bool Repair(const std::string& db_path); |
| + void HandleError(const tracked_objects::Location& from_here, |
| + const leveldb::Status& status); |
| + void ReportInitStatus(const leveldb::Status& status); |
| + |
| + const FilePath profile_path_; |
| + scoped_ptr<leveldb::DB> db_; |
| + base::Time last_reported_time_; |
| + bool db_disabled_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(TrackerDB); |
| +}; |
| + |
| +// LocalFileChangeTracker ------------------------------------------------------ |
| + |
| LocalFileChangeTracker::LocalFileChangeTracker( |
| LocalFileSyncStatus* sync_status, |
| + const FilePath& profile_path, |
| base::SequencedTaskRunner* file_task_runner) |
| : sync_status_(sync_status), |
| - file_task_runner_(file_task_runner) {} |
| + file_task_runner_(file_task_runner), |
| + tracker_db_(new TrackerDB(profile_path)) {} |
| LocalFileChangeTracker::~LocalFileChangeTracker() { |
| - // file_task_runner_->PostTask(FROM_HERE, base::Bind(&DropDatabase)); |
| - // TODO(kinuko): implement. |
| + if (tracker_db_.get()) |
| + file_task_runner_->DeleteSoon(FROM_HERE, tracker_db_.release()); |
| } |
| void LocalFileChangeTracker::OnStartUpdate(const FileSystemURL& url) { |
| @@ -107,11 +164,129 @@ void LocalFileChangeTracker::RecordChange( |
| } |
| void LocalFileChangeTracker::MarkDirtyOnDatabase(const FileSystemURL& url) { |
| - // TODO(kinuko): implement. |
| + tracker_db_->MarkDirty(url); |
| } |
| void LocalFileChangeTracker::ClearDirtyOnDatabase(const FileSystemURL& url) { |
| - // TODO(kinuko): implement. |
| + tracker_db_->ClearDirty(url); |
| +} |
| + |
| +// TrackerDB ------------------------------------------------------------------- |
| + |
| +LocalFileChangeTracker::TrackerDB::TrackerDB(const FilePath& profile_path) |
| + : profile_path_(profile_path), |
| + db_disabled_(false) {} |
| + |
| +bool LocalFileChangeTracker::TrackerDB::Init(RecoveryOption recovery_option) { |
| + if (db_.get()) |
| + return true; |
| + |
| + std::string path = FilePathToString(profile_path_.Append(kDatabaseName)); |
| + leveldb::Options options; |
| + options.create_if_missing = true; |
| + leveldb::DB* db; |
| + leveldb::Status status = leveldb::DB::Open(options, path, &db); |
| + ReportInitStatus(status); |
| + if (status.ok()) { |
| + db_.reset(db); |
| + return true; |
| + } |
| + |
| + HandleError(FROM_HERE, status); |
| + if (!status.IsCorruption()) |
| + return false; |
| + |
| + switch (recovery_option) { |
| + case FAIL_ON_CORRUPTION: |
| + return false; |
| + case REPAIR_ON_CORRUPTION: |
| + if (Repair(path)) |
|
tzik
2012/09/20 05:24:50
can be just return Repair(path);
nhiroki
2012/09/20 09:30:33
Done.
|
| + return true; |
| + return false; |
| + } |
| + NOTREACHED(); |
| + return false; |
| +} |
| + |
| +bool LocalFileChangeTracker::TrackerDB::Repair(const std::string& db_path) { |
| + DCHECK(!db_.get()); |
| + LOG(WARNING) << "Attempting to repair TrackerDB."; |
| + |
| + if (leveldb::RepairDB(db_path, leveldb::Options()).ok() || |
|
tzik
2012/09/20 05:24:50
We might lose some database entry on this method.
nhiroki
2012/09/20 09:30:33
For now, added TODO comments.
|
| + Init(FAIL_ON_CORRUPTION)) { |
| + LOG(WARNING) << "Repairing TrackerDB completed."; |
| + return true; |
| + } |
| + |
| + LOG(WARNING) << "Failed to repair TrackerDB."; |
| + return false; |
| +} |
| + |
| +void LocalFileChangeTracker::TrackerDB::HandleError( |
| + const tracked_objects::Location& from_here, |
| + const leveldb::Status& status) { |
| + LOG(ERROR) << "LocalFileChangeTracker::TrackerDB failed at: " |
| + << from_here.ToString() << " with error: " << status.ToString(); |
|
kinuko
2012/09/20 05:09:11
(Not directly related to this cl but would be nice
nhiroki
2012/09/20 09:30:33
Added TODO comments.
|
| +} |
| + |
| +void LocalFileChangeTracker::TrackerDB::ReportInitStatus( |
| + const leveldb::Status& status) { |
| + base::Time now = base::Time::Now(); |
| + base::TimeDelta minimum_interval = |
| + base::TimeDelta::FromHours(kMinimumReportIntervalHours); |
| + if (last_reported_time_ + minimum_interval >= now) |
| + return; |
| + last_reported_time_ = now; |
| + |
| + if (status.ok()) { |
| + UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel, |
| + INIT_STATUS_OK, INIT_STATUS_MAX); |
| + } else if (status.IsCorruption()) { |
| + UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel, |
| + INIT_STATUS_CORRUPTION, INIT_STATUS_OK); |
| + } else if (status.IsIOError()) { |
| + UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel, |
| + INIT_STATUS_IO_ERROR, INIT_STATUS_MAX); |
| + } else { |
| + UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel, |
| + INIT_STATUS_UNKNOWN_ERROR, INIT_STATUS_MAX); |
|
kinuko
2012/09/20 05:09:11
Adding UMA is great, but can we not add these in t
nhiroki
2012/09/20 09:30:33
Okay, removed it.
|
| + } |
| +} |
| + |
| +void LocalFileChangeTracker::TrackerDB::MarkDirty(const FileSystemURL& url) { |
|
kinuko
2012/09/20 05:09:11
Can we return success/fail here?
nhiroki
2012/09/20 09:30:33
Done.
|
| + if (db_disabled_) |
| + return; |
| + |
| + if (!Init(REPAIR_ON_CORRUPTION)) { |
| + db_disabled_ = true; |
| + return; |
| + } |
| + |
| + leveldb::Status status = db_->Put(leveldb::WriteOptions(), url.spec(), kMark); |
|
kinuko
2012/09/20 05:09:11
I'm afraid Current spec() impl does not maintain a
nhiroki
2012/09/20 09:30:33
Added serialize/deserialize methods.
|
| + if (!status.ok()) { |
| + HandleError(FROM_HERE, status); |
| + db_disabled_ = true; |
| + return; |
| + } |
| + return; |
| +} |
| + |
| +void LocalFileChangeTracker::TrackerDB::ClearDirty(const FileSystemURL& url) { |
|
kinuko
2012/09/20 05:09:11
Can we return success/fail here?
nhiroki
2012/09/20 09:30:33
Done.
|
| + if (db_disabled_) |
| + return; |
| + |
| + if (!Init(REPAIR_ON_CORRUPTION)) { |
| + db_disabled_ = true; |
| + return; |
| + } |
| + |
| + leveldb::Status status = db_->Delete(leveldb::WriteOptions(), url.spec()); |
| + if (!status.ok() && !status.IsNotFound()) { |
| + HandleError(FROM_HERE, status); |
| + db_disabled_ = true; |
| + return; |
| + } |
| + return; |
| } |
| } // namespace fileapi |