OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2011 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/extensions/extension_settings_leveldb_storage.h" | |
6 | |
7 #include "base/bind.h" | |
8 #include "base/json/json_reader.h" | |
9 #include "base/json/json_writer.h" | |
10 #include "base/logging.h" | |
11 #include "content/browser/browser_thread.h" | |
12 #include "third_party/leveldb/include/leveldb/iterator.h" | |
13 #include "third_party/leveldb/include/leveldb/write_batch.h" | |
14 | |
15 namespace { | |
16 | |
17 // General closure type for computing a value on the FILE thread and calling | |
18 // back on the UI thread. The *OnFileThread methods should run on the file | |
19 // thread, all others should run on the UI thread. | |
20 // | |
21 // Subclasses should implement RunOnFileThreadImpl(), manipulating settings_ | |
22 // or error_, then call either SucceedOnFileThread() or FailOnFileThread(). | |
23 class ResultClosure { | |
24 public: | |
25 ResultClosure(leveldb::DB* db, ExtensionSettingsStorage::Callback* callback) | |
26 : db_(db), settings_(new DictionaryValue()), callback_(callback) {} | |
27 | |
28 ~ResultClosure() {} | |
29 | |
30 void Run() { | |
31 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
32 BrowserThread::PostTask( | |
33 BrowserThread::FILE, | |
34 FROM_HERE, | |
35 base::Bind(&ResultClosure::RunOnFileThread, base::Unretained(this))); | |
36 } | |
37 | |
38 protected: | |
39 virtual void RunOnFileThreadImpl() = 0; | |
40 | |
41 void SucceedOnFileThread() { | |
42 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | |
43 BrowserThread::PostTask( | |
44 BrowserThread::UI, | |
45 FROM_HERE, | |
46 base::Bind(&ResultClosure::Succeed, base::Unretained(this))); | |
47 } | |
48 | |
49 void FailOnFileThread(std::string error) { | |
50 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | |
51 error_ = error; | |
52 BrowserThread::PostTask( | |
53 BrowserThread::UI, | |
54 FROM_HERE, | |
55 base::Bind(&ResultClosure::Fail, base::Unretained(this))); | |
56 } | |
57 | |
58 // Helper for the Get() methods; reads a single key from the database using | |
59 // an existing leveldb options object. | |
60 // Returns whether the read was successful, in which case the given settings | |
61 // will have the value set. Otherwise, error will be set. | |
62 bool ReadFromDb(leveldb::ReadOptions options, const std::string& key, | |
63 DictionaryValue* settings, std::string* error) { | |
64 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | |
65 std::string value_as_json; | |
66 leveldb::Status s = db_->Get(options, key, &value_as_json); | |
67 if (s.ok()) { | |
68 base::JSONReader json_reader; | |
69 Value* value = json_reader.JsonToValue(value_as_json, false, false); | |
70 if (value != NULL) { | |
71 settings->Set(key, value); | |
72 return true; | |
73 } else { | |
74 // TODO(kalman): clear the offending non-JSON value from the database. | |
75 LOG(ERROR) << "Invalid JSON: " << value_as_json; | |
76 *error = "Invalid JSON"; | |
77 return false; | |
78 } | |
79 } else if (s.IsNotFound()) { | |
80 // Despite there being no value, it was still a success. | |
81 return true; | |
82 } else { | |
83 LOG(ERROR) << "Error reading from database: " << s.ToString(); | |
84 *error = s.ToString(); | |
85 return false; | |
86 } | |
87 } | |
88 | |
89 leveldb::DB* db_; | |
90 DictionaryValue* settings_; | |
91 std::string error_; | |
92 | |
93 private: | |
94 void RunOnFileThread() { | |
95 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | |
96 RunOnFileThreadImpl(); | |
97 } | |
98 | |
99 void Succeed() { | |
100 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
101 callback_->OnSuccess(settings_); | |
102 delete this; | |
103 } | |
104 | |
105 void Fail() { | |
106 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
107 delete settings_; | |
108 callback_->OnFailure(error_); | |
109 delete this; | |
110 } | |
111 | |
112 scoped_ptr<ExtensionSettingsStorage::Callback> callback_; | |
113 }; | |
114 | |
115 // Result closure for Get(key). | |
116 class Get1ResultClosure : public ResultClosure { | |
117 public: | |
118 Get1ResultClosure(leveldb::DB* db, | |
119 ExtensionSettingsStorage::Callback* callback, const std::string& key) | |
120 : ResultClosure(db, callback), key_(key) {} | |
121 | |
122 protected: | |
123 void RunOnFileThreadImpl() { | |
124 if (ReadFromDb(leveldb::ReadOptions(), key_, settings_, &error_)) { | |
125 SucceedOnFileThread(); | |
126 } else { | |
127 FailOnFileThread("Failed"); | |
128 } | |
129 } | |
130 | |
131 private: | |
132 std::string key_; | |
133 }; | |
134 | |
135 // Result closure for Get(keys). | |
136 class Get2ResultClosure : public ResultClosure { | |
137 public: | |
138 // Ownership of keys is taken. | |
139 Get2ResultClosure(leveldb::DB* db, | |
140 ExtensionSettingsStorage::Callback* callback, ListValue* keys) | |
141 : ResultClosure(db, callback), keys_(keys) {} | |
142 | |
143 protected: | |
144 void RunOnFileThreadImpl() { | |
145 leveldb::ReadOptions options; | |
146 options.snapshot = db_->GetSnapshot(); | |
dgrogan
2011/06/18 01:54:40
Why do you use snapshots when you're reading? Won
not at google - send to devlin
2011/06/20 05:33:11
Hm. Yes. It just seems... instinctively wrong to
dgrogan
2011/06/21 02:36:05
Ok, they are just defensive. Using them is fine,
not at google - send to devlin
2011/06/22 09:40:38
Cool, I added a comment.
| |
147 bool success = true; | |
148 std::string key; | |
149 | |
150 for (ListValue::const_iterator it = keys_->begin(); | |
151 success && it != keys_->end(); ++it) { | |
152 if ((*it)->GetAsString(&key)) { | |
153 if (!ReadFromDb(options, key, settings_, &error_)) { | |
154 success = false; | |
155 } | |
156 } | |
157 } | |
158 db_->ReleaseSnapshot(options.snapshot); | |
159 | |
160 if (success) { | |
161 SucceedOnFileThread(); | |
162 } else { | |
163 FailOnFileThread("Failed"); | |
164 } | |
165 } | |
166 | |
167 private: | |
168 scoped_ptr<ListValue> keys_; | |
169 }; | |
170 | |
171 // Result closure for Get() . | |
172 class Get3ResultClosure : public ResultClosure { | |
173 public: | |
174 Get3ResultClosure(leveldb::DB* db, | |
175 ExtensionSettingsStorage::Callback* callback) | |
176 : ResultClosure(db, callback) { | |
177 } | |
178 | |
179 protected: | |
180 void RunOnFileThreadImpl() { | |
181 base::JSONReader json_reader; | |
182 leveldb::ReadOptions options = leveldb::ReadOptions(); | |
183 options.snapshot = db_->GetSnapshot(); | |
184 scoped_ptr<leveldb::Iterator> it(db_->NewIterator(options)); | |
185 | |
186 for (it->SeekToFirst(); it->Valid(); it->Next()) { | |
187 Value* value = | |
188 json_reader.JsonToValue(it->value().ToString(), false, false); | |
189 if (value != NULL) { | |
190 settings_->Set(it->key().ToString(), value); | |
191 } else { | |
192 // TODO(kalman): clear the offending non-JSON value from the database. | |
193 LOG(ERROR) << "Invalid JSON: " << it->value().ToString(); | |
194 } | |
195 } | |
196 db_->ReleaseSnapshot(options.snapshot); | |
197 | |
198 if (!it->status().ok()) { | |
199 LOG(ERROR) << "DB iteration failed: " << it->status().ToString(); | |
200 FailOnFileThread("Failed"); | |
dgrogan
2011/06/18 01:54:40
Why don't you pass it->status().ToString() back th
not at google - send to devlin
2011/06/20 05:33:11
This is the message sent back to the extension. I
dgrogan
2011/06/21 02:36:05
Ok, I see where you're coming from.
not at google - send to devlin
2011/06/22 09:40:38
Btw I had a look through the leveldb code but wasn
Mihai Parparita -not on Chrome
2011/06/23 03:16:46
Sorry, didn't see this question earlier. Unless th
not at google - send to devlin
2011/06/23 13:44:26
Done.
| |
201 } else { | |
202 SucceedOnFileThread(); | |
203 } | |
204 } | |
205 }; | |
206 | |
207 // Result closure for Set(key, value). | |
208 class Set1ResultClosure : public ResultClosure { | |
209 public: | |
210 // Ownership of the value is taken. | |
211 Set1ResultClosure(leveldb::DB* db, | |
212 ExtensionSettingsStorage::Callback* callback, const std::string& key, | |
213 Value* value) : ResultClosure(db, callback), key_(key), value_(value) {} | |
214 | |
215 protected: | |
216 void RunOnFileThreadImpl() { | |
217 std::string value_as_json; | |
218 base::JSONWriter::Write(value_, false, &value_as_json); | |
219 leveldb::Status status = | |
220 db_->Put(leveldb::WriteOptions(), key_, value_as_json); | |
221 if (status.ok()) { | |
222 settings_->Set(key_, value_); | |
223 SucceedOnFileThread(); | |
224 } else { | |
225 LOG(WARNING) << "DB write failed: " << status.ToString(); | |
226 delete value_; | |
227 FailOnFileThread("Failed"); | |
228 } | |
229 } | |
230 | |
231 private: | |
232 std::string key_; | |
233 Value* value_; | |
234 }; | |
235 | |
236 // Result callback for Set(values). | |
237 class Set2ResultClosure : public ResultClosure { | |
238 public: | |
239 // Ownership of values is taken. | |
240 Set2ResultClosure(leveldb::DB* db, | |
241 ExtensionSettingsStorage::Callback* callback, DictionaryValue* values) | |
242 : ResultClosure(db, callback), values_(values) { | |
243 } | |
244 | |
245 protected: | |
246 void RunOnFileThreadImpl() { | |
247 // Gather keys since a dictionary can't be modified during iteration. | |
248 std::vector<std::string> keys; | |
249 for (DictionaryValue::key_iterator it = values_->begin_keys(); | |
250 it != values_->end_keys(); ++it) { | |
251 keys.push_back(*it); | |
252 } | |
253 // Write values while transferring ownership from values_ to settings_. | |
254 std::string value_as_json; | |
255 leveldb::WriteBatch batch; | |
256 for (unsigned i = 0; i < keys.size(); ++i) { | |
257 Value* value = NULL; | |
258 values_->RemoveWithoutPathExpansion(keys[i], &value); | |
259 base::JSONWriter::Write(value, false, &value_as_json); | |
260 batch.Put(keys[i], value_as_json); | |
261 settings_->SetWithoutPathExpansion(keys[i], value); | |
262 } | |
263 DCHECK(values_->empty()); | |
264 | |
265 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch); | |
266 if (status.ok()) { | |
267 SucceedOnFileThread(); | |
268 } else { | |
269 LOG(WARNING) << "DB batch write failed: " << status.ToString(); | |
270 FailOnFileThread("Failed"); | |
271 } | |
272 } | |
273 | |
274 private: | |
275 scoped_ptr<DictionaryValue> values_; // will be empty on destruction | |
276 }; | |
277 | |
278 // Result closure for Remove(key). | |
279 class Remove1ResultClosure : public ResultClosure { | |
280 public: | |
281 Remove1ResultClosure(leveldb::DB* db, | |
282 ExtensionSettingsStorage::Callback* callback, const std::string& key) | |
283 : ResultClosure(db, callback), key_(key) { | |
284 } | |
285 | |
286 protected: | |
287 void RunOnFileThreadImpl() { | |
288 leveldb::Status status = db_->Delete(leveldb::WriteOptions(), key_); | |
289 if (status.ok() || status.IsNotFound()) { | |
290 SucceedOnFileThread(); | |
291 } else { | |
292 LOG(WARNING) << "DB delete failed: " << status.ToString(); | |
293 FailOnFileThread("Failed"); | |
294 } | |
295 } | |
296 | |
297 private: | |
298 std::string key_; | |
299 }; | |
300 | |
301 // Result closure for Remove(keys). | |
302 class Remove2ResultClosure : public ResultClosure { | |
303 public: | |
304 // Ownership of keys is taken. | |
305 Remove2ResultClosure(leveldb::DB* db, | |
306 ExtensionSettingsStorage::Callback* callback, ListValue* keys) | |
307 : ResultClosure(db, callback), keys_(keys) { | |
308 } | |
309 | |
310 protected: | |
311 void RunOnFileThreadImpl() { | |
312 std::string key; | |
313 leveldb::WriteBatch batch; | |
314 for (ListValue::const_iterator it = keys_->begin(); | |
315 it != keys_->end(); ++it) { | |
316 if ((*it)->GetAsString(&key)) { | |
317 batch.Delete(key); | |
318 } | |
319 } | |
320 | |
321 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch); | |
322 if (status.ok() || status.IsNotFound()) { | |
323 SucceedOnFileThread(); | |
324 } else { | |
325 LOG(WARNING) << "DB batch delete failed: " << status.ToString(); | |
326 FailOnFileThread("Failed"); | |
327 } | |
328 } | |
329 | |
330 private: | |
331 scoped_ptr<ListValue> keys_; | |
332 }; | |
333 | |
334 // Result closure for Clear(). | |
335 class ClearResultClosure : public ResultClosure { | |
336 public: | |
337 ClearResultClosure(leveldb::DB* db, | |
338 ExtensionSettingsStorage::Callback* callback) | |
339 : ResultClosure(db, callback) { | |
340 } | |
341 | |
342 protected: | |
343 void RunOnFileThreadImpl() { | |
344 leveldb::ReadOptions options; | |
345 options.snapshot = db_->GetSnapshot(); | |
346 scoped_ptr<leveldb::Iterator> it(db_->NewIterator(options)); | |
347 leveldb::WriteBatch batch; | |
348 | |
349 for (it->SeekToFirst(); it->Valid(); it->Next()) { | |
350 batch.Delete(it->key()); | |
dgrogan
2011/06/18 01:54:40
Does batching up a bunch of deletes on an iterator
not at google - send to devlin
2011/06/20 05:33:11
Maybe I'm misunderstanding something. The snapsho
dgrogan
2011/06/21 02:36:05
Oh, I didn't realize it gets a consistent set of k
not at google - send to devlin
2011/06/22 09:40:38
I think it gets a consistent view of both keys and
| |
351 } | |
352 db_->ReleaseSnapshot(options.snapshot); | |
353 | |
354 if (it->status().ok()) { | |
355 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch); | |
356 if (status.ok() || status.IsNotFound()) { | |
357 SucceedOnFileThread(); | |
358 } else { | |
359 LOG(WARNING) << "Clear failed: " << status.ToString(); | |
360 FailOnFileThread("Failed"); | |
361 } | |
362 } else { | |
363 LOG(WARNING) << "Clear iteration failed: " << it->status().ToString(); | |
364 FailOnFileThread("Failed"); | |
365 } | |
366 } | |
367 }; | |
368 | |
369 } // namespace | |
370 | |
371 ExtensionSettingsLeveldbStorage::ExtensionSettingsLeveldbStorage( | |
372 leveldb::DB* db) : db_(db) { | |
373 } | |
374 | |
375 void ExtensionSettingsLeveldbStorage::Get(const std::string& key, | |
376 ExtensionSettingsStorage::Callback* callback) { | |
377 (new Get1ResultClosure(db_.get(), callback, key))->Run(); | |
378 } | |
379 | |
380 void ExtensionSettingsLeveldbStorage::Get(const ListValue& keys, | |
381 ExtensionSettingsStorage::Callback* callback) { | |
382 (new Get2ResultClosure(db_.get(), callback, keys.DeepCopy()))->Run(); | |
383 } | |
384 | |
385 void ExtensionSettingsLeveldbStorage::Get( | |
386 ExtensionSettingsStorage::Callback* callback) { | |
387 (new Get3ResultClosure(db_.get(), callback))->Run(); | |
388 } | |
389 | |
390 void ExtensionSettingsLeveldbStorage::Set(const std::string& key, | |
391 const Value& value, ExtensionSettingsStorage::Callback* callback) { | |
392 (new Set1ResultClosure(db_.get(), callback, key, value.DeepCopy()))->Run(); | |
393 } | |
394 | |
395 void ExtensionSettingsLeveldbStorage::Set(const DictionaryValue& values, | |
396 ExtensionSettingsStorage::Callback* callback) { | |
397 (new Set2ResultClosure(db_.get(), callback, values.DeepCopy()))->Run(); | |
398 } | |
399 | |
400 void ExtensionSettingsLeveldbStorage::Remove(const std::string& key, | |
401 ExtensionSettingsStorage::Callback *callback) { | |
402 (new Remove1ResultClosure(db_.get(), callback, key))->Run(); | |
403 } | |
404 | |
405 void ExtensionSettingsLeveldbStorage::Remove(const ListValue& keys, | |
406 ExtensionSettingsStorage::Callback *callback) { | |
407 (new Remove2ResultClosure(db_.get(), callback, keys.DeepCopy()))->Run(); | |
408 } | |
409 | |
410 void ExtensionSettingsLeveldbStorage::Clear( | |
411 ExtensionSettingsStorage::Callback* callback) { | |
412 (new ClearResultClosure(db_.get(), callback))->Run(); | |
413 } | |
OLD | NEW |