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.
5 #include "components/dom_distiller/core/dom_distiller_database.h"
8 #include "base/file_util.h"
9 #include "base/message_loop/message_loop.h"
10 #include "base/sequenced_task_runner.h"
11 #include "base/strings/string_util.h"
12 #include "base/threading/sequenced_worker_pool.h"
13 #include "components/dom_distiller/core/article_entry.h"
14 #include "third_party/leveldatabase/src/include/leveldb/db.h"
15 #include "third_party/leveldatabase/src/include/leveldb/iterator.h"
16 #include "third_party/leveldatabase/src/include/leveldb/options.h"
17 #include "third_party/leveldatabase/src/include/leveldb/slice.h"
18 #include "third_party/leveldatabase/src/include/leveldb/status.h"
19 #include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
21 using base::MessageLoop
;
22 using base::SequencedTaskRunner
;
24 namespace dom_distiller
{
26 DomDistillerDatabase::LevelDB::LevelDB() {}
28 DomDistillerDatabase::LevelDB::~LevelDB() {
29 DFAKE_SCOPED_LOCK(thread_checker_
);
32 bool DomDistillerDatabase::LevelDB::Init(const base::FilePath
& database_dir
) {
33 DFAKE_SCOPED_LOCK(thread_checker_
);
35 leveldb::Options options
;
36 options
.create_if_missing
= true;
37 options
.max_open_files
= 0; // Use minimum.
39 std::string path
= database_dir
.AsUTF8Unsafe();
41 leveldb::DB
* db
= NULL
;
42 leveldb::Status status
= leveldb::DB::Open(options
, path
, &db
);
43 if (status
.IsCorruption()) {
44 base::DeleteFile(database_dir
, true);
45 status
= leveldb::DB::Open(options
, path
, &db
);
54 LOG(WARNING
) << "Unable to open " << database_dir
.value() << ": "
59 bool DomDistillerDatabase::LevelDB::Save(const EntryVector
& entries_to_save
,
60 const EntryVector
& entries_to_remove
) {
61 DFAKE_SCOPED_LOCK(thread_checker_
);
63 leveldb::WriteBatch updates
;
64 for (EntryVector::const_iterator it
= entries_to_save
.begin();
65 it
!= entries_to_save
.end();
67 updates
.Put(leveldb::Slice(it
->entry_id()),
68 leveldb::Slice(it
->SerializeAsString()));
70 for (EntryVector::const_iterator it
= entries_to_remove
.begin();
71 it
!= entries_to_remove
.end();
73 updates
.Delete(leveldb::Slice(it
->entry_id()));
76 leveldb::WriteOptions options
;
78 leveldb::Status status
= db_
->Write(options
, &updates
);
82 DLOG(WARNING
) << "Failed writing dom_distiller entries: "
87 bool DomDistillerDatabase::LevelDB::Load(EntryVector
* entries
) {
88 DFAKE_SCOPED_LOCK(thread_checker_
);
90 leveldb::ReadOptions options
;
91 scoped_ptr
<leveldb::Iterator
> db_iterator(db_
->NewIterator(options
));
92 for (db_iterator
->SeekToFirst(); db_iterator
->Valid(); db_iterator
->Next()) {
93 leveldb::Slice value_slice
= db_iterator
->value();
96 if (!entry
.ParseFromArray(value_slice
.data(), value_slice
.size())) {
97 DLOG(WARNING
) << "Unable to parse dom_distiller entry "
98 << db_iterator
->key().ToString();
99 // TODO(cjhopman): Decide what to do about un-parseable entries.
101 entries
->push_back(entry
);
108 void RunInitCallback(DomDistillerDatabaseInterface::InitCallback callback
,
109 const bool* success
) {
110 callback
.Run(*success
);
113 void RunUpdateCallback(DomDistillerDatabaseInterface::UpdateCallback callback
,
114 const bool* success
) {
115 callback
.Run(*success
);
118 void RunLoadCallback(DomDistillerDatabaseInterface::LoadCallback callback
,
120 scoped_ptr
<EntryVector
> entries
) {
121 callback
.Run(*success
, entries
.Pass());
124 void InitFromTaskRunner(DomDistillerDatabase::Database
* database
,
125 const base::FilePath
& database_dir
,
129 // TODO(cjhopman): Histogram for database size.
130 *success
= database
->Init(database_dir
);
133 void UpdateEntriesFromTaskRunner(DomDistillerDatabase::Database
* database
,
134 scoped_ptr
<EntryVector
> entries_to_save
,
135 scoped_ptr
<EntryVector
> entries_to_remove
,
138 *success
= database
->Save(*entries_to_save
, *entries_to_remove
);
141 void LoadEntriesFromTaskRunner(DomDistillerDatabase::Database
* database
,
142 EntryVector
* entries
,
148 *success
= database
->Load(entries
);
153 DomDistillerDatabase::DomDistillerDatabase(
154 scoped_refptr
<base::SequencedTaskRunner
> task_runner
)
155 : task_runner_(task_runner
) {
158 void DomDistillerDatabase::Init(const base::FilePath
& database_dir
,
159 InitCallback callback
) {
160 DCHECK(thread_checker_
.CalledOnValidThread());
161 InitWithDatabase(scoped_ptr
<Database
>(new LevelDB()), database_dir
, callback
);
164 void DomDistillerDatabase::InitWithDatabase(scoped_ptr
<Database
> database
,
165 const base::FilePath
& database_dir
,
166 InitCallback callback
) {
167 DCHECK(thread_checker_
.CalledOnValidThread());
170 db_
.reset(database
.release());
171 bool* success
= new bool(false);
172 task_runner_
->PostTaskAndReply(
174 base::Bind(InitFromTaskRunner
,
175 base::Unretained(db_
.get()),
178 base::Bind(RunInitCallback
, callback
, base::Owned(success
)));
181 void DomDistillerDatabase::UpdateEntries(
182 scoped_ptr
<EntryVector
> entries_to_save
,
183 scoped_ptr
<EntryVector
> entries_to_remove
,
184 UpdateCallback callback
) {
185 DCHECK(thread_checker_
.CalledOnValidThread());
186 bool* success
= new bool(false);
187 task_runner_
->PostTaskAndReply(
189 base::Bind(UpdateEntriesFromTaskRunner
,
190 base::Unretained(db_
.get()),
191 base::Passed(&entries_to_save
),
192 base::Passed(&entries_to_remove
),
194 base::Bind(RunUpdateCallback
, callback
, base::Owned(success
)));
197 void DomDistillerDatabase::LoadEntries(LoadCallback callback
) {
198 DCHECK(thread_checker_
.CalledOnValidThread());
199 bool* success
= new bool(false);
201 scoped_ptr
<EntryVector
> entries(new EntryVector());
202 // Get this pointer before entries is base::Passed() so we can use it below.
203 EntryVector
* entries_ptr
= entries
.get();
205 task_runner_
->PostTaskAndReply(
207 base::Bind(LoadEntriesFromTaskRunner
,
208 base::Unretained(db_
.get()),
211 base::Bind(RunLoadCallback
,
213 base::Owned(success
),
214 base::Passed(&entries
)));
217 DomDistillerDatabase::~DomDistillerDatabase() {
218 DCHECK(thread_checker_
.CalledOnValidThread());
219 if (!task_runner_
->DeleteSoon(FROM_HERE
, db_
.release())) {
220 DLOG(WARNING
) << "DOM distiller database will not be deleted.";
224 } // namespace dom_distiller