| // Copyright 2014 The Chromium Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "components/leveldb_proto/leveldb_database.h" | 
 |  | 
 | #include <map> | 
 | #include <string> | 
 | #include <vector> | 
 |  | 
 | #include "base/files/file_path.h" | 
 | #include "base/files/file_util.h" | 
 | #include "base/metrics/histogram.h" | 
 | #include "base/strings/string_number_conversions.h" | 
 | #include "base/strings/string_split.h" | 
 | #include "base/threading/thread_checker.h" | 
 | #include "third_party/leveldatabase/env_chromium.h" | 
 | #include "third_party/leveldatabase/leveldb_chrome.h" | 
 | #include "third_party/leveldatabase/src/include/leveldb/cache.h" | 
 | #include "third_party/leveldatabase/src/include/leveldb/db.h" | 
 | #include "third_party/leveldatabase/src/include/leveldb/iterator.h" | 
 | #include "third_party/leveldatabase/src/include/leveldb/options.h" | 
 | #include "third_party/leveldatabase/src/include/leveldb/slice.h" | 
 | #include "third_party/leveldatabase/src/include/leveldb/status.h" | 
 | #include "third_party/leveldatabase/src/include/leveldb/write_batch.h" | 
 |  | 
 | namespace leveldb_proto { | 
 |  | 
 | namespace { | 
 |  | 
 | // Covers 8MB block cache, | 
 | const int kMaxApproxMemoryUseMB = 16; | 
 |  | 
 | }  // namespace | 
 |  | 
 | LevelDB::LevelDB(const char* client_name) | 
 |     : open_histogram_(nullptr), destroy_histogram_(nullptr) { | 
 |   // Used in lieu of UMA_HISTOGRAM_ENUMERATION because the histogram name is | 
 |   // not a constant. | 
 |   open_histogram_ = base::LinearHistogram::FactoryGet( | 
 |       std::string("LevelDB.Open.") + client_name, 1, | 
 |       leveldb_env::LEVELDB_STATUS_MAX, leveldb_env::LEVELDB_STATUS_MAX + 1, | 
 |       base::Histogram::kUmaTargetedHistogramFlag); | 
 |   destroy_histogram_ = base::LinearHistogram::FactoryGet( | 
 |       std::string("LevelDB.Destroy.") + client_name, 1, | 
 |       leveldb_env::LEVELDB_STATUS_MAX, leveldb_env::LEVELDB_STATUS_MAX + 1, | 
 |       base::Histogram::kUmaTargetedHistogramFlag); | 
 |   approx_memtable_mem_histogram_ = base::LinearHistogram::FactoryGet( | 
 |       std::string("LevelDB.ApproximateMemTableMemoryUse.") + client_name, 1, | 
 |       kMaxApproxMemoryUseMB * 1048576, kMaxApproxMemoryUseMB * 4, | 
 |       base::Histogram::kUmaTargetedHistogramFlag); | 
 | } | 
 |  | 
 | LevelDB::~LevelDB() { | 
 |   DFAKE_SCOPED_LOCK(thread_checker_); | 
 | } | 
 |  | 
 | bool LevelDB::Init(const base::FilePath& database_dir, | 
 |                    const leveldb_env::Options& options) { | 
 |   auto status = Init(database_dir, options, true /* destroy_on_corruption */); | 
 |   return status.ok(); | 
 | } | 
 |  | 
 | leveldb::Status LevelDB::Init(const base::FilePath& database_dir, | 
 |                               const leveldb_env::Options& options, | 
 |                               bool destroy_on_corruption) { | 
 |   DFAKE_SCOPED_LOCK(thread_checker_); | 
 |   database_dir_ = database_dir; | 
 |   open_options_ = options; | 
 |  | 
 |   bool in_mem = database_dir.empty(); | 
 |   if (in_mem) { | 
 |     env_ = leveldb_chrome::NewMemEnv("LevelDB"); | 
 |     open_options_.env = env_.get(); | 
 |   } | 
 |  | 
 |   const std::string path = database_dir.AsUTF8Unsafe(); | 
 |  | 
 |   leveldb::Status status = leveldb_env::OpenDB(open_options_, path, &db_); | 
 |   if (open_histogram_) | 
 |     open_histogram_->Add(leveldb_env::GetLevelDBStatusUMAValue(status)); | 
 |   if (destroy_on_corruption && status.IsCorruption()) { | 
 |     auto destroy_status = Destroy(); | 
 |     if (!destroy_status.ok()) | 
 |       return status; | 
 |     status = leveldb_env::OpenDB(open_options_, path, &db_); | 
 |     // Intentionally do not log the status of the second open. Doing so destroys | 
 |     // the meaning of corruptions/open which is an important statistic. | 
 |   } | 
 |  | 
 |   if (status.ok()) { | 
 |     if (!in_mem) { | 
 |       // Record the approximate memory usage of this DB right after init. | 
 |       // This should just be the size of the MemTable since we haven't done any | 
 |       // reads/writes and the block cache should be empty. | 
 |       uint64_t approx_mem = 0; | 
 |       std::string usage_string; | 
 |       if (GetApproximateMemoryUse(&approx_mem)) { | 
 |         approx_memtable_mem_histogram_->Add( | 
 |             approx_mem - | 
 |             leveldb_chrome::GetSharedBrowserBlockCache()->TotalCharge()); | 
 |       } | 
 |     } | 
 |   } else { | 
 |     LOG(WARNING) << "Unable to open " << database_dir.value() << ": " | 
 |                  << status.ToString(); | 
 |   } | 
 |   return status; | 
 | } | 
 |  | 
 | bool LevelDB::Save(const base::StringPairs& entries_to_save, | 
 |                    const std::vector<std::string>& keys_to_remove, | 
 |                    leveldb::Status* status) { | 
 |   DCHECK(status); | 
 |   DFAKE_SCOPED_LOCK(thread_checker_); | 
 |   if (!db_) | 
 |     return false; | 
 |  | 
 |   leveldb::WriteBatch updates; | 
 |   for (const auto& pair : entries_to_save) | 
 |     updates.Put(leveldb::Slice(pair.first), leveldb::Slice(pair.second)); | 
 |  | 
 |   for (const auto& key : keys_to_remove) | 
 |     updates.Delete(leveldb::Slice(key)); | 
 |  | 
 |   leveldb::WriteOptions options; | 
 |   options.sync = true; | 
 |  | 
 |   *status = db_->Write(options, &updates); | 
 |   if (status->ok()) | 
 |     return true; | 
 |  | 
 |   DLOG(WARNING) << "Failed writing leveldb_proto entries: " | 
 |                 << status->ToString(); | 
 |   return false; | 
 | } | 
 |  | 
 | bool LevelDB::UpdateWithRemoveFilter(const base::StringPairs& entries_to_save, | 
 |                                      const KeyFilter& delete_key_filter, | 
 |                                      leveldb::Status* status) { | 
 |   return UpdateWithRemoveFilter(entries_to_save, delete_key_filter, | 
 |                                 std::string(), status); | 
 | } | 
 |  | 
 | bool LevelDB::UpdateWithRemoveFilter(const base::StringPairs& entries_to_save, | 
 |                                      const KeyFilter& delete_key_filter, | 
 |                                      const std::string& target_prefix, | 
 |                                      leveldb::Status* status) { | 
 |   DCHECK(status); | 
 |   DFAKE_SCOPED_LOCK(thread_checker_); | 
 |   if (!db_) | 
 |     return false; | 
 |  | 
 |   leveldb::WriteBatch updates; | 
 |   for (const auto& pair : entries_to_save) | 
 |     updates.Put(leveldb::Slice(pair.first), leveldb::Slice(pair.second)); | 
 |  | 
 |   leveldb::Slice target(target_prefix); | 
 |   if (!delete_key_filter.is_null()) { | 
 |     leveldb::ReadOptions read_options; | 
 |     std::unique_ptr<leveldb::Iterator> db_iterator( | 
 |         db_->NewIterator(read_options)); | 
 |     for (db_iterator->Seek(target); | 
 |          db_iterator->Valid() && db_iterator->key().starts_with(target); | 
 |          db_iterator->Next()) { | 
 |       leveldb::Slice key_slice = db_iterator->key(); | 
 |       std::string key(key_slice.data(), key_slice.size()); | 
 |       if (delete_key_filter.Run(key)) | 
 |         updates.Delete(leveldb::Slice(key)); | 
 |     } | 
 |   } | 
 |  | 
 |   leveldb::WriteOptions write_options; | 
 |   write_options.sync = true; | 
 |   *status = db_->Write(write_options, &updates); | 
 |   if (status->ok()) | 
 |     return true; | 
 |  | 
 |   DLOG(WARNING) << "Failed deleting leveldb_proto entries: " | 
 |                 << status->ToString(); | 
 |   return false; | 
 | } | 
 |  | 
 | bool LevelDB::Load(std::vector<std::string>* entries) { | 
 |   return LoadWithFilter(KeyFilter(), entries); | 
 | } | 
 |  | 
 | bool LevelDB::LoadWithFilter(const KeyFilter& filter, | 
 |                              std::vector<std::string>* entries) { | 
 |   return LoadWithFilter(filter, entries, leveldb::ReadOptions(), std::string()); | 
 | } | 
 |  | 
 | bool LevelDB::LoadWithFilter(const KeyFilter& filter, | 
 |                              std::vector<std::string>* entries, | 
 |                              const leveldb::ReadOptions& options, | 
 |                              const std::string& target_prefix) { | 
 |   std::map<std::string, std::string> keys_entries; | 
 |   bool result = LoadKeysAndEntriesWithFilter(filter, &keys_entries, options, | 
 |                                              target_prefix); | 
 |   if (!result) | 
 |     return false; | 
 |  | 
 |   for (const auto& pair : keys_entries) | 
 |     entries->push_back(pair.second); | 
 |   return true; | 
 | } | 
 |  | 
 | bool LevelDB::LoadKeysAndEntries( | 
 |     std::map<std::string, std::string>* keys_entries) { | 
 |   return LoadKeysAndEntriesWithFilter(KeyFilter(), keys_entries); | 
 | } | 
 |  | 
 | bool LevelDB::LoadKeysAndEntriesWithFilter( | 
 |     const KeyFilter& filter, | 
 |     std::map<std::string, std::string>* keys_entries) { | 
 |   return LoadKeysAndEntriesWithFilter(filter, keys_entries, | 
 |                                       leveldb::ReadOptions(), std::string()); | 
 | } | 
 |  | 
 | bool LevelDB::LoadKeysAndEntriesWithFilter( | 
 |     const KeyFilter& filter, | 
 |     std::map<std::string, std::string>* keys_entries, | 
 |     const leveldb::ReadOptions& options, | 
 |     const std::string& target_prefix) { | 
 |   DFAKE_SCOPED_LOCK(thread_checker_); | 
 |   if (!db_) | 
 |     return false; | 
 |  | 
 |   std::unique_ptr<leveldb::Iterator> db_iterator(db_->NewIterator(options)); | 
 |   leveldb::Slice target(target_prefix); | 
 |   for (db_iterator->Seek(target); | 
 |        db_iterator->Valid() && db_iterator->key().starts_with(target); | 
 |        db_iterator->Next()) { | 
 |     leveldb::Slice key_slice = db_iterator->key(); | 
 |     std::string key_slice_str(key_slice.data(), key_slice.size()); | 
 |     if (!filter.is_null() && !filter.Run(key_slice_str)) { | 
 |       continue; | 
 |     } | 
 |  | 
 |     leveldb::Slice value_slice = db_iterator->value(); | 
 |     keys_entries->insert(std::make_pair( | 
 |         key_slice_str, std::string(value_slice.data(), value_slice.size()))); | 
 |   } | 
 |   return true; | 
 | } | 
 |  | 
 | bool LevelDB::LoadKeys(std::vector<std::string>* keys) { | 
 |   return LoadKeys(std::string(), keys); | 
 | } | 
 |  | 
 | bool LevelDB::LoadKeys(const std::string& target_prefix, | 
 |                        std::vector<std::string>* keys) { | 
 |   leveldb::ReadOptions options; | 
 |   options.fill_cache = false; | 
 |   std::map<std::string, std::string> keys_entries; | 
 |   bool result = LoadKeysAndEntriesWithFilter(KeyFilter(), &keys_entries, | 
 |                                              options, target_prefix); | 
 |   if (!result) | 
 |     return false; | 
 |  | 
 |   for (const auto& pair : keys_entries) | 
 |     keys->push_back(pair.first); | 
 |   return true; | 
 | } | 
 |  | 
 | bool LevelDB::Get(const std::string& key, | 
 |                   bool* found, | 
 |                   std::string* entry, | 
 |                   leveldb::Status* status) { | 
 |   DCHECK(status); | 
 |   DFAKE_SCOPED_LOCK(thread_checker_); | 
 |   if (!db_) | 
 |     return false; | 
 |  | 
 |   leveldb::ReadOptions options; | 
 |   *status = db_->Get(options, key, entry); | 
 |   if (status->ok()) { | 
 |     *found = true; | 
 |     return true; | 
 |   } | 
 |   if (status->IsNotFound()) { | 
 |     *found = false; | 
 |     return true; | 
 |   } | 
 |  | 
 |   DLOG(WARNING) << "Failed loading leveldb_proto entry with key \"" << key | 
 |                 << "\": " << status->ToString(); | 
 |   return false; | 
 | } | 
 |  | 
 | leveldb::Status LevelDB::Destroy() { | 
 |   db_.reset(); | 
 |   const std::string path = database_dir_.AsUTF8Unsafe(); | 
 |   leveldb::Status status = leveldb::DestroyDB(path, open_options_); | 
 |   if (!status.ok()) | 
 |     LOG(WARNING) << "Unable to destroy " << path << ": " << status.ToString(); | 
 |   if (destroy_histogram_) | 
 |     destroy_histogram_->Add(leveldb_env::GetLevelDBStatusUMAValue(status)); | 
 |   return status; | 
 | } | 
 |  | 
 | bool LevelDB::GetApproximateMemoryUse(uint64_t* approx_mem) { | 
 |   std::string usage_string; | 
 |   return (db_->GetProperty("leveldb.approximate-memory-usage", &usage_string) && | 
 |           base::StringToUint64(usage_string, approx_mem)); | 
 | } | 
 |  | 
 | }  // namespace leveldb_proto |