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.
5 #include "chrome/browser/sync_file_system/local/local_file_change_tracker.h"
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"
21 using fileapi::FileSystemContext;
22 using fileapi::FileSystemFileUtil;
23 using fileapi::FileSystemOperationContext;
24 using fileapi::FileSystemURL;
25 using fileapi::FileSystemURLSet;
27 namespace sync_file_system {
30 const base::FilePath::CharType kDatabaseName[] =
31 FILE_PATH_LITERAL("LocalFileChangeTracker");
32 const char kMark[] = "d";
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 {
39 explicit TrackerDB(const base::FilePath& base_path);
41 SyncStatusCode MarkDirty(const std::string& url);
42 SyncStatusCode ClearDirty(const std::string& url);
43 SyncStatusCode GetDirtyEntries(
44 std::queue<FileSystemURL>* dirty_files);
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);
57 const base::FilePath base_path_;
58 scoped_ptr<leveldb::DB> db_;
59 SyncStatusCode db_status_;
61 DISALLOW_COPY_AND_ASSIGN(TrackerDB);
64 LocalFileChangeTracker::ChangeInfo::ChangeInfo() : change_seq(-1) {}
65 LocalFileChangeTracker::ChangeInfo::~ChangeInfo() {}
67 // LocalFileChangeTracker ------------------------------------------------------
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),
79 LocalFileChangeTracker::~LocalFileChangeTracker() {
80 DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
84 void LocalFileChangeTracker::OnStartUpdate(const FileSystemURL& url) {
85 DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
86 if (ContainsKey(changes_, url))
88 // TODO(nhiroki): propagate the error code (see http://crbug.com/152127).
89 MarkDirtyOnDatabase(url);
92 void LocalFileChangeTracker::OnEndUpdate(const FileSystemURL& url) {}
94 void LocalFileChangeTracker::OnCreateFile(const FileSystemURL& url) {
95 RecordChange(url, FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
96 SYNC_FILE_TYPE_FILE));
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));
105 void LocalFileChangeTracker::OnRemoveFile(const FileSystemURL& url) {
106 RecordChange(url, FileChange(FileChange::FILE_CHANGE_DELETE,
107 SYNC_FILE_TYPE_FILE));
110 void LocalFileChangeTracker::OnModifyFile(const FileSystemURL& url) {
111 RecordChange(url, FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
112 SYNC_FILE_TYPE_FILE));
115 void LocalFileChangeTracker::OnCreateDirectory(const FileSystemURL& url) {
116 RecordChange(url, FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
117 SYNC_FILE_TYPE_DIRECTORY));
120 void LocalFileChangeTracker::OnRemoveDirectory(const FileSystemURL& url) {
121 RecordChange(url, FileChange(FileChange::FILE_CHANGE_DELETE,
122 SYNC_FILE_TYPE_DIRECTORY));
125 void LocalFileChangeTracker::GetNextChangedURLs(
126 std::deque<FileSystemURL>* urls, int max_urls) {
128 DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
130 // Mildly prioritizes the URLs that older changes and have not been updated
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));
136 urls->push_back(iter->second);
140 void LocalFileChangeTracker::GetChangesForURL(
141 const FileSystemURL& url, FileChangeList* changes) {
142 DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
145 FileChangeMap::iterator found = changes_.find(url);
146 if (found == changes_.end())
148 *changes = found->second.change_list;
151 void LocalFileChangeTracker::ClearChangesForURL(const FileSystemURL& url) {
152 DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
153 ClearDirtyOnDatabase(url);
154 mirror_changes_.erase(url);
155 FileChangeMap::iterator found = changes_.find(url);
156 if (found == changes_.end())
158 change_seqs_.erase(found->second.change_seq);
159 changes_.erase(found);
163 void LocalFileChangeTracker::CreateFreshMirrorForURL(
164 const fileapi::FileSystemURL& url) {
165 DCHECK(!ContainsKey(mirror_changes_, url));
166 mirror_changes_[url] = ChangeInfo();
169 void LocalFileChangeTracker::RemoveMirrorAndCommitChangesForURL(
170 const fileapi::FileSystemURL& url) {
171 FileChangeMap::iterator found = mirror_changes_.find(url);
172 if (found == mirror_changes_.end())
174 mirror_changes_.erase(found);
176 if (ContainsKey(changes_, url))
177 MarkDirtyOnDatabase(url);
179 ClearDirtyOnDatabase(url);
183 void LocalFileChangeTracker::ResetToMirrorAndCommitChangesForURL(
184 const fileapi::FileSystemURL& url) {
185 FileChangeMap::iterator found = mirror_changes_.find(url);
186 if (found == mirror_changes_.end() || found->second.change_list.empty()) {
187 ClearChangesForURL(url);
190 const ChangeInfo& info = found->second;
191 change_seqs_[info.change_seq] = url;
192 changes_[url] = info;
193 RemoveMirrorAndCommitChangesForURL(url);
196 SyncStatusCode LocalFileChangeTracker::Initialize(
197 FileSystemContext* file_system_context) {
198 DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
199 DCHECK(!initialized_);
200 DCHECK(file_system_context);
202 SyncStatusCode status = CollectLastDirtyChanges(file_system_context);
203 if (status == SYNC_STATUS_OK)
208 void LocalFileChangeTracker::UpdateNumChanges() {
209 base::AutoLock lock(num_changes_lock_);
210 num_changes_ = static_cast<int64>(change_seqs_.size());
213 void LocalFileChangeTracker::GetAllChangedURLs(FileSystemURLSet* urls) {
214 std::deque<FileSystemURL> url_deque;
215 GetNextChangedURLs(&url_deque, 0);
217 urls->insert(url_deque.begin(), url_deque.end());
220 void LocalFileChangeTracker::DropAllChanges() {
222 change_seqs_.clear();
223 mirror_changes_.clear();
226 SyncStatusCode LocalFileChangeTracker::MarkDirtyOnDatabase(
227 const FileSystemURL& url) {
228 std::string serialized_url;
229 if (!SerializeSyncableFileSystemURL(url, &serialized_url))
230 return SYNC_FILE_ERROR_INVALID_URL;
232 return tracker_db_->MarkDirty(serialized_url);
235 SyncStatusCode LocalFileChangeTracker::ClearDirtyOnDatabase(
236 const FileSystemURL& url) {
237 std::string serialized_url;
238 if (!SerializeSyncableFileSystemURL(url, &serialized_url))
239 return SYNC_FILE_ERROR_INVALID_URL;
241 return tracker_db_->ClearDirty(serialized_url);
244 SyncStatusCode LocalFileChangeTracker::CollectLastDirtyChanges(
245 FileSystemContext* file_system_context) {
246 DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
248 std::queue<FileSystemURL> dirty_files;
249 const SyncStatusCode status = tracker_db_->GetDirtyEntries(&dirty_files);
250 if (status != SYNC_STATUS_OK)
253 FileSystemFileUtil* file_util =
254 file_system_context->sandbox_delegate()->sync_file_util();
256 scoped_ptr<FileSystemOperationContext> context(
257 new FileSystemOperationContext(file_system_context));
259 base::PlatformFileInfo file_info;
260 base::FilePath platform_path;
262 while (!dirty_files.empty()) {
263 const FileSystemURL url = dirty_files.front();
265 DCHECK_EQ(url.type(), fileapi::kFileSystemTypeSyncable);
267 switch (file_util->GetFileInfo(context.get(), url,
268 &file_info, &platform_path)) {
269 case base::PLATFORM_FILE_OK: {
270 if (!file_info.is_directory) {
271 RecordChange(url, FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
272 SYNC_FILE_TYPE_FILE));
276 RecordChange(url, FileChange(
277 FileChange::FILE_CHANGE_ADD_OR_UPDATE,
278 SYNC_FILE_TYPE_DIRECTORY));
280 // Push files and directories in this directory into |dirty_files|.
281 scoped_ptr<FileSystemFileUtil::AbstractFileEnumerator> enumerator(
282 file_util->CreateFileEnumerator(context.get(), url));
283 base::FilePath path_each;
284 while (!(path_each = enumerator->Next()).empty()) {
285 dirty_files.push(CreateSyncableFileSystemURL(
286 url.origin(), path_each));
290 case base::PLATFORM_FILE_ERROR_NOT_FOUND: {
291 // File represented by |url| has already been deleted. Since we cannot
292 // figure out if this file was directory or not from the URL, file
293 // type is treated as SYNC_FILE_TYPE_UNKNOWN.
295 // NOTE: Directory to have been reverted (that is, ADD -> DELETE) is
296 // also treated as FILE_CHANGE_DELETE.
297 RecordChange(url, FileChange(FileChange::FILE_CHANGE_DELETE,
298 SYNC_FILE_TYPE_UNKNOWN));
301 case base::PLATFORM_FILE_ERROR_FAILED:
303 // TODO(nhiroki): handle file access error (http://crbug.com/155251).
304 LOG(WARNING) << "Failed to access local file.";
308 return SYNC_STATUS_OK;
311 void LocalFileChangeTracker::RecordChange(
312 const FileSystemURL& url, const FileChange& change) {
313 DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
314 int change_seq = current_change_seq_++;
315 RecordChangeToChangeMaps(url, change, change_seq, &changes_, &change_seqs_);
316 if (ContainsKey(mirror_changes_, url))
317 RecordChangeToChangeMaps(url, change, change_seq, &mirror_changes_, NULL);
321 void LocalFileChangeTracker::RecordChangeToChangeMaps(
322 const FileSystemURL& url,
323 const FileChange& change,
325 FileChangeMap* changes,
326 ChangeSeqMap* change_seqs) {
327 ChangeInfo& info = (*changes)[url];
328 if (info.change_seq >= 0 && change_seqs)
329 change_seqs->erase(info.change_seq);
330 info.change_list.Update(change);
331 if (info.change_list.empty()) {
335 info.change_seq = new_change_seq;
337 (*change_seqs)[info.change_seq] = url;
340 // TrackerDB -------------------------------------------------------------------
342 LocalFileChangeTracker::TrackerDB::TrackerDB(const base::FilePath& base_path)
343 : base_path_(base_path),
344 db_status_(SYNC_STATUS_OK) {}
346 SyncStatusCode LocalFileChangeTracker::TrackerDB::Init(
347 RecoveryOption recovery_option) {
348 if (db_.get() && db_status_ == SYNC_STATUS_OK)
349 return SYNC_STATUS_OK;
351 std::string path = fileapi::FilePathToString(
352 base_path_.Append(kDatabaseName));
353 leveldb::Options options;
354 options.max_open_files = 0; // Use minimum.
355 options.create_if_missing = true;
357 leveldb::Status status = leveldb::DB::Open(options, path, &db);
360 return SYNC_STATUS_OK;
363 HandleError(FROM_HERE, status);
364 if (!status.IsCorruption())
365 return LevelDBStatusToSyncStatusCode(status);
367 // Try to repair the corrupted DB.
368 switch (recovery_option) {
369 case FAIL_ON_CORRUPTION:
370 return SYNC_DATABASE_ERROR_CORRUPTION;
371 case REPAIR_ON_CORRUPTION:
375 return SYNC_DATABASE_ERROR_FAILED;
378 SyncStatusCode LocalFileChangeTracker::TrackerDB::Repair(
379 const std::string& db_path) {
381 LOG(WARNING) << "Attempting to repair TrackerDB.";
383 leveldb::Options options;
384 options.max_open_files = 0; // Use minimum.
385 if (leveldb::RepairDB(db_path, options).ok() &&
386 Init(FAIL_ON_CORRUPTION) == SYNC_STATUS_OK) {
387 // TODO(nhiroki): perform some consistency checks between TrackerDB and
388 // syncable file system.
389 LOG(WARNING) << "Repairing TrackerDB completed.";
390 return SYNC_STATUS_OK;
393 LOG(WARNING) << "Failed to repair TrackerDB.";
394 return SYNC_DATABASE_ERROR_CORRUPTION;
397 // TODO(nhiroki): factor out the common methods into somewhere else.
398 void LocalFileChangeTracker::TrackerDB::HandleError(
399 const tracked_objects::Location& from_here,
400 const leveldb::Status& status) {
401 LOG(ERROR) << "LocalFileChangeTracker::TrackerDB failed at: "
402 << from_here.ToString() << " with error: " << status.ToString();
405 SyncStatusCode LocalFileChangeTracker::TrackerDB::MarkDirty(
406 const std::string& url) {
407 if (db_status_ != SYNC_STATUS_OK)
410 db_status_ = Init(REPAIR_ON_CORRUPTION);
411 if (db_status_ != SYNC_STATUS_OK) {
416 leveldb::Status status = db_->Put(leveldb::WriteOptions(), url, kMark);
418 HandleError(FROM_HERE, status);
419 db_status_ = LevelDBStatusToSyncStatusCode(status);
423 return SYNC_STATUS_OK;
426 SyncStatusCode LocalFileChangeTracker::TrackerDB::ClearDirty(
427 const std::string& url) {
428 if (db_status_ != SYNC_STATUS_OK)
431 // Should not reach here before initializing the database. The database should
432 // be cleared after read, and should be initialized during read if
436 leveldb::Status status = db_->Delete(leveldb::WriteOptions(), url);
437 if (!status.ok() && !status.IsNotFound()) {
438 HandleError(FROM_HERE, status);
439 db_status_ = LevelDBStatusToSyncStatusCode(status);
443 return SYNC_STATUS_OK;
446 SyncStatusCode LocalFileChangeTracker::TrackerDB::GetDirtyEntries(
447 std::queue<FileSystemURL>* dirty_files) {
448 if (db_status_ != SYNC_STATUS_OK)
451 db_status_ = Init(REPAIR_ON_CORRUPTION);
452 if (db_status_ != SYNC_STATUS_OK) {
457 scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions()));
460 while (iter->Valid()) {
461 if (!DeserializeSyncableFileSystemURL(iter->key().ToString(), &url)) {
462 LOG(WARNING) << "Failed to deserialize an URL. "
463 << "TrackerDB might be corrupted.";
464 db_status_ = SYNC_DATABASE_ERROR_CORRUPTION;
468 dirty_files->push(url);
471 return SYNC_STATUS_OK;
474 } // namespace sync_file_system