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.
4
5 #include "chrome/browser/sync_file_system/local/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 "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/helpers/memenv/memenv.h"
16 #include "third_party/leveldatabase/src/include/leveldb/db.h"
17 #include "third_party/leveldatabase/src/include/leveldb/env.h"
18 #include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
19 #include "webkit/browser/fileapi/file_system_context.h"
20 #include "webkit/browser/fileapi/file_system_file_util.h"
21 #include "webkit/browser/fileapi/file_system_operation_context.h"
22 #include "webkit/common/fileapi/file_system_util.h"
23
24 using fileapi::FileSystemContext;
25 using fileapi::FileSystemFileUtil;
26 using fileapi::FileSystemOperationContext;
27 using fileapi::FileSystemURL;
28 using fileapi::FileSystemURLSet;
29
30 namespace sync_file_system {
31
32 namespace {
33 const base::FilePath::CharType kDatabaseName[] =
34 FILE_PATH_LITERAL("LocalFileChangeTracker");
35 const char kMark[] = "d";
36 } // namespace
37
38 // A database class that stores local file changes in a local database. This
39 // object must be destructed on file_task_runner.
40 class LocalFileChangeTracker::TrackerDB {
41 public:
42 TrackerDB(const base::FilePath& base_path,
43 leveldb::Env* env_override);
44
45 SyncStatusCode MarkDirty(const std::string& url);
46 SyncStatusCode ClearDirty(const std::string& url);
47 SyncStatusCode GetDirtyEntries(
48 std::queue<FileSystemURL>* dirty_files);
49 SyncStatusCode WriteBatch(scoped_ptr<leveldb::WriteBatch> batch);
50
51 private:
52 enum RecoveryOption {
53 REPAIR_ON_CORRUPTION,
54 FAIL_ON_CORRUPTION,
55 };
56
57 SyncStatusCode Init(RecoveryOption recovery_option);
58 SyncStatusCode Repair(const std::string& db_path);
59 void HandleError(const tracked_objects::Location& from_here,
60 const leveldb::Status& status);
61
62 const base::FilePath base_path_;
63 leveldb::Env* env_override_;
64 scoped_ptr<leveldb::DB> db_;
65 SyncStatusCode db_status_;
66
67 DISALLOW_COPY_AND_ASSIGN(TrackerDB);
68 };
69
ChangeInfo()70 LocalFileChangeTracker::ChangeInfo::ChangeInfo() : change_seq(-1) {}
~ChangeInfo()71 LocalFileChangeTracker::ChangeInfo::~ChangeInfo() {}
72
73 // LocalFileChangeTracker ------------------------------------------------------
74
LocalFileChangeTracker(const base::FilePath & base_path,leveldb::Env * env_override,base::SequencedTaskRunner * file_task_runner)75 LocalFileChangeTracker::LocalFileChangeTracker(
76 const base::FilePath& base_path,
77 leveldb::Env* env_override,
78 base::SequencedTaskRunner* file_task_runner)
79 : initialized_(false),
80 file_task_runner_(file_task_runner),
81 tracker_db_(new TrackerDB(base_path, env_override)),
82 current_change_seq_(0),
83 num_changes_(0) {
84 }
85
~LocalFileChangeTracker()86 LocalFileChangeTracker::~LocalFileChangeTracker() {
87 DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
88 tracker_db_.reset();
89 }
90
OnStartUpdate(const FileSystemURL & url)91 void LocalFileChangeTracker::OnStartUpdate(const FileSystemURL& url) {
92 DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
93 if (ContainsKey(changes_, url))
94 return;
95 // TODO(nhiroki): propagate the error code (see http://crbug.com/152127).
96 MarkDirtyOnDatabase(url);
97 }
98
OnEndUpdate(const FileSystemURL & url)99 void LocalFileChangeTracker::OnEndUpdate(const FileSystemURL& url) {}
100
OnCreateFile(const FileSystemURL & url)101 void LocalFileChangeTracker::OnCreateFile(const FileSystemURL& url) {
102 RecordChange(url, FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
103 SYNC_FILE_TYPE_FILE));
104 }
105
OnCreateFileFrom(const FileSystemURL & url,const FileSystemURL & src)106 void LocalFileChangeTracker::OnCreateFileFrom(const FileSystemURL& url,
107 const FileSystemURL& src) {
108 RecordChange(url, FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
109 SYNC_FILE_TYPE_FILE));
110 }
111
OnRemoveFile(const FileSystemURL & url)112 void LocalFileChangeTracker::OnRemoveFile(const FileSystemURL& url) {
113 RecordChange(url, FileChange(FileChange::FILE_CHANGE_DELETE,
114 SYNC_FILE_TYPE_FILE));
115 }
116
OnModifyFile(const FileSystemURL & url)117 void LocalFileChangeTracker::OnModifyFile(const FileSystemURL& url) {
118 RecordChange(url, FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
119 SYNC_FILE_TYPE_FILE));
120 }
121
OnCreateDirectory(const FileSystemURL & url)122 void LocalFileChangeTracker::OnCreateDirectory(const FileSystemURL& url) {
123 RecordChange(url, FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
124 SYNC_FILE_TYPE_DIRECTORY));
125 }
126
OnRemoveDirectory(const FileSystemURL & url)127 void LocalFileChangeTracker::OnRemoveDirectory(const FileSystemURL& url) {
128 RecordChange(url, FileChange(FileChange::FILE_CHANGE_DELETE,
129 SYNC_FILE_TYPE_DIRECTORY));
130 }
131
GetNextChangedURLs(std::deque<FileSystemURL> * urls,int max_urls)132 void LocalFileChangeTracker::GetNextChangedURLs(
133 std::deque<FileSystemURL>* urls, int max_urls) {
134 DCHECK(urls);
135 DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
136 urls->clear();
137 // Mildly prioritizes the URLs that older changes and have not been updated
138 // for a while.
139 for (ChangeSeqMap::iterator iter = change_seqs_.begin();
140 iter != change_seqs_.end() &&
141 (max_urls == 0 || urls->size() < static_cast<size_t>(max_urls));
142 ++iter) {
143 urls->push_back(iter->second);
144 }
145 }
146
GetChangesForURL(const FileSystemURL & url,FileChangeList * changes)147 void LocalFileChangeTracker::GetChangesForURL(
148 const FileSystemURL& url, FileChangeList* changes) {
149 DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
150 DCHECK(changes);
151 changes->clear();
152 FileChangeMap::iterator found = changes_.find(url);
153 if (found == changes_.end()) {
154 found = demoted_changes_.find(url);
155 if (found == demoted_changes_.end())
156 return;
157 }
158 *changes = found->second.change_list;
159 }
160
ClearChangesForURL(const FileSystemURL & url)161 void LocalFileChangeTracker::ClearChangesForURL(const FileSystemURL& url) {
162 DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
163 ClearDirtyOnDatabase(url);
164 mirror_changes_.erase(url);
165 demoted_changes_.erase(url);
166 FileChangeMap::iterator found = changes_.find(url);
167 if (found == changes_.end())
168 return;
169 change_seqs_.erase(found->second.change_seq);
170 changes_.erase(found);
171 UpdateNumChanges();
172 }
173
CreateFreshMirrorForURL(const fileapi::FileSystemURL & url)174 void LocalFileChangeTracker::CreateFreshMirrorForURL(
175 const fileapi::FileSystemURL& url) {
176 DCHECK(!ContainsKey(mirror_changes_, url));
177 mirror_changes_[url] = ChangeInfo();
178 }
179
RemoveMirrorAndCommitChangesForURL(const fileapi::FileSystemURL & url)180 void LocalFileChangeTracker::RemoveMirrorAndCommitChangesForURL(
181 const fileapi::FileSystemURL& url) {
182 FileChangeMap::iterator found = mirror_changes_.find(url);
183 if (found == mirror_changes_.end())
184 return;
185 mirror_changes_.erase(found);
186
187 if (ContainsKey(changes_, url))
188 MarkDirtyOnDatabase(url);
189 else
190 ClearDirtyOnDatabase(url);
191 UpdateNumChanges();
192 }
193
ResetToMirrorAndCommitChangesForURL(const fileapi::FileSystemURL & url)194 void LocalFileChangeTracker::ResetToMirrorAndCommitChangesForURL(
195 const fileapi::FileSystemURL& url) {
196 FileChangeMap::iterator found = mirror_changes_.find(url);
197 if (found == mirror_changes_.end() || found->second.change_list.empty()) {
198 ClearChangesForURL(url);
199 return;
200 }
201 const ChangeInfo& info = found->second;
202 change_seqs_[info.change_seq] = url;
203 changes_[url] = info;
204 RemoveMirrorAndCommitChangesForURL(url);
205 }
206
DemoteChangesForURL(const fileapi::FileSystemURL & url)207 void LocalFileChangeTracker::DemoteChangesForURL(
208 const fileapi::FileSystemURL& url) {
209 FileChangeMap::iterator found = changes_.find(url);
210 if (found == changes_.end())
211 return;
212 FileChangeList changes = found->second.change_list;
213
214 mirror_changes_.erase(url);
215 change_seqs_.erase(found->second.change_seq);
216 changes_.erase(found);
217
218 FileChangeList::List change_list = changes.list();
219 while (!change_list.empty()) {
220 RecordChangeToChangeMaps(url, change_list.front(), 0,
221 &demoted_changes_, NULL);
222 change_list.pop_front();
223 }
224 }
225
PromoteDemotedChanges()226 bool LocalFileChangeTracker::PromoteDemotedChanges() {
227 if (demoted_changes_.empty())
228 return false;
229 for (FileChangeMap::iterator iter = demoted_changes_.begin();
230 iter != demoted_changes_.end();) {
231 fileapi::FileSystemURL url = iter->first;
232 FileChangeList::List change_list = iter->second.change_list.list();
233 demoted_changes_.erase(iter++);
234
235 // Make sure that this URL is in no queues.
236 DCHECK(!ContainsKey(changes_, url));
237 DCHECK(!ContainsKey(demoted_changes_, url));
238 DCHECK(!ContainsKey(mirror_changes_, url));
239
240 while (!change_list.empty()) {
241 RecordChange(url, change_list.front());
242 change_list.pop_front();
243 }
244 }
245 demoted_changes_.clear();
246 return true;
247 }
248
Initialize(FileSystemContext * file_system_context)249 SyncStatusCode LocalFileChangeTracker::Initialize(
250 FileSystemContext* file_system_context) {
251 DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
252 DCHECK(!initialized_);
253 DCHECK(file_system_context);
254
255 SyncStatusCode status = CollectLastDirtyChanges(file_system_context);
256 if (status == SYNC_STATUS_OK)
257 initialized_ = true;
258 return status;
259 }
260
ResetForFileSystem(const GURL & origin,fileapi::FileSystemType type)261 void LocalFileChangeTracker::ResetForFileSystem(
262 const GURL& origin,
263 fileapi::FileSystemType type) {
264 DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
265 scoped_ptr<leveldb::WriteBatch> batch(new leveldb::WriteBatch);
266 for (FileChangeMap::iterator iter = changes_.begin();
267 iter != changes_.end();) {
268 fileapi::FileSystemURL url = iter->first;
269 if (url.origin() != origin || url.type() != type) {
270 ++iter;
271 continue;
272 }
273 mirror_changes_.erase(url);
274 demoted_changes_.erase(url);
275 change_seqs_.erase(iter->second.change_seq);
276 changes_.erase(iter++);
277
278 std::string serialized_url;
279 const bool should_success =
280 SerializeSyncableFileSystemURL(url, &serialized_url);
281 if (!should_success) {
282 NOTREACHED() << "Failed to serialize: " << url.DebugString();
283 continue;
284 }
285 batch->Delete(serialized_url);
286 }
287 // Fail to apply batch to database wouldn't have critical effect, they'll be
288 // just marked deleted on next relaunch.
289 tracker_db_->WriteBatch(batch.Pass());
290 UpdateNumChanges();
291 }
292
UpdateNumChanges()293 void LocalFileChangeTracker::UpdateNumChanges() {
294 base::AutoLock lock(num_changes_lock_);
295 num_changes_ = static_cast<int64>(change_seqs_.size());
296 }
297
GetAllChangedURLs(FileSystemURLSet * urls)298 void LocalFileChangeTracker::GetAllChangedURLs(FileSystemURLSet* urls) {
299 std::deque<FileSystemURL> url_deque;
300 GetNextChangedURLs(&url_deque, 0);
301 urls->clear();
302 urls->insert(url_deque.begin(), url_deque.end());
303 }
304
DropAllChanges()305 void LocalFileChangeTracker::DropAllChanges() {
306 changes_.clear();
307 change_seqs_.clear();
308 mirror_changes_.clear();
309 }
310
MarkDirtyOnDatabase(const FileSystemURL & url)311 SyncStatusCode LocalFileChangeTracker::MarkDirtyOnDatabase(
312 const FileSystemURL& url) {
313 std::string serialized_url;
314 if (!SerializeSyncableFileSystemURL(url, &serialized_url))
315 return SYNC_FILE_ERROR_INVALID_URL;
316
317 return tracker_db_->MarkDirty(serialized_url);
318 }
319
ClearDirtyOnDatabase(const FileSystemURL & url)320 SyncStatusCode LocalFileChangeTracker::ClearDirtyOnDatabase(
321 const FileSystemURL& url) {
322 std::string serialized_url;
323 if (!SerializeSyncableFileSystemURL(url, &serialized_url))
324 return SYNC_FILE_ERROR_INVALID_URL;
325
326 return tracker_db_->ClearDirty(serialized_url);
327 }
328
CollectLastDirtyChanges(FileSystemContext * file_system_context)329 SyncStatusCode LocalFileChangeTracker::CollectLastDirtyChanges(
330 FileSystemContext* file_system_context) {
331 DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
332
333 std::queue<FileSystemURL> dirty_files;
334 const SyncStatusCode status = tracker_db_->GetDirtyEntries(&dirty_files);
335 if (status != SYNC_STATUS_OK)
336 return status;
337
338 FileSystemFileUtil* file_util =
339 file_system_context->sandbox_delegate()->sync_file_util();
340 DCHECK(file_util);
341 scoped_ptr<FileSystemOperationContext> context(
342 new FileSystemOperationContext(file_system_context));
343
344 base::File::Info file_info;
345 base::FilePath platform_path;
346
347 while (!dirty_files.empty()) {
348 const FileSystemURL url = dirty_files.front();
349 dirty_files.pop();
350 DCHECK_EQ(url.type(), fileapi::kFileSystemTypeSyncable);
351
352 switch (file_util->GetFileInfo(context.get(), url,
353 &file_info, &platform_path)) {
354 case base::File::FILE_OK: {
355 if (!file_info.is_directory) {
356 RecordChange(url, FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
357 SYNC_FILE_TYPE_FILE));
358 break;
359 }
360
361 RecordChange(url, FileChange(
362 FileChange::FILE_CHANGE_ADD_OR_UPDATE,
363 SYNC_FILE_TYPE_DIRECTORY));
364
365 // Push files and directories in this directory into |dirty_files|.
366 scoped_ptr<FileSystemFileUtil::AbstractFileEnumerator> enumerator(
367 file_util->CreateFileEnumerator(context.get(), url));
368 base::FilePath path_each;
369 while (!(path_each = enumerator->Next()).empty()) {
370 dirty_files.push(CreateSyncableFileSystemURL(
371 url.origin(), path_each));
372 }
373 break;
374 }
375 case base::File::FILE_ERROR_NOT_FOUND: {
376 // File represented by |url| has already been deleted. Since we cannot
377 // figure out if this file was directory or not from the URL, file
378 // type is treated as SYNC_FILE_TYPE_UNKNOWN.
379 //
380 // NOTE: Directory to have been reverted (that is, ADD -> DELETE) is
381 // also treated as FILE_CHANGE_DELETE.
382 RecordChange(url, FileChange(FileChange::FILE_CHANGE_DELETE,
383 SYNC_FILE_TYPE_UNKNOWN));
384 break;
385 }
386 case base::File::FILE_ERROR_FAILED:
387 default:
388 // TODO(nhiroki): handle file access error (http://crbug.com/155251).
389 LOG(WARNING) << "Failed to access local file.";
390 break;
391 }
392 }
393 return SYNC_STATUS_OK;
394 }
395
RecordChange(const FileSystemURL & url,const FileChange & change)396 void LocalFileChangeTracker::RecordChange(
397 const FileSystemURL& url, const FileChange& change) {
398 DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
399 if (ContainsKey(demoted_changes_, url)) {
400 RecordChangeToChangeMaps(url, change, 0, &demoted_changes_, NULL);
401 return;
402 }
403 int change_seq = current_change_seq_++;
404 RecordChangeToChangeMaps(url, change, change_seq, &changes_, &change_seqs_);
405 if (ContainsKey(mirror_changes_, url))
406 RecordChangeToChangeMaps(url, change, change_seq, &mirror_changes_, NULL);
407 UpdateNumChanges();
408 }
409
RecordChangeToChangeMaps(const FileSystemURL & url,const FileChange & change,int new_change_seq,FileChangeMap * changes,ChangeSeqMap * change_seqs)410 void LocalFileChangeTracker::RecordChangeToChangeMaps(
411 const FileSystemURL& url,
412 const FileChange& change,
413 int new_change_seq,
414 FileChangeMap* changes,
415 ChangeSeqMap* change_seqs) {
416 ChangeInfo& info = (*changes)[url];
417 if (info.change_seq >= 0 && change_seqs)
418 change_seqs->erase(info.change_seq);
419 info.change_list.Update(change);
420 if (info.change_list.empty()) {
421 changes->erase(url);
422 return;
423 }
424 info.change_seq = new_change_seq;
425 if (change_seqs)
426 (*change_seqs)[info.change_seq] = url;
427 }
428
429 // TrackerDB -------------------------------------------------------------------
430
TrackerDB(const base::FilePath & base_path,leveldb::Env * env_override)431 LocalFileChangeTracker::TrackerDB::TrackerDB(const base::FilePath& base_path,
432 leveldb::Env* env_override)
433 : base_path_(base_path),
434 env_override_(env_override),
435 db_status_(SYNC_STATUS_OK) {}
436
Init(RecoveryOption recovery_option)437 SyncStatusCode LocalFileChangeTracker::TrackerDB::Init(
438 RecoveryOption recovery_option) {
439 if (db_.get() && db_status_ == SYNC_STATUS_OK)
440 return SYNC_STATUS_OK;
441
442 std::string path = fileapi::FilePathToString(
443 base_path_.Append(kDatabaseName));
444 leveldb::Options options;
445 options.max_open_files = 0; // Use minimum.
446 options.create_if_missing = true;
447 if (env_override_)
448 options.env = env_override_;
449 leveldb::DB* db;
450 leveldb::Status status = leveldb::DB::Open(options, path, &db);
451 if (status.ok()) {
452 db_.reset(db);
453 return SYNC_STATUS_OK;
454 }
455
456 HandleError(FROM_HERE, status);
457 if (!status.IsCorruption())
458 return LevelDBStatusToSyncStatusCode(status);
459
460 // Try to repair the corrupted DB.
461 switch (recovery_option) {
462 case FAIL_ON_CORRUPTION:
463 return SYNC_DATABASE_ERROR_CORRUPTION;
464 case REPAIR_ON_CORRUPTION:
465 return Repair(path);
466 }
467 NOTREACHED();
468 return SYNC_DATABASE_ERROR_FAILED;
469 }
470
Repair(const std::string & db_path)471 SyncStatusCode LocalFileChangeTracker::TrackerDB::Repair(
472 const std::string& db_path) {
473 DCHECK(!db_.get());
474 LOG(WARNING) << "Attempting to repair TrackerDB.";
475
476 leveldb::Options options;
477 options.max_open_files = 0; // Use minimum.
478 if (leveldb::RepairDB(db_path, options).ok() &&
479 Init(FAIL_ON_CORRUPTION) == SYNC_STATUS_OK) {
480 // TODO(nhiroki): perform some consistency checks between TrackerDB and
481 // syncable file system.
482 LOG(WARNING) << "Repairing TrackerDB completed.";
483 return SYNC_STATUS_OK;
484 }
485
486 LOG(WARNING) << "Failed to repair TrackerDB.";
487 return SYNC_DATABASE_ERROR_CORRUPTION;
488 }
489
490 // TODO(nhiroki): factor out the common methods into somewhere else.
HandleError(const tracked_objects::Location & from_here,const leveldb::Status & status)491 void LocalFileChangeTracker::TrackerDB::HandleError(
492 const tracked_objects::Location& from_here,
493 const leveldb::Status& status) {
494 LOG(ERROR) << "LocalFileChangeTracker::TrackerDB failed at: "
495 << from_here.ToString() << " with error: " << status.ToString();
496 }
497
MarkDirty(const std::string & url)498 SyncStatusCode LocalFileChangeTracker::TrackerDB::MarkDirty(
499 const std::string& url) {
500 if (db_status_ != SYNC_STATUS_OK)
501 return db_status_;
502
503 db_status_ = Init(REPAIR_ON_CORRUPTION);
504 if (db_status_ != SYNC_STATUS_OK) {
505 db_.reset();
506 return db_status_;
507 }
508
509 leveldb::Status status = db_->Put(leveldb::WriteOptions(), url, kMark);
510 if (!status.ok()) {
511 HandleError(FROM_HERE, status);
512 db_status_ = LevelDBStatusToSyncStatusCode(status);
513 db_.reset();
514 return db_status_;
515 }
516 return SYNC_STATUS_OK;
517 }
518
ClearDirty(const std::string & url)519 SyncStatusCode LocalFileChangeTracker::TrackerDB::ClearDirty(
520 const std::string& url) {
521 if (db_status_ != SYNC_STATUS_OK)
522 return db_status_;
523
524 // Should not reach here before initializing the database. The database should
525 // be cleared after read, and should be initialized during read if
526 // uninitialized.
527 DCHECK(db_.get());
528
529 leveldb::Status status = db_->Delete(leveldb::WriteOptions(), url);
530 if (!status.ok() && !status.IsNotFound()) {
531 HandleError(FROM_HERE, status);
532 db_status_ = LevelDBStatusToSyncStatusCode(status);
533 db_.reset();
534 return db_status_;
535 }
536 return SYNC_STATUS_OK;
537 }
538
GetDirtyEntries(std::queue<FileSystemURL> * dirty_files)539 SyncStatusCode LocalFileChangeTracker::TrackerDB::GetDirtyEntries(
540 std::queue<FileSystemURL>* dirty_files) {
541 if (db_status_ != SYNC_STATUS_OK)
542 return db_status_;
543
544 db_status_ = Init(REPAIR_ON_CORRUPTION);
545 if (db_status_ != SYNC_STATUS_OK) {
546 db_.reset();
547 return db_status_;
548 }
549
550 scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions()));
551 iter->SeekToFirst();
552 FileSystemURL url;
553 while (iter->Valid()) {
554 if (!DeserializeSyncableFileSystemURL(iter->key().ToString(), &url)) {
555 LOG(WARNING) << "Failed to deserialize an URL. "
556 << "TrackerDB might be corrupted.";
557 db_status_ = SYNC_DATABASE_ERROR_CORRUPTION;
558 db_.reset();
559 return db_status_;
560 }
561 dirty_files->push(url);
562 iter->Next();
563 }
564 return SYNC_STATUS_OK;
565 }
566
WriteBatch(scoped_ptr<leveldb::WriteBatch> batch)567 SyncStatusCode LocalFileChangeTracker::TrackerDB::WriteBatch(
568 scoped_ptr<leveldb::WriteBatch> batch) {
569 if (db_status_ != SYNC_STATUS_OK)
570 return db_status_;
571
572 leveldb::Status status = db_->Write(leveldb::WriteOptions(), batch.get());
573 if (!status.ok() && !status.IsNotFound()) {
574 HandleError(FROM_HERE, status);
575 db_status_ = LevelDBStatusToSyncStatusCode(status);
576 db_.reset();
577 return db_status_;
578 }
579 return SYNC_STATUS_OK;
580 }
581
582 } // namespace sync_file_system
583