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/location.h" |
11 #include "base/logging.h" | 11 #include "base/logging.h" |
12 #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 "base/strings/stringprintf.h" | |
16 #include "content/browser/service_worker/service_worker_database.pb.h" | |
13 #include "third_party/leveldatabase/src/helpers/memenv/memenv.h" | 17 #include "third_party/leveldatabase/src/helpers/memenv/memenv.h" |
14 #include "third_party/leveldatabase/src/include/leveldb/db.h" | 18 #include "third_party/leveldatabase/src/include/leveldb/db.h" |
15 #include "third_party/leveldatabase/src/include/leveldb/env.h" | 19 #include "third_party/leveldatabase/src/include/leveldb/env.h" |
16 #include "third_party/leveldatabase/src/include/leveldb/write_batch.h" | 20 #include "third_party/leveldatabase/src/include/leveldb/write_batch.h" |
17 | 21 |
18 // LevelDB database schema | 22 // LevelDB database schema |
19 // ======================= | 23 // ======================= |
20 // | 24 // |
21 // NOTE | 25 // NOTE |
22 // - int64 value is serialized as a string by base::Int64ToString(). | 26 // - int64 value is serialized as a string by base::Int64ToString(). |
23 // | 27 // |
24 // Version 1 (in sorted order) | 28 // Version 1 (in sorted order) |
25 // key: "DB_VERSION" | 29 // key: "INITDATA_DB_VERSION" |
26 // value: "1" | 30 // value: "1" |
27 // | 31 // |
28 // key: "NEXT_REGISTRATION_ID" | 32 // key: "INITDATA_NEXT_REGISTRATION_ID" |
29 // value: <int64 'next_available_registration_id'> | 33 // value: <int64 'next_available_registration_id'> |
30 // | 34 // |
31 // key: "NEXT_RESOURCE_ID" | 35 // key: "INITDATA_NEXT_RESOURCE_ID" |
32 // value: <int64 'next_available_resource_id'> | 36 // value: <int64 'next_available_resource_id'> |
33 // | 37 // |
34 // key: "NEXT_VERSION_ID" | 38 // key: "INITDATA_NEXT_VERSION_ID" |
35 // value: <int64 'next_available_version_id'> | 39 // value: <int64 'next_available_version_id'> |
40 // | |
41 // key: "INITDATA_UNIQUE_ORIGIN:" + <GURL 'origin' serialized by GURL::spec()> | |
42 // value: <empty> | |
43 // | |
44 // key: "REG:" + (1) + '\x00' + (2) | |
45 // (1) <GURL 'origin' serialized by GURL::spec()> | |
46 // (2) <int64 'registration_id'> | |
47 // (ex. "REG:http://example.com\x00123456") | |
48 // value: <ServiceWorkerRegistrationData serialized as a string> | |
36 | 49 |
37 namespace content { | 50 namespace content { |
38 | 51 |
39 namespace { | 52 namespace { |
40 | 53 |
41 const char kDatabaseVersionKey[] = "DB_VERSION"; | 54 const char kDatabaseVersionKey[] = "INITDATA_DB_VERSION"; |
42 const char kNextRegIdKey[] = "NEXT_REGISTRATION_ID"; | 55 const char kNextRegIdKey[] = "INITDATA_NEXT_REGISTRATION_ID"; |
43 const char kNextResIdKey[] = "NEXT_RESOURCE_ID"; | 56 const char kNextResIdKey[] = "INITDATA_NEXT_RESOURCE_ID"; |
44 const char kNextVerIdKey[] = "NEXT_VERSION_ID"; | 57 const char kNextVerIdKey[] = "INITDATA_NEXT_VERSION_ID"; |
58 const char kUniqueOriginKey[] = "INITDATA_UNIQUE_ORIGIN:"; | |
59 | |
60 const char kRegKeyPrefix[] = "REG:"; | |
61 const char kKeySeparator = '\x00'; | |
45 | 62 |
46 const int64 kCurrentSchemaVersion = 1; | 63 const int64 kCurrentSchemaVersion = 1; |
47 | 64 |
48 } // namespace | 65 bool RemovePrefix(const std::string& str, |
49 | 66 const std::string& prefix, |
50 ServiceWorkerDatabase::RegistrationData::RegistrationData() | 67 std::string* out) { |
51 : registration_id(-1), | 68 DCHECK(out); |
52 version_id(-1), | 69 if (!StartsWithASCII(str, prefix, true)) |
53 is_active(false), | 70 return false; |
54 has_fetch_handler(false) { | 71 *out = str.substr(prefix.size()); |
72 return true; | |
55 } | 73 } |
56 | 74 |
57 ServiceWorkerDatabase::RegistrationData::~RegistrationData() { | 75 std::string CreateRegistrationKey(int64 registration_id, |
76 const GURL& origin) { | |
77 return base::StringPrintf("%s%s%c%s", | |
78 kRegKeyPrefix, | |
79 origin.spec().c_str(), | |
80 kKeySeparator, | |
81 base::Int64ToString(registration_id).c_str()); | |
58 } | 82 } |
59 | 83 |
84 std::string CreateUniqueOriginKey(const GURL& origin) { | |
85 return base::StringPrintf("%s%s", kUniqueOriginKey, origin.spec().c_str()); | |
86 } | |
87 | |
88 void PutRegistrationDataToBatch(const ServiceWorkerRegistrationData& data, | |
89 leveldb::WriteBatch* batch) { | |
90 DCHECK(batch); | |
91 std::string value; | |
92 bool success = data.SerializeToString(&value); | |
93 DCHECK(success); | |
94 GURL origin = GURL(data.scope_url()).GetOrigin(); | |
michaeln
2014/04/25 00:48:21
Maybe this class should entirely encapsulate the u
nhiroki
2014/04/25 02:06:11
I see. Will make them as required.
kinuko
2014/04/25 03:43:27
Fyi, it's possible to populate a field with a defa
nhiroki
2014/04/25 11:01:05
And there is base::Time issue.
michaeln
2014/04/26 00:29:02
I think the serialization format is an impl detail
| |
95 batch->Put(CreateRegistrationKey(data.registration_id(), origin), value); | |
96 } | |
97 | |
98 void PutUniqueOriginToBatch(const GURL& origin, | |
99 leveldb::WriteBatch* batch) { | |
100 // Value should be empty. | |
101 batch->Put(CreateUniqueOriginKey(origin), ""); | |
102 } | |
103 | |
104 bool ParseRegistrationData(const std::string& serialized, | |
105 ServiceWorkerRegistrationData* out) { | |
106 DCHECK(out); | |
107 ServiceWorkerRegistrationData data; | |
108 if (!data.ParseFromString(serialized)) | |
109 return false; | |
110 if (GURL(data.scope_url()).GetOrigin() != GURL(data.script_url()).GetOrigin()) | |
111 return false; | |
112 *out = data; | |
113 return true; | |
114 } | |
115 | |
116 } // namespace | |
117 | |
60 ServiceWorkerDatabase::ServiceWorkerDatabase(const base::FilePath& path) | 118 ServiceWorkerDatabase::ServiceWorkerDatabase(const base::FilePath& path) |
61 : path_(path), | 119 : path_(path), |
62 is_disabled_(false), | 120 is_disabled_(false), |
63 was_corruption_detected_(false) { | 121 was_corruption_detected_(false) { |
64 } | 122 } |
65 | 123 |
66 ServiceWorkerDatabase::~ServiceWorkerDatabase() { | 124 ServiceWorkerDatabase::~ServiceWorkerDatabase() { |
67 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); | 125 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); |
68 db_.reset(); | 126 db_.reset(); |
69 } | 127 } |
(...skipping 18 matching lines...) Expand all Loading... | |
88 !ReadNextAvailableId(kNextVerIdKey, &ver_id) || | 146 !ReadNextAvailableId(kNextVerIdKey, &ver_id) || |
89 !ReadNextAvailableId(kNextResIdKey, &res_id)) | 147 !ReadNextAvailableId(kNextResIdKey, &res_id)) |
90 return false; | 148 return false; |
91 | 149 |
92 *next_avail_registration_id = reg_id; | 150 *next_avail_registration_id = reg_id; |
93 *next_avail_version_id = ver_id; | 151 *next_avail_version_id = ver_id; |
94 *next_avail_resource_id = res_id; | 152 *next_avail_resource_id = res_id; |
95 return true; | 153 return true; |
96 } | 154 } |
97 | 155 |
156 bool ServiceWorkerDatabase::GetOriginsWithRegistrations( | |
157 std::set<GURL>* origins) { | |
158 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); | |
159 DCHECK(origins); | |
160 | |
161 if (!LazyOpen(false) || is_disabled_) | |
162 return false; | |
163 | |
164 scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions())); | |
165 for (itr->Seek(kUniqueOriginKey); itr->Valid(); itr->Next()) { | |
166 if (!itr->status().ok()) { | |
167 HandleError(FROM_HERE, itr->status()); | |
168 origins->clear(); | |
169 return false; | |
170 } | |
171 | |
172 std::string key = itr->key().ToString(); | |
173 if (!StartsWithASCII(key, kUniqueOriginKey, true)) | |
174 break; | |
175 | |
176 std::string origin; | |
177 bool success = RemovePrefix(key, kUniqueOriginKey, &origin); | |
michaeln
2014/04/25 00:48:21
This function is funky? Either it doesn't need to
nhiroki
2014/04/25 11:01:05
Done.
| |
178 DCHECK(success); | |
179 origins->insert(GURL(origin)); | |
180 } | |
181 return true; | |
182 } | |
183 | |
184 bool ServiceWorkerDatabase::GetRegistrationsForOrigin( | |
185 const GURL& origin, | |
186 std::vector<ServiceWorkerRegistrationData>* registrations) { | |
187 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); | |
188 DCHECK(registrations); | |
189 | |
190 if (!LazyOpen(false) || is_disabled_) | |
191 return false; | |
192 | |
193 // Create a key prefix for registrations. | |
194 std::string prefix = base::StringPrintf( | |
195 "%s%s%c", kRegKeyPrefix, origin.spec().c_str(), kKeySeparator); | |
196 | |
197 scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions())); | |
198 for (itr->Seek(prefix); itr->Valid(); itr->Next()) { | |
199 if (!itr->status().ok()) { | |
200 HandleError(FROM_HERE, itr->status()); | |
201 registrations->clear(); | |
202 return false; | |
203 } | |
204 | |
205 std::string key = itr->key().ToString(); | |
206 if (!StartsWithASCII(key, prefix, true)) | |
207 break; | |
208 | |
209 ServiceWorkerRegistrationData registration; | |
210 if (!ParseRegistrationData(itr->value().ToString(), ®istration)) { | |
211 HandleError(FROM_HERE, leveldb::Status::Corruption("failed to parse")); | |
212 registrations->clear(); | |
213 return false; | |
214 } | |
215 registrations->push_back(registration); | |
216 } | |
217 return true; | |
218 } | |
219 | |
220 bool ServiceWorkerDatabase::ReadRegistration( | |
221 int64 registration_id, | |
222 const GURL& origin, | |
223 ServiceWorkerRegistrationData* registration, | |
224 std::vector<ServiceWorkerResourceRecord>* resources) { | |
225 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); | |
226 DCHECK(registration); | |
227 DCHECK(resources); | |
228 | |
229 if (!LazyOpen(false) || is_disabled_) | |
230 return false; | |
231 | |
232 ServiceWorkerRegistrationData value; | |
233 if (!ReadRegistrationData(registration_id, origin, &value)) | |
234 return false; | |
235 | |
236 // TODO(nhiroki): Read ResourceRecords tied with this registration. | |
237 | |
238 *registration = value; | |
239 return true; | |
240 } | |
241 | |
242 bool ServiceWorkerDatabase::WriteRegistration( | |
243 const ServiceWorkerRegistrationData& registration, | |
244 const std::vector<ServiceWorkerResourceRecord>& resources) { | |
245 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); | |
246 if (!LazyOpen(true) || is_disabled_) | |
247 return false; | |
248 | |
249 leveldb::WriteBatch batch; | |
250 if (!BumpNextAvailableIdIfNeeded( | |
251 kNextRegIdKey, registration.registration_id(), &batch) || | |
252 !BumpNextAvailableIdIfNeeded( | |
253 kNextVerIdKey, registration.version_id(), &batch)) { | |
254 return false; | |
255 } | |
256 | |
257 PutUniqueOriginToBatch(GURL(registration.scope_url()).GetOrigin(), &batch); | |
michaeln
2014/04/25 00:48:21
is leveldb smart about noticing when a write doesn
nhiroki
2014/04/25 02:06:11
AFAICS, leveldb always writes log record even if t
nhiroki
2014/04/25 11:01:05
Let me address this in a following patch (added TO
| |
258 PutRegistrationDataToBatch(registration, &batch); | |
259 | |
260 // TODO(nhiroki): Write |resources| into the database. | |
261 return WriteBatch(&batch); | |
262 } | |
263 | |
264 bool ServiceWorkerDatabase::UpdateVersionToActive(int64 registration_id, | |
265 const GURL& origin) { | |
266 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); | |
267 if (!LazyOpen(false) || is_disabled_) | |
268 return false; | |
269 | |
270 ServiceWorkerRegistrationData registration; | |
271 if (!ReadRegistrationData(registration_id, origin, ®istration)) | |
272 return false; | |
273 | |
274 registration.set_is_active(true); | |
275 | |
276 leveldb::WriteBatch batch; | |
277 PutRegistrationDataToBatch(registration, &batch); | |
278 return WriteBatch(&batch); | |
279 } | |
280 | |
281 bool ServiceWorkerDatabase::UpdateLastCheckTime(int64 registration_id, | |
282 const GURL& origin, | |
283 const base::Time& time) { | |
284 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); | |
285 if (!LazyOpen(false) || is_disabled_) | |
286 return false; | |
287 | |
288 ServiceWorkerRegistrationData registration; | |
289 if (!ReadRegistrationData(registration_id, origin, ®istration)) | |
290 return false; | |
291 | |
292 DCHECK_LT(registration.last_update_check_time(), time.ToInternalValue()); | |
293 registration.set_last_update_check_time(time.ToInternalValue()); | |
294 | |
295 leveldb::WriteBatch batch; | |
296 PutRegistrationDataToBatch(registration, &batch); | |
297 return WriteBatch(&batch); | |
298 } | |
299 | |
300 bool ServiceWorkerDatabase::DeleteRegistration(int64 registration_id, | |
301 const GURL& origin) { | |
302 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); | |
303 if (!LazyOpen(false) || is_disabled_) | |
304 return false; | |
305 | |
306 leveldb::WriteBatch batch; | |
307 | |
308 // Remove |origin| from unique origins if a registration specified by | |
309 // |registration_id| is the only one for |origin|. | |
310 // TODO(nhiroki): Check the uniqueness by more efficient way. | |
311 std::vector<ServiceWorkerRegistrationData> registrations; | |
312 if (!GetRegistrationsForOrigin(origin, ®istrations)) | |
313 return false; | |
314 if (registrations.size() == 1 && | |
315 registrations[0].registration_id() == registration_id) { | |
316 batch.Delete(CreateUniqueOriginKey(origin)); | |
317 } | |
318 | |
319 batch.Delete(CreateRegistrationKey(registration_id, origin)); | |
320 | |
321 // TODO(nhiroki): Delete ResourceRecords tied with this registration. | |
322 return WriteBatch(&batch); | |
323 } | |
324 | |
98 bool ServiceWorkerDatabase::LazyOpen(bool create_if_needed) { | 325 bool ServiceWorkerDatabase::LazyOpen(bool create_if_needed) { |
99 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); | 326 DCHECK(sequence_checker_.CalledOnValidSequencedThread()); |
100 if (IsOpen()) | 327 if (IsOpen()) |
101 return true; | 328 return true; |
102 | 329 |
103 // Do not try to open a database if we tried and failed once. | 330 // Do not try to open a database if we tried and failed once. |
104 if (is_disabled_) | 331 if (is_disabled_) |
105 return false; | 332 return false; |
106 | 333 |
107 // When |path_| is empty, open a database in-memory. | 334 // When |path_| is empty, open a database in-memory. |
(...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
166 int64 parsed; | 393 int64 parsed; |
167 if (!base::StringToInt64(value, &parsed)) { | 394 if (!base::StringToInt64(value, &parsed)) { |
168 HandleError(FROM_HERE, leveldb::Status::Corruption("failed to parse")); | 395 HandleError(FROM_HERE, leveldb::Status::Corruption("failed to parse")); |
169 return false; | 396 return false; |
170 } | 397 } |
171 | 398 |
172 *next_avail_id = parsed; | 399 *next_avail_id = parsed; |
173 return true; | 400 return true; |
174 } | 401 } |
175 | 402 |
403 bool ServiceWorkerDatabase::ReadRegistrationData( | |
404 int64 registration_id, | |
405 const GURL& origin, | |
406 ServiceWorkerRegistrationData* registration) { | |
407 DCHECK(registration); | |
408 | |
409 std::string key = CreateRegistrationKey(registration_id, origin); | |
410 | |
411 std::string value; | |
412 leveldb::Status status = db_->Get(leveldb::ReadOptions(), key, &value); | |
413 if (!status.ok()) { | |
414 if (!status.IsNotFound()) | |
415 HandleError(FROM_HERE, status); | |
416 return false; | |
417 } | |
418 | |
419 ServiceWorkerRegistrationData parsed; | |
420 if (!ParseRegistrationData(value, &parsed)) { | |
421 HandleError(FROM_HERE, leveldb::Status::Corruption("failed to parse")); | |
422 return false; | |
423 } | |
424 | |
425 *registration = parsed; | |
426 return true; | |
427 } | |
428 | |
176 bool ServiceWorkerDatabase::WriteBatch(leveldb::WriteBatch* batch) { | 429 bool ServiceWorkerDatabase::WriteBatch(leveldb::WriteBatch* batch) { |
177 DCHECK(batch); | 430 DCHECK(batch); |
178 DCHECK(!is_disabled_); | 431 DCHECK(!is_disabled_); |
179 leveldb::Status status = db_->Write(leveldb::WriteOptions(), batch); | 432 leveldb::Status status = db_->Write(leveldb::WriteOptions(), batch); |
180 if (!status.ok()) { | 433 if (!status.ok()) { |
181 HandleError(FROM_HERE, status); | 434 HandleError(FROM_HERE, status); |
182 return false; | 435 return false; |
183 } | 436 } |
184 return true; | 437 return true; |
185 } | 438 } |
186 | 439 |
440 bool ServiceWorkerDatabase::BumpNextAvailableIdIfNeeded( | |
michaeln
2014/04/25 00:48:21
As coded this function will have problems if an id
nhiroki
2014/04/25 11:01:05
Good idea! Addressed it.
| |
441 const char* id_key, int64 used_id, leveldb::WriteBatch* batch) { | |
442 DCHECK(batch); | |
443 int64 next_available_id; | |
444 if (!ReadNextAvailableId(id_key, &next_available_id)) | |
445 return false; | |
446 if (next_available_id <= used_id) | |
447 batch->Put(id_key, base::Int64ToString(used_id + 1)); | |
448 return true; | |
449 } | |
450 | |
187 bool ServiceWorkerDatabase::IsOpen() { | 451 bool ServiceWorkerDatabase::IsOpen() { |
188 return db_.get() != NULL; | 452 return db_.get() != NULL; |
189 } | 453 } |
190 | 454 |
191 bool ServiceWorkerDatabase::IsEmpty() { | 455 bool ServiceWorkerDatabase::IsEmpty() { |
192 scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions())); | 456 scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions())); |
193 itr->SeekToFirst(); | 457 itr->SeekToFirst(); |
194 // TODO(nhiroki): Handle an error. | 458 // TODO(nhiroki): Handle an error. |
195 DCHECK(itr->status().ok()); | 459 DCHECK(itr->status().ok()); |
196 return !itr->Valid(); | 460 return !itr->Valid(); |
197 } | 461 } |
198 | 462 |
199 void ServiceWorkerDatabase::HandleError( | 463 void ServiceWorkerDatabase::HandleError( |
200 const tracked_objects::Location& from_here, | 464 const tracked_objects::Location& from_here, |
201 const leveldb::Status& status) { | 465 const leveldb::Status& status) { |
202 // TODO(nhiroki): Add an UMA histogram. | 466 // TODO(nhiroki): Add an UMA histogram. |
203 DLOG(ERROR) << "Failed at: " << from_here.ToString() | 467 DLOG(ERROR) << "Failed at: " << from_here.ToString() |
204 << " with error: " << status.ToString(); | 468 << " with error: " << status.ToString(); |
205 is_disabled_ = true; | 469 is_disabled_ = true; |
206 if (status.IsCorruption()) | 470 if (status.IsCorruption()) |
207 was_corruption_detected_ = true; | 471 was_corruption_detected_ = true; |
208 db_.reset(); | 472 db_.reset(); |
209 } | 473 } |
210 | 474 |
211 } // namespace content | 475 } // namespace content |
OLD | NEW |