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/compiler_specific.h" |
| 9 #include "base/json/json_reader.h" |
| 10 #include "base/json/json_writer.h" |
| 11 #include "base/logging.h" |
| 12 #include "base/sys_string_conversions.h" |
| 13 #include "content/browser/browser_thread.h" |
| 14 #include "third_party/leveldb/include/leveldb/iterator.h" |
| 15 #include "third_party/leveldb/include/leveldb/write_batch.h" |
| 16 |
| 17 namespace { |
| 18 |
| 19 // Generic error message sent to extensions on failure. |
| 20 const char* kGenericOnFailureMessage = "Extension settings failed"; |
| 21 |
| 22 // General closure type for computing a value on the FILE thread and calling |
| 23 // back on the UI thread. The *OnFileThread methods should run on the file |
| 24 // thread, all others should run on the UI thread. |
| 25 // |
| 26 // Subclasses should implement RunOnFileThread(), manipulating settings_ |
| 27 // or error_, then call either SucceedOnFileThread() or FailOnFileThread(). |
| 28 class ResultClosure { |
| 29 public: |
| 30 // Ownership of callback is taken. |
| 31 ResultClosure( |
| 32 leveldb::DB* db, |
| 33 ExtensionSettingsStorage::Callback* callback) |
| 34 : db_(db), |
| 35 settings_(new DictionaryValue()), |
| 36 callback_(callback) {} |
| 37 |
| 38 ~ResultClosure() {} |
| 39 |
| 40 void Run() { |
| 41 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 42 BrowserThread::PostTask( |
| 43 BrowserThread::FILE, |
| 44 FROM_HERE, |
| 45 base::Bind(&ResultClosure::RunOnFileThread, base::Unretained(this))); |
| 46 } |
| 47 |
| 48 protected: |
| 49 virtual void RunOnFileThread() = 0; |
| 50 |
| 51 void SucceedOnFileThread() { |
| 52 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 53 BrowserThread::PostTask( |
| 54 BrowserThread::UI, |
| 55 FROM_HERE, |
| 56 base::Bind(&ResultClosure::Succeed, base::Unretained(this))); |
| 57 } |
| 58 |
| 59 void FailOnFileThread(std::string error) { |
| 60 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 61 error_ = error; |
| 62 BrowserThread::PostTask( |
| 63 BrowserThread::UI, |
| 64 FROM_HERE, |
| 65 base::Bind(&ResultClosure::Fail, base::Unretained(this))); |
| 66 } |
| 67 |
| 68 // Helper for the Get() methods; reads a single key from the database using |
| 69 // an existing leveldb options object. |
| 70 // Returns whether the read was successful, in which case the given settings |
| 71 // will have the value set. Otherwise, error will be set. |
| 72 bool ReadFromDb( |
| 73 leveldb::ReadOptions options, |
| 74 const std::string& key, |
| 75 DictionaryValue* settings, |
| 76 std::string* error) { |
| 77 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 78 std::string value_as_json; |
| 79 leveldb::Status s = db_->Get(options, key, &value_as_json); |
| 80 if (s.ok()) { |
| 81 base::JSONReader json_reader; |
| 82 Value* value = json_reader.JsonToValue(value_as_json, false, false); |
| 83 if (value != NULL) { |
| 84 settings->Set(key, value); |
| 85 return true; |
| 86 } else { |
| 87 // TODO(kalman): clear the offending non-JSON value from the database. |
| 88 LOG(ERROR) << "Invalid JSON: " << value_as_json; |
| 89 *error = "Invalid JSON"; |
| 90 return false; |
| 91 } |
| 92 } else if (s.IsNotFound()) { |
| 93 // Despite there being no value, it was still a success. |
| 94 return true; |
| 95 } else { |
| 96 LOG(ERROR) << "Error reading from database: " << s.ToString(); |
| 97 *error = s.ToString(); |
| 98 return false; |
| 99 } |
| 100 } |
| 101 |
| 102 // The leveldb database this closure interacts with. Owned by an |
| 103 // ExtensionSettingsLeveldbStorage instance, but only accessed from the FILE |
| 104 // thread part of the closure (RunOnFileThread), and only deleted on the FILE |
| 105 // thread from DeleteSoon() method, guaranteed to run afterwards (see |
| 106 // marked_for_deletion_). |
| 107 leveldb::DB* db_; |
| 108 |
| 109 scoped_ptr<DictionaryValue> settings_; |
| 110 std::string error_; |
| 111 |
| 112 private: |
| 113 void Succeed() { |
| 114 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 115 callback_->OnSuccess(settings_.release()); |
| 116 delete this; |
| 117 } |
| 118 |
| 119 void Fail() { |
| 120 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 121 callback_->OnFailure(error_); |
| 122 delete this; |
| 123 } |
| 124 |
| 125 scoped_ptr<ExtensionSettingsStorage::Callback> callback_; |
| 126 |
| 127 DISALLOW_COPY_AND_ASSIGN(ResultClosure); |
| 128 }; |
| 129 |
| 130 // Result closure for Get(key). |
| 131 class Get1ResultClosure : public ResultClosure { |
| 132 public: |
| 133 Get1ResultClosure( |
| 134 leveldb::DB* db, |
| 135 ExtensionSettingsStorage::Callback* callback, |
| 136 const std::string& key) |
| 137 : ResultClosure(db, callback), key_(key) {} |
| 138 |
| 139 protected: |
| 140 virtual void RunOnFileThread() OVERRIDE { |
| 141 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 142 if (ReadFromDb(leveldb::ReadOptions(), key_, settings_.get(), &error_)) { |
| 143 SucceedOnFileThread(); |
| 144 } else { |
| 145 FailOnFileThread(kGenericOnFailureMessage); |
| 146 } |
| 147 } |
| 148 |
| 149 private: |
| 150 std::string key_; |
| 151 }; |
| 152 |
| 153 // Result closure for Get(keys). |
| 154 class Get2ResultClosure : public ResultClosure { |
| 155 public: |
| 156 // Ownership of keys is taken. |
| 157 Get2ResultClosure( |
| 158 leveldb::DB* db, |
| 159 ExtensionSettingsStorage::Callback* callback, |
| 160 ListValue* keys) |
| 161 : ResultClosure(db, callback), keys_(keys) {} |
| 162 |
| 163 protected: |
| 164 virtual void RunOnFileThread() OVERRIDE { |
| 165 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 166 leveldb::ReadOptions options; |
| 167 // All interaction with the db is done on the same thread, so snapshotting |
| 168 // isn't strictly necessary. This is just defensive. |
| 169 options.snapshot = db_->GetSnapshot(); |
| 170 bool success = true; |
| 171 std::string key; |
| 172 |
| 173 for (ListValue::const_iterator it = keys_->begin(); |
| 174 success && it != keys_->end(); ++it) { |
| 175 if ((*it)->GetAsString(&key)) { |
| 176 if (!ReadFromDb(options, key, settings_.get(), &error_)) { |
| 177 success = false; |
| 178 } |
| 179 } |
| 180 } |
| 181 db_->ReleaseSnapshot(options.snapshot); |
| 182 |
| 183 if (success) { |
| 184 SucceedOnFileThread(); |
| 185 } else { |
| 186 FailOnFileThread(kGenericOnFailureMessage); |
| 187 } |
| 188 } |
| 189 |
| 190 private: |
| 191 scoped_ptr<ListValue> keys_; |
| 192 }; |
| 193 |
| 194 // Result closure for Get() . |
| 195 class Get3ResultClosure : public ResultClosure { |
| 196 public: |
| 197 Get3ResultClosure( |
| 198 leveldb::DB* db, |
| 199 ExtensionSettingsStorage::Callback* callback) |
| 200 : ResultClosure(db, callback) { |
| 201 } |
| 202 |
| 203 protected: |
| 204 virtual void RunOnFileThread() OVERRIDE { |
| 205 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 206 base::JSONReader json_reader; |
| 207 leveldb::ReadOptions options = leveldb::ReadOptions(); |
| 208 // All interaction with the db is done on the same thread, so snapshotting |
| 209 // isn't strictly necessary. This is just defensive. |
| 210 options.snapshot = db_->GetSnapshot(); |
| 211 scoped_ptr<leveldb::Iterator> it(db_->NewIterator(options)); |
| 212 |
| 213 for (it->SeekToFirst(); it->Valid(); it->Next()) { |
| 214 Value* value = |
| 215 json_reader.JsonToValue(it->value().ToString(), false, false); |
| 216 if (value != NULL) { |
| 217 settings_->Set(it->key().ToString(), value); |
| 218 } else { |
| 219 // TODO(kalman): clear the offending non-JSON value from the database. |
| 220 LOG(ERROR) << "Invalid JSON: " << it->value().ToString(); |
| 221 } |
| 222 } |
| 223 db_->ReleaseSnapshot(options.snapshot); |
| 224 |
| 225 if (!it->status().ok()) { |
| 226 LOG(ERROR) << "DB iteration failed: " << it->status().ToString(); |
| 227 FailOnFileThread(kGenericOnFailureMessage); |
| 228 } else { |
| 229 SucceedOnFileThread(); |
| 230 } |
| 231 } |
| 232 }; |
| 233 |
| 234 // Result closure for Set(key, value). |
| 235 class Set1ResultClosure : public ResultClosure { |
| 236 public: |
| 237 // Ownership of the value is taken. |
| 238 Set1ResultClosure( |
| 239 leveldb::DB* db, |
| 240 ExtensionSettingsStorage::Callback* callback, |
| 241 const std::string& key, |
| 242 Value* value) |
| 243 : ResultClosure(db, callback), key_(key), value_(value) {} |
| 244 |
| 245 protected: |
| 246 virtual void RunOnFileThread() OVERRIDE { |
| 247 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 248 std::string value_as_json; |
| 249 base::JSONWriter::Write(value_.get(), false, &value_as_json); |
| 250 leveldb::Status status = |
| 251 db_->Put(leveldb::WriteOptions(), key_, value_as_json); |
| 252 if (status.ok()) { |
| 253 settings_->Set(key_, value_.release()); |
| 254 SucceedOnFileThread(); |
| 255 } else { |
| 256 LOG(WARNING) << "DB write failed: " << status.ToString(); |
| 257 FailOnFileThread(kGenericOnFailureMessage); |
| 258 } |
| 259 } |
| 260 |
| 261 private: |
| 262 std::string key_; |
| 263 scoped_ptr<Value> value_; |
| 264 }; |
| 265 |
| 266 // Result callback for Set(values). |
| 267 class Set2ResultClosure : public ResultClosure { |
| 268 public: |
| 269 // Ownership of values is taken. |
| 270 Set2ResultClosure( |
| 271 leveldb::DB* db, |
| 272 ExtensionSettingsStorage::Callback* callback, |
| 273 DictionaryValue* values) |
| 274 : ResultClosure(db, callback), values_(values) { |
| 275 } |
| 276 |
| 277 protected: |
| 278 virtual void RunOnFileThread() OVERRIDE { |
| 279 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 280 // Gather keys since a dictionary can't be modified during iteration. |
| 281 std::vector<std::string> keys; |
| 282 for (DictionaryValue::key_iterator it = values_->begin_keys(); |
| 283 it != values_->end_keys(); ++it) { |
| 284 keys.push_back(*it); |
| 285 } |
| 286 // Write values while transferring ownership from values_ to settings_. |
| 287 std::string value_as_json; |
| 288 leveldb::WriteBatch batch; |
| 289 for (unsigned i = 0; i < keys.size(); ++i) { |
| 290 Value* value = NULL; |
| 291 values_->RemoveWithoutPathExpansion(keys[i], &value); |
| 292 base::JSONWriter::Write(value, false, &value_as_json); |
| 293 batch.Put(keys[i], value_as_json); |
| 294 settings_->SetWithoutPathExpansion(keys[i], value); |
| 295 } |
| 296 DCHECK(values_->empty()); |
| 297 |
| 298 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch); |
| 299 if (status.ok()) { |
| 300 SucceedOnFileThread(); |
| 301 } else { |
| 302 LOG(WARNING) << "DB batch write failed: " << status.ToString(); |
| 303 FailOnFileThread(kGenericOnFailureMessage); |
| 304 } |
| 305 } |
| 306 |
| 307 private: |
| 308 scoped_ptr<DictionaryValue> values_; // will be empty on destruction |
| 309 }; |
| 310 |
| 311 // Result closure for Remove(key). |
| 312 class Remove1ResultClosure : public ResultClosure { |
| 313 public: |
| 314 Remove1ResultClosure( |
| 315 leveldb::DB* db, |
| 316 ExtensionSettingsStorage::Callback* callback, |
| 317 const std::string& key) |
| 318 : ResultClosure(db, callback), key_(key) { |
| 319 } |
| 320 |
| 321 protected: |
| 322 virtual void RunOnFileThread() OVERRIDE { |
| 323 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 324 leveldb::Status status = db_->Delete(leveldb::WriteOptions(), key_); |
| 325 if (status.ok() || status.IsNotFound()) { |
| 326 SucceedOnFileThread(); |
| 327 } else { |
| 328 LOG(WARNING) << "DB delete failed: " << status.ToString(); |
| 329 FailOnFileThread(kGenericOnFailureMessage); |
| 330 } |
| 331 } |
| 332 |
| 333 private: |
| 334 std::string key_; |
| 335 }; |
| 336 |
| 337 // Result closure for Remove(keys). |
| 338 class Remove2ResultClosure : public ResultClosure { |
| 339 public: |
| 340 // Ownership of keys is taken. |
| 341 Remove2ResultClosure( |
| 342 leveldb::DB* db, |
| 343 ExtensionSettingsStorage::Callback* callback, |
| 344 ListValue* keys) |
| 345 : ResultClosure(db, callback), keys_(keys) { |
| 346 } |
| 347 |
| 348 protected: |
| 349 virtual void RunOnFileThread() OVERRIDE { |
| 350 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 351 std::string key; |
| 352 leveldb::WriteBatch batch; |
| 353 for (ListValue::const_iterator it = keys_->begin(); |
| 354 it != keys_->end(); ++it) { |
| 355 if ((*it)->GetAsString(&key)) { |
| 356 batch.Delete(key); |
| 357 } |
| 358 } |
| 359 |
| 360 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch); |
| 361 if (status.ok() || status.IsNotFound()) { |
| 362 SucceedOnFileThread(); |
| 363 } else { |
| 364 LOG(WARNING) << "DB batch delete failed: " << status.ToString(); |
| 365 FailOnFileThread(kGenericOnFailureMessage); |
| 366 } |
| 367 } |
| 368 |
| 369 private: |
| 370 scoped_ptr<ListValue> keys_; |
| 371 }; |
| 372 |
| 373 // Result closure for Clear(). |
| 374 class ClearResultClosure : public ResultClosure { |
| 375 public: |
| 376 ClearResultClosure(leveldb::DB* db, |
| 377 ExtensionSettingsStorage::Callback* callback) |
| 378 : ResultClosure(db, callback) { |
| 379 } |
| 380 |
| 381 protected: |
| 382 virtual void RunOnFileThread() OVERRIDE { |
| 383 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 384 leveldb::ReadOptions options; |
| 385 // All interaction with the db is done on the same thread, so snapshotting |
| 386 // isn't strictly necessary. This is just defensive. |
| 387 options.snapshot = db_->GetSnapshot(); |
| 388 scoped_ptr<leveldb::Iterator> it(db_->NewIterator(options)); |
| 389 leveldb::WriteBatch batch; |
| 390 |
| 391 for (it->SeekToFirst(); it->Valid(); it->Next()) { |
| 392 batch.Delete(it->key()); |
| 393 } |
| 394 db_->ReleaseSnapshot(options.snapshot); |
| 395 |
| 396 if (it->status().ok()) { |
| 397 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch); |
| 398 if (status.ok() || status.IsNotFound()) { |
| 399 SucceedOnFileThread(); |
| 400 } else { |
| 401 LOG(WARNING) << "Clear failed: " << status.ToString(); |
| 402 FailOnFileThread(kGenericOnFailureMessage); |
| 403 } |
| 404 } else { |
| 405 LOG(WARNING) << "Clear iteration failed: " << it->status().ToString(); |
| 406 FailOnFileThread(kGenericOnFailureMessage); |
| 407 } |
| 408 } |
| 409 }; |
| 410 |
| 411 } // namespace |
| 412 |
| 413 /* static */ |
| 414 ExtensionSettingsLeveldbStorage* ExtensionSettingsLeveldbStorage::Create( |
| 415 const FilePath& base_path, |
| 416 const std::string& extension_id) { |
| 417 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 418 FilePath path = base_path.AppendASCII(extension_id); |
| 419 |
| 420 #if defined(OS_POSIX) |
| 421 std::string os_path(path.value()); |
| 422 #elif defined(OS_WIN) |
| 423 std::string os_path = base::SysWideToUTF8(path.value()); |
| 424 #endif |
| 425 |
| 426 leveldb::Options options; |
| 427 options.create_if_missing = true; |
| 428 leveldb::DB* db; |
| 429 leveldb::Status status = leveldb::DB::Open(options, os_path, &db); |
| 430 if (!status.ok()) { |
| 431 LOG(WARNING) << "Failed to create leveldb at " << path.value() << |
| 432 ": " << status.ToString(); |
| 433 return NULL; |
| 434 } |
| 435 return new ExtensionSettingsLeveldbStorage(db); |
| 436 } |
| 437 |
| 438 ExtensionSettingsLeveldbStorage::ExtensionSettingsLeveldbStorage( |
| 439 leveldb::DB* db) |
| 440 : db_(db), marked_for_deletion_(false) { |
| 441 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 442 } |
| 443 |
| 444 ExtensionSettingsLeveldbStorage::~ExtensionSettingsLeveldbStorage() { |
| 445 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 446 } |
| 447 |
| 448 void ExtensionSettingsLeveldbStorage::DeleteSoon() { |
| 449 CHECK(!marked_for_deletion_); |
| 450 marked_for_deletion_ = true; |
| 451 BrowserThread::DeleteSoon(BrowserThread::FILE, FROM_HERE, this); |
| 452 } |
| 453 |
| 454 void ExtensionSettingsLeveldbStorage::Get( |
| 455 const std::string& key, ExtensionSettingsStorage::Callback* callback) { |
| 456 CHECK(!marked_for_deletion_); |
| 457 (new Get1ResultClosure(db_.get(), callback, key))->Run(); |
| 458 } |
| 459 |
| 460 void ExtensionSettingsLeveldbStorage::Get( |
| 461 const ListValue& keys, ExtensionSettingsStorage::Callback* callback) { |
| 462 CHECK(!marked_for_deletion_); |
| 463 (new Get2ResultClosure(db_.get(), callback, keys.DeepCopy()))->Run(); |
| 464 } |
| 465 |
| 466 void ExtensionSettingsLeveldbStorage::Get( |
| 467 ExtensionSettingsStorage::Callback* callback) { |
| 468 CHECK(!marked_for_deletion_); |
| 469 (new Get3ResultClosure(db_.get(), callback))->Run(); |
| 470 } |
| 471 |
| 472 void ExtensionSettingsLeveldbStorage::Set( |
| 473 const std::string& key, |
| 474 const Value& value, |
| 475 ExtensionSettingsStorage::Callback* callback) { |
| 476 CHECK(!marked_for_deletion_); |
| 477 (new Set1ResultClosure(db_.get(), callback, key, value.DeepCopy()))->Run(); |
| 478 } |
| 479 |
| 480 void ExtensionSettingsLeveldbStorage::Set( |
| 481 const DictionaryValue& values, |
| 482 ExtensionSettingsStorage::Callback* callback) { |
| 483 CHECK(!marked_for_deletion_); |
| 484 (new Set2ResultClosure(db_.get(), callback, values.DeepCopy()))->Run(); |
| 485 } |
| 486 |
| 487 void ExtensionSettingsLeveldbStorage::Remove( |
| 488 const std::string& key, ExtensionSettingsStorage::Callback *callback) { |
| 489 CHECK(!marked_for_deletion_); |
| 490 (new Remove1ResultClosure(db_.get(), callback, key))->Run(); |
| 491 } |
| 492 |
| 493 void ExtensionSettingsLeveldbStorage::Remove( |
| 494 const ListValue& keys, ExtensionSettingsStorage::Callback *callback) { |
| 495 CHECK(!marked_for_deletion_); |
| 496 (new Remove2ResultClosure(db_.get(), callback, keys.DeepCopy()))->Run(); |
| 497 } |
| 498 |
| 499 void ExtensionSettingsLeveldbStorage::Clear( |
| 500 ExtensionSettingsStorage::Callback* callback) { |
| 501 CHECK(!marked_for_deletion_); |
| 502 (new ClearResultClosure(db_.get(), callback))->Run(); |
| 503 } |
OLD | NEW |