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..942d3e5f251a726c3561817da9821a3a42b3abd8 100644 |
--- a/webkit/fileapi/syncable/local_file_change_tracker.cc |
+++ b/webkit/fileapi/syncable/local_file_change_tracker.cc |
@@ -4,20 +4,70 @@ |
#include "webkit/fileapi/syncable/local_file_change_tracker.h" |
+#include "base/location.h" |
+#include "base/logging.h" |
#include "base/sequenced_task_runner.h" |
+#include "third_party/leveldatabase/src/include/leveldb/db.h" |
+#include "webkit/fileapi/file_system_util.h" |
#include "webkit/fileapi/syncable/local_file_sync_status.h" |
+namespace { |
+const FilePath::CharType kDatabaseName[] = |
+ FILE_PATH_LITERAL("LocalFileChangeTracker"); |
+const char kMark[] = "d"; |
+} // namespace |
+ |
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: |
+ explicit TrackerDB(const FilePath& profile_path); |
+ |
+ bool MarkDirty(const std::string& url); |
+ bool ClearDirty(const std::string& 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); |
+ |
+ 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 +157,120 @@ void LocalFileChangeTracker::RecordChange( |
} |
void LocalFileChangeTracker::MarkDirtyOnDatabase(const FileSystemURL& url) { |
- // TODO(kinuko): implement. |
+ tracker_db_->MarkDirty(SerializeExternalFileSystemURL(url)); |
} |
void LocalFileChangeTracker::ClearDirtyOnDatabase(const FileSystemURL& url) { |
- // TODO(kinuko): implement. |
+ tracker_db_->ClearDirty(SerializeExternalFileSystemURL(url)); |
+} |
+ |
+std::string LocalFileChangeTracker::SerializeExternalFileSystemURL( |
+ const FileSystemURL& url) { |
+ return GetFileSystemRootURI(url.origin(), kFileSystemTypeExternal).spec() + |
+ url.filesystem_id() + "/" + url.path().AsUTF8Unsafe(); |
+} |
+ |
+FileSystemURL LocalFileChangeTracker::DeserializeExternalFileSystemURL( |
+ const std::string& url) { |
+ return FileSystemURL(GURL(url)); |
kinuko
2012/09/21 08:12:32
I prefer making this method like:
bool Deserializ
nhiroki
2012/09/24 07:01:40
Done.
|
+} |
+ |
+// 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); |
+ 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: |
+ return Repair(path); |
+ } |
+ 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() && |
+ Init(FAIL_ON_CORRUPTION)) { |
+ // TODO(nhiroki): perform some consistency checks between TrackerDB and |
+ // cloud-backed file system. |
kinuko
2012/09/21 08:12:32
nit: cloud-backed -> syncable
nhiroki
2012/09/24 07:01:40
Done.
|
+ LOG(WARNING) << "Repairing TrackerDB completed."; |
+ return true; |
+ } |
+ |
+ LOG(WARNING) << "Failed to repair TrackerDB."; |
+ return false; |
+} |
+ |
+// TODO(nhiroki): factor out the common methods into somewhere else. |
+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(); |
+} |
+ |
+bool LocalFileChangeTracker::TrackerDB::MarkDirty(const std::string& url) { |
+ if (db_disabled_) |
+ return false; |
+ |
+ if (!Init(REPAIR_ON_CORRUPTION)) { |
+ db_disabled_ = true; |
+ db_.reset(); |
+ return false; |
+ } |
+ |
+ leveldb::Status status = db_->Put(leveldb::WriteOptions(), url, kMark); |
+ if (!status.ok()) { |
+ HandleError(FROM_HERE, status); |
+ db_disabled_ = true; |
+ db_.reset(); |
+ return false; |
+ } |
+ return true; |
+} |
+ |
+bool LocalFileChangeTracker::TrackerDB::ClearDirty(const std::string& url) { |
+ if (db_disabled_) |
+ return false; |
+ |
+ if (!Init(REPAIR_ON_CORRUPTION)) { |
kinuko
2012/09/21 08:12:32
Not sure if this could get called before initializ
nhiroki
2012/09/24 07:01:40
I understand. Yes, we can just do DCHECK().
|
+ db_disabled_ = true; |
+ db_.reset(); |
+ return false; |
+ } |
+ |
+ leveldb::Status status = db_->Delete(leveldb::WriteOptions(), url); |
+ if (!status.ok() && !status.IsNotFound()) { |
+ HandleError(FROM_HERE, status); |
+ db_disabled_ = true; |
+ db_.reset(); |
+ return false; |
+ } |
+ return true; |
} |
} // namespace fileapi |