OLD | NEW |
---|---|
(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 | |
OLD | NEW |