Chromium Code Reviews| 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 |