Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(322)

Unified Diff: webkit/dom_storage/session_storage_database.cc

Issue 9963107: Persist sessionStorage on disk. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 8 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: webkit/dom_storage/session_storage_database.cc
diff --git a/webkit/dom_storage/session_storage_database.cc b/webkit/dom_storage/session_storage_database.cc
new file mode 100644
index 0000000000000000000000000000000000000000..ba2f9044d26391e79e072ec81e7f678853330beb
--- /dev/null
+++ b/webkit/dom_storage/session_storage_database.cc
@@ -0,0 +1,197 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/dom_storage/session_storage_database.h"
+
+#include "base/file_util.h"
+#include "base/string_number_conversions.h"
+#include "base/utf_string_conversions.h"
+#include "third_party/leveldatabase/src/include/leveldb/db.h"
+#include "third_party/leveldatabase/src/include/leveldb/iterator.h"
+#include "third_party/leveldatabase/src/include/leveldb/status.h"
+#include "third_party/leveldatabase/src/include/leveldb/options.h"
+
+#include <sstream>
+
+// Layout of the database:
michaeln 2012/04/03 22:56:45 This seems like the best place to start hammering
+// | key | value |
+// ---------------------------------------------------
+// | area-1 (1 = namespace id) | dummy | start of area-1-* keys
michaeln 2012/04/03 22:56:45 what's the benefit of the dummy entries?
marja 2012/04/10 15:34:45 To be able to iterate "all origins that belong to
+// | area-1-origin1 | 1 (mapid) |
+// | area-1-origin2 | 2 |
+// | area-2 | dummy |
+// | area-2-origin1 | 1 | shallow copy
+// | area-3 | |
+// | area-3-origin1 | 3 | deep copy
+// | map-1 | dummy | start of map-1-* keys
+// | map-1-a | b | a = b in map 1
+// | ... | |
+// | next-map-id | 4 |
+
+namespace {
+
+// Helper functions for creating the keys.
+std::string AreaKey(int64 namespace_id, const GURL& origin) {
+ std::ostringstream stream;
michaeln 2012/04/03 22:56:45 do we use sstream elswhere in the project? if not,
marja 2012/04/10 15:34:45 Done.
+ stream << "area-" << namespace_id << "-" << origin.spec();
+ return stream.str();
+}
+
+std::string MapStartKey(const std::string& map_id) {
+ std::ostringstream stream;
+ stream << "map-" << map_id;
+ return stream.str();
+}
+
+std::string MapKey(const std::string& map_id, const std::string& key) {
+ std::ostringstream stream;
+ stream << "value-" << map_id << "-" << key;
+ return stream.str();
+}
+
+std::string NextMapIdKey() {
+ return "next-map-id";
+}
+
+} // namespace
+
+namespace dom_storage {
+
+SessionStorageDatabase::SessionStorageDatabase(const FilePath& file_path)
+ : DomStorageDatabase(file_path) {
+}
+
+SessionStorageDatabase::~SessionStorageDatabase() { }
+
+void SessionStorageDatabase::ReadAllValues(int64 namespace_id,
+ const GURL& origin,
+ ValuesMap* result) {
+ // We don't create a database if it doesn't exist. In that case, there is
+ // nothing to be added to the result.
+ if (!LazyOpen(false))
+ return;
+ // Check if there is map for namespace_id-origin.
+ std::string area_key = AreaKey(namespace_id, origin);
+ std::string map_id;
+ leveldb::Status s = db_->Get(leveldb::ReadOptions(), area_key, &map_id);
+ if (!s.ok() || map_id.empty())
+ return;
+ ReadAllValuesInMap(map_id, result);
+}
+
+bool SessionStorageDatabase::CommitChanges(int64 namespace_id,
michaeln 2012/04/03 22:56:45 Another detail to hammer out is around the read/wr
marja 2012/04/10 15:34:45 Added WriteBatch. If we allow writing from 2 threa
+ const GURL& origin,
+ bool clear_all_first,
+ const ValuesMap& changes) {
+ if (!LazyOpen(!changes.empty())) {
+ // If we're being asked to commit changes that will result in an
+ // empty database, we return true if the database file doesn't exist.
+ return clear_all_first && changes.empty() &&
+ !file_util::PathExists(file_path_);
+ }
+ std::string area_key = AreaKey(namespace_id, origin);
+ std::string map_id;
+ leveldb::Status s = db_->Get(leveldb::ReadOptions(), area_key, &map_id);
+ DCHECK(s.ok() || s.IsNotFound());
+ if (s.IsNotFound()) {
+ // Create a new map.
+ std::string next_map_id_key = NextMapIdKey();
+ s = db_->Get(leveldb::ReadOptions(), next_map_id_key, &map_id);
+ DCHECK(s.ok() || s.IsNotFound());
+ int64 next_map_id = 0;
+ if (!s.IsNotFound() || !base::StringToInt64(map_id, &next_map_id))
+ map_id = "0";
+ ++next_map_id;
+ s = db_->Put(leveldb::WriteOptions(), next_map_id_key,
+ base::Int64ToString(next_map_id));
+ DCHECK(s.ok());
+ s = db_->Put(leveldb::WriteOptions(), area_key, map_id);
+ } else if (clear_all_first) {
+ ValuesMap values;
+ ReadAllValuesInMap(map_id, &values);
michaeln 2012/04/03 22:56:45 is there a way to clear values w/o having to read
marja 2012/04/10 15:34:45 No, afaik. The interface is just Get(), Put(), Del
+ for (ValuesMap::const_iterator it = values.begin(); it != values.end();
+ ++it) {
+ leveldb::Status s =
+ db_->Delete(leveldb::WriteOptions(), UTF16ToUTF8(it->first));
+ DCHECK(s.ok());
+ }
+ }
+
+ for(ValuesMap::const_iterator it = changes.begin();
+ it != changes.end(); ++it) {
+ NullableString16 value = it->second;
+ std::string key = MapKey(map_id, UTF16ToUTF8(it->first));
+ if (value.is_null()) {
+ s = db_->Delete(leveldb::WriteOptions(), key);
+ DCHECK(s.ok());
+ } else {
+ s = db_->Put(leveldb::WriteOptions(), key, UTF16ToUTF8(value.string()));
michaeln 2012/04/03 22:56:45 i think we need to treat the 'value' as binary dat
marja 2012/04/10 15:34:45 Ok, UTF16ToUTF8 was definitely wrong. Otoh, we can
+ DCHECK(s.ok());
+ }
+ }
+ return true;
+}
+
+bool SessionStorageDatabase::LazyOpen(bool create_if_needed) {
michaeln 2012/04/03 22:56:45 This stuff about lazily creating the directory on
+ if (failed_to_open_) {
+ // Don't try to open a database that we know has failed
+ // already.
+ return false;
+ }
+
+ if (IsOpen())
+ return true;
+
+ bool database_exists = file_util::PathExists(file_path_);
+
+ if (!database_exists && !create_if_needed) {
+ // If the file doesn't exist already and we haven't been asked to create
+ // a file on disk, then we don't bother opening the database. This means
+ // we wait until we absolutely need to put something onto disk before we
+ // do so.
+ return false;
+ }
+
+ leveldb::Options options;
+ options.create_if_missing = create_if_needed;
+ leveldb::Status s;
+ leveldb::DB* db;
+#if defined(OS_WIN)
+ s = leveldb::DB::Open(options, WideToUTF8(file_path_.value()), &db);
+#elif defined(OS_POSIX)
+ s = leveldb::DB::Open(options, file_path_.value(), &db);
+#endif
+ if (!s.ok()) {
+ DCHECK(db == NULL);
+ failed_to_open_ = true;
+ return false;
+ }
+
+ db_.reset(db);
+ return true;
+}
+
+bool SessionStorageDatabase::IsOpen() const {
+ return db_.get();
+}
+
+void SessionStorageDatabase::ReadAllValuesInMap(const std::string map_id,
+ ValuesMap* result) {
+ scoped_ptr<leveldb::Iterator> it(db_->NewIterator(leveldb::ReadOptions()));
+ std::string map_start_key = MapStartKey(map_id);
+ // Skip the dummy entry, then start iterating the keys in the map.
+ for (it->Seek(map_start_key), it->Next(); it->Valid(); it->Next()) {
+ string16 key = UTF8ToUTF16(it->key().ToString());
+ string16 value = UTF8ToUTF16(it->value().ToString());
+ // Key is of the form "value-<mapid>-<key>".
+ size_t second_dash = key.find('-', 6);
+ if (second_dash == std::string::npos) {
+ // Iterated beyond the keys in this map.
+ break;
+ }
+ (*result)[key.substr(second_dash + 1)] = NullableString16(value, false);
+ }
+}
+
+} // namespace dom_storage

Powered by Google App Engine
This is Rietveld 408576698