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..f10ef7ae283104b7c127aa312f594ec443774fe2 100644 |
--- a/webkit/fileapi/syncable/local_file_change_tracker.cc |
+++ b/webkit/fileapi/syncable/local_file_change_tracker.cc |
@@ -4,20 +4,69 @@ |
#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_; |
+ 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) { |
@@ -103,15 +152,126 @@ void LocalFileChangeTracker::CollectLastDirtyChanges(FileChangeMap* changes) { |
void LocalFileChangeTracker::RecordChange( |
const FileSystemURL& url, const FileChange& change) { |
DCHECK(file_task_runner_->RunsTasksOnCurrentThread()); |
+ // TODO(nhiroki): propagate the error code (see http://crbug.com/152127). |
+ MarkDirtyOnDatabase(url); |
changes_[url].Update(change); |
} |
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(); |
+} |
+ |
+bool LocalFileChangeTracker::DeserializeExternalFileSystemURL( |
+ const std::string& serialized_url, FileSystemURL* url) { |
+ *url = FileSystemURL(GURL(serialized_url)); |
+ return url->is_valid(); |
+} |
+ |
+// 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 |
+ // syncable file system. |
+ 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; |
+ |
+ // Should not reach here before initializing the database. The database should |
+ // be cleared after read, and should be initialized during read if |
+ // uninitialized. |
+ DCHECK(db_.get()); |
+ |
+ 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 |