OLD | NEW |
| (Empty) |
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 "components/dom_distiller/core/dom_distiller_database.h" | |
6 | |
7 #include "base/bind.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" | |
20 | |
21 using base::MessageLoop; | |
22 using base::SequencedTaskRunner; | |
23 | |
24 namespace dom_distiller { | |
25 | |
26 DomDistillerDatabase::LevelDB::LevelDB() {} | |
27 | |
28 DomDistillerDatabase::LevelDB::~LevelDB() { | |
29 DFAKE_SCOPED_LOCK(thread_checker_); | |
30 } | |
31 | |
32 bool DomDistillerDatabase::LevelDB::Init(const base::FilePath& database_dir) { | |
33 DFAKE_SCOPED_LOCK(thread_checker_); | |
34 | |
35 leveldb::Options options; | |
36 options.create_if_missing = true; | |
37 options.max_open_files = 0; // Use minimum. | |
38 | |
39 std::string path = database_dir.AsUTF8Unsafe(); | |
40 | |
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); | |
46 } | |
47 | |
48 if (status.ok()) { | |
49 CHECK(db); | |
50 db_.reset(db); | |
51 return true; | |
52 } | |
53 | |
54 LOG(WARNING) << "Unable to open " << database_dir.value() << ": " | |
55 << status.ToString(); | |
56 return false; | |
57 } | |
58 | |
59 bool DomDistillerDatabase::LevelDB::Save(const EntryVector& entries_to_save, | |
60 const EntryVector& entries_to_remove) { | |
61 DFAKE_SCOPED_LOCK(thread_checker_); | |
62 | |
63 leveldb::WriteBatch updates; | |
64 for (EntryVector::const_iterator it = entries_to_save.begin(); | |
65 it != entries_to_save.end(); | |
66 ++it) { | |
67 updates.Put(leveldb::Slice(it->entry_id()), | |
68 leveldb::Slice(it->SerializeAsString())); | |
69 } | |
70 for (EntryVector::const_iterator it = entries_to_remove.begin(); | |
71 it != entries_to_remove.end(); | |
72 ++it) { | |
73 updates.Delete(leveldb::Slice(it->entry_id())); | |
74 } | |
75 | |
76 leveldb::WriteOptions options; | |
77 options.sync = true; | |
78 leveldb::Status status = db_->Write(options, &updates); | |
79 if (status.ok()) | |
80 return true; | |
81 | |
82 DLOG(WARNING) << "Failed writing dom_distiller entries: " | |
83 << status.ToString(); | |
84 return false; | |
85 } | |
86 | |
87 bool DomDistillerDatabase::LevelDB::Load(EntryVector* entries) { | |
88 DFAKE_SCOPED_LOCK(thread_checker_); | |
89 | |
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(); | |
94 | |
95 ArticleEntry entry; | |
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. | |
100 } | |
101 entries->push_back(entry); | |
102 } | |
103 return true; | |
104 } | |
105 | |
106 namespace { | |
107 | |
108 void RunInitCallback(DomDistillerDatabaseInterface::InitCallback callback, | |
109 const bool* success) { | |
110 callback.Run(*success); | |
111 } | |
112 | |
113 void RunUpdateCallback(DomDistillerDatabaseInterface::UpdateCallback callback, | |
114 const bool* success) { | |
115 callback.Run(*success); | |
116 } | |
117 | |
118 void RunLoadCallback(DomDistillerDatabaseInterface::LoadCallback callback, | |
119 const bool* success, | |
120 scoped_ptr<EntryVector> entries) { | |
121 callback.Run(*success, entries.Pass()); | |
122 } | |
123 | |
124 void InitFromTaskRunner(DomDistillerDatabase::Database* database, | |
125 const base::FilePath& database_dir, | |
126 bool* success) { | |
127 DCHECK(success); | |
128 | |
129 // TODO(cjhopman): Histogram for database size. | |
130 *success = database->Init(database_dir); | |
131 } | |
132 | |
133 void UpdateEntriesFromTaskRunner(DomDistillerDatabase::Database* database, | |
134 scoped_ptr<EntryVector> entries_to_save, | |
135 scoped_ptr<EntryVector> entries_to_remove, | |
136 bool* success) { | |
137 DCHECK(success); | |
138 *success = database->Save(*entries_to_save, *entries_to_remove); | |
139 } | |
140 | |
141 void LoadEntriesFromTaskRunner(DomDistillerDatabase::Database* database, | |
142 EntryVector* entries, | |
143 bool* success) { | |
144 DCHECK(success); | |
145 DCHECK(entries); | |
146 | |
147 entries->clear(); | |
148 *success = database->Load(entries); | |
149 } | |
150 | |
151 } // namespace | |
152 | |
153 DomDistillerDatabase::DomDistillerDatabase( | |
154 scoped_refptr<base::SequencedTaskRunner> task_runner) | |
155 : task_runner_(task_runner) { | |
156 } | |
157 | |
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); | |
162 } | |
163 | |
164 void DomDistillerDatabase::InitWithDatabase(scoped_ptr<Database> database, | |
165 const base::FilePath& database_dir, | |
166 InitCallback callback) { | |
167 DCHECK(thread_checker_.CalledOnValidThread()); | |
168 DCHECK(!db_); | |
169 DCHECK(database); | |
170 db_.reset(database.release()); | |
171 bool* success = new bool(false); | |
172 task_runner_->PostTaskAndReply( | |
173 FROM_HERE, | |
174 base::Bind(InitFromTaskRunner, | |
175 base::Unretained(db_.get()), | |
176 database_dir, | |
177 success), | |
178 base::Bind(RunInitCallback, callback, base::Owned(success))); | |
179 } | |
180 | |
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( | |
188 FROM_HERE, | |
189 base::Bind(UpdateEntriesFromTaskRunner, | |
190 base::Unretained(db_.get()), | |
191 base::Passed(&entries_to_save), | |
192 base::Passed(&entries_to_remove), | |
193 success), | |
194 base::Bind(RunUpdateCallback, callback, base::Owned(success))); | |
195 } | |
196 | |
197 void DomDistillerDatabase::LoadEntries(LoadCallback callback) { | |
198 DCHECK(thread_checker_.CalledOnValidThread()); | |
199 bool* success = new bool(false); | |
200 | |
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(); | |
204 | |
205 task_runner_->PostTaskAndReply( | |
206 FROM_HERE, | |
207 base::Bind(LoadEntriesFromTaskRunner, | |
208 base::Unretained(db_.get()), | |
209 entries_ptr, | |
210 success), | |
211 base::Bind(RunLoadCallback, | |
212 callback, | |
213 base::Owned(success), | |
214 base::Passed(&entries))); | |
215 } | |
216 | |
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."; | |
221 } | |
222 } | |
223 | |
224 } // namespace dom_distiller | |
OLD | NEW |