1// Copyright (c) 2012 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/chromeos/drive/file_cache_metadata.h" 6 7#include "base/callback.h" 8#include "base/file_util.h" 9#include "base/files/file_enumerator.h" 10#include "base/metrics/histogram.h" 11#include "base/sequenced_task_runner.h" 12#include "base/threading/thread_restrictions.h" 13#include "chrome/browser/chromeos/drive/drive.pb.h" 14#include "chrome/browser/chromeos/drive/file_system_util.h" 15#include "third_party/leveldatabase/src/include/leveldb/db.h" 16 17namespace drive { 18namespace internal { 19 20namespace { 21 22enum DBOpenStatus { 23 DB_OPEN_SUCCESS, 24 DB_OPEN_FAILURE_CORRUPTION, 25 DB_OPEN_FAILURE_OTHER, 26 DB_OPEN_FAILURE_UNRECOVERABLE, 27 DB_OPEN_MAX_VALUE, 28}; 29 30} // namespace 31 32FileCacheMetadata::Iterator::Iterator(scoped_ptr<leveldb::Iterator> it) 33 : it_(it.Pass()) { 34 base::ThreadRestrictions::AssertIOAllowed(); 35 DCHECK(it_); 36 37 it_->SeekToFirst(); 38 AdvanceInternal(); 39} 40 41FileCacheMetadata::Iterator::~Iterator() { 42 base::ThreadRestrictions::AssertIOAllowed(); 43} 44 45bool FileCacheMetadata::Iterator::IsAtEnd() const { 46 base::ThreadRestrictions::AssertIOAllowed(); 47 return !it_->Valid(); 48} 49 50std::string FileCacheMetadata::Iterator::GetKey() const { 51 base::ThreadRestrictions::AssertIOAllowed(); 52 DCHECK(!IsAtEnd()); 53 return it_->key().ToString(); 54} 55 56const FileCacheEntry& FileCacheMetadata::Iterator::GetValue() const { 57 base::ThreadRestrictions::AssertIOAllowed(); 58 DCHECK(!IsAtEnd()); 59 return entry_; 60} 61 62void FileCacheMetadata::Iterator::Advance() { 63 base::ThreadRestrictions::AssertIOAllowed(); 64 DCHECK(!IsAtEnd()); 65 66 it_->Next(); 67 AdvanceInternal(); 68} 69 70bool FileCacheMetadata::Iterator::HasError() const { 71 base::ThreadRestrictions::AssertIOAllowed(); 72 return !it_->status().ok(); 73} 74 75void FileCacheMetadata::Iterator::AdvanceInternal() { 76 for (; it_->Valid(); it_->Next()) { 77 // Skip unparsable broken entries. 78 // TODO(hashimoto): Broken entries should be cleaned up at some point. 79 if (entry_.ParseFromArray(it_->value().data(), it_->value().size())) 80 break; 81 } 82} 83 84FileCacheMetadata::FileCacheMetadata( 85 base::SequencedTaskRunner* blocking_task_runner) 86 : blocking_task_runner_(blocking_task_runner) { 87 AssertOnSequencedWorkerPool(); 88} 89 90FileCacheMetadata::~FileCacheMetadata() { 91 AssertOnSequencedWorkerPool(); 92} 93 94FileCacheMetadata::InitializeResult FileCacheMetadata::Initialize( 95 const base::FilePath& db_path) { 96 AssertOnSequencedWorkerPool(); 97 98 bool created = !base::PathExists(db_path); 99 100 leveldb::DB* level_db = NULL; 101 leveldb::Options options; 102 options.create_if_missing = true; 103 options.max_open_files = 64; // Use minimum. 104 leveldb::Status db_status = leveldb::DB::Open(options, db_path.AsUTF8Unsafe(), 105 &level_db); 106 107 // Delete the db and scan the physical cache. This will fix a corrupt db, but 108 // perhaps not other causes of failed DB::Open. 109 DBOpenStatus uma_status = DB_OPEN_SUCCESS; 110 if (!db_status.ok()) { 111 LOG(WARNING) << "Cache db failed to open: " << db_status.ToString(); 112 uma_status = db_status.IsCorruption() ? 113 DB_OPEN_FAILURE_CORRUPTION : DB_OPEN_FAILURE_OTHER; 114 const bool deleted = base::DeleteFile(db_path, true); 115 DCHECK(deleted); 116 db_status = leveldb::DB::Open(options, db_path.value(), &level_db); 117 if (!db_status.ok()) { 118 LOG(WARNING) << "Still failed to open: " << db_status.ToString(); 119 UMA_HISTOGRAM_ENUMERATION("Drive.CacheDBOpenStatus", 120 DB_OPEN_FAILURE_UNRECOVERABLE, 121 DB_OPEN_MAX_VALUE); 122 return INITIALIZE_FAILED; 123 } 124 125 created = true; 126 } 127 UMA_HISTOGRAM_ENUMERATION("Drive.CacheDBOpenStatus", uma_status, 128 DB_OPEN_MAX_VALUE); 129 DCHECK(level_db); 130 level_db_.reset(level_db); 131 132 return created ? INITIALIZE_CREATED : INITIALIZE_OPENED; 133} 134 135void FileCacheMetadata::AddOrUpdateCacheEntry( 136 const std::string& resource_id, 137 const FileCacheEntry& cache_entry) { 138 AssertOnSequencedWorkerPool(); 139 140 DVLOG(1) << "AddOrUpdateCacheEntry, resource_id=" << resource_id; 141 std::string serialized; 142 const bool ok = cache_entry.SerializeToString(&serialized); 143 if (ok) 144 level_db_->Put(leveldb::WriteOptions(), 145 leveldb::Slice(resource_id), 146 leveldb::Slice(serialized)); 147} 148 149void FileCacheMetadata::RemoveCacheEntry(const std::string& resource_id) { 150 AssertOnSequencedWorkerPool(); 151 152 DVLOG(1) << "RemoveCacheEntry, resource_id=" << resource_id; 153 level_db_->Delete(leveldb::WriteOptions(), leveldb::Slice(resource_id)); 154} 155 156bool FileCacheMetadata::GetCacheEntry(const std::string& resource_id, 157 FileCacheEntry* entry) { 158 DCHECK(entry); 159 AssertOnSequencedWorkerPool(); 160 161 std::string serialized; 162 const leveldb::Status status = level_db_->Get( 163 leveldb::ReadOptions(), 164 leveldb::Slice(resource_id), &serialized); 165 if (!status.ok()) { 166 DVLOG(1) << "Can't find " << resource_id << " in cache db"; 167 return false; 168 } 169 170 if (!entry->ParseFromString(serialized)) { 171 LOG(ERROR) << "Failed to parse " << serialized; 172 return false; 173 } 174 return true; 175} 176 177scoped_ptr<FileCacheMetadata::Iterator> FileCacheMetadata::GetIterator() { 178 AssertOnSequencedWorkerPool(); 179 180 scoped_ptr<leveldb::Iterator> iter(level_db_->NewIterator( 181 leveldb::ReadOptions())); 182 return make_scoped_ptr(new Iterator(iter.Pass())); 183} 184 185void FileCacheMetadata::AssertOnSequencedWorkerPool() { 186 DCHECK(!blocking_task_runner_.get() || 187 blocking_task_runner_->RunsTasksOnCurrentThread()); 188} 189 190} // namespace internal 191} // namespace drive 192