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

Side by Side 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, 8 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 unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2012 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 "webkit/dom_storage/session_storage_database.h"
6
7 #include "base/file_util.h"
8 #include "base/string_number_conversions.h"
9 #include "base/utf_string_conversions.h"
10 #include "third_party/leveldatabase/src/include/leveldb/db.h"
11 #include "third_party/leveldatabase/src/include/leveldb/iterator.h"
12 #include "third_party/leveldatabase/src/include/leveldb/status.h"
13 #include "third_party/leveldatabase/src/include/leveldb/options.h"
14
15 #include <sstream>
16
17 // Layout of the database:
michaeln 2012/04/03 22:56:45 This seems like the best place to start hammering
18 // | key | value |
19 // ---------------------------------------------------
20 // | 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
21 // | area-1-origin1 | 1 (mapid) |
22 // | area-1-origin2 | 2 |
23 // | area-2 | dummy |
24 // | area-2-origin1 | 1 | shallow copy
25 // | area-3 | |
26 // | area-3-origin1 | 3 | deep copy
27 // | map-1 | dummy | start of map-1-* keys
28 // | map-1-a | b | a = b in map 1
29 // | ... | |
30 // | next-map-id | 4 |
31
32 namespace {
33
34 // Helper functions for creating the keys.
35 std::string AreaKey(int64 namespace_id, const GURL& origin) {
36 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.
37 stream << "area-" << namespace_id << "-" << origin.spec();
38 return stream.str();
39 }
40
41 std::string MapStartKey(const std::string& map_id) {
42 std::ostringstream stream;
43 stream << "map-" << map_id;
44 return stream.str();
45 }
46
47 std::string MapKey(const std::string& map_id, const std::string& key) {
48 std::ostringstream stream;
49 stream << "value-" << map_id << "-" << key;
50 return stream.str();
51 }
52
53 std::string NextMapIdKey() {
54 return "next-map-id";
55 }
56
57 } // namespace
58
59 namespace dom_storage {
60
61 SessionStorageDatabase::SessionStorageDatabase(const FilePath& file_path)
62 : DomStorageDatabase(file_path) {
63 }
64
65 SessionStorageDatabase::~SessionStorageDatabase() { }
66
67 void SessionStorageDatabase::ReadAllValues(int64 namespace_id,
68 const GURL& origin,
69 ValuesMap* result) {
70 // We don't create a database if it doesn't exist. In that case, there is
71 // nothing to be added to the result.
72 if (!LazyOpen(false))
73 return;
74 // Check if there is map for namespace_id-origin.
75 std::string area_key = AreaKey(namespace_id, origin);
76 std::string map_id;
77 leveldb::Status s = db_->Get(leveldb::ReadOptions(), area_key, &map_id);
78 if (!s.ok() || map_id.empty())
79 return;
80 ReadAllValuesInMap(map_id, result);
81 }
82
83 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
84 const GURL& origin,
85 bool clear_all_first,
86 const ValuesMap& changes) {
87 if (!LazyOpen(!changes.empty())) {
88 // If we're being asked to commit changes that will result in an
89 // empty database, we return true if the database file doesn't exist.
90 return clear_all_first && changes.empty() &&
91 !file_util::PathExists(file_path_);
92 }
93 std::string area_key = AreaKey(namespace_id, origin);
94 std::string map_id;
95 leveldb::Status s = db_->Get(leveldb::ReadOptions(), area_key, &map_id);
96 DCHECK(s.ok() || s.IsNotFound());
97 if (s.IsNotFound()) {
98 // Create a new map.
99 std::string next_map_id_key = NextMapIdKey();
100 s = db_->Get(leveldb::ReadOptions(), next_map_id_key, &map_id);
101 DCHECK(s.ok() || s.IsNotFound());
102 int64 next_map_id = 0;
103 if (!s.IsNotFound() || !base::StringToInt64(map_id, &next_map_id))
104 map_id = "0";
105 ++next_map_id;
106 s = db_->Put(leveldb::WriteOptions(), next_map_id_key,
107 base::Int64ToString(next_map_id));
108 DCHECK(s.ok());
109 s = db_->Put(leveldb::WriteOptions(), area_key, map_id);
110 } else if (clear_all_first) {
111 ValuesMap values;
112 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
113 for (ValuesMap::const_iterator it = values.begin(); it != values.end();
114 ++it) {
115 leveldb::Status s =
116 db_->Delete(leveldb::WriteOptions(), UTF16ToUTF8(it->first));
117 DCHECK(s.ok());
118 }
119 }
120
121 for(ValuesMap::const_iterator it = changes.begin();
122 it != changes.end(); ++it) {
123 NullableString16 value = it->second;
124 std::string key = MapKey(map_id, UTF16ToUTF8(it->first));
125 if (value.is_null()) {
126 s = db_->Delete(leveldb::WriteOptions(), key);
127 DCHECK(s.ok());
128 } else {
129 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
130 DCHECK(s.ok());
131 }
132 }
133 return true;
134 }
135
136 bool SessionStorageDatabase::LazyOpen(bool create_if_needed) {
michaeln 2012/04/03 22:56:45 This stuff about lazily creating the directory on
137 if (failed_to_open_) {
138 // Don't try to open a database that we know has failed
139 // already.
140 return false;
141 }
142
143 if (IsOpen())
144 return true;
145
146 bool database_exists = file_util::PathExists(file_path_);
147
148 if (!database_exists && !create_if_needed) {
149 // If the file doesn't exist already and we haven't been asked to create
150 // a file on disk, then we don't bother opening the database. This means
151 // we wait until we absolutely need to put something onto disk before we
152 // do so.
153 return false;
154 }
155
156 leveldb::Options options;
157 options.create_if_missing = create_if_needed;
158 leveldb::Status s;
159 leveldb::DB* db;
160 #if defined(OS_WIN)
161 s = leveldb::DB::Open(options, WideToUTF8(file_path_.value()), &db);
162 #elif defined(OS_POSIX)
163 s = leveldb::DB::Open(options, file_path_.value(), &db);
164 #endif
165 if (!s.ok()) {
166 DCHECK(db == NULL);
167 failed_to_open_ = true;
168 return false;
169 }
170
171 db_.reset(db);
172 return true;
173 }
174
175 bool SessionStorageDatabase::IsOpen() const {
176 return db_.get();
177 }
178
179 void SessionStorageDatabase::ReadAllValuesInMap(const std::string map_id,
180 ValuesMap* result) {
181 scoped_ptr<leveldb::Iterator> it(db_->NewIterator(leveldb::ReadOptions()));
182 std::string map_start_key = MapStartKey(map_id);
183 // Skip the dummy entry, then start iterating the keys in the map.
184 for (it->Seek(map_start_key), it->Next(); it->Valid(); it->Next()) {
185 string16 key = UTF8ToUTF16(it->key().ToString());
186 string16 value = UTF8ToUTF16(it->value().ToString());
187 // Key is of the form "value-<mapid>-<key>".
188 size_t second_dash = key.find('-', 6);
189 if (second_dash == std::string::npos) {
190 // Iterated beyond the keys in this map.
191 break;
192 }
193 (*result)[key.substr(second_dash + 1)] = NullableString16(value, false);
194 }
195 }
196
197 } // namespace dom_storage
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698