blob: a4d599c3401be6d189bb1527d99a42bfe844beb4 [file] [log] [blame]
// Copyright 2013 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/dom_distiller/core/dom_distiller_database.h"
#include "base/bind.h"
#include "base/file_util.h"
#include "base/message_loop/message_loop.h"
#include "base/sequenced_task_runner.h"
#include "base/strings/string_util.h"
#include "base/threading/sequenced_worker_pool.h"
#include "components/dom_distiller/core/article_entry.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"
using base::MessageLoop;
using base::SequencedTaskRunner;
namespace dom_distiller {
DomDistillerDatabase::LevelDB::LevelDB() {}
DomDistillerDatabase::LevelDB::~LevelDB() {}
bool DomDistillerDatabase::LevelDB::Init(const base::FilePath& database_dir) {
leveldb::Options options;
options.create_if_missing = true;
options.max_open_files = 0; // Use minimum.
std::string path = database_dir.AsUTF8Unsafe();
leveldb::DB* db = NULL;
leveldb::Status status = leveldb::DB::Open(options, path, &db);
if (status.IsCorruption()) {
LOG(WARNING) << "Deleting possibly-corrupt database";
base::DeleteFile(database_dir, true);
status = leveldb::DB::Open(options, path, &db);
}
if (status.ok()) {
CHECK(db);
db_.reset(db);
return true;
}
LOG(WARNING) << "Unable to open " << database_dir.value() << ": "
<< status.ToString();
return false;
}
bool DomDistillerDatabase::LevelDB::Save(const EntryVector& entries) {
leveldb::WriteBatch updates;
for (EntryVector::const_iterator it = entries.begin(); it != entries.end();
++it) {
updates.Put(leveldb::Slice(it->entry_id()),
leveldb::Slice(it->SerializeAsString()));
}
leveldb::WriteOptions options;
options.sync = true;
leveldb::Status status = db_->Write(options, &updates);
if (status.ok())
return true;
LOG(WARNING) << "Failed writing dom_distiller entries: " << status.ToString();
return false;
}
bool DomDistillerDatabase::LevelDB::Load(EntryVector* entries) {
leveldb::ReadOptions options;
scoped_ptr<leveldb::Iterator> db_iterator(db_->NewIterator(options));
for (db_iterator->SeekToFirst(); db_iterator->Valid(); db_iterator->Next()) {
leveldb::Slice value_slice = db_iterator->value();
ArticleEntry entry;
if (!entry.ParseFromArray(value_slice.data(), value_slice.size())) {
LOG(WARNING) << "Unable to parse dom_distiller entry "
<< db_iterator->key().ToString();
// TODO(cjhopman): Decide what to do about un-parseable entries.
}
entries->push_back(entry);
}
return true;
}
DomDistillerDatabase::DomDistillerDatabase(
scoped_refptr<base::SequencedTaskRunner> task_runner)
: task_runner_(task_runner), weak_ptr_factory_(this) {
main_loop_ = MessageLoop::current();
}
void DomDistillerDatabase::Destroy() {
DCHECK(IsRunOnMainLoop());
weak_ptr_factory_.InvalidateWeakPtrs();
task_runner_->PostNonNestableTask(
FROM_HERE,
base::Bind(&DomDistillerDatabase::DestroyFromTaskRunner,
base::Unretained(this)));
}
void DomDistillerDatabase::Init(const base::FilePath& database_dir,
InitCallback callback) {
InitWithDatabase(scoped_ptr<Database>(new LevelDB()), database_dir, callback);
}
namespace {
void RunInitCallback(DomDistillerDatabaseInterface::InitCallback callback,
const bool* success) {
callback.Run(*success);
}
void RunSaveCallback(DomDistillerDatabaseInterface::SaveCallback callback,
const bool* success) {
callback.Run(*success);
}
void RunLoadCallback(DomDistillerDatabaseInterface::LoadCallback callback,
const bool* success,
scoped_ptr<EntryVector> entries) {
callback.Run(*success, entries.Pass());
}
} // namespace
void DomDistillerDatabase::InitWithDatabase(scoped_ptr<Database> database,
const base::FilePath& database_dir,
InitCallback callback) {
DCHECK(IsRunOnMainLoop());
DCHECK(!db_);
DCHECK(database);
db_.reset(database.release());
bool* success = new bool(false);
task_runner_->PostTaskAndReply(
FROM_HERE,
base::Bind(&DomDistillerDatabase::InitFromTaskRunner,
base::Unretained(this),
database_dir,
success),
base::Bind(RunInitCallback, callback, base::Owned(success)));
}
void DomDistillerDatabase::SaveEntries(scoped_ptr<EntryVector> entries,
SaveCallback callback) {
DCHECK(IsRunOnMainLoop());
bool* success = new bool(false);
task_runner_->PostTaskAndReply(
FROM_HERE,
base::Bind(&DomDistillerDatabase::SaveEntriesFromTaskRunner,
base::Unretained(this),
base::Passed(&entries),
success),
base::Bind(RunSaveCallback, callback, base::Owned(success)));
}
void DomDistillerDatabase::LoadEntries(LoadCallback callback) {
DCHECK(IsRunOnMainLoop());
bool* success = new bool(false);
scoped_ptr<EntryVector> entries(new EntryVector());
// Get this pointer before entries is base::Passed() so we can use it below.
EntryVector* entries_ptr = entries.get();
task_runner_->PostTaskAndReply(
FROM_HERE,
base::Bind(&DomDistillerDatabase::LoadEntriesFromTaskRunner,
base::Unretained(this),
entries_ptr,
success),
base::Bind(RunLoadCallback,
callback,
base::Owned(success),
base::Passed(&entries)));
}
DomDistillerDatabase::~DomDistillerDatabase() { DCHECK(IsRunByTaskRunner()); }
bool DomDistillerDatabase::IsRunByTaskRunner() const {
return task_runner_->RunsTasksOnCurrentThread();
}
bool DomDistillerDatabase::IsRunOnMainLoop() const {
return MessageLoop::current() == main_loop_;
}
void DomDistillerDatabase::DestroyFromTaskRunner() {
DCHECK(IsRunByTaskRunner());
delete this;
}
void DomDistillerDatabase::InitFromTaskRunner(
const base::FilePath& database_dir,
bool* success) {
DCHECK(IsRunByTaskRunner());
DCHECK(success);
VLOG(1) << "Opening " << database_dir.value();
// TODO(cjhopman): Histogram for database size.
*success = db_->Init(database_dir);
}
void DomDistillerDatabase::SaveEntriesFromTaskRunner(
scoped_ptr<EntryVector> entries,
bool* success) {
DCHECK(IsRunByTaskRunner());
DCHECK(success);
VLOG(1) << "Saving " << entries->size() << " entry(ies) to database ";
*success = db_->Save(*entries);
}
void DomDistillerDatabase::LoadEntriesFromTaskRunner(EntryVector* entries,
bool* success) {
DCHECK(IsRunByTaskRunner());
DCHECK(success);
DCHECK(entries);
entries->clear();
*success = db_->Load(entries);
}
} // namespace dom_distiller