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 it is impossible for this to |
| 104 // be invalid during the FILE thread lifetime of this closure; no closures |
| 105 // will be created after DeleteSoon() is called (see marked_for_deletion_). |
| 106 leveldb::DB* db_; |
| 107 |
| 108 scoped_ptr<DictionaryValue> settings_; |
| 109 std::string error_; |
| 110 |
| 111 private: |
| 112 void Succeed() { |
| 113 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 114 callback_->OnSuccess(settings_.release()); |
| 115 delete this; |
| 116 } |
| 117 |
| 118 void Fail() { |
| 119 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 120 callback_->OnFailure(error_); |
| 121 delete this; |
| 122 } |
| 123 |
| 124 scoped_ptr<ExtensionSettingsStorage::Callback> callback_; |
| 125 |
| 126 DISALLOW_COPY_AND_ASSIGN(ResultClosure); |
| 127 }; |
| 128 |
| 129 // Result closure for Get(key). |
| 130 class Get1ResultClosure : public ResultClosure { |
| 131 public: |
| 132 Get1ResultClosure( |
| 133 leveldb::DB* db, |
| 134 ExtensionSettingsStorage::Callback* callback, |
| 135 const std::string& key) |
| 136 : ResultClosure(db, callback), key_(key) {} |
| 137 |
| 138 protected: |
| 139 virtual void RunOnFileThread() OVERRIDE { |
| 140 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 141 if (ReadFromDb(leveldb::ReadOptions(), key_, settings_.get(), &error_)) { |
| 142 SucceedOnFileThread(); |
| 143 } else { |
| 144 FailOnFileThread(kGenericOnFailureMessage); |
| 145 } |
| 146 } |
| 147 |
| 148 private: |
| 149 std::string key_; |
| 150 }; |
| 151 |
| 152 // Result closure for Get(keys). |
| 153 class Get2ResultClosure : public ResultClosure { |
| 154 public: |
| 155 // Ownership of keys is taken. |
| 156 Get2ResultClosure( |
| 157 leveldb::DB* db, |
| 158 ExtensionSettingsStorage::Callback* callback, |
| 159 ListValue* keys) |
| 160 : ResultClosure(db, callback), keys_(keys) {} |
| 161 |
| 162 protected: |
| 163 virtual void RunOnFileThread() OVERRIDE { |
| 164 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 165 leveldb::ReadOptions options; |
| 166 // All interaction with the db is done on the same thread, so snapshotting |
| 167 // isn't strictly necessary. This is just defensive. |
| 168 options.snapshot = db_->GetSnapshot(); |
| 169 bool success = true; |
| 170 std::string key; |
| 171 |
| 172 for (ListValue::const_iterator it = keys_->begin(); |
| 173 success && it != keys_->end(); ++it) { |
| 174 if ((*it)->GetAsString(&key)) { |
| 175 if (!ReadFromDb(options, key, settings_.get(), &error_)) { |
| 176 success = false; |
| 177 } |
| 178 } |
| 179 } |
| 180 db_->ReleaseSnapshot(options.snapshot); |
| 181 |
| 182 if (success) { |
| 183 SucceedOnFileThread(); |
| 184 } else { |
| 185 FailOnFileThread(kGenericOnFailureMessage); |
| 186 } |
| 187 } |
| 188 |
| 189 private: |
| 190 scoped_ptr<ListValue> keys_; |
| 191 }; |
| 192 |
| 193 // Result closure for Get() . |
| 194 class Get3ResultClosure : public ResultClosure { |
| 195 public: |
| 196 Get3ResultClosure( |
| 197 leveldb::DB* db, |
| 198 ExtensionSettingsStorage::Callback* callback) |
| 199 : ResultClosure(db, callback) { |
| 200 } |
| 201 |
| 202 protected: |
| 203 virtual void RunOnFileThread() OVERRIDE { |
| 204 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 205 base::JSONReader json_reader; |
| 206 leveldb::ReadOptions options = leveldb::ReadOptions(); |
| 207 // All interaction with the db is done on the same thread, so snapshotting |
| 208 // isn't strictly necessary. This is just defensive. |
| 209 options.snapshot = db_->GetSnapshot(); |
| 210 scoped_ptr<leveldb::Iterator> it(db_->NewIterator(options)); |
| 211 |
| 212 for (it->SeekToFirst(); it->Valid(); it->Next()) { |
| 213 Value* value = |
| 214 json_reader.JsonToValue(it->value().ToString(), false, false); |
| 215 if (value != NULL) { |
| 216 settings_->Set(it->key().ToString(), value); |
| 217 } else { |
| 218 // TODO(kalman): clear the offending non-JSON value from the database. |
| 219 LOG(ERROR) << "Invalid JSON: " << it->value().ToString(); |
| 220 } |
| 221 } |
| 222 db_->ReleaseSnapshot(options.snapshot); |
| 223 |
| 224 if (!it->status().ok()) { |
| 225 LOG(ERROR) << "DB iteration failed: " << it->status().ToString(); |
| 226 FailOnFileThread(kGenericOnFailureMessage); |
| 227 } else { |
| 228 SucceedOnFileThread(); |
| 229 } |
| 230 } |
| 231 }; |
| 232 |
| 233 // Result closure for Set(key, value). |
| 234 class Set1ResultClosure : public ResultClosure { |
| 235 public: |
| 236 // Ownership of the value is taken. |
| 237 Set1ResultClosure( |
| 238 leveldb::DB* db, |
| 239 ExtensionSettingsStorage::Callback* callback, |
| 240 const std::string& key, |
| 241 Value* value) |
| 242 : ResultClosure(db, callback), key_(key), value_(value) {} |
| 243 |
| 244 protected: |
| 245 virtual void RunOnFileThread() OVERRIDE { |
| 246 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 247 std::string value_as_json; |
| 248 base::JSONWriter::Write(value_.get(), false, &value_as_json); |
| 249 leveldb::Status status = |
| 250 db_->Put(leveldb::WriteOptions(), key_, value_as_json); |
| 251 if (status.ok()) { |
| 252 settings_->Set(key_, value_.release()); |
| 253 SucceedOnFileThread(); |
| 254 } else { |
| 255 LOG(WARNING) << "DB write failed: " << status.ToString(); |
| 256 FailOnFileThread(kGenericOnFailureMessage); |
| 257 } |
| 258 } |
| 259 |
| 260 private: |
| 261 std::string key_; |
| 262 scoped_ptr<Value> value_; |
| 263 }; |
| 264 |
| 265 // Result callback for Set(values). |
| 266 class Set2ResultClosure : public ResultClosure { |
| 267 public: |
| 268 // Ownership of values is taken. |
| 269 Set2ResultClosure( |
| 270 leveldb::DB* db, |
| 271 ExtensionSettingsStorage::Callback* callback, |
| 272 DictionaryValue* values) |
| 273 : ResultClosure(db, callback), values_(values) { |
| 274 } |
| 275 |
| 276 protected: |
| 277 virtual void RunOnFileThread() OVERRIDE { |
| 278 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 279 // Gather keys since a dictionary can't be modified during iteration. |
| 280 std::vector<std::string> keys; |
| 281 for (DictionaryValue::key_iterator it = values_->begin_keys(); |
| 282 it != values_->end_keys(); ++it) { |
| 283 keys.push_back(*it); |
| 284 } |
| 285 // Write values while transferring ownership from values_ to settings_. |
| 286 std::string value_as_json; |
| 287 leveldb::WriteBatch batch; |
| 288 for (unsigned i = 0; i < keys.size(); ++i) { |
| 289 Value* value = NULL; |
| 290 values_->RemoveWithoutPathExpansion(keys[i], &value); |
| 291 base::JSONWriter::Write(value, false, &value_as_json); |
| 292 batch.Put(keys[i], value_as_json); |
| 293 settings_->SetWithoutPathExpansion(keys[i], value); |
| 294 } |
| 295 DCHECK(values_->empty()); |
| 296 |
| 297 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch); |
| 298 if (status.ok()) { |
| 299 SucceedOnFileThread(); |
| 300 } else { |
| 301 LOG(WARNING) << "DB batch write failed: " << status.ToString(); |
| 302 FailOnFileThread(kGenericOnFailureMessage); |
| 303 } |
| 304 } |
| 305 |
| 306 private: |
| 307 scoped_ptr<DictionaryValue> values_; // will be empty on destruction |
| 308 }; |
| 309 |
| 310 // Result closure for Remove(key). |
| 311 class Remove1ResultClosure : public ResultClosure { |
| 312 public: |
| 313 Remove1ResultClosure( |
| 314 leveldb::DB* db, |
| 315 ExtensionSettingsStorage::Callback* callback, |
| 316 const std::string& key) |
| 317 : ResultClosure(db, callback), key_(key) { |
| 318 } |
| 319 |
| 320 protected: |
| 321 virtual void RunOnFileThread() OVERRIDE { |
| 322 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 323 leveldb::Status status = db_->Delete(leveldb::WriteOptions(), key_); |
| 324 if (status.ok() || status.IsNotFound()) { |
| 325 SucceedOnFileThread(); |
| 326 } else { |
| 327 LOG(WARNING) << "DB delete failed: " << status.ToString(); |
| 328 FailOnFileThread(kGenericOnFailureMessage); |
| 329 } |
| 330 } |
| 331 |
| 332 private: |
| 333 std::string key_; |
| 334 }; |
| 335 |
| 336 // Result closure for Remove(keys). |
| 337 class Remove2ResultClosure : public ResultClosure { |
| 338 public: |
| 339 // Ownership of keys is taken. |
| 340 Remove2ResultClosure( |
| 341 leveldb::DB* db, |
| 342 ExtensionSettingsStorage::Callback* callback, |
| 343 ListValue* keys) |
| 344 : ResultClosure(db, callback), keys_(keys) { |
| 345 } |
| 346 |
| 347 protected: |
| 348 virtual void RunOnFileThread() OVERRIDE { |
| 349 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 350 std::string key; |
| 351 leveldb::WriteBatch batch; |
| 352 for (ListValue::const_iterator it = keys_->begin(); |
| 353 it != keys_->end(); ++it) { |
| 354 if ((*it)->GetAsString(&key)) { |
| 355 batch.Delete(key); |
| 356 } |
| 357 } |
| 358 |
| 359 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch); |
| 360 if (status.ok() || status.IsNotFound()) { |
| 361 SucceedOnFileThread(); |
| 362 } else { |
| 363 LOG(WARNING) << "DB batch delete failed: " << status.ToString(); |
| 364 FailOnFileThread(kGenericOnFailureMessage); |
| 365 } |
| 366 } |
| 367 |
| 368 private: |
| 369 scoped_ptr<ListValue> keys_; |
| 370 }; |
| 371 |
| 372 // Result closure for Clear(). |
| 373 class ClearResultClosure : public ResultClosure { |
| 374 public: |
| 375 ClearResultClosure(leveldb::DB* db, |
| 376 ExtensionSettingsStorage::Callback* callback) |
| 377 : ResultClosure(db, callback) { |
| 378 } |
| 379 |
| 380 protected: |
| 381 virtual void RunOnFileThread() OVERRIDE { |
| 382 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 383 leveldb::ReadOptions options; |
| 384 // All interaction with the db is done on the same thread, so snapshotting |
| 385 // isn't strictly necessary. This is just defensive. |
| 386 options.snapshot = db_->GetSnapshot(); |
| 387 scoped_ptr<leveldb::Iterator> it(db_->NewIterator(options)); |
| 388 leveldb::WriteBatch batch; |
| 389 |
| 390 for (it->SeekToFirst(); it->Valid(); it->Next()) { |
| 391 batch.Delete(it->key()); |
| 392 } |
| 393 db_->ReleaseSnapshot(options.snapshot); |
| 394 |
| 395 if (it->status().ok()) { |
| 396 leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch); |
| 397 if (status.ok() || status.IsNotFound()) { |
| 398 SucceedOnFileThread(); |
| 399 } else { |
| 400 LOG(WARNING) << "Clear failed: " << status.ToString(); |
| 401 FailOnFileThread(kGenericOnFailureMessage); |
| 402 } |
| 403 } else { |
| 404 LOG(WARNING) << "Clear iteration failed: " << it->status().ToString(); |
| 405 FailOnFileThread(kGenericOnFailureMessage); |
| 406 } |
| 407 } |
| 408 }; |
| 409 |
| 410 } // namespace |
| 411 |
| 412 /* static */ |
| 413 ExtensionSettingsLeveldbStorage* ExtensionSettingsLeveldbStorage::Create( |
| 414 const FilePath& base_path, |
| 415 const std::string& extension_id) { |
| 416 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 417 FilePath path = base_path.AppendASCII(extension_id); |
| 418 |
| 419 #if defined(OS_POSIX) |
| 420 std::string os_path(path.value()); |
| 421 #elif defined(OS_WIN) |
| 422 std::string os_path = base::SysWideToUTF8(path.value()); |
| 423 #endif |
| 424 |
| 425 leveldb::Options options; |
| 426 options.create_if_missing = true; |
| 427 leveldb::DB* db; |
| 428 leveldb::Status status = leveldb::DB::Open(options, os_path, &db); |
| 429 if (!status.ok()) { |
| 430 LOG(WARNING) << "Failed to create leveldb at " << path.value() << |
| 431 ": " << status.ToString(); |
| 432 return NULL; |
| 433 } |
| 434 return new ExtensionSettingsLeveldbStorage(db); |
| 435 } |
| 436 |
| 437 ExtensionSettingsLeveldbStorage::ExtensionSettingsLeveldbStorage( |
| 438 leveldb::DB* db) |
| 439 : db_(db), marked_for_deletion_(false) { |
| 440 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 441 } |
| 442 |
| 443 ExtensionSettingsLeveldbStorage::~ExtensionSettingsLeveldbStorage() { |
| 444 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 445 } |
| 446 |
| 447 void ExtensionSettingsLeveldbStorage::DeleteSoon() { |
| 448 CHECK(!marked_for_deletion_); |
| 449 marked_for_deletion_ = true; |
| 450 BrowserThread::DeleteSoon(BrowserThread::FILE, FROM_HERE, this); |
| 451 } |
| 452 |
| 453 void ExtensionSettingsLeveldbStorage::Get( |
| 454 const std::string& key, ExtensionSettingsStorage::Callback* callback) { |
| 455 CHECK(!marked_for_deletion_); |
| 456 (new Get1ResultClosure(db_.get(), callback, key))->Run(); |
| 457 } |
| 458 |
| 459 void ExtensionSettingsLeveldbStorage::Get( |
| 460 const ListValue& keys, ExtensionSettingsStorage::Callback* callback) { |
| 461 CHECK(!marked_for_deletion_); |
| 462 (new Get2ResultClosure(db_.get(), callback, keys.DeepCopy()))->Run(); |
| 463 } |
| 464 |
| 465 void ExtensionSettingsLeveldbStorage::Get( |
| 466 ExtensionSettingsStorage::Callback* callback) { |
| 467 CHECK(!marked_for_deletion_); |
| 468 (new Get3ResultClosure(db_.get(), callback))->Run(); |
| 469 } |
| 470 |
| 471 void ExtensionSettingsLeveldbStorage::Set( |
| 472 const std::string& key, |
| 473 const Value& value, |
| 474 ExtensionSettingsStorage::Callback* callback) { |
| 475 CHECK(!marked_for_deletion_); |
| 476 (new Set1ResultClosure(db_.get(), callback, key, value.DeepCopy()))->Run(); |
| 477 } |
| 478 |
| 479 void ExtensionSettingsLeveldbStorage::Set( |
| 480 const DictionaryValue& values, |
| 481 ExtensionSettingsStorage::Callback* callback) { |
| 482 CHECK(!marked_for_deletion_); |
| 483 (new Set2ResultClosure(db_.get(), callback, values.DeepCopy()))->Run(); |
| 484 } |
| 485 |
| 486 void ExtensionSettingsLeveldbStorage::Remove( |
| 487 const std::string& key, ExtensionSettingsStorage::Callback *callback) { |
| 488 CHECK(!marked_for_deletion_); |
| 489 (new Remove1ResultClosure(db_.get(), callback, key))->Run(); |
| 490 } |
| 491 |
| 492 void ExtensionSettingsLeveldbStorage::Remove( |
| 493 const ListValue& keys, ExtensionSettingsStorage::Callback *callback) { |
| 494 CHECK(!marked_for_deletion_); |
| 495 (new Remove2ResultClosure(db_.get(), callback, keys.DeepCopy()))->Run(); |
| 496 } |
| 497 |
| 498 void ExtensionSettingsLeveldbStorage::Clear( |
| 499 ExtensionSettingsStorage::Callback* callback) { |
| 500 CHECK(!marked_for_deletion_); |
| 501 (new ClearResultClosure(db_.get(), callback))->Run(); |
| 502 } |
OLD | NEW |