OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 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. | |
4 | |
5 #include "webkit/fileapi/syncable/local_file_change_tracker.h" | |
6 | |
7 #include <queue> | |
8 | |
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/common/fileapi/file_system_util.h" | |
18 #include "webkit/fileapi/syncable/local_file_sync_status.h" | |
19 #include "webkit/fileapi/syncable/syncable_file_system_util.h" | |
20 | |
21 using fileapi::FileSystemContext; | |
22 using fileapi::FileSystemFileUtil; | |
23 using fileapi::FileSystemOperationContext; | |
24 using fileapi::FileSystemURL; | |
25 using fileapi::FileSystemURLSet; | |
26 | |
27 namespace sync_file_system { | |
28 | |
29 namespace { | |
30 const base::FilePath::CharType kDatabaseName[] = | |
31 FILE_PATH_LITERAL("LocalFileChangeTracker"); | |
32 const char kMark[] = "d"; | |
33 } // namespace | |
34 | |
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); | |
40 | |
41 SyncStatusCode MarkDirty(const std::string& url); | |
42 SyncStatusCode ClearDirty(const std::string& url); | |
43 SyncStatusCode GetDirtyEntries( | |
44 std::queue<FileSystemURL>* dirty_files); | |
45 | |
46 private: | |
47 enum RecoveryOption { | |
48 REPAIR_ON_CORRUPTION, | |
49 FAIL_ON_CORRUPTION, | |
50 }; | |
51 | |
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); | |
56 | |
57 const base::FilePath base_path_; | |
58 scoped_ptr<leveldb::DB> db_; | |
59 SyncStatusCode db_status_; | |
60 | |
61 DISALLOW_COPY_AND_ASSIGN(TrackerDB); | |
62 }; | |
63 | |
64 LocalFileChangeTracker::ChangeInfo::ChangeInfo() : change_seq(-1) {} | |
65 LocalFileChangeTracker::ChangeInfo::~ChangeInfo() {} | |
66 | |
67 // LocalFileChangeTracker ------------------------------------------------------ | |
68 | |
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) { | |
77 } | |
78 | |
79 LocalFileChangeTracker::~LocalFileChangeTracker() { | |
80 DCHECK(file_task_runner_->RunsTasksOnCurrentThread()); | |
81 tracker_db_.reset(); | |
82 } | |
83 | |
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); | |
90 } | |
91 | |
92 void LocalFileChangeTracker::OnEndUpdate(const FileSystemURL& url) {} | |
93 | |
94 void LocalFileChangeTracker::OnCreateFile(const FileSystemURL& url) { | |
95 RecordChange(url, FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE, | |
96 SYNC_FILE_TYPE_FILE)); | |
97 } | |
98 | |
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)); | |
103 } | |
104 | |
105 void LocalFileChangeTracker::OnRemoveFile(const FileSystemURL& url) { | |
106 RecordChange(url, FileChange(FileChange::FILE_CHANGE_DELETE, | |
107 SYNC_FILE_TYPE_FILE)); | |
108 } | |
109 | |
110 void LocalFileChangeTracker::OnModifyFile(const FileSystemURL& url) { | |
111 RecordChange(url, FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE, | |
112 SYNC_FILE_TYPE_FILE)); | |
113 } | |
114 | |
115 void LocalFileChangeTracker::OnCreateDirectory(const FileSystemURL& url) { | |
116 RecordChange(url, FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE, | |
117 SYNC_FILE_TYPE_DIRECTORY)); | |
118 } | |
119 | |
120 void LocalFileChangeTracker::OnRemoveDirectory(const FileSystemURL& url) { | |
121 RecordChange(url, FileChange(FileChange::FILE_CHANGE_DELETE, | |
122 SYNC_FILE_TYPE_DIRECTORY)); | |
123 } | |
124 | |
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); | |
137 } | |
138 } | |
139 | |
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; | |
149 } | |
150 | |
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); | |
155 | |
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(); | |
162 } | |
163 | |
164 SyncStatusCode LocalFileChangeTracker::Initialize( | |
165 FileSystemContext* file_system_context) { | |
166 DCHECK(file_task_runner_->RunsTasksOnCurrentThread()); | |
167 DCHECK(!initialized_); | |
168 DCHECK(file_system_context); | |
169 | |
170 SyncStatusCode status = CollectLastDirtyChanges(file_system_context); | |
171 if (status == SYNC_STATUS_OK) | |
172 initialized_ = true; | |
173 return status; | |
174 } | |
175 | |
176 void LocalFileChangeTracker::UpdateNumChanges() { | |
177 base::AutoLock lock(num_changes_lock_); | |
178 num_changes_ = static_cast<int64>(change_seqs_.size()); | |
179 } | |
180 | |
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()); | |
186 } | |
187 | |
188 void LocalFileChangeTracker::DropAllChanges() { | |
189 changes_.clear(); | |
190 change_seqs_.clear(); | |
191 } | |
192 | |
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; | |
198 | |
199 return tracker_db_->MarkDirty(serialized_url); | |
200 } | |
201 | |
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; | |
207 | |
208 return tracker_db_->ClearDirty(serialized_url); | |
209 } | |
210 | |
211 SyncStatusCode LocalFileChangeTracker::CollectLastDirtyChanges( | |
212 FileSystemContext* file_system_context) { | |
213 DCHECK(file_task_runner_->RunsTasksOnCurrentThread()); | |
214 | |
215 std::queue<FileSystemURL> dirty_files; | |
216 const SyncStatusCode status = tracker_db_->GetDirtyEntries(&dirty_files); | |
217 if (status != SYNC_STATUS_OK) | |
218 return status; | |
219 | |
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)); | |
225 | |
226 base::PlatformFileInfo file_info; | |
227 base::FilePath platform_path; | |
228 | |
229 while (!dirty_files.empty()) { | |
230 const FileSystemURL url = dirty_files.front(); | |
231 dirty_files.pop(); | |
232 DCHECK_EQ(url.type(), fileapi::kFileSystemTypeSyncable); | |
233 | |
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; | |
241 } | |
242 | |
243 RecordChange(url, FileChange( | |
244 FileChange::FILE_CHANGE_ADD_OR_UPDATE, | |
245 SYNC_FILE_TYPE_DIRECTORY)); | |
246 | |
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(), url.filesystem_id(), path_each)); | |
254 } | |
255 break; | |
256 } | |
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. | |
261 // | |
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; | |
267 } | |
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; | |
273 } | |
274 } | |
275 return SYNC_STATUS_OK; | |
276 } | |
277 | |
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; | |
289 } | |
290 info.change_seq = current_change_seq_++; | |
291 change_seqs_[info.change_seq] = url; | |
292 UpdateNumChanges(); | |
293 } | |
294 | |
295 // TrackerDB ------------------------------------------------------------------- | |
296 | |
297 LocalFileChangeTracker::TrackerDB::TrackerDB(const base::FilePath& base_path) | |
298 : base_path_(base_path), | |
299 db_status_(SYNC_STATUS_OK) {} | |
300 | |
301 SyncStatusCode LocalFileChangeTracker::TrackerDB::Init( | |
302 RecoveryOption recovery_option) { | |
303 if (db_.get() && db_status_ == SYNC_STATUS_OK) | |
304 return SYNC_STATUS_OK; | |
305 | |
306 std::string path = fileapi::FilePathToString( | |
307 base_path_.Append(kDatabaseName)); | |
308 leveldb::Options options; | |
309 options.create_if_missing = true; | |
310 leveldb::DB* db; | |
311 leveldb::Status status = leveldb::DB::Open(options, path, &db); | |
312 if (status.ok()) { | |
313 db_.reset(db); | |
314 return SYNC_STATUS_OK; | |
315 } | |
316 | |
317 HandleError(FROM_HERE, status); | |
318 if (!status.IsCorruption()) | |
319 return LevelDBStatusToSyncStatusCode(status); | |
320 | |
321 // Try to repair the corrupted DB. | |
322 switch (recovery_option) { | |
323 case FAIL_ON_CORRUPTION: | |
324 return SYNC_DATABASE_ERROR_CORRUPTION; | |
325 case REPAIR_ON_CORRUPTION: | |
326 return Repair(path); | |
327 } | |
328 NOTREACHED(); | |
329 return SYNC_DATABASE_ERROR_FAILED; | |
330 } | |
331 | |
332 SyncStatusCode LocalFileChangeTracker::TrackerDB::Repair( | |
333 const std::string& db_path) { | |
334 DCHECK(!db_.get()); | |
335 LOG(WARNING) << "Attempting to repair TrackerDB."; | |
336 | |
337 if (leveldb::RepairDB(db_path, leveldb::Options()).ok() && | |
338 Init(FAIL_ON_CORRUPTION) == SYNC_STATUS_OK) { | |
339 // TODO(nhiroki): perform some consistency checks between TrackerDB and | |
340 // syncable file system. | |
341 LOG(WARNING) << "Repairing TrackerDB completed."; | |
342 return SYNC_STATUS_OK; | |
343 } | |
344 | |
345 LOG(WARNING) << "Failed to repair TrackerDB."; | |
346 return SYNC_DATABASE_ERROR_CORRUPTION; | |
347 } | |
348 | |
349 // TODO(nhiroki): factor out the common methods into somewhere else. | |
350 void LocalFileChangeTracker::TrackerDB::HandleError( | |
351 const tracked_objects::Location& from_here, | |
352 const leveldb::Status& status) { | |
353 LOG(ERROR) << "LocalFileChangeTracker::TrackerDB failed at: " | |
354 << from_here.ToString() << " with error: " << status.ToString(); | |
355 } | |
356 | |
357 SyncStatusCode LocalFileChangeTracker::TrackerDB::MarkDirty( | |
358 const std::string& url) { | |
359 if (db_status_ != SYNC_STATUS_OK) | |
360 return db_status_; | |
361 | |
362 db_status_ = Init(REPAIR_ON_CORRUPTION); | |
363 if (db_status_ != SYNC_STATUS_OK) { | |
364 db_.reset(); | |
365 return db_status_; | |
366 } | |
367 | |
368 leveldb::Status status = db_->Put(leveldb::WriteOptions(), url, kMark); | |
369 if (!status.ok()) { | |
370 HandleError(FROM_HERE, status); | |
371 db_status_ = LevelDBStatusToSyncStatusCode(status); | |
372 db_.reset(); | |
373 return db_status_; | |
374 } | |
375 return SYNC_STATUS_OK; | |
376 } | |
377 | |
378 SyncStatusCode LocalFileChangeTracker::TrackerDB::ClearDirty( | |
379 const std::string& url) { | |
380 if (db_status_ != SYNC_STATUS_OK) | |
381 return db_status_; | |
382 | |
383 // Should not reach here before initializing the database. The database should | |
384 // be cleared after read, and should be initialized during read if | |
385 // uninitialized. | |
386 DCHECK(db_.get()); | |
387 | |
388 leveldb::Status status = db_->Delete(leveldb::WriteOptions(), url); | |
389 if (!status.ok() && !status.IsNotFound()) { | |
390 HandleError(FROM_HERE, status); | |
391 db_status_ = LevelDBStatusToSyncStatusCode(status); | |
392 db_.reset(); | |
393 return db_status_; | |
394 } | |
395 return SYNC_STATUS_OK; | |
396 } | |
397 | |
398 SyncStatusCode LocalFileChangeTracker::TrackerDB::GetDirtyEntries( | |
399 std::queue<FileSystemURL>* dirty_files) { | |
400 if (db_status_ != SYNC_STATUS_OK) | |
401 return db_status_; | |
402 | |
403 db_status_ = Init(REPAIR_ON_CORRUPTION); | |
404 if (db_status_ != SYNC_STATUS_OK) { | |
405 db_.reset(); | |
406 return db_status_; | |
407 } | |
408 | |
409 scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions())); | |
410 iter->SeekToFirst(); | |
411 FileSystemURL url; | |
412 while (iter->Valid()) { | |
413 if (!DeserializeSyncableFileSystemURL(iter->key().ToString(), &url)) { | |
414 LOG(WARNING) << "Failed to deserialize an URL. " | |
415 << "TrackerDB might be corrupted."; | |
416 db_status_ = SYNC_DATABASE_ERROR_CORRUPTION; | |
417 db_.reset(); | |
418 return db_status_; | |
419 } | |
420 dirty_files->push(url); | |
421 iter->Next(); | |
422 } | |
423 return SYNC_STATUS_OK; | |
424 } | |
425 | |
426 } // namespace sync_file_system | |
OLD | NEW |