Home | History | Annotate | Download | only in local
      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 
     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(), 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.max_open_files = 64;  // 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 
    333 SyncStatusCode 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 = 64;  // 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.
    353 void 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 
    360 SyncStatusCode 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 
    381 SyncStatusCode 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 
    401 SyncStatusCode 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