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 and settings are taken. |
| 31 // Settings may be NULL (for Remove/Clear). |
| 32 ResultClosure( |
| 33 leveldb::DB* db, |
| 34 DictionaryValue* settings, |
| 35 ExtensionSettingsStorage::Callback* callback) |
| 36 : db_(db), settings_(settings), callback_(callback) {} |
| 37 |
| 38 virtual ~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, new DictionaryValue(), 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, new DictionaryValue(), 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, new DictionaryValue(), 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, new DictionaryValue(), callback), |
| 244 key_(key), |
| 245 value_(value) {} |
| 246 |
| 247 protected: |
| 248 virtual void RunOnFileThread() OVERRIDE { |
| 249 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 250 std::string value_as_json; |
| 251 base::JSONWriter::Write(value_.get(), false, &value_as_json); |
| 252 leveldb::Status status = |
| 253 db_->Put(leveldb::WriteOptions(), key_, value_as_json); |
| 254 if (status.ok()) { |
| 255 settings_->Set(key_, value_.release()); |
| 256 SucceedOnFileThread(); |
| 257 } else { |
| 258 LOG(WARNING) << "DB write failed: " << status.ToString(); |
| 259 FailOnFileThread(kGenericOnFailureMessage); |
| 260 } |
| 261 } |
| 262 |
| 263 private: |
| 264 std::string key_; |
| 265 scoped_ptr<Value> value_; |
| 266 }; |
| 267 |
| 268 // Result callback for Set(values). |
| 269 class Set2ResultClosure : public ResultClosure { |
| 270 public: |
| 271 // Ownership of values is taken. |
| 272 Set2ResultClosure( |
| 273 leveldb::DB* db, |
| 274 ExtensionSettingsStorage::Callback* callback, |
| 275 DictionaryValue* values) |
| 276 : ResultClosure(db, new DictionaryValue(), callback), values_(values) { |
| 277 } |
| 278 |
| 279 protected: |
| 280 virtual void RunOnFileThread() OVERRIDE { |
| 281 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 282 // Gather keys since a dictionary can't be modified during iteration. |
| 283 std::vector<std::string> keys; |
| 284 for (DictionaryValue::key_iterator it = values_->begin_keys(); |
| 285 it != values_->end_keys(); ++it) { |
| 286 keys.push_back(*it); |
| 287 } |
| 288 // Write values while transferring ownership from values_ to settings_. |
| 289 std::string value_as_json; |
| 290 leveldb::WriteBatch batch; |
| 291 for (unsigned i = 0; i < keys.size(); ++i) { |
| 292 Value* value = NULL; |
| 293 values_->RemoveWithoutPathExpansion(keys[i], &value); |
| 294 base::JSONWriter::Write(value, false, &value_as_json); |
| 295 batch.Put(keys[i], value_as_json); |
| 296 settings_->SetWithoutPathExpansion(keys[i], value); |
| 297 } |
| 298 DCHECK(values_->empty()); |
| 299 |
| 300 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch); |
| 301 if (status.ok()) { |
| 302 SucceedOnFileThread(); |
| 303 } else { |
| 304 LOG(WARNING) << "DB batch write failed: " << status.ToString(); |
| 305 FailOnFileThread(kGenericOnFailureMessage); |
| 306 } |
| 307 } |
| 308 |
| 309 private: |
| 310 scoped_ptr<DictionaryValue> values_; // will be empty on destruction |
| 311 }; |
| 312 |
| 313 // Result closure for Remove(key). |
| 314 class Remove1ResultClosure : public ResultClosure { |
| 315 public: |
| 316 Remove1ResultClosure( |
| 317 leveldb::DB* db, |
| 318 ExtensionSettingsStorage::Callback* callback, |
| 319 const std::string& key) |
| 320 : ResultClosure(db, NULL, callback), key_(key) { |
| 321 } |
| 322 |
| 323 protected: |
| 324 virtual void RunOnFileThread() OVERRIDE { |
| 325 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 326 leveldb::Status status = db_->Delete(leveldb::WriteOptions(), key_); |
| 327 if (status.ok() || status.IsNotFound()) { |
| 328 SucceedOnFileThread(); |
| 329 } else { |
| 330 LOG(WARNING) << "DB delete failed: " << status.ToString(); |
| 331 FailOnFileThread(kGenericOnFailureMessage); |
| 332 } |
| 333 } |
| 334 |
| 335 private: |
| 336 std::string key_; |
| 337 }; |
| 338 |
| 339 // Result closure for Remove(keys). |
| 340 class Remove2ResultClosure : public ResultClosure { |
| 341 public: |
| 342 // Ownership of keys is taken. |
| 343 Remove2ResultClosure( |
| 344 leveldb::DB* db, |
| 345 ExtensionSettingsStorage::Callback* callback, |
| 346 ListValue* keys) |
| 347 : ResultClosure(db, NULL, callback), keys_(keys) { |
| 348 } |
| 349 |
| 350 protected: |
| 351 virtual void RunOnFileThread() OVERRIDE { |
| 352 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 353 std::string key; |
| 354 leveldb::WriteBatch batch; |
| 355 for (ListValue::const_iterator it = keys_->begin(); |
| 356 it != keys_->end(); ++it) { |
| 357 if ((*it)->GetAsString(&key)) { |
| 358 batch.Delete(key); |
| 359 } |
| 360 } |
| 361 |
| 362 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch); |
| 363 if (status.ok() || status.IsNotFound()) { |
| 364 SucceedOnFileThread(); |
| 365 } else { |
| 366 LOG(WARNING) << "DB batch delete failed: " << status.ToString(); |
| 367 FailOnFileThread(kGenericOnFailureMessage); |
| 368 } |
| 369 } |
| 370 |
| 371 private: |
| 372 scoped_ptr<ListValue> keys_; |
| 373 }; |
| 374 |
| 375 // Result closure for Clear(). |
| 376 class ClearResultClosure : public ResultClosure { |
| 377 public: |
| 378 ClearResultClosure(leveldb::DB* db, |
| 379 ExtensionSettingsStorage::Callback* callback) |
| 380 : ResultClosure(db, NULL, callback) { |
| 381 } |
| 382 |
| 383 protected: |
| 384 virtual void RunOnFileThread() OVERRIDE { |
| 385 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 386 leveldb::ReadOptions options; |
| 387 // All interaction with the db is done on the same thread, so snapshotting |
| 388 // isn't strictly necessary. This is just defensive. |
| 389 options.snapshot = db_->GetSnapshot(); |
| 390 scoped_ptr<leveldb::Iterator> it(db_->NewIterator(options)); |
| 391 leveldb::WriteBatch batch; |
| 392 |
| 393 for (it->SeekToFirst(); it->Valid(); it->Next()) { |
| 394 batch.Delete(it->key()); |
| 395 } |
| 396 db_->ReleaseSnapshot(options.snapshot); |
| 397 |
| 398 if (it->status().ok()) { |
| 399 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch); |
| 400 if (status.ok() || status.IsNotFound()) { |
| 401 SucceedOnFileThread(); |
| 402 } else { |
| 403 LOG(WARNING) << "Clear failed: " << status.ToString(); |
| 404 FailOnFileThread(kGenericOnFailureMessage); |
| 405 } |
| 406 } else { |
| 407 LOG(WARNING) << "Clear iteration failed: " << it->status().ToString(); |
| 408 FailOnFileThread(kGenericOnFailureMessage); |
| 409 } |
| 410 } |
| 411 }; |
| 412 |
| 413 } // namespace |
| 414 |
| 415 /* static */ |
| 416 ExtensionSettingsLeveldbStorage* ExtensionSettingsLeveldbStorage::Create( |
| 417 const FilePath& base_path, |
| 418 const std::string& extension_id) { |
| 419 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 420 FilePath path = base_path.AppendASCII(extension_id); |
| 421 |
| 422 #if defined(OS_POSIX) |
| 423 std::string os_path(path.value()); |
| 424 #elif defined(OS_WIN) |
| 425 std::string os_path = base::SysWideToUTF8(path.value()); |
| 426 #endif |
| 427 |
| 428 leveldb::Options options; |
| 429 options.create_if_missing = true; |
| 430 leveldb::DB* db; |
| 431 leveldb::Status status = leveldb::DB::Open(options, os_path, &db); |
| 432 if (!status.ok()) { |
| 433 LOG(WARNING) << "Failed to create leveldb at " << path.value() << |
| 434 ": " << status.ToString(); |
| 435 return NULL; |
| 436 } |
| 437 return new ExtensionSettingsLeveldbStorage(db); |
| 438 } |
| 439 |
| 440 ExtensionSettingsLeveldbStorage::ExtensionSettingsLeveldbStorage( |
| 441 leveldb::DB* db) |
| 442 : db_(db), marked_for_deletion_(false) { |
| 443 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 444 } |
| 445 |
| 446 ExtensionSettingsLeveldbStorage::~ExtensionSettingsLeveldbStorage() { |
| 447 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 448 } |
| 449 |
| 450 void ExtensionSettingsLeveldbStorage::DeleteSoon() { |
| 451 CHECK(!marked_for_deletion_); |
| 452 marked_for_deletion_ = true; |
| 453 BrowserThread::DeleteSoon(BrowserThread::FILE, FROM_HERE, this); |
| 454 } |
| 455 |
| 456 void ExtensionSettingsLeveldbStorage::Get( |
| 457 const std::string& key, ExtensionSettingsStorage::Callback* callback) { |
| 458 CHECK(!marked_for_deletion_); |
| 459 (new Get1ResultClosure(db_.get(), callback, key))->Run(); |
| 460 } |
| 461 |
| 462 void ExtensionSettingsLeveldbStorage::Get( |
| 463 const ListValue& keys, ExtensionSettingsStorage::Callback* callback) { |
| 464 CHECK(!marked_for_deletion_); |
| 465 (new Get2ResultClosure(db_.get(), callback, keys.DeepCopy()))->Run(); |
| 466 } |
| 467 |
| 468 void ExtensionSettingsLeveldbStorage::Get( |
| 469 ExtensionSettingsStorage::Callback* callback) { |
| 470 CHECK(!marked_for_deletion_); |
| 471 (new Get3ResultClosure(db_.get(), callback))->Run(); |
| 472 } |
| 473 |
| 474 void ExtensionSettingsLeveldbStorage::Set( |
| 475 const std::string& key, |
| 476 const Value& value, |
| 477 ExtensionSettingsStorage::Callback* callback) { |
| 478 CHECK(!marked_for_deletion_); |
| 479 (new Set1ResultClosure(db_.get(), callback, key, value.DeepCopy()))->Run(); |
| 480 } |
| 481 |
| 482 void ExtensionSettingsLeveldbStorage::Set( |
| 483 const DictionaryValue& values, |
| 484 ExtensionSettingsStorage::Callback* callback) { |
| 485 CHECK(!marked_for_deletion_); |
| 486 (new Set2ResultClosure(db_.get(), callback, values.DeepCopy()))->Run(); |
| 487 } |
| 488 |
| 489 void ExtensionSettingsLeveldbStorage::Remove( |
| 490 const std::string& key, ExtensionSettingsStorage::Callback *callback) { |
| 491 CHECK(!marked_for_deletion_); |
| 492 (new Remove1ResultClosure(db_.get(), callback, key))->Run(); |
| 493 } |
| 494 |
| 495 void ExtensionSettingsLeveldbStorage::Remove( |
| 496 const ListValue& keys, ExtensionSettingsStorage::Callback *callback) { |
| 497 CHECK(!marked_for_deletion_); |
| 498 (new Remove2ResultClosure(db_.get(), callback, keys.DeepCopy()))->Run(); |
| 499 } |
| 500 |
| 501 void ExtensionSettingsLeveldbStorage::Clear( |
| 502 ExtensionSettingsStorage::Callback* callback) { |
| 503 CHECK(!marked_for_deletion_); |
| 504 (new ClearResultClosure(db_.get(), callback))->Run(); |
| 505 } |
OLD | NEW |