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