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 |