Only allow leveldb to use the minimum amount of file descriptors.
[chromium-blink-merge.git] / webkit / browser / fileapi / syncable / local_file_change_tracker.cc
blob9e0a725a26a5ab4dfee1835ca8b31efc1249ed46
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 "webkit/browser/fileapi/syncable/local_file_change_tracker.h"
7 #include <queue>
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/browser/fileapi/file_system_context.h"
15 #include "webkit/browser/fileapi/file_system_file_util.h"
16 #include "webkit/browser/fileapi/file_system_operation_context.h"
17 #include "webkit/browser/fileapi/syncable/local_file_sync_status.h"
18 #include "webkit/browser/fileapi/syncable/syncable_file_system_util.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 {
29 namespace {
30 const base::FilePath::CharType kDatabaseName[] =
31 FILE_PATH_LITERAL("LocalFileChangeTracker");
32 const char kMark[] = "d";
33 } // namespace
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);
41 SyncStatusCode MarkDirty(const std::string& url);
42 SyncStatusCode ClearDirty(const std::string& url);
43 SyncStatusCode GetDirtyEntries(
44 std::queue<FileSystemURL>* dirty_files);
46 private:
47 enum RecoveryOption {
48 REPAIR_ON_CORRUPTION,
49 FAIL_ON_CORRUPTION,
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),
76 num_changes_(0) {
79 LocalFileChangeTracker::~LocalFileChangeTracker() {
80 DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
81 tracker_db_.reset();
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);
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) {
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);
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;
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);
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();
164 SyncStatusCode LocalFileChangeTracker::Initialize(
165 FileSystemContext* file_system_context) {
166 DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
167 DCHECK(!initialized_);
168 DCHECK(file_system_context);
170 SyncStatusCode status = CollectLastDirtyChanges(file_system_context);
171 if (status == SYNC_STATUS_OK)
172 initialized_ = true;
173 return status;
176 void LocalFileChangeTracker::UpdateNumChanges() {
177 base::AutoLock lock(num_changes_lock_);
178 num_changes_ = static_cast<int64>(change_seqs_.size());
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());
188 void LocalFileChangeTracker::DropAllChanges() {
189 changes_.clear();
190 change_seqs_.clear();
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;
199 return tracker_db_->MarkDirty(serialized_url);
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;
208 return tracker_db_->ClearDirty(serialized_url);
211 SyncStatusCode LocalFileChangeTracker::CollectLastDirtyChanges(
212 FileSystemContext* file_system_context) {
213 DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
215 std::queue<FileSystemURL> dirty_files;
216 const SyncStatusCode status = tracker_db_->GetDirtyEntries(&dirty_files);
217 if (status != SYNC_STATUS_OK)
218 return status;
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));
226 base::PlatformFileInfo file_info;
227 base::FilePath platform_path;
229 while (!dirty_files.empty()) {
230 const FileSystemURL url = dirty_files.front();
231 dirty_files.pop();
232 DCHECK_EQ(url.type(), fileapi::kFileSystemTypeSyncable);
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;
243 RecordChange(url, FileChange(
244 FileChange::FILE_CHANGE_ADD_OR_UPDATE,
245 SYNC_FILE_TYPE_DIRECTORY));
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(), path_each));
255 break;
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.
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;
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;
275 return SYNC_STATUS_OK;
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;
290 info.change_seq = current_change_seq_++;
291 change_seqs_[info.change_seq] = url;
292 UpdateNumChanges();
295 // TrackerDB -------------------------------------------------------------------
297 LocalFileChangeTracker::TrackerDB::TrackerDB(const base::FilePath& base_path)
298 : base_path_(base_path),
299 db_status_(SYNC_STATUS_OK) {}
301 SyncStatusCode LocalFileChangeTracker::TrackerDB::Init(
302 RecoveryOption recovery_option) {
303 if (db_.get() && db_status_ == SYNC_STATUS_OK)
304 return SYNC_STATUS_OK;
306 std::string path = fileapi::FilePathToString(
307 base_path_.Append(kDatabaseName));
308 leveldb::Options options;
309 options.max_open_files = 0; // Use minimum.
310 options.create_if_missing = true;
311 leveldb::DB* db;
312 leveldb::Status status = leveldb::DB::Open(options, path, &db);
313 if (status.ok()) {
314 db_.reset(db);
315 return SYNC_STATUS_OK;
318 HandleError(FROM_HERE, status);
319 if (!status.IsCorruption())
320 return LevelDBStatusToSyncStatusCode(status);
322 // Try to repair the corrupted DB.
323 switch (recovery_option) {
324 case FAIL_ON_CORRUPTION:
325 return SYNC_DATABASE_ERROR_CORRUPTION;
326 case REPAIR_ON_CORRUPTION:
327 return Repair(path);
329 NOTREACHED();
330 return SYNC_DATABASE_ERROR_FAILED;
333 SyncStatusCode LocalFileChangeTracker::TrackerDB::Repair(
334 const std::string& db_path) {
335 DCHECK(!db_.get());
336 LOG(WARNING) << "Attempting to repair TrackerDB.";
338 leveldb::Options options;
339 options.max_open_files = 0; // Use minimum.
340 if (leveldb::RepairDB(db_path, options).ok() &&
341 Init(FAIL_ON_CORRUPTION) == SYNC_STATUS_OK) {
342 // TODO(nhiroki): perform some consistency checks between TrackerDB and
343 // syncable file system.
344 LOG(WARNING) << "Repairing TrackerDB completed.";
345 return SYNC_STATUS_OK;
348 LOG(WARNING) << "Failed to repair TrackerDB.";
349 return SYNC_DATABASE_ERROR_CORRUPTION;
352 // TODO(nhiroki): factor out the common methods into somewhere else.
353 void LocalFileChangeTracker::TrackerDB::HandleError(
354 const tracked_objects::Location& from_here,
355 const leveldb::Status& status) {
356 LOG(ERROR) << "LocalFileChangeTracker::TrackerDB failed at: "
357 << from_here.ToString() << " with error: " << status.ToString();
360 SyncStatusCode LocalFileChangeTracker::TrackerDB::MarkDirty(
361 const std::string& url) {
362 if (db_status_ != SYNC_STATUS_OK)
363 return db_status_;
365 db_status_ = Init(REPAIR_ON_CORRUPTION);
366 if (db_status_ != SYNC_STATUS_OK) {
367 db_.reset();
368 return db_status_;
371 leveldb::Status status = db_->Put(leveldb::WriteOptions(), url, kMark);
372 if (!status.ok()) {
373 HandleError(FROM_HERE, status);
374 db_status_ = LevelDBStatusToSyncStatusCode(status);
375 db_.reset();
376 return db_status_;
378 return SYNC_STATUS_OK;
381 SyncStatusCode LocalFileChangeTracker::TrackerDB::ClearDirty(
382 const std::string& url) {
383 if (db_status_ != SYNC_STATUS_OK)
384 return db_status_;
386 // Should not reach here before initializing the database. The database should
387 // be cleared after read, and should be initialized during read if
388 // uninitialized.
389 DCHECK(db_.get());
391 leveldb::Status status = db_->Delete(leveldb::WriteOptions(), url);
392 if (!status.ok() && !status.IsNotFound()) {
393 HandleError(FROM_HERE, status);
394 db_status_ = LevelDBStatusToSyncStatusCode(status);
395 db_.reset();
396 return db_status_;
398 return SYNC_STATUS_OK;
401 SyncStatusCode LocalFileChangeTracker::TrackerDB::GetDirtyEntries(
402 std::queue<FileSystemURL>* dirty_files) {
403 if (db_status_ != SYNC_STATUS_OK)
404 return db_status_;
406 db_status_ = Init(REPAIR_ON_CORRUPTION);
407 if (db_status_ != SYNC_STATUS_OK) {
408 db_.reset();
409 return db_status_;
412 scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions()));
413 iter->SeekToFirst();
414 FileSystemURL url;
415 while (iter->Valid()) {
416 if (!DeserializeSyncableFileSystemURL(iter->key().ToString(), &url)) {
417 LOG(WARNING) << "Failed to deserialize an URL. "
418 << "TrackerDB might be corrupted.";
419 db_status_ = SYNC_DATABASE_ERROR_CORRUPTION;
420 db_.reset();
421 return db_status_;
423 dirty_files->push(url);
424 iter->Next();
426 return SYNC_STATUS_OK;
429 } // namespace sync_file_system