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"
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
{
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 // 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())
159 change_seqs_
.erase(found
->second
.change_seq
);
160 changes_
.erase(found
);
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
)
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);
185 urls
->insert(url_deque
.begin(), url_deque
.end());
188 void LocalFileChangeTracker::DropAllChanges() {
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
)
220 FileSystemFileUtil
* file_util
=
221 file_system_context
->GetFileUtil(fileapi::kFileSystemTypeSyncable
);
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();
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
));
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
));
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
));
268 case base::PLATFORM_FILE_ERROR_FAILED
:
270 // TODO(nhiroki): handle file access error (http://crbug.com/155251).
271 LOG(WARNING
) << "Failed to access local file.";
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()) {
290 info
.change_seq
= current_change_seq_
++;
291 change_seqs_
[info
.change_seq
] = url
;
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;
312 leveldb::Status status
= leveldb::DB::Open(options
, path
, &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
:
330 return SYNC_DATABASE_ERROR_FAILED
;
333 SyncStatusCode
LocalFileChangeTracker::TrackerDB::Repair(
334 const std::string
& db_path
) {
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
)
365 db_status_
= Init(REPAIR_ON_CORRUPTION
);
366 if (db_status_
!= SYNC_STATUS_OK
) {
371 leveldb::Status status
= db_
->Put(leveldb::WriteOptions(), url
, kMark
);
373 HandleError(FROM_HERE
, status
);
374 db_status_
= LevelDBStatusToSyncStatusCode(status
);
378 return SYNC_STATUS_OK
;
381 SyncStatusCode
LocalFileChangeTracker::TrackerDB::ClearDirty(
382 const std::string
& url
) {
383 if (db_status_
!= SYNC_STATUS_OK
)
386 // Should not reach here before initializing the database. The database should
387 // be cleared after read, and should be initialized during read if
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
);
398 return SYNC_STATUS_OK
;
401 SyncStatusCode
LocalFileChangeTracker::TrackerDB::GetDirtyEntries(
402 std::queue
<FileSystemURL
>* dirty_files
) {
403 if (db_status_
!= SYNC_STATUS_OK
)
406 db_status_
= Init(REPAIR_ON_CORRUPTION
);
407 if (db_status_
!= SYNC_STATUS_OK
) {
412 scoped_ptr
<leveldb::Iterator
> iter(db_
->NewIterator(leveldb::ReadOptions()));
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
;
423 dirty_files
->push(url
);
426 return SYNC_STATUS_OK
;
429 } // namespace sync_file_system