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