local_file_change_tracker.cc revision 2385ea399aae016c0806a4f9ef3c9cfe3d2a39df
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/include/leveldb/db.h"
16#include "webkit/browser/fileapi/file_system_context.h"
17#include "webkit/browser/fileapi/file_system_file_util.h"
18#include "webkit/browser/fileapi/file_system_operation_context.h"
19#include "webkit/common/fileapi/file_system_util.h"
20
21using fileapi::FileSystemContext;
22using fileapi::FileSystemFileUtil;
23using fileapi::FileSystemOperationContext;
24using fileapi::FileSystemURL;
25using fileapi::FileSystemURLSet;
26
27namespace sync_file_system {
28
29namespace {
30const base::FilePath::CharType kDatabaseName[] =
31    FILE_PATH_LITERAL("LocalFileChangeTracker");
32const 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.
37class 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
64LocalFileChangeTracker::ChangeInfo::ChangeInfo() : change_seq(-1) {}
65LocalFileChangeTracker::ChangeInfo::~ChangeInfo() {}
66
67// LocalFileChangeTracker ------------------------------------------------------
68
69LocalFileChangeTracker::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
79LocalFileChangeTracker::~LocalFileChangeTracker() {
80  DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
81  tracker_db_.reset();
82}
83
84void 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
92void LocalFileChangeTracker::OnEndUpdate(const FileSystemURL& url) {}
93
94void LocalFileChangeTracker::OnCreateFile(const FileSystemURL& url) {
95  RecordChange(url, FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
96                               SYNC_FILE_TYPE_FILE));
97}
98
99void 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
105void LocalFileChangeTracker::OnRemoveFile(const FileSystemURL& url) {
106  RecordChange(url, FileChange(FileChange::FILE_CHANGE_DELETE,
107                               SYNC_FILE_TYPE_FILE));
108}
109
110void LocalFileChangeTracker::OnModifyFile(const FileSystemURL& url) {
111  RecordChange(url, FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
112                               SYNC_FILE_TYPE_FILE));
113}
114
115void LocalFileChangeTracker::OnCreateDirectory(const FileSystemURL& url) {
116  RecordChange(url, FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
117                               SYNC_FILE_TYPE_DIRECTORY));
118}
119
120void LocalFileChangeTracker::OnRemoveDirectory(const FileSystemURL& url) {
121  RecordChange(url, FileChange(FileChange::FILE_CHANGE_DELETE,
122                               SYNC_FILE_TYPE_DIRECTORY));
123}
124
125void 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
140void 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
151void 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
164SyncStatusCode 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
176void LocalFileChangeTracker::UpdateNumChanges() {
177  base::AutoLock lock(num_changes_lock_);
178  num_changes_ = static_cast<int64>(change_seqs_.size());
179}
180
181void 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
188void LocalFileChangeTracker::DropAllChanges() {
189  changes_.clear();
190  change_seqs_.clear();
191}
192
193SyncStatusCode 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
202SyncStatusCode 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
211SyncStatusCode 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(), 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
278void 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
297LocalFileChangeTracker::TrackerDB::TrackerDB(const base::FilePath& base_path)
298  : base_path_(base_path),
299    db_status_(SYNC_STATUS_OK) {}
300
301SyncStatusCode 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.max_open_files = 0;  // Use minimum.
310  options.create_if_missing = true;
311  leveldb::DB* db;
312  leveldb::Status status = leveldb::DB::Open(options, path, &db);
313  if (status.ok()) {
314    db_.reset(db);
315    return SYNC_STATUS_OK;
316  }
317
318  HandleError(FROM_HERE, status);
319  if (!status.IsCorruption())
320    return LevelDBStatusToSyncStatusCode(status);
321
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:
327      return Repair(path);
328  }
329  NOTREACHED();
330  return SYNC_DATABASE_ERROR_FAILED;
331}
332
333SyncStatusCode LocalFileChangeTracker::TrackerDB::Repair(
334    const std::string& db_path) {
335  DCHECK(!db_.get());
336  LOG(WARNING) << "Attempting to repair TrackerDB.";
337
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;
346  }
347
348  LOG(WARNING) << "Failed to repair TrackerDB.";
349  return SYNC_DATABASE_ERROR_CORRUPTION;
350}
351
352// TODO(nhiroki): factor out the common methods into somewhere else.
353void 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();
358}
359
360SyncStatusCode LocalFileChangeTracker::TrackerDB::MarkDirty(
361    const std::string& url) {
362  if (db_status_ != SYNC_STATUS_OK)
363    return db_status_;
364
365  db_status_ = Init(REPAIR_ON_CORRUPTION);
366  if (db_status_ != SYNC_STATUS_OK) {
367    db_.reset();
368    return db_status_;
369  }
370
371  leveldb::Status status = db_->Put(leveldb::WriteOptions(), url, kMark);
372  if (!status.ok()) {
373    HandleError(FROM_HERE, status);
374    db_status_ = LevelDBStatusToSyncStatusCode(status);
375    db_.reset();
376    return db_status_;
377  }
378  return SYNC_STATUS_OK;
379}
380
381SyncStatusCode LocalFileChangeTracker::TrackerDB::ClearDirty(
382    const std::string& url) {
383  if (db_status_ != SYNC_STATUS_OK)
384    return db_status_;
385
386  // Should not reach here before initializing the database. The database should
387  // be cleared after read, and should be initialized during read if
388  // uninitialized.
389  DCHECK(db_.get());
390
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);
395    db_.reset();
396    return db_status_;
397  }
398  return SYNC_STATUS_OK;
399}
400
401SyncStatusCode LocalFileChangeTracker::TrackerDB::GetDirtyEntries(
402    std::queue<FileSystemURL>* dirty_files) {
403  if (db_status_ != SYNC_STATUS_OK)
404    return db_status_;
405
406  db_status_ = Init(REPAIR_ON_CORRUPTION);
407  if (db_status_ != SYNC_STATUS_OK) {
408    db_.reset();
409    return db_status_;
410  }
411
412  scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions()));
413  iter->SeekToFirst();
414  FileSystemURL url;
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;
420      db_.reset();
421      return db_status_;
422    }
423    dirty_files->push(url);
424    iter->Next();
425  }
426  return SYNC_STATUS_OK;
427}
428
429}  // namespace sync_file_system
430