OLD | NEW |
---|---|
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "content/browser/service_worker/service_worker_database.h" | 5 #include "content/browser/service_worker/service_worker_database.h" |
6 | 6 |
7 #include <string> | 7 #include <string> |
8 | 8 |
9 #include "base/file_util.h" | 9 #include "base/file_util.h" |
10 #include "base/location.h" | |
10 #include "base/logging.h" | 11 #include "base/logging.h" |
11 #include "base/strings/string_number_conversions.h" | 12 #include "base/strings/string_number_conversions.h" |
13 #include "base/strings/string_split.h" | |
14 #include "base/strings/string_util.h" | |
15 #include "content/browser/service_worker/service_worker_database.pb.h" | |
michaeln
2014/04/24 00:35:04
so a build step on the .proto file produces c stru
nhiroki
2014/04/24 05:57:30
Yes, you're right.
| |
12 #include "third_party/leveldatabase/src/helpers/memenv/memenv.h" | 16 #include "third_party/leveldatabase/src/helpers/memenv/memenv.h" |
13 #include "third_party/leveldatabase/src/include/leveldb/db.h" | 17 #include "third_party/leveldatabase/src/include/leveldb/db.h" |
14 #include "third_party/leveldatabase/src/include/leveldb/env.h" | 18 #include "third_party/leveldatabase/src/include/leveldb/env.h" |
15 #include "third_party/leveldatabase/src/include/leveldb/write_batch.h" | 19 #include "third_party/leveldatabase/src/include/leveldb/write_batch.h" |
16 | 20 |
17 // LevelDB database schema | 21 // LevelDB database schema |
18 // ======================= | 22 // ======================= |
19 // | 23 // |
24 // NOTE | |
25 // - int64 value is serialized as a string by base::Int64ToString(). | |
26 // | |
20 // Version 1 (in sorted order) | 27 // Version 1 (in sorted order) |
21 // key: "DB_VERSION" | 28 // key: "DB_VERSION" |
22 // value: <int64 serialized as a string> | 29 // value: "1" |
23 // | 30 // |
24 // key: "NEXT_REGISTRATION_ID" | 31 // key: "NEXT_REGISTRATION_ID" |
25 // value: <int64 serialized as a string> | 32 // value: <int64 'next_available_registration_id'> |
26 // | 33 // |
27 // key: "NEXT_RESOURCE_ID" | 34 // key: "NEXT_RESOURCE_ID" |
28 // value: <int64 serialized as a string> | 35 // value: <int64 'next_available_resource_id'> |
29 // | 36 // |
30 // key: "NEXT_VERSION_ID" | 37 // key: "NEXT_VERSION_ID" |
31 // value: <int64 serialized as a string> | 38 // value: <int64 'next_available_version_id'> |
39 // | |
40 // key: "PRES:" + <int64 'purgeable_resource_id'> | |
41 // value: <empty> | |
42 // | |
43 // key: "REG:" + (1) + '\x00' + (2) | |
44 // (1) <GURL 'origin_url' serialized by GURL::spec()> | |
45 // (2) <int64 'registration_id'> | |
46 // (ex. "REG:http://example.com\x00123456") | |
47 // value: <ServiceWorkerRegistrationData serialized as a string> | |
48 // | |
49 // key: "RES:" + <int64 'version_id'> + '\x00' + <int64 'resource_id'> | |
50 // (ex. "RES:123456\x00654321") | |
51 // value: <ServiceWorkerResourceRecord serialized as a string> | |
52 // | |
53 // key: "URES:" + <int64 'uncommitted_resource_id'> | |
54 // value: <empty> | |
32 | 55 |
33 namespace content { | 56 namespace content { |
34 | 57 |
35 namespace { | 58 namespace { |
36 | 59 |
37 const char kDatabaseVersionKey[] = "DB_VERSION"; | 60 const char kDatabaseVersionKey[] = "DB_VERSION"; |
38 const char kNextRegIdKey[] = "NEXT_REGISTRATION_ID"; | 61 const char kNextRegIdKey[] = "NEXT_REGISTRATION_ID"; |
39 const char kNextResIdKey[] = "NEXT_RESOURCE_ID"; | 62 const char kNextResIdKey[] = "NEXT_RESOURCE_ID"; |
40 const char kNextVerIdKey[] = "NEXT_VERSION_ID"; | 63 const char kNextVerIdKey[] = "NEXT_VERSION_ID"; |
41 | 64 |
65 const char kRegKeyPrefix[] = "REG:"; | |
66 const char kResKeyPrefix[] = "RES:"; | |
67 const char kUncommittedResKeyPrefix[] = "URES:"; | |
68 const char kPurgeableResKeyPrefix[] = "PRES:"; | |
69 const char kKeySeparator = '\x00'; | |
70 | |
42 const int64 kCurrentSchemaVersion = 1; | 71 const int64 kCurrentSchemaVersion = 1; |
43 | 72 |
44 } // namespace | 73 std::string RemovePrefix(const std::string& str, |
jsbell
2014/04/23 22:55:54
This is a little weird since you can't tell if it
nhiroki
2014/04/24 12:12:38
Done.
| |
45 | 74 const std::string& prefix) { |
46 ServiceWorkerDatabase::RegistrationData::RegistrationData() | 75 if (StartsWithASCII(str, prefix, true)) |
47 : registration_id(-1), | 76 return str.substr(prefix.size()); |
48 version_id(-1), | 77 return str; |
49 is_active(false), | |
50 has_fetch_handler(false) { | |
51 } | 78 } |
52 | 79 |
53 ServiceWorkerDatabase::RegistrationData::~RegistrationData() { | 80 std::string CreateRegistrationKey(const ServiceWorkerRegistrationData& data) { |
81 GURL origin = GURL(data.scope_url()).GetOrigin(); | |
82 std::ostringstream out; | |
jsbell
2014/04/23 22:55:54
#include <sstream> for ostringstream?
michaeln
2014/04/24 00:35:04
should probably use #include "base/strings/stringp
nhiroki
2014/04/24 12:12:38
Replaced them with StringPrintf.
| |
83 out << kRegKeyPrefix << origin.spec() << kKeySeparator | |
84 << base::Int64ToString(data.registration_id()); | |
85 return out.str(); | |
54 } | 86 } |
55 | 87 |
88 std::string CreateResourceKey( | |
89 const ServiceWorkerResourceRecord& resource, | |
90 int64 version_id) { | |
91 std::ostringstream out; | |
92 out << kResKeyPrefix << base::Int64ToString(version_id) | |
93 << kKeySeparator << base::Int64ToString(resource.resource_id()); | |
94 return out.str(); | |
95 } | |
96 | |
97 std::string CreateUncommittedResourceKey(int64 resource_id) { | |
98 std::ostringstream out; | |
99 out << kUncommittedResKeyPrefix << base::Int64ToString(resource_id); | |
100 return out.str(); | |
101 } | |
102 | |
103 std::string CreatePurgeableResourceKey(int64 resource_id) { | |
104 std::ostringstream out; | |
105 out << kPurgeableResKeyPrefix << base::Int64ToString(resource_id); | |
106 return out.str(); | |
107 } | |
108 | |
109 void PutRegistrationDataToBatch(const ServiceWorkerRegistrationData& data, | |
110 leveldb::WriteBatch* batch) { | |
111 DCHECK(batch); | |
112 std::string value; | |
113 bool success = data.SerializeToString(&value); | |
114 DCHECK(success); | |
115 batch->Put(CreateRegistrationKey(data), value); | |
116 } | |
117 | |
118 void PutResourceRecordToBatch(const ServiceWorkerResourceRecord& resource, | |
119 int64 version_id, | |
120 leveldb::WriteBatch* batch) { | |
121 DCHECK(batch); | |
122 std::string value; | |
123 bool success = resource.SerializeToString(&value); | |
124 DCHECK(success); | |
125 batch->Put(CreateResourceKey(resource, version_id), value); | |
126 } | |
127 | |
128 bool ParseRegistrationKey(const std::string& key, | |
129 GURL* origin_out, | |
130 int64* registration_id_out) { | |
131 std::string unprefixed = RemovePrefix(key, kRegKeyPrefix); | |
132 std::vector<std::string> tokens; | |
133 base::SplitString(unprefixed, kKeySeparator, &tokens); | |
134 if (tokens.size() != 2) | |
135 return false; | |
136 | |
137 GURL origin(tokens[0]); | |
138 if (!origin.is_valid()) | |
139 return false; | |
140 | |
141 int64 registration_id; | |
142 if (!base::StringToInt64(tokens[1], ®istration_id)) | |
143 return false; | |
144 | |
145 if (origin_out) | |
146 *origin_out = origin; | |
147 if (registration_id_out) | |
148 *registration_id_out = registration_id; | |
149 return true; | |
150 } | |
151 | |
152 bool ParseResourceKey(const std::string& key, | |
153 int64* version_id_out, | |
154 int64* resource_id_out) { | |
155 std::string unprefixed = RemovePrefix(key, kResKeyPrefix); | |
156 std::vector<std::string> tokens; | |
157 base::SplitString(unprefixed, kKeySeparator, &tokens); | |
158 if (tokens.size() != 2) | |
159 return false; | |
160 | |
161 int64 version_id; | |
162 if (!base::StringToInt64(tokens[0], &version_id)) | |
163 return false; | |
164 | |
165 int64 resource_id; | |
166 if (!base::StringToInt64(tokens[1], &resource_id)) | |
167 return false; | |
168 | |
169 if (version_id_out) | |
170 *version_id_out = version_id; | |
171 if (resource_id_out) | |
172 *resource_id_out = resource_id; | |
173 return true; | |
174 } | |
175 | |
176 } // namespace | |
177 | |
56 ServiceWorkerDatabase::ServiceWorkerDatabase(const base::FilePath& path) | 178 ServiceWorkerDatabase::ServiceWorkerDatabase(const base::FilePath& path) |
57 : path_(path), | 179 : path_(path), |
58 is_disabled_(false), | 180 is_disabled_(false), |
59 was_corruption_detected_(false) { | 181 was_corruption_detected_(false) { |
60 } | 182 } |
61 | 183 |
62 ServiceWorkerDatabase::~ServiceWorkerDatabase() { | 184 ServiceWorkerDatabase::~ServiceWorkerDatabase() { |
63 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); | 185 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); |
64 db_.reset(); | 186 db_.reset(); |
65 } | 187 } |
66 | 188 |
67 bool ServiceWorkerDatabase::GetNextAvailableIds( | 189 bool ServiceWorkerDatabase::GetNextAvailableIds( |
68 int64* next_avail_registration_id, | 190 int64* next_avail_registration_id, |
69 int64* next_avail_version_id, | 191 int64* next_avail_version_id, |
70 int64* next_avail_resource_id) { | 192 int64* next_avail_resource_id) { |
71 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); | 193 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); |
72 DCHECK(next_avail_registration_id); | 194 DCHECK(next_avail_registration_id); |
73 DCHECK(next_avail_version_id); | 195 DCHECK(next_avail_version_id); |
74 DCHECK(next_avail_resource_id); | 196 DCHECK(next_avail_resource_id); |
75 | 197 |
76 if (!LazyOpen(false) || is_disabled_) | 198 if (!LazyOpen(false) || is_disabled_) |
77 return false; | 199 return false; |
78 | 200 |
79 int64 reg_id = -1; | 201 int64 reg_id = -1; |
80 int64 ver_id = -1; | 202 int64 ver_id = -1; |
81 int64 res_id = -1; | 203 int64 res_id = -1; |
82 | 204 |
83 if (!ReadInt64(kNextRegIdKey, ®_id) || | 205 if (!ReadNextAvailableId(kNextRegIdKey, ®_id) || |
84 !ReadInt64(kNextVerIdKey, &ver_id) || | 206 !ReadNextAvailableId(kNextVerIdKey, &ver_id) || |
85 !ReadInt64(kNextResIdKey, &res_id)) | 207 !ReadNextAvailableId(kNextResIdKey, &res_id)) |
86 return false; | 208 return false; |
87 | 209 |
88 *next_avail_registration_id = reg_id; | 210 *next_avail_registration_id = reg_id; |
89 *next_avail_version_id = ver_id; | 211 *next_avail_version_id = ver_id; |
90 *next_avail_resource_id = res_id; | 212 *next_avail_resource_id = res_id; |
91 return true; | 213 return true; |
92 } | 214 } |
93 | 215 |
216 bool ServiceWorkerDatabase::GetOriginsWithRegistrations( | |
michaeln
2014/04/24 00:35:04
This method and GetNextAvailableIds will be called
nhiroki
2014/04/24 05:57:30
I looked over some docs and sourcecode for leveldb
nhiroki
2014/04/24 12:12:38
Done.
| |
217 std::set<GURL>* origins) { | |
218 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); | |
219 DCHECK(origins); | |
220 | |
221 if (!LazyOpen(false) || is_disabled_) | |
222 return false; | |
223 | |
224 GURL origin; | |
225 scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions())); | |
226 for (itr->Seek(kRegKeyPrefix); itr->Valid(); itr->Next()) { | |
227 std::string key = itr->key().ToString(); | |
228 if (!StartsWithASCII(key, kRegKeyPrefix, true)) | |
229 break; | |
230 | |
231 if (!ParseRegistrationKey(key, &origin, NULL /* registration_id */)) { | |
232 HandleError(FROM_HERE, leveldb::Status::Corruption("failed to parse")); | |
233 origins->clear(); | |
234 return false; | |
235 } | |
236 origins->insert(origin); | |
237 } | |
238 return true; | |
jsbell
2014/04/23 22:55:54
If the iterator fails (!itr->status().ok()) should
nhiroki
2014/04/24 12:12:38
Done.
| |
239 } | |
240 | |
241 bool ServiceWorkerDatabase::GetRegistrationsForOrigin( | |
242 const GURL& origin, | |
243 std::vector<ServiceWorkerRegistrationData>* registrations) { | |
244 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); | |
245 DCHECK(registrations); | |
246 | |
247 if (!LazyOpen(false) || is_disabled_) | |
248 return false; | |
249 | |
250 // Create a key prefix for registrations. | |
251 std::ostringstream out; | |
252 out << kRegKeyPrefix << origin.spec() << kKeySeparator; | |
253 std::string prefix = out.str(); | |
254 | |
255 scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions())); | |
256 for (itr->Seek(prefix); itr->Valid(); itr->Next()) { | |
257 std::string key = itr->key().ToString(); | |
258 if (!StartsWithASCII(key, prefix, true)) | |
259 break; | |
260 | |
261 ServiceWorkerRegistrationData registration; | |
262 if (!registration.ParseFromString(itr->value().ToString())) { | |
jsbell
2014/04/23 22:55:54
Is there any other post-parsing sanity checking th
nhiroki
2014/04/24 12:12:38
For now, we seems to be able to check only script/
| |
263 HandleError(FROM_HERE, leveldb::Status::Corruption("failed to parse")); | |
264 registrations->clear(); | |
265 return false; | |
266 } | |
267 | |
268 registrations->push_back(registration); | |
269 } | |
270 return true; | |
271 } | |
272 | |
273 bool ServiceWorkerDatabase::ReadRegistration( | |
274 int64 registration_id, | |
275 ServiceWorkerRegistrationData* registration, | |
276 std::vector<ServiceWorkerResourceRecord>* resources) { | |
277 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); | |
278 DCHECK(registration); | |
279 DCHECK(resources); | |
280 | |
281 if (!LazyOpen(false) || is_disabled_) | |
282 return false; | |
283 | |
284 ServiceWorkerRegistrationData value; | |
285 if (!ReadRegistrationData(registration_id, &value)) | |
286 return false; | |
287 | |
288 if (!ReadResourceRecords(value.version_id(), resources)) | |
289 return false; | |
290 | |
291 *registration = value; | |
292 return true; | |
293 } | |
294 | |
295 bool ServiceWorkerDatabase::WriteRegistration( | |
296 const ServiceWorkerRegistrationData& registration, | |
297 const std::vector<ServiceWorkerResourceRecord>& resources) { | |
298 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); | |
299 if (!LazyOpen(true) || is_disabled_) | |
300 return false; | |
301 | |
302 leveldb::WriteBatch batch; | |
303 if (!BumpNextAvailableIdIfNeeded( | |
304 kNextRegIdKey, registration.registration_id(), &batch) || | |
305 !BumpNextAvailableIdIfNeeded( | |
306 kNextVerIdKey, registration.version_id(), &batch)) { | |
307 return false; | |
308 } | |
309 | |
310 PutRegistrationDataToBatch(registration, &batch); | |
311 | |
312 std::vector<ServiceWorkerResourceRecord>::const_iterator itr; | |
313 for (itr = resources.begin(); itr != resources.end(); ++itr) | |
314 PutResourceRecordToBatch(*itr, registration.version_id(), &batch); | |
michaeln
2014/04/24 00:35:04
If it helps, we could defer dealing with ResourceR
nhiroki
2014/04/24 12:12:38
Okay, dropped them.
| |
315 | |
316 return WriteBatch(&batch); | |
317 } | |
318 | |
319 bool ServiceWorkerDatabase::UpdateVersionToActive(int64 registration_id) { | |
320 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); | |
321 if (!LazyOpen(false) || is_disabled_) | |
322 return false; | |
323 | |
324 ServiceWorkerRegistrationData data; | |
325 if (!ReadRegistrationData(registration_id, &data)) | |
326 return false; | |
327 | |
328 data.set_is_active(true); | |
329 | |
330 leveldb::WriteBatch batch; | |
331 PutRegistrationDataToBatch(data, &batch); | |
332 return WriteBatch(&batch); | |
333 } | |
334 | |
335 bool ServiceWorkerDatabase::UpdateLastCheckTime( | |
336 int64 registration_id, | |
337 const base::Time& time) { | |
338 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); | |
339 if (!LazyOpen(false) || is_disabled_) | |
340 return false; | |
341 | |
342 ServiceWorkerRegistrationData data; | |
343 if (!ReadRegistrationData(registration_id, &data)) | |
344 return false; | |
345 | |
346 data.set_last_update_check_time(time.ToInternalValue()); | |
347 | |
348 leveldb::WriteBatch batch; | |
349 PutRegistrationDataToBatch(data, &batch); | |
350 return WriteBatch(&batch); | |
351 } | |
352 | |
353 bool ServiceWorkerDatabase::DeleteRegistration( | |
354 int64 registration_id) { | |
355 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); | |
356 if (!LazyOpen(false) || is_disabled_) | |
357 return false; | |
358 | |
359 leveldb::WriteBatch batch; | |
360 ServiceWorkerRegistrationData data; | |
361 if (!ReadRegistrationData(registration_id, &data)) { | |
362 if (is_disabled_) | |
363 return false; | |
364 // Just not found. | |
365 return true; | |
366 } | |
367 batch.Delete(CreateRegistrationKey(data)); | |
368 | |
369 // Create a key prefix for resource records. | |
370 std::ostringstream out; | |
371 out << kResKeyPrefix << data.version_id() << kKeySeparator; | |
372 std::string prefix = out.str(); | |
373 | |
374 scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions())); | |
375 for (itr->Seek(prefix); itr->Valid(); itr->Next()) { | |
376 std::string key = itr->key().ToString(); | |
377 if (!StartsWithASCII(key, prefix, true)) | |
378 break; | |
379 batch.Delete(key); | |
380 } | |
381 | |
382 return WriteBatch(&batch); | |
383 } | |
384 | |
385 bool ServiceWorkerDatabase::GetUncommittedResourceIds(std::set<int64>* ids) { | |
386 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); | |
387 DCHECK(ids); | |
388 | |
389 if (!LazyOpen(false) || is_disabled_) | |
390 return false; | |
391 | |
392 scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions())); | |
393 for (itr->Seek(kUncommittedResKeyPrefix); itr->Valid(); itr->Next()) { | |
394 std::string key = itr->key().ToString(); | |
395 if (!StartsWithASCII(key, kUncommittedResKeyPrefix, true)) | |
396 break; | |
397 | |
398 std::string unprefixed = RemovePrefix(key, kUncommittedResKeyPrefix); | |
399 int64 resource_id; | |
400 if (!base::StringToInt64(unprefixed, &resource_id)) { | |
401 HandleError(FROM_HERE, leveldb::Status::Corruption("failed to parse")); | |
402 ids->clear(); | |
403 return false; | |
404 } | |
405 ids->insert(resource_id); | |
406 } | |
407 return true; | |
408 } | |
409 | |
410 bool ServiceWorkerDatabase::WriteUncommittedResourceIds( | |
411 const std::set<int64>& ids) { | |
412 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); | |
413 if (!LazyOpen(true) || is_disabled_) | |
414 return false; | |
415 if (ids.empty()) | |
416 return true; | |
417 | |
418 leveldb::WriteBatch batch; | |
419 for (std::set<int64>::const_iterator itr = ids.begin(); | |
420 itr != ids.end(); ++itr) { | |
421 // Value should be empty. | |
422 batch.Put(CreateUncommittedResourceKey(*itr), ""); | |
423 } | |
424 return WriteBatch(&batch); | |
425 } | |
426 | |
427 bool ServiceWorkerDatabase::ClearUncommittedResourceIds( | |
428 const std::set<int64>& ids) { | |
429 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); | |
430 if (!LazyOpen(true) || is_disabled_) | |
431 return false; | |
432 if (ids.empty()) | |
433 return true; | |
434 | |
435 leveldb::WriteBatch batch; | |
436 for (std::set<int64>::const_iterator itr = ids.begin(); | |
437 itr != ids.end(); ++itr) { | |
438 batch.Delete(CreateUncommittedResourceKey(*itr)); | |
439 } | |
440 return WriteBatch(&batch); | |
441 } | |
442 | |
443 bool ServiceWorkerDatabase::GetPurgeableResourceIds(std::set<int64>* ids) { | |
444 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); | |
445 DCHECK(ids); | |
446 | |
447 if (!LazyOpen(false) || is_disabled_) | |
448 return false; | |
449 | |
450 scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions())); | |
451 for (itr->Seek(kPurgeableResKeyPrefix); itr->Valid(); itr->Next()) { | |
452 std::string key = itr->key().ToString(); | |
453 if (!StartsWithASCII(key, kPurgeableResKeyPrefix, true)) | |
454 break; | |
455 | |
456 std::string unprefixed = RemovePrefix(key, kPurgeableResKeyPrefix); | |
457 int64 resource_id; | |
458 if (!base::StringToInt64(unprefixed, &resource_id)) { | |
459 HandleError(FROM_HERE, leveldb::Status::Corruption("failed to parse")); | |
460 ids->clear(); | |
461 return false; | |
462 } | |
463 ids->insert(resource_id); | |
464 } | |
465 return true; | |
466 } | |
467 | |
468 bool ServiceWorkerDatabase::WritePurgeableResourceIds( | |
469 const std::set<int64>& ids) { | |
470 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); | |
471 if (!LazyOpen(true) || is_disabled_) | |
472 return false; | |
473 if (ids.empty()) | |
474 return true; | |
475 | |
476 leveldb::WriteBatch batch; | |
477 for (std::set<int64>::const_iterator itr = ids.begin(); | |
478 itr != ids.end(); ++itr) { | |
479 // Value should be empty. | |
480 batch.Put(CreatePurgeableResourceKey(*itr), ""); | |
481 } | |
482 return WriteBatch(&batch); | |
483 } | |
484 | |
485 bool ServiceWorkerDatabase::ClearPurgeableResourceIds( | |
486 const std::set<int64>& ids) { | |
487 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); | |
488 if (!LazyOpen(true) || is_disabled_) | |
489 return false; | |
490 if (ids.empty()) | |
491 return true; | |
492 | |
493 leveldb::WriteBatch batch; | |
494 for (std::set<int64>::const_iterator itr = ids.begin(); | |
495 itr != ids.end(); ++itr) { | |
496 batch.Delete(CreatePurgeableResourceKey(*itr)); | |
497 } | |
498 return WriteBatch(&batch); | |
499 } | |
500 | |
501 bool ServiceWorkerDatabase::DeleteAllDataForOrigin( | |
502 const GURL& origin) { | |
503 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); | |
504 NOTIMPLEMENTED(); | |
505 return false; | |
506 } | |
507 | |
508 bool ServiceWorkerDatabase::DeleteAllData() { | |
509 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); | |
510 NOTIMPLEMENTED(); | |
511 return false; | |
512 } | |
513 | |
94 bool ServiceWorkerDatabase::LazyOpen(bool create_if_needed) { | 514 bool ServiceWorkerDatabase::LazyOpen(bool create_if_needed) { |
95 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); | 515 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); |
96 if (IsOpen()) | 516 if (IsOpen()) |
97 return true; | 517 return true; |
98 | 518 |
99 // Do not try to open a database if we tried and failed once. | 519 // Do not try to open a database if we tried and failed once. |
100 if (is_disabled_) | 520 if (is_disabled_) |
101 return false; | 521 return false; |
102 | 522 |
103 // When |path_| is empty, open a database in-memory. | 523 // When |path_| is empty, open a database in-memory. |
(...skipping 14 matching lines...) Expand all Loading... | |
118 env_.reset(leveldb::NewMemEnv(leveldb::Env::Default())); | 538 env_.reset(leveldb::NewMemEnv(leveldb::Env::Default())); |
119 options.env = env_.get(); | 539 options.env = env_.get(); |
120 } | 540 } |
121 | 541 |
122 leveldb::DB* db = NULL; | 542 leveldb::DB* db = NULL; |
123 leveldb::Status status = | 543 leveldb::Status status = |
124 leveldb::DB::Open(options, path_.AsUTF8Unsafe(), &db); | 544 leveldb::DB::Open(options, path_.AsUTF8Unsafe(), &db); |
125 if (!status.ok()) { | 545 if (!status.ok()) { |
126 DCHECK(!db); | 546 DCHECK(!db); |
127 // TODO(nhiroki): Should we retry to open the database? | 547 // TODO(nhiroki): Should we retry to open the database? |
128 DLOG(ERROR) << "Failed to open LevelDB database: " << status.ToString(); | 548 HandleError(FROM_HERE, status); |
129 is_disabled_ = true; | |
130 return false; | 549 return false; |
131 } | 550 } |
132 db_.reset(db); | 551 db_.reset(db); |
133 | 552 |
134 if (IsEmpty() && !PopulateInitialData()) { | 553 if (IsEmpty() && !PopulateInitialData()) { |
135 DLOG(ERROR) << "Failed to populate the database."; | 554 DLOG(ERROR) << "Failed to populate the database."; |
136 is_disabled_ = true; | |
137 db_.reset(); | 555 db_.reset(); |
138 return false; | 556 return false; |
139 } | 557 } |
140 return true; | 558 return true; |
141 } | 559 } |
142 | 560 |
143 bool ServiceWorkerDatabase::PopulateInitialData() { | 561 bool ServiceWorkerDatabase::PopulateInitialData() { |
144 scoped_ptr<leveldb::WriteBatch> batch(new leveldb::WriteBatch); | 562 leveldb::WriteBatch batch; |
145 batch->Put(kDatabaseVersionKey, base::Int64ToString(kCurrentSchemaVersion)); | 563 batch.Put(kDatabaseVersionKey, base::Int64ToString(kCurrentSchemaVersion)); |
146 batch->Put(kNextRegIdKey, "0"); | 564 batch.Put(kNextRegIdKey, "0"); |
147 batch->Put(kNextResIdKey, "0"); | 565 batch.Put(kNextResIdKey, "0"); |
148 batch->Put(kNextVerIdKey, "0"); | 566 batch.Put(kNextVerIdKey, "0"); |
149 return WriteBatch(batch.Pass()); | 567 return WriteBatch(&batch); |
150 } | 568 } |
151 | 569 |
152 bool ServiceWorkerDatabase::ReadInt64( | 570 bool ServiceWorkerDatabase::ReadNextAvailableId( |
153 const leveldb::Slice& key, | 571 const char* id_key, int64* next_avail_id) { |
154 int64* value_out) { | 572 DCHECK(id_key); |
155 DCHECK(value_out); | 573 DCHECK(next_avail_id); |
156 | 574 |
157 std::string value; | 575 std::string value; |
158 leveldb::Status status = db_->Get(leveldb::ReadOptions(), key, &value); | 576 leveldb::Status status = db_->Get(leveldb::ReadOptions(), id_key, &value); |
159 if (!status.ok()) { | 577 if (!status.ok()) { |
160 DLOG(ERROR) << "Failed to read data keyed by " | 578 HandleError(FROM_HERE, status); |
161 << key.ToString() << ": " << status.ToString(); | |
162 is_disabled_ = true; | |
163 if (status.IsCorruption()) | |
164 was_corruption_detected_ = true; | |
165 return false; | 579 return false; |
166 } | 580 } |
167 | 581 |
168 int64 parsed = -1; | 582 int64 parsed_id; |
169 if (!base::StringToInt64(value, &parsed)) { | 583 if (!base::StringToInt64(value, &parsed_id)) { |
170 DLOG(ERROR) << "Database might be corrupted: " | 584 HandleError(FROM_HERE, leveldb::Status::Corruption("failed to parse")); |
171 << key.ToString() << ", " << value; | |
172 is_disabled_ = true; | |
173 was_corruption_detected_ = true; | |
174 return false; | 585 return false; |
175 } | 586 } |
176 | 587 |
177 *value_out = parsed; | 588 *next_avail_id = parsed_id; |
178 return true; | 589 return true; |
179 } | 590 } |
180 | 591 |
181 bool ServiceWorkerDatabase::WriteBatch(scoped_ptr<leveldb::WriteBatch> batch) { | 592 bool ServiceWorkerDatabase::ReadRegistrationData( |
182 if (!batch) | 593 int64 registration_id, |
594 ServiceWorkerRegistrationData* data_out) { | |
595 int64 parsed_id; | |
596 scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions())); | |
597 for (itr->Seek(kRegKeyPrefix); itr->Valid(); itr->Next()) { | |
michaeln
2014/04/24 00:35:04
Oh no... linear scans. Let's massage things so thi
nhiroki
2014/04/24 05:57:30
That sounds reasonable and I thought I'd like to c
| |
598 std::string key = itr->key().ToString(); | |
599 if (!StartsWithASCII(key, kRegKeyPrefix, true)) | |
600 return false; | |
601 | |
602 if (!ParseRegistrationKey(key, NULL /* origin */, &parsed_id)) { | |
603 HandleError(FROM_HERE, leveldb::Status::Corruption("failed to parse")); | |
604 return false; | |
605 } | |
606 if (registration_id != parsed_id) | |
607 continue; | |
608 | |
609 ServiceWorkerRegistrationData data; | |
610 if (!data.ParseFromString(itr->value().ToString())) { | |
611 HandleError(FROM_HERE, leveldb::Status::Corruption("failed to parse")); | |
612 return false; | |
613 } | |
614 | |
615 *data_out = data; | |
183 return true; | 616 return true; |
617 } | |
184 | 618 |
185 leveldb::Status status = db_->Write(leveldb::WriteOptions(), batch.get()); | 619 return false; |
186 if (status.ok()) | 620 } |
187 return true; | |
188 | 621 |
189 DLOG(ERROR) << "Failed to write the batch: " << status.ToString(); | 622 bool ServiceWorkerDatabase::ReadResourceRecords( |
190 is_disabled_ = true; | 623 int64 version_id, |
191 if (status.IsCorruption()) | 624 std::vector<ServiceWorkerResourceRecord>* resources) { |
192 was_corruption_detected_ = true; | 625 DCHECK(resources); |
193 return false; | 626 |
627 // Create a key prefix for resource records. | |
628 std::ostringstream out; | |
629 out << kResKeyPrefix << version_id << kKeySeparator; | |
630 std::string prefix = out.str(); | |
631 | |
632 int64 resource_id; | |
633 scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions())); | |
634 for (itr->Seek(prefix); itr->Valid(); itr->Next()) { | |
635 std::string key = itr->key().ToString(); | |
636 if (!StartsWithASCII(key, prefix, true)) | |
637 break; | |
638 | |
639 if (!ParseResourceKey(key, NULL /* version_id */, &resource_id)) { | |
640 HandleError(FROM_HERE, leveldb::Status::Corruption("failed to parse")); | |
641 resources->clear(); | |
642 return false; | |
643 } | |
644 | |
645 ServiceWorkerResourceRecord resource; | |
646 if (!resource.ParseFromString(itr->value().ToString())) { | |
647 HandleError(FROM_HERE, leveldb::Status::Corruption("failed to parse")); | |
648 resources->clear(); | |
649 return false; | |
650 } | |
651 | |
652 resources->push_back(resource); | |
653 } | |
654 return true; | |
655 } | |
656 | |
657 bool ServiceWorkerDatabase::WriteBatch(leveldb::WriteBatch* batch) { | |
658 DCHECK(batch); | |
659 leveldb::Status status = db_->Write(leveldb::WriteOptions(), batch); | |
660 if (!status.ok()) { | |
661 HandleError(FROM_HERE, status); | |
662 return false; | |
663 } | |
664 return true; | |
665 } | |
666 | |
667 bool ServiceWorkerDatabase::BumpNextAvailableIdIfNeeded( | |
668 const char* id_key, int64 used_id, leveldb::WriteBatch* batch) { | |
669 DCHECK(batch); | |
670 int64 next_available_id; | |
671 if (!ReadNextAvailableId(id_key, &next_available_id)) | |
672 return false; | |
673 if (next_available_id <= used_id) | |
674 batch->Put(id_key, base::Int64ToString(used_id + 1)); | |
675 return true; | |
194 } | 676 } |
195 | 677 |
196 bool ServiceWorkerDatabase::IsOpen() { | 678 bool ServiceWorkerDatabase::IsOpen() { |
197 return db_.get() != NULL; | 679 return db_.get() != NULL; |
198 } | 680 } |
199 | 681 |
200 bool ServiceWorkerDatabase::IsEmpty() { | 682 bool ServiceWorkerDatabase::IsEmpty() { |
201 scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions())); | 683 scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions())); |
202 itr->SeekToFirst(); | 684 itr->SeekToFirst(); |
203 return !itr->Valid(); | 685 return !itr->Valid(); |
204 } | 686 } |
205 | 687 |
688 void ServiceWorkerDatabase::HandleError( | |
jsbell
2014/04/23 22:55:54
Can you add a TODO: for adding an UMA histogram?
nhiroki
2014/04/24 12:12:38
Done.
| |
689 const tracked_objects::Location& from_here, | |
690 const leveldb::Status& status) { | |
691 DLOG(ERROR) << "Failed at: " << from_here.ToString() | |
692 << " with error: " << status.ToString(); | |
693 is_disabled_ = true; | |
694 if (status.IsCorruption()) | |
695 was_corruption_detected_ = true; | |
696 } | |
697 | |
206 } // namespace content | 698 } // namespace content |
OLD | NEW |