local_file_change_tracker.cc revision 03b57e008b61dfcb1fbad3aea950ae0e001748b0
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
24using storage::FileSystemContext;
25using storage::FileSystemFileUtil;
26using storage::FileSystemOperationContext;
27using storage::FileSystemURL;
28using storage::FileSystemURLSet;
29
30namespace sync_file_system {
31
32namespace {
33const base::FilePath::CharType kDatabaseName[] =
34    FILE_PATH_LITERAL("LocalFileChangeTracker");
35const 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.
40class 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
70LocalFileChangeTracker::ChangeInfo::ChangeInfo() : change_seq(-1) {}
71LocalFileChangeTracker::ChangeInfo::~ChangeInfo() {}
72
73// LocalFileChangeTracker ------------------------------------------------------
74
75LocalFileChangeTracker::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
86LocalFileChangeTracker::~LocalFileChangeTracker() {
87  DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
88  tracker_db_.reset();
89}
90
91void 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
99void LocalFileChangeTracker::OnEndUpdate(const FileSystemURL& url) {}
100
101void LocalFileChangeTracker::OnCreateFile(const FileSystemURL& url) {
102  RecordChange(url, FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
103                               SYNC_FILE_TYPE_FILE));
104}
105
106void 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
112void LocalFileChangeTracker::OnRemoveFile(const FileSystemURL& url) {
113  RecordChange(url, FileChange(FileChange::FILE_CHANGE_DELETE,
114                               SYNC_FILE_TYPE_FILE));
115}
116
117void LocalFileChangeTracker::OnModifyFile(const FileSystemURL& url) {
118  RecordChange(url, FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
119                               SYNC_FILE_TYPE_FILE));
120}
121
122void LocalFileChangeTracker::OnCreateDirectory(const FileSystemURL& url) {
123  RecordChange(url, FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
124                               SYNC_FILE_TYPE_DIRECTORY));
125}
126
127void LocalFileChangeTracker::OnRemoveDirectory(const FileSystemURL& url) {
128  RecordChange(url, FileChange(FileChange::FILE_CHANGE_DELETE,
129                               SYNC_FILE_TYPE_DIRECTORY));
130}
131
132void 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
147void 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
161void 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
174void 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
181void 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
196void 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
216void 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
230void 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
249bool 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
261SyncStatusCode 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
273void 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
305void LocalFileChangeTracker::UpdateNumChanges() {
306  base::AutoLock lock(num_changes_lock_);
307  num_changes_ = static_cast<int64>(change_seqs_.size());
308}
309
310void 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
318void LocalFileChangeTracker::DropAllChanges() {
319  DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
320  changes_.clear();
321  change_seqs_.clear();
322  mirror_changes_.clear();
323  UpdateNumChanges();
324}
325
326SyncStatusCode 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
335SyncStatusCode 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
345SyncStatusCode 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
412void 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
428void 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
447void 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
465LocalFileChangeTracker::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
471SyncStatusCode 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
505SyncStatusCode 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.
525void 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
532SyncStatusCode 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
553SyncStatusCode 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
573SyncStatusCode 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
601SyncStatusCode 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