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

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/stringprintf.h"
9 #include "base/string_number_conversions.h"
10 #include "base/utf_string_conversions.h"
11 #include "third_party/leveldatabase/src/include/leveldb/db.h"
12 #include "third_party/leveldatabase/src/include/leveldb/iterator.h"
13 #include "third_party/leveldatabase/src/include/leveldb/status.h"
14 #include "third_party/leveldatabase/src/include/leveldb/options.h"
15 #include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
16
17 // Layout of the database:
18 // | key | value |
19 // ---------------------------------------------------
20 // | area-1 (1 = namespace id) | dummy | start of area-1-* keys
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 | 2 (refcount) | start of map-1-* keys
28 // | map-1-a | b | a = b in map 1
29 // | ... | |
30 // | next-map-id | 4 |
michaeln 2012/04/12 01:48:46 Do we need to ensure that session namespace_ids in
marja 2012/04/19 10:20:50 Done. In the latest patch, I made it so that Sessi
michaeln 2012/04/22 21:43:34 Sounds like a plausible approach.
31
32 namespace {
33
34 // Helper functions for creating the keys needed for the schema.
35 std::string AreaStartKey(const std::string& namespace_id) {
michaeln 2012/04/12 01:48:46 Maybe NamespaceStartKey() for clarity?
marja 2012/04/19 10:20:50 Done.
36 return base::StringPrintf("area-%s", namespace_id.c_str());
37 }
38
39 std::string AreaStartKey(int64 namespace_id) {
michaeln 2012/04/12 01:48:46 ditto
marja 2012/04/19 10:20:50 Done.
40 return AreaStartKey(base::Int64ToString(namespace_id));
41 }
42
43 std::string AreaKey(const std::string& namespace_id,
44 const std::string& origin) {
45 return base::StringPrintf("area-%s-%s", namespace_id.c_str(), origin.c_str());
46 }
47
48 std::string AreaKey(int64 namespace_id, const GURL& origin) {
49 return AreaKey(base::Int64ToString(namespace_id), origin.spec());
50 }
51
52 std::string MapStartKey(const std::string& map_id) {
michaeln 2012/04/12 01:48:46 Maybe MapRefcountKey for clarity?
marja 2012/04/19 10:20:50 Done.
53 return base::StringPrintf("map-%s", map_id.c_str());
54 }
55
56 std::string MapKey(const std::string& map_id, const std::string& key) {
57 return base::StringPrintf("map-%s-%s", map_id.c_str(), key.c_str());
58 }
59
60 std::string NextMapIdKey() {
61 return "next-map-id";
62 }
63
64 } // namespace
65
66 namespace dom_storage {
67
68 SessionStorageDatabase::SessionStorageDatabase(const FilePath& file_path)
69 : DomStorageDatabase(file_path) { }
70
71 SessionStorageDatabase::~SessionStorageDatabase() { }
72
73 void SessionStorageDatabase::ReadAllValues(int64 namespace_id,
74 const GURL& origin,
75 ValuesMap* result) {
76 // We don't create a database if it doesn't exist. In that case, there is
77 // nothing to be added to the result.
78 if (!LazyOpen(false))
79 return;
80 // Check if there is map for namespace_id-origin.
81 std::string area_key = AreaKey(namespace_id, origin);
82 std::string map_id;
83 leveldb::Status s = db_->Get(leveldb::ReadOptions(), area_key, &map_id);
michaeln 2012/04/12 01:48:46 How should we handle genuine leveldb errors in thi
marja 2012/04/19 10:20:50 Yes, I'll add proper error handling when I'm more
84 if (!s.ok() || map_id.empty())
85 return;
86 ReadValuesInMap(map_id, result);
87 }
88
89 bool SessionStorageDatabase::CommitChanges(int64 namespace_id,
90 const GURL& origin,
91 bool clear_all_first,
92 const ValuesMap& changes) {
93 if (!LazyOpen(!changes.empty())) {
94 // If we're being asked to commit changes that will result in an
95 // empty database, we return true if the database file doesn't exist.
96 return clear_all_first && changes.empty() &&
97 !file_util::PathExists(file_path_);
98 }
99 leveldb::WriteBatch batch;
100 // Ensure that the key area-N (see the schema above) exists.
101 batch.Put(AreaStartKey(namespace_id), "");
102
103 std::string area_key = AreaKey(namespace_id, origin);
104 std::string map_id;
105 leveldb::Status s;
106 s = db_->Get(leveldb::ReadOptions(), area_key, &map_id);
107 DCHECK(s.ok() || s.IsNotFound());
108 if (s.IsNotFound()) {
michaeln 2012/04/12 01:48:46 style nit: dont need {}'s for one-liners
marja 2012/04/19 10:20:50 Done.
109 CreateNewMap(area_key, &batch, &map_id);
110 } else if (clear_all_first) {
111 DeleteValuesInMap(map_id, &batch);
112 }
113
114 WriteValuesToMap(map_id, changes, &batch);
115
116 s = db_->Write(leveldb::WriteOptions(), &batch);
117 DCHECK(s.ok());
118 return true;
119 }
120
121 bool SessionStorageDatabase::ShallowCopy(int64 namespace_id,
122 const GURL& origin,
123 int64 new_namespace_id) {
124 // When doing a shallow copy, an existing map is associated with |origin| in
125 // |namespace_id| and its ref count is increased.
michaeln 2012/04/12 01:48:46 When we a Clone() a session namespace, we're going
marja 2012/04/19 10:20:50 Done. For this, I needed an uglyish hack for commi
126
127 // Example, data before shallow copy:
128 // | area-1 (1 = namespace id) | dummy |
129 // | area-1-origin1 | 1 (mapid) |
130 // | map-1 | 1 (refcount) |
131 // | map-1-a | b | a = b in map 1
132
133 // Example, data after shallow copy:
134 // | area-1 (1 = namespace id) | dummy |
135 // | area-1-origin1 | 1 (mapid) |
136 // | area-2 | dummy |
137 // | area-2-origin1 | 1 (mapid) | references the same map
138 // | map-1 | 2 (inc. refcount) |
139 // | map-1-a | b | a = b in map 1
140
141 if (!LazyOpen(true)) {
142 return false;
143 }
144 leveldb::Status s;
145 std::string old_area_key = AreaKey(namespace_id, origin);
146 std::string new_area_key = AreaKey(new_namespace_id, origin);
147 leveldb::WriteBatch batch;
148 batch.Put(AreaStartKey(new_namespace_id), "");
149 std::string map_id;
150 s = db_->Get(leveldb::ReadOptions(), old_area_key, &map_id);
151 DCHECK(s.ok());
152 batch.Put(new_area_key, map_id);
153 // Increase the ref count for the map.
154 std::string map_key = MapStartKey(map_id);
155 std::string old_ref_count_string;
156 s = db_->Get(leveldb::ReadOptions(), map_key, &old_ref_count_string);
157 DCHECK(s.ok());
158 int64 old_ref_count;
159 bool conversion_ok =
160 base::StringToInt64(old_ref_count_string, &old_ref_count);
161 DCHECK(conversion_ok);
162 batch.Put(map_key, base::Int64ToString(++old_ref_count));
163 s = db_->Write(leveldb::WriteOptions(), &batch);
164 DCHECK(s.ok());
165 return true;
166 }
167
168 bool SessionStorageDatabase::DeepCopy(int64 namespace_id,
169 const GURL& origin) {
170 // When doing a shallow copy, an existing map is associated with |origin| in
171 // |namespace_id| and its ref count is increased.
172
173 // Example, data before shallow copy:
174 // | area-1 (1 = namespace id) | dummy |
175 // | area-1-origin1 | 1 (mapid) |
176 // | area-2 | dummy |
177 // | area-2-origin1 | 1 (mapid) | references the same map
178 // | map-1 | 2 (refcount) |
179 // | map-1-a | b | a = b in map 1
180
181 // Example, data before shallow copy:
182 // | area-1 (1 = namespace id) | dummy |
183 // | area-1-origin1 | 1 (mapid) |
184 // | area-2 | dummy |
185 // | area-2-origin1 | 2 (mapid) | references the new map
186 // | map-1 | 1 (dec. refcount) |
187 // | map-1-a | b | a = b in map 1
188 // | map-2 | 1 (refcount) |
189 // | map-2-a | b | a = b in map 2
190
191 if (!LazyOpen(true)) {
192 return false;
193 }
194
195 leveldb::Status s;
196 std::string area_key = AreaKey(namespace_id, origin);
197 leveldb::WriteBatch batch;
198 std::string old_map_id;
199 s = db_->Get(leveldb::ReadOptions(), area_key, &old_map_id);
200 DCHECK(s.ok());
201 std::string new_map_id;
202 CreateNewMap(area_key, &batch, &new_map_id);
203
204 DecreaseRefCount(old_map_id, &batch);
205
206 // Copy the values in the map.
207 ValuesMap values;
208 ReadValuesInMap(old_map_id, &values);
209 WriteValuesToMap(new_map_id, values, &batch);
210
211 s = db_->Write(leveldb::WriteOptions(), &batch);
212 DCHECK(s.ok());
213 return true;
214 }
215
216 void SessionStorageDatabase::DeleteOrigin(int64 namespace_id,
217 const GURL& origin) {
218 if (!LazyOpen(false)) {
219 // No need to create the database if it doesn't exist.
220 return;
221 }
222 leveldb::WriteBatch batch;
223 DeleteOrigin(base::Int64ToString(namespace_id), origin.spec(), &batch);
224 leveldb::Status s = db_->Write(leveldb::WriteOptions(), &batch);
225 DCHECK(s.ok());
226 }
227
228 void SessionStorageDatabase::DeleteNamespace(int64 namespace_id) {
229 if (!LazyOpen(false)) {
230 // No need to create the database if it doesn't exist.
231 return;
232 }
233 leveldb::WriteBatch batch;
234 DeleteNamespace(base::Int64ToString(namespace_id), &batch);
235 leveldb::Status s = db_->Write(leveldb::WriteOptions(), &batch);
236 DCHECK(s.ok());
237 }
238
239 void SessionStorageDatabase::DeleteLeftoverData() {
michaeln 2012/04/12 01:48:46 As coded, this method appears to delete everything
marja 2012/04/19 10:20:50 I was planning to do the "protect some origins fro
240 if (!LazyOpen(false)) {
241 // No need to create the database if it doesn't exist.
242 return;
243 }
244 leveldb::WriteBatch batch;
245 scoped_ptr<leveldb::Iterator> it(db_->NewIterator(leveldb::ReadOptions()));
246 for (it->SeekToFirst(); it->Valid(); it->Next()) {
247 // If is of the form "area-<namespaceid>", delete the corresponding
248 // namespace.
249 std::string key = it->key().ToString();
250 if (key.find("area-") != 0) {
251 // Itereated past the namespaces.
252 break;
253 }
254 size_t second_dash = key.find('-', 5);
255 if (second_dash == std::string::npos) {
256 std::string namespace_id = key.substr(5);
257 DeleteNamespace(namespace_id, &batch);
258 }
259 }
260 leveldb::Status s = db_->Write(leveldb::WriteOptions(), &batch);
261 DCHECK(s.ok());
262 }
263
264 bool SessionStorageDatabase::LazyOpen(bool create_if_needed) {
265 if (failed_to_open_) {
266 // Don't try to open a database that we know has failed
267 // already.
268 return false;
269 }
270
271 if (IsOpen())
272 return true;
273
274 bool directory_exists = file_util::PathExists(file_path_);
275
276 if (!directory_exists && !create_if_needed) {
277 // If the directory doesn't exist already and we haven't been asked to
278 // create a file on disk, then we don't bother opening the database. This
279 // means we wait until we absolutely need to put something onto disk before
280 // we do so.
281 return false;
282 }
283
284 leveldb::Options options;
285 // The directory exists but a valid leveldb database might not exist inside it
286 // (e.g., a subset of the needed fiels might be missing). Handle this
287 // situation gracefully by creating the database now.
michaeln 2012/04/12 01:48:46 tzik recently added logic to handle this sort of s
marja 2012/04/19 10:20:50 Do you mean the recovery options in FileSystemDire
288 options.create_if_missing = true;
289 leveldb::Status s;
290 leveldb::DB* db;
291 #if defined(OS_WIN)
292 s = leveldb::DB::Open(options, WideToUTF8(file_path_.value()), &db);
293 #elif defined(OS_POSIX)
294 s = leveldb::DB::Open(options, file_path_.value(), &db);
295 #endif
296 if (!s.ok()) {
297 LOG(WARNING) << "Failed to open leveldb in " << file_path_.value()
298 << ", error: " << s.ToString();
299 DCHECK(db == NULL);
300 failed_to_open_ = true;
301 return false;
302 }
303
304 db_.reset(db);
305 return true;
306 }
307
308 bool SessionStorageDatabase::IsOpen() const {
309 return db_.get();
310 }
311
312 void SessionStorageDatabase::ReadValuesInMap(const std::string& map_id,
313 ValuesMap* result) {
314 scoped_ptr<leveldb::Iterator> it(db_->NewIterator(leveldb::ReadOptions()));
315 std::string map_start_key = MapStartKey(map_id);
316 // Skip the dummy entry, then start iterating the keys in the map.
317 for (it->Seek(map_start_key), it->Next(); it->Valid(); it->Next()) {
michaeln 2012/04/12 01:48:46 is it valid to call Next() on an invalid iterator,
marja 2012/04/19 10:20:50 Done. (It's not ok to call Next() on an invalid it
318 // Key is of the form "map-<mapid>-<key>".
319 std::string key = it->key().ToString();
320 size_t second_dash = key.find('-', 4);
321 if (second_dash == std::string::npos ||
322 key.substr(4, second_dash - 4) != map_id) {
323 // Iterated beyond the keys in this map.
324 break;
325 }
326 string16 key16 = UTF8ToUTF16(key.substr(second_dash + 1));
327 // Convert the raw data stored in std::string (it->value()) to raw data
328 // stored in string16.
329 // FIXME(marja): Add tests.
330 string16 value;
331 size_t len = it->value().size() / sizeof(char16);
332 value.resize(len);
333 value.assign(reinterpret_cast<const char16*>(it->value().data()), len);
334 (*result)[key16] = NullableString16(value, false);
335 }
336 }
337
338 bool SessionStorageDatabase::CreateNewMap(const std::string& area_key,
339 leveldb::WriteBatch* batch,
340 std::string* map_id) {
341 // Create a new map.
342 std::string next_map_id_key = NextMapIdKey();
343 leveldb::Status s = db_->Get(leveldb::ReadOptions(), next_map_id_key, map_id);
344 DCHECK(s.ok() || s.IsNotFound());
345 int64 next_map_id = 0;
346 if (s.IsNotFound()) {
347 *map_id = "0";
348 } else {
349 bool conversion_ok = base::StringToInt64(*map_id, &next_map_id);
350 DCHECK(conversion_ok);
351 // FIXME(marja): What to do if the database is corrupt?
352 }
353 batch->Put(next_map_id_key, base::Int64ToString(++next_map_id));
354 batch->Put(area_key, *map_id);
355 batch->Put(MapStartKey(*map_id), base::Int64ToString(1));
356 return true;
357 }
358
359 void SessionStorageDatabase::WriteValuesToMap(const std::string& map_id,
360 const ValuesMap& values,
361 leveldb::WriteBatch* batch) {
362 for(ValuesMap::const_iterator it = values.begin(); it != values.end(); ++it) {
363 NullableString16 value = it->second;
364 std::string key = MapKey(map_id, UTF16ToUTF8(it->first));
365 if (value.is_null()) {
366 batch->Delete(key);
367 } else {
368 // Convert the raw data stored in string16 to raw data stored in
369 // std::string.
370 // FIXME(marja): Add tests.
371 const char* data = reinterpret_cast<const char*>(value.string().data());
372 size_t size = value.string().size() * 2;
373 batch->Put(key, std::string(data, size));
374 }
375 }
376 }
377
378 void SessionStorageDatabase::DecreaseRefCount(const std::string& map_id,
379 leveldb::WriteBatch* batch) {
380 // Decrease the ref count for the map.
381 std::string map_key = MapStartKey(map_id);
382 std::string ref_count_string;
383 leveldb::Status s =
384 db_->Get(leveldb::ReadOptions(), map_key, &ref_count_string);
385 DCHECK(s.ok());
386 int64 ref_count;
387 bool conversion_ok = base::StringToInt64(ref_count_string, &ref_count);
388 DCHECK(conversion_ok);
389 if (--ref_count > 0) {
390 batch->Put(map_key, base::Int64ToString(ref_count));
391 } else {
392 // Clear all keys in the map.
393 DeleteValuesInMap(map_id, batch);
michaeln 2012/04/12 01:48:46 This method name is a little decieving since it al
marja 2012/04/19 10:20:50 Done. DeleteValuesInMap -> ClearMap.
394 batch->Delete(map_key);
395 }
396 }
397
398 void SessionStorageDatabase::DeleteValuesInMap(const std::string& map_id,
399 leveldb::WriteBatch* batch) {
400 ValuesMap values;
401 ReadValuesInMap(map_id, &values);
michaeln 2012/04/12 01:48:46 Might be nice to read the keys only since that's a
marja 2012/04/19 10:20:50 Done. (Only reading the keys, not the RangeDelete.
402 for (ValuesMap::const_iterator it = values.begin(); it != values.end(); ++it)
403 batch->Delete(MapKey(map_id, UTF16ToUTF8(it->first)));
404 }
405
406 void SessionStorageDatabase::DeleteOrigin(const std::string& namespace_id,
407 const std::string& origin,
408 leveldb::WriteBatch* batch) {
409 std::string area_key = AreaKey(namespace_id, origin);
410 std::string map_id;
411 leveldb::Status s = db_->Get(leveldb::ReadOptions(), area_key, &map_id);
412 DCHECK(s.ok() || s.IsNotFound());
413 if (s.IsNotFound())
414 return; // Nothing to delete.
415 DecreaseRefCount(map_id, batch);
416 batch->Delete(area_key);
417 }
418
419 void SessionStorageDatabase::DeleteNamespace(const std::string& namespace_id,
420 leveldb::WriteBatch* batch) {
421 std::string area_start_key = AreaStartKey(namespace_id);
422 // Skip the dummy entry, then start iterating the origins in the area.
michaeln 2012/04/12 01:48:46 The "origins in the area" is a confusing comment s
marja 2012/04/19 10:20:50 Done. (Fixed the comment, AreaStartKey is now Name
423 scoped_ptr<leveldb::Iterator> it(db_->NewIterator(leveldb::ReadOptions()));
424 for (it->Seek(area_start_key), it->Next(); it->Valid(); it->Next()) {
425 // Key is of the form "area-<namespaceid>-<origin>".
426 std::string key = it->key().ToString();
427 size_t second_dash = key.find('-', 5);
428 if (second_dash == std::string::npos ||
429 key.substr(5, second_dash - 5) != namespace_id) {
430 // Iterated beyond the keys in this map.
431 break;
432 }
433 std::string origin = key.substr(second_dash + 1);
434 DeleteOrigin(namespace_id, origin, batch);
435 }
436 batch->Delete(area_start_key);
437 }
438
439 // FIXME(marja): Remove this (or dump more intelligently).
440 void SessionStorageDatabase::DumpData() const {
441 scoped_ptr<leveldb::Iterator> it(db_->NewIterator(leveldb::ReadOptions()));
442 LOG(WARNING) << "Dumping session storage";
443 for (it->SeekToFirst(); it->Valid(); it->Next())
444 LOG(WARNING) << it->key().ToString() << ": " << it->value().ToString();
445 LOG(WARNING) << "Dumping session storage complete";
446 }
447
448 } // namespace dom_storage
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698