1 | /* |
2 | * Copyright (C) 2017 Apple Inc. All rights reserved. |
3 | * |
4 | * Redistribution and use in source and binary forms, with or without |
5 | * modification, are permitted provided that the following conditions |
6 | * are met: |
7 | * 1. Redistributions of source code must retain the above copyright |
8 | * notice, this list of conditions and the following disclaimer. |
9 | * 2. Redistributions in binary form must reproduce the above copyright |
10 | * notice, this list of conditions and the following disclaimer in the |
11 | * documentation and/or other materials provided with the distribution. |
12 | * |
13 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
14 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
15 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
16 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
20 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
21 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
23 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
24 | */ |
25 | |
26 | #include "config.h" |
27 | #include "CacheStorageEngine.h" |
28 | |
29 | #include "Logging.h" |
30 | #include "NetworkCacheCoders.h" |
31 | #include "NetworkCacheIOChannel.h" |
32 | #include <WebCore/SecurityOrigin.h> |
33 | #include <WebCore/StorageQuotaManager.h> |
34 | #include <wtf/RunLoop.h> |
35 | #include <wtf/UUID.h> |
36 | #include <wtf/text/StringBuilder.h> |
37 | |
38 | namespace WebKit { |
39 | |
40 | namespace CacheStorage { |
41 | using namespace WebCore::DOMCacheEngine; |
42 | using namespace NetworkCache; |
43 | |
44 | static inline String cachesListFilename(const String& cachesRootPath) |
45 | { |
46 | return FileSystem::pathByAppendingComponent(cachesRootPath, "cacheslist"_s ); |
47 | } |
48 | |
49 | static inline String cachesOriginFilename(const String& cachesRootPath) |
50 | { |
51 | return FileSystem::pathByAppendingComponent(cachesRootPath, "origin"_s ); |
52 | } |
53 | |
54 | Ref<Caches> Caches::create(Engine& engine, WebCore::ClientOrigin&& origin, String&& rootPath, WebCore::StorageQuotaManager& quotaManager) |
55 | { |
56 | auto caches = adoptRef(*new Caches { engine, WTFMove(origin), WTFMove(rootPath), quotaManager }); |
57 | quotaManager.addUser(caches.get()); |
58 | return caches; |
59 | } |
60 | |
61 | Caches::Caches(Engine& engine, WebCore::ClientOrigin&& origin, String&& rootPath, WebCore::StorageQuotaManager& quotaManager) |
62 | : m_engine(&engine) |
63 | , m_origin(WTFMove(origin)) |
64 | , m_rootPath(WTFMove(rootPath)) |
65 | , m_quotaManager(makeWeakPtr(quotaManager)) |
66 | { |
67 | } |
68 | |
69 | Caches::~Caches() |
70 | { |
71 | ASSERT(m_pendingWritingCachesToDiskCallbacks.isEmpty()); |
72 | |
73 | if (m_quotaManager) |
74 | m_quotaManager->removeUser(*this); |
75 | } |
76 | |
77 | void Caches::whenInitialized(CompletionHandler<void()>&& callback) |
78 | { |
79 | initialize([callback = WTFMove(callback)](auto&& error) mutable { |
80 | if (error) |
81 | RELEASE_LOG_ERROR(CacheStorage, "Caches::initialize failed, reported space used will be zero" ); |
82 | callback(); |
83 | }); |
84 | } |
85 | |
86 | void Caches::retrieveOriginFromDirectory(const String& folderPath, WorkQueue& queue, WTF::CompletionHandler<void(Optional<WebCore::ClientOrigin>&&)>&& completionHandler) |
87 | { |
88 | queue.dispatch([completionHandler = WTFMove(completionHandler), filename = cachesOriginFilename(folderPath)]() mutable { |
89 | if (!FileSystem::fileExists(filename)) { |
90 | RunLoop::main().dispatch([completionHandler = WTFMove(completionHandler)]() mutable { |
91 | completionHandler(WTF::nullopt); |
92 | }); |
93 | return; |
94 | } |
95 | |
96 | auto channel = IOChannel::open(filename, IOChannel::Type::Read); |
97 | channel->read(0, std::numeric_limits<size_t>::max(), nullptr, [completionHandler = WTFMove(completionHandler)](const Data& data, int error) mutable { |
98 | ASSERT(RunLoop::isMain()); |
99 | if (error) { |
100 | RELEASE_LOG_ERROR(CacheStorage, "Caches::retrieveOriginFromDirectory failed reading channel with error %d" , error); |
101 | completionHandler(WTF::nullopt); |
102 | return; |
103 | } |
104 | completionHandler(readOrigin(data)); |
105 | }); |
106 | }); |
107 | } |
108 | |
109 | void Caches::storeOrigin(CompletionCallback&& completionHandler) |
110 | { |
111 | WTF::Persistence::Encoder encoder; |
112 | encoder << m_origin.topOrigin.protocol; |
113 | encoder << m_origin.topOrigin.host; |
114 | encoder << m_origin.topOrigin.port; |
115 | encoder << m_origin.clientOrigin.protocol; |
116 | encoder << m_origin.clientOrigin.host; |
117 | encoder << m_origin.clientOrigin.port; |
118 | m_engine->writeFile(cachesOriginFilename(m_rootPath), Data { encoder.buffer(), encoder.bufferSize() }, [protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)] (Optional<Error>&& error) mutable { |
119 | completionHandler(WTFMove(error)); |
120 | }); |
121 | } |
122 | |
123 | Optional<WebCore::ClientOrigin> Caches::readOrigin(const Data& data) |
124 | { |
125 | // FIXME: We should be able to use modern decoders for persistent data. |
126 | WebCore::SecurityOriginData topOrigin, clientOrigin; |
127 | WTF::Persistence::Decoder decoder(data.data(), data.size()); |
128 | |
129 | if (!decoder.decode(topOrigin.protocol)) |
130 | return WTF::nullopt; |
131 | if (!decoder.decode(topOrigin.host)) |
132 | return WTF::nullopt; |
133 | if (!decoder.decode(topOrigin.port)) |
134 | return WTF::nullopt; |
135 | if (!decoder.decode(clientOrigin.protocol)) |
136 | return WTF::nullopt; |
137 | if (!decoder.decode(clientOrigin.host)) |
138 | return WTF::nullopt; |
139 | if (!decoder.decode(clientOrigin.port)) |
140 | return WTF::nullopt; |
141 | return WebCore::ClientOrigin { WTFMove(topOrigin), WTFMove(clientOrigin) }; |
142 | } |
143 | |
144 | void Caches::initialize(WebCore::DOMCacheEngine::CompletionCallback&& callback) |
145 | { |
146 | if (m_isInitialized) { |
147 | callback(WTF::nullopt); |
148 | return; |
149 | } |
150 | |
151 | if (m_rootPath.isNull()) { |
152 | makeDirty(); |
153 | m_isInitialized = true; |
154 | callback(WTF::nullopt); |
155 | return; |
156 | } |
157 | |
158 | if (m_storage) { |
159 | m_pendingInitializationCallbacks.append(WTFMove(callback)); |
160 | return; |
161 | } |
162 | |
163 | auto storage = Storage::open(m_rootPath, Storage::Mode::AvoidRandomness); |
164 | if (!storage) { |
165 | RELEASE_LOG_ERROR(CacheStorage, "Caches::initialize failed opening storage" ); |
166 | callback(Error::WriteDisk); |
167 | return; |
168 | } |
169 | |
170 | m_pendingInitializationCallbacks.append(WTFMove(callback)); |
171 | m_storage = storage.releaseNonNull(); |
172 | m_storage->writeWithoutWaiting(); |
173 | |
174 | storeOrigin([this] (Optional<Error>&& error) mutable { |
175 | if (error) { |
176 | RELEASE_LOG_ERROR(CacheStorage, "Caches::initialize failed storing origin with error %d" , static_cast<int>(*error)); |
177 | |
178 | auto pendingCallbacks = WTFMove(m_pendingInitializationCallbacks); |
179 | for (auto& callback : pendingCallbacks) |
180 | callback(Error::WriteDisk); |
181 | |
182 | m_storage = nullptr; |
183 | return; |
184 | } |
185 | |
186 | readCachesFromDisk([this](Expected<Vector<Cache>, Error>&& result) mutable { |
187 | makeDirty(); |
188 | |
189 | if (!result.has_value()) { |
190 | RELEASE_LOG_ERROR(CacheStorage, "Caches::initialize failed reading caches from disk with error %d" , static_cast<int>(result.error())); |
191 | |
192 | auto pendingCallbacks = WTFMove(m_pendingInitializationCallbacks); |
193 | for (auto& callback : pendingCallbacks) |
194 | callback(result.error()); |
195 | |
196 | m_storage = nullptr; |
197 | return; |
198 | } |
199 | m_caches = WTFMove(result.value()); |
200 | |
201 | initializeSize(); |
202 | }); |
203 | }); |
204 | } |
205 | |
206 | void Caches::initializeSize() |
207 | { |
208 | if (!m_storage) { |
209 | auto pendingCallbacks = WTFMove(m_pendingInitializationCallbacks); |
210 | for (auto& callback : pendingCallbacks) |
211 | callback(Error::Internal); |
212 | return; |
213 | } |
214 | |
215 | uint64_t size = 0; |
216 | m_storage->traverse({ }, { }, [protectedThis = makeRef(*this), this, protectedStorage = makeRef(*m_storage), size](const auto* storage, const auto& information) mutable { |
217 | if (!storage) { |
218 | if (m_pendingInitializationCallbacks.isEmpty()) { |
219 | // Caches was cleared so let's not get initialized. |
220 | m_storage = nullptr; |
221 | return; |
222 | } |
223 | m_size = size; |
224 | m_isInitialized = true; |
225 | auto pendingCallbacks = WTFMove(m_pendingInitializationCallbacks); |
226 | for (auto& callback : pendingCallbacks) |
227 | callback(WTF::nullopt); |
228 | |
229 | return; |
230 | } |
231 | auto decoded = Cache::decodeRecordHeader(*storage); |
232 | if (decoded) |
233 | size += decoded->size; |
234 | }); |
235 | } |
236 | |
237 | void Caches::detach() |
238 | { |
239 | m_engine = nullptr; |
240 | m_rootPath = { }; |
241 | clearPendingWritingCachesToDiskCallbacks(); |
242 | } |
243 | |
244 | void Caches::clear(CompletionHandler<void()>&& completionHandler) |
245 | { |
246 | if (m_isWritingCachesToDisk) { |
247 | m_pendingWritingCachesToDiskCallbacks.append([this, completionHandler = WTFMove(completionHandler)] (auto&& error) mutable { |
248 | this->clear(WTFMove(completionHandler)); |
249 | }); |
250 | return; |
251 | } |
252 | |
253 | auto pendingCallbacks = WTFMove(m_pendingInitializationCallbacks); |
254 | for (auto& callback : pendingCallbacks) |
255 | callback(Error::Internal); |
256 | |
257 | if (m_engine) |
258 | m_engine->removeFile(cachesListFilename(m_rootPath)); |
259 | if (m_storage) { |
260 | m_storage->clear(String { }, -WallTime::infinity(), [protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)]() mutable { |
261 | ASSERT(RunLoop::isMain()); |
262 | protectedThis->clearMemoryRepresentation(); |
263 | protectedThis->resetSpaceUsed(); |
264 | completionHandler(); |
265 | }); |
266 | return; |
267 | } |
268 | clearMemoryRepresentation(); |
269 | resetSpaceUsed(); |
270 | clearPendingWritingCachesToDiskCallbacks(); |
271 | completionHandler(); |
272 | } |
273 | |
274 | void Caches::clearPendingWritingCachesToDiskCallbacks() |
275 | { |
276 | auto pendingWritingCachesToDiskCallbacks = WTFMove(m_pendingWritingCachesToDiskCallbacks); |
277 | for (auto& callback : pendingWritingCachesToDiskCallbacks) |
278 | callback(Error::Internal); |
279 | } |
280 | |
281 | Cache* Caches::find(const String& name) |
282 | { |
283 | auto position = m_caches.findMatching([&](const auto& item) { return item.name() == name; }); |
284 | return (position != notFound) ? &m_caches[position] : nullptr; |
285 | } |
286 | |
287 | Cache* Caches::find(uint64_t identifier) |
288 | { |
289 | auto position = m_caches.findMatching([&](const auto& item) { return item.identifier() == identifier; }); |
290 | if (position != notFound) |
291 | return &m_caches[position]; |
292 | |
293 | position = m_removedCaches.findMatching([&](const auto& item) { return item.identifier() == identifier; }); |
294 | return (position != notFound) ? &m_removedCaches[position] : nullptr; |
295 | } |
296 | |
297 | void Caches::open(const String& name, CacheIdentifierCallback&& callback) |
298 | { |
299 | ASSERT(m_isInitialized); |
300 | ASSERT(m_engine); |
301 | |
302 | if (m_isWritingCachesToDisk) { |
303 | m_pendingWritingCachesToDiskCallbacks.append([this, name, callback = WTFMove(callback)] (auto&& error) mutable { |
304 | if (error) { |
305 | callback(makeUnexpected(error.value())); |
306 | return; |
307 | } |
308 | this->open(name, WTFMove(callback)); |
309 | }); |
310 | return; |
311 | } |
312 | |
313 | if (auto* cache = find(name)) { |
314 | cache->open([cacheIdentifier = cache->identifier(), callback = WTFMove(callback)](Optional<Error>&& error) mutable { |
315 | if (error) { |
316 | callback(makeUnexpected(error.value())); |
317 | return; |
318 | } |
319 | callback(CacheIdentifierOperationResult { cacheIdentifier, false }); |
320 | }); |
321 | return; |
322 | } |
323 | |
324 | makeDirty(); |
325 | |
326 | uint64_t cacheIdentifier = m_engine->nextCacheIdentifier(); |
327 | m_caches.append(Cache { *this, cacheIdentifier, Cache::State::Open, String { name }, createCanonicalUUIDString() }); |
328 | |
329 | writeCachesToDisk([callback = WTFMove(callback), cacheIdentifier](Optional<Error>&& error) mutable { |
330 | callback(CacheIdentifierOperationResult { cacheIdentifier, !!error }); |
331 | }); |
332 | } |
333 | |
334 | void Caches::remove(uint64_t identifier, CacheIdentifierCallback&& callback) |
335 | { |
336 | ASSERT(m_isInitialized); |
337 | ASSERT(m_engine); |
338 | |
339 | if (m_isWritingCachesToDisk) { |
340 | m_pendingWritingCachesToDiskCallbacks.append([this, identifier, callback = WTFMove(callback)] (auto&& error) mutable { |
341 | if (error) { |
342 | callback(makeUnexpected(error.value())); |
343 | return; |
344 | } |
345 | this->remove(identifier, WTFMove(callback)); |
346 | }); |
347 | return; |
348 | } |
349 | |
350 | auto position = m_caches.findMatching([&](const auto& item) { return item.identifier() == identifier; }); |
351 | |
352 | if (position == notFound) { |
353 | ASSERT(m_removedCaches.findMatching([&](const auto& item) { return item.identifier() == identifier; }) != notFound); |
354 | callback(CacheIdentifierOperationResult { 0, false }); |
355 | return; |
356 | } |
357 | |
358 | makeDirty(); |
359 | |
360 | m_removedCaches.append(WTFMove(m_caches[position])); |
361 | m_caches.remove(position); |
362 | |
363 | writeCachesToDisk([callback = WTFMove(callback), identifier](Optional<Error>&& error) mutable { |
364 | callback(CacheIdentifierOperationResult { identifier, !!error }); |
365 | }); |
366 | } |
367 | |
368 | bool Caches::hasActiveCache() const |
369 | { |
370 | if (m_removedCaches.size()) |
371 | return true; |
372 | return m_caches.findMatching([](const auto& item) { return item.isActive(); }) != notFound; |
373 | } |
374 | |
375 | void Caches::dispose(Cache& cache) |
376 | { |
377 | auto position = m_removedCaches.findMatching([&](const auto& item) { return item.identifier() == cache.identifier(); }); |
378 | if (position != notFound) { |
379 | if (m_storage) |
380 | m_storage->remove(cache.keys(), [] { }); |
381 | |
382 | m_removedCaches.remove(position); |
383 | return; |
384 | } |
385 | ASSERT(m_caches.findMatching([&](const auto& item) { return item.identifier() == cache.identifier(); }) != notFound); |
386 | cache.clearMemoryRepresentation(); |
387 | |
388 | if (!hasActiveCache()) |
389 | clearMemoryRepresentation(); |
390 | } |
391 | |
392 | static inline Data encodeCacheNames(const Vector<Cache>& caches) |
393 | { |
394 | WTF::Persistence::Encoder encoder; |
395 | |
396 | uint64_t size = caches.size(); |
397 | encoder << size; |
398 | for (auto& cache : caches) { |
399 | encoder << cache.name(); |
400 | encoder << cache.uniqueName(); |
401 | } |
402 | |
403 | return Data { encoder.buffer(), encoder.bufferSize() }; |
404 | } |
405 | |
406 | static inline Expected<Vector<std::pair<String, String>>, Error> decodeCachesNames(const Data& data) |
407 | { |
408 | WTF::Persistence::Decoder decoder(data.data(), data.size()); |
409 | uint64_t count; |
410 | if (!decoder.decode(count)) |
411 | return makeUnexpected(Error::ReadDisk); |
412 | |
413 | Vector<std::pair<String, String>> names; |
414 | names.reserveInitialCapacity(count); |
415 | for (size_t index = 0; index < count; ++index) { |
416 | String name; |
417 | if (!decoder.decode(name)) |
418 | return makeUnexpected(Error::ReadDisk); |
419 | String uniqueName; |
420 | if (!decoder.decode(uniqueName)) |
421 | return makeUnexpected(Error::ReadDisk); |
422 | |
423 | names.uncheckedAppend(std::pair<String, String> { WTFMove(name), WTFMove(uniqueName) }); |
424 | } |
425 | return names; |
426 | } |
427 | |
428 | void Caches::readCachesFromDisk(WTF::Function<void(Expected<Vector<Cache>, Error>&&)>&& callback) |
429 | { |
430 | ASSERT(m_engine); |
431 | ASSERT(!m_isInitialized); |
432 | ASSERT(m_caches.isEmpty()); |
433 | |
434 | if (!shouldPersist()) { |
435 | callback(Vector<Cache> { }); |
436 | return; |
437 | } |
438 | |
439 | auto filename = cachesListFilename(m_rootPath); |
440 | if (!FileSystem::fileExists(filename)) { |
441 | callback(Vector<Cache> { }); |
442 | return; |
443 | } |
444 | |
445 | m_engine->readFile(filename, [protectedThis = makeRef(*this), this, callback = WTFMove(callback)](const Data& data, int error) mutable { |
446 | if (!m_engine) { |
447 | callback(Vector<Cache> { }); |
448 | return; |
449 | } |
450 | |
451 | if (error) { |
452 | RELEASE_LOG_ERROR(CacheStorage, "Caches::readCachesFromDisk failed reading caches from disk with error %d" , error); |
453 | callback(makeUnexpected(Error::ReadDisk)); |
454 | return; |
455 | } |
456 | |
457 | auto result = decodeCachesNames(data); |
458 | if (!result.has_value()) { |
459 | RELEASE_LOG_ERROR(CacheStorage, "Caches::decodeCachesNames failed decoding caches with error %d" , static_cast<int>(result.error())); |
460 | callback(makeUnexpected(result.error())); |
461 | return; |
462 | } |
463 | callback(WTF::map(WTFMove(result.value()), [this] (auto&& pair) { |
464 | return Cache { *this, m_engine->nextCacheIdentifier(), Cache::State::Uninitialized, WTFMove(pair.first), WTFMove(pair.second) }; |
465 | })); |
466 | }); |
467 | } |
468 | |
469 | void Caches::writeCachesToDisk(CompletionCallback&& callback) |
470 | { |
471 | ASSERT(!m_isWritingCachesToDisk); |
472 | ASSERT(m_isInitialized); |
473 | if (!shouldPersist()) { |
474 | callback(WTF::nullopt); |
475 | return; |
476 | } |
477 | |
478 | ASSERT(m_engine); |
479 | |
480 | if (m_caches.isEmpty()) { |
481 | m_engine->removeFile(cachesListFilename(m_rootPath)); |
482 | callback(WTF::nullopt); |
483 | return; |
484 | } |
485 | |
486 | m_isWritingCachesToDisk = true; |
487 | m_engine->writeFile(cachesListFilename(m_rootPath), encodeCacheNames(m_caches), [this, protectedThis = makeRef(*this), callback = WTFMove(callback)](Optional<Error>&& error) mutable { |
488 | m_isWritingCachesToDisk = false; |
489 | if (error) |
490 | RELEASE_LOG_ERROR(CacheStorage, "Caches::writeCachesToDisk failed writing caches to disk with error %d" , static_cast<int>(*error)); |
491 | |
492 | callback(WTFMove(error)); |
493 | while (!m_pendingWritingCachesToDiskCallbacks.isEmpty() && !m_isWritingCachesToDisk) |
494 | m_pendingWritingCachesToDiskCallbacks.takeFirst()(WTF::nullopt); |
495 | }); |
496 | } |
497 | |
498 | void Caches::readRecordsList(Cache& cache, NetworkCache::Storage::TraverseHandler&& callback) |
499 | { |
500 | ASSERT(m_isInitialized); |
501 | |
502 | if (!m_storage) { |
503 | callback(nullptr, { }); |
504 | return; |
505 | } |
506 | m_storage->traverse(cache.uniqueName(), { }, [protectedStorage = makeRef(*m_storage), callback = WTFMove(callback)](const auto* storage, const auto& information) { |
507 | callback(storage, information); |
508 | }); |
509 | } |
510 | |
511 | void Caches::requestSpace(uint64_t spaceRequired, WebCore::DOMCacheEngine::CompletionCallback&& callback) |
512 | { |
513 | if (!m_quotaManager) { |
514 | callback(Error::QuotaExceeded); |
515 | return; |
516 | } |
517 | |
518 | m_quotaManager->requestSpace(spaceRequired, [callback = WTFMove(callback)](auto decision) mutable { |
519 | switch (decision) { |
520 | case WebCore::StorageQuotaManager::Decision::Deny: |
521 | callback(Error::QuotaExceeded); |
522 | return; |
523 | case WebCore::StorageQuotaManager::Decision::Grant: |
524 | callback({ }); |
525 | }; |
526 | }); |
527 | } |
528 | |
529 | void Caches::writeRecord(const Cache& cache, const RecordInformation& recordInformation, Record&& record, uint64_t previousRecordSize, CompletionCallback&& callback) |
530 | { |
531 | ASSERT(m_isInitialized); |
532 | |
533 | ASSERT(m_size >= previousRecordSize); |
534 | m_size += recordInformation.size; |
535 | m_size -= previousRecordSize; |
536 | |
537 | if (!shouldPersist()) { |
538 | m_volatileStorage.set(recordInformation.key, WTFMove(record)); |
539 | callback(WTF::nullopt); |
540 | return; |
541 | } |
542 | |
543 | m_storage->store(Cache::encode(recordInformation, record), { }, [protectedStorage = makeRef(*m_storage), callback = WTFMove(callback)](int error) mutable { |
544 | if (error) { |
545 | RELEASE_LOG_ERROR(CacheStorage, "Caches::writeRecord failed with error %d" , error); |
546 | callback(Error::WriteDisk); |
547 | return; |
548 | } |
549 | callback(WTF::nullopt); |
550 | }); |
551 | } |
552 | |
553 | void Caches::readRecord(const NetworkCache::Key& key, WTF::Function<void(Expected<Record, Error>&&)>&& callback) |
554 | { |
555 | ASSERT(m_isInitialized); |
556 | |
557 | if (!shouldPersist()) { |
558 | auto iterator = m_volatileStorage.find(key); |
559 | if (iterator == m_volatileStorage.end()) { |
560 | callback(makeUnexpected(Error::Internal)); |
561 | return; |
562 | } |
563 | callback(iterator->value.copy()); |
564 | return; |
565 | } |
566 | |
567 | m_storage->retrieve(key, 4, [protectedStorage = makeRef(*m_storage), callback = WTFMove(callback)](std::unique_ptr<Storage::Record> storage, const Storage::Timings&) mutable { |
568 | if (!storage) { |
569 | RELEASE_LOG_ERROR(CacheStorage, "Caches::readRecord failed reading record from disk" ); |
570 | callback(makeUnexpected(Error::ReadDisk)); |
571 | return false; |
572 | } |
573 | |
574 | auto record = Cache::decode(*storage); |
575 | if (!record) { |
576 | RELEASE_LOG_ERROR(CacheStorage, "Caches::readRecord failed decoding record from disk" ); |
577 | callback(makeUnexpected(Error::ReadDisk)); |
578 | return false; |
579 | } |
580 | |
581 | callback(WTFMove(record.value())); |
582 | return true; |
583 | }); |
584 | } |
585 | |
586 | void Caches::removeRecord(const RecordInformation& record) |
587 | { |
588 | ASSERT(m_isInitialized); |
589 | |
590 | ASSERT(m_size >= record.size); |
591 | m_size -= record.size; |
592 | removeCacheEntry(record.key); |
593 | } |
594 | |
595 | void Caches::removeCacheEntry(const NetworkCache::Key& key) |
596 | { |
597 | ASSERT(m_isInitialized); |
598 | |
599 | if (!shouldPersist()) { |
600 | m_volatileStorage.remove(key); |
601 | return; |
602 | } |
603 | m_storage->remove(key); |
604 | } |
605 | |
606 | void Caches::resetSpaceUsed() |
607 | { |
608 | m_size = 0; |
609 | if (m_quotaManager) { |
610 | m_quotaManager->removeUser(*this); |
611 | m_quotaManager->addUser(*this); |
612 | } |
613 | } |
614 | |
615 | void Caches::clearMemoryRepresentation() |
616 | { |
617 | if (!m_isInitialized) { |
618 | ASSERT(!m_storage || !hasActiveCache() || !m_pendingInitializationCallbacks.isEmpty()); |
619 | // m_storage might not be null in case Caches is being initialized. This is fine as nullify it below is a memory optimization. |
620 | m_caches.clear(); |
621 | return; |
622 | } |
623 | |
624 | makeDirty(); |
625 | m_caches.clear(); |
626 | m_isInitialized = false; |
627 | |
628 | // Clear storages as a memory optimization. |
629 | m_storage = nullptr; |
630 | m_volatileStorage.clear(); |
631 | } |
632 | |
633 | bool Caches::isDirty(uint64_t updateCounter) const |
634 | { |
635 | ASSERT(m_updateCounter >= updateCounter); |
636 | return m_updateCounter != updateCounter; |
637 | } |
638 | |
639 | const NetworkCache::Salt& Caches::salt() const |
640 | { |
641 | if (m_engine) |
642 | return m_engine->salt(); |
643 | |
644 | if (!m_volatileSalt) |
645 | m_volatileSalt = Salt { }; |
646 | |
647 | return m_volatileSalt.value(); |
648 | } |
649 | |
650 | void Caches::cacheInfos(uint64_t updateCounter, CacheInfosCallback&& callback) |
651 | { |
652 | if (m_isWritingCachesToDisk) { |
653 | m_pendingWritingCachesToDiskCallbacks.append([this, updateCounter, callback = WTFMove(callback)] (auto&& error) mutable { |
654 | if (error) { |
655 | callback(makeUnexpected(error.value())); |
656 | return; |
657 | } |
658 | this->cacheInfos(updateCounter, WTFMove(callback)); |
659 | }); |
660 | return; |
661 | } |
662 | |
663 | Vector<CacheInfo> cacheInfos; |
664 | if (isDirty(updateCounter)) { |
665 | cacheInfos.reserveInitialCapacity(m_caches.size()); |
666 | for (auto& cache : m_caches) |
667 | cacheInfos.uncheckedAppend(CacheInfo { cache.identifier(), cache.name() }); |
668 | } |
669 | callback(CacheInfos { WTFMove(cacheInfos), m_updateCounter }); |
670 | } |
671 | |
672 | void Caches::appendRepresentation(StringBuilder& builder) const |
673 | { |
674 | builder.append("{ \"persistent\": [" ); |
675 | |
676 | bool isFirst = true; |
677 | for (auto& cache : m_caches) { |
678 | if (!isFirst) |
679 | builder.append(", " ); |
680 | isFirst = false; |
681 | builder.append("\"" ); |
682 | builder.append(cache.name()); |
683 | builder.append("\"" ); |
684 | } |
685 | |
686 | builder.append("], \"removed\": [" ); |
687 | |
688 | isFirst = true; |
689 | for (auto& cache : m_removedCaches) { |
690 | if (!isFirst) |
691 | builder.append(", " ); |
692 | isFirst = false; |
693 | builder.append("\"" ); |
694 | builder.append(cache.name()); |
695 | builder.append("\"" ); |
696 | } |
697 | builder.append("]}\n" ); |
698 | } |
699 | |
700 | uint64_t Caches::storageSize() const |
701 | { |
702 | ASSERT(m_isInitialized); |
703 | if (!shouldPersist()) |
704 | return 0; |
705 | return m_storage->approximateSize(); |
706 | } |
707 | |
708 | } // namespace CacheStorage |
709 | |
710 | } // namespace WebKit |
711 | |