f9da869b84e151483ddec3b91fe39e646143d83f
[WebKit-https.git] / Source / WebKit2 / NetworkProcess / cache / NetworkCacheStorageCocoa.mm
1 /*
2  * Copyright (C) 2014-2015 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. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "NetworkCacheStorage.h"
28
29 #if ENABLE(NETWORK_CACHE)
30
31 #include "Logging.h"
32 #include "NetworkCacheCoders.h"
33 #include "NetworkCacheFileSystemPosix.h"
34 #include "NetworkCacheIOChannel.h"
35 #include <dispatch/dispatch.h>
36 #include <wtf/PageBlock.h>
37 #include <wtf/RandomNumber.h>
38 #include <wtf/RunLoop.h>
39 #include <wtf/text/CString.h>
40 #include <wtf/text/StringBuilder.h>
41
42 namespace WebKit {
43 namespace NetworkCache {
44
45 static const char networkCacheSubdirectory[] = "WebKitCache";
46 static const char versionDirectoryPrefix[] = "Version ";
47
48 std::unique_ptr<Storage> Storage::open(const String& cachePath)
49 {
50     ASSERT(RunLoop::isMain());
51
52     String networkCachePath = WebCore::pathByAppendingComponent(cachePath, networkCacheSubdirectory);
53     if (!WebCore::makeAllDirectories(networkCachePath))
54         return nullptr;
55     return std::unique_ptr<Storage>(new Storage(networkCachePath));
56 }
57
58 static String makeVersionedDirectoryPath(const String& baseDirectoryPath)
59 {
60     String versionSubdirectory = versionDirectoryPrefix + String::number(Storage::version);
61     return WebCore::pathByAppendingComponent(baseDirectoryPath, versionSubdirectory);
62 }
63
64 Storage::Storage(const String& baseDirectoryPath)
65     : m_baseDirectoryPath(baseDirectoryPath)
66     , m_directoryPath(makeVersionedDirectoryPath(baseDirectoryPath))
67     , m_ioQueue(WorkQueue::create("com.apple.WebKit.Cache.Storage", WorkQueue::Type::Concurrent))
68     , m_backgroundIOQueue(WorkQueue::create("com.apple.WebKit.Cache.Storage", WorkQueue::Type::Concurrent, WorkQueue::QOS::Background))
69 {
70     deleteOldVersions();
71     initialize();
72 }
73
74 void Storage::initialize()
75 {
76     ASSERT(RunLoop::isMain());
77
78     StringCapture cachePathCapture(m_directoryPath);
79
80     backgroundIOQueue().dispatch([this, cachePathCapture] {
81         String cachePath = cachePathCapture.string();
82         traverseCacheFiles(cachePath, [this](const String& fileName, const String& partitionPath) {
83             Key::HashType hash;
84             if (!Key::stringToHash(fileName, hash))
85                 return;
86             unsigned shortHash = Key::toShortHash(hash);
87             RunLoop::main().dispatch([this, shortHash] {
88                 m_contentsFilter.add(shortHash);
89             });
90             auto filePath = WebCore::pathByAppendingComponent(partitionPath, fileName);
91             long long fileSize = 0;
92             WebCore::getFileSize(filePath, fileSize);
93             m_approximateSize += fileSize;
94         });
95     });
96 }
97
98 static String directoryPathForKey(const Key& key, const String& cachePath)
99 {
100     ASSERT(!key.partition().isEmpty());
101     return WebCore::pathByAppendingComponent(cachePath, key.partition());
102 }
103
104 static String fileNameForKey(const Key& key)
105 {
106     return key.hashAsString();
107 }
108
109 static String filePathForKey(const Key& key, const String& cachePath)
110 {
111     return WebCore::pathByAppendingComponent(directoryPathForKey(key, cachePath), fileNameForKey(key));
112 }
113
114 static Ref<IOChannel> openFileForKey(const Key& key, IOChannel::Type type, const String& cachePath)
115 {
116     auto directoryPath = directoryPathForKey(key, cachePath);
117     auto filePath = WebCore::pathByAppendingComponent(directoryPath, fileNameForKey(key));
118     if (type == IOChannel::Type::Create)
119         WebCore::makeAllDirectories(directoryPath);
120     return IOChannel::open(filePath, type);
121 }
122
123 static unsigned hashData(const Data& data)
124 {
125     StringHasher hasher;
126     data.apply([&hasher](const uint8_t* data, size_t size) {
127         hasher.addCharacters(data, size);
128         return true;
129     });
130     return hasher.hash();
131 }
132
133 struct EntryMetaData {
134     EntryMetaData() { }
135     explicit EntryMetaData(const Key& key)
136         : cacheStorageVersion(Storage::version)
137         , key(key)
138     { }
139
140     unsigned cacheStorageVersion;
141     Key key;
142     std::chrono::milliseconds timeStamp;
143     unsigned headerChecksum;
144     uint64_t headerOffset;
145     uint64_t headerSize;
146     unsigned bodyChecksum;
147     uint64_t bodyOffset;
148     uint64_t bodySize;
149 };
150
151 static bool decodeEntryMetaData(EntryMetaData& metaData, const Data& fileData)
152 {
153     bool success = false;
154     fileData.apply([&metaData, &success](const uint8_t* data, size_t size) {
155         Decoder decoder(data, size);
156         if (!decoder.decode(metaData.cacheStorageVersion))
157             return false;
158         if (!decoder.decode(metaData.key))
159             return false;
160         if (!decoder.decode(metaData.timeStamp))
161             return false;
162         if (!decoder.decode(metaData.headerChecksum))
163             return false;
164         if (!decoder.decode(metaData.headerSize))
165             return false;
166         if (!decoder.decode(metaData.bodyChecksum))
167             return false;
168         if (!decoder.decode(metaData.bodySize))
169             return false;
170         if (!decoder.verifyChecksum())
171             return false;
172         metaData.headerOffset = decoder.currentOffset();
173         metaData.bodyOffset = WTF::roundUpToMultipleOf(pageSize(), metaData.headerOffset + metaData.headerSize);
174         success = true;
175         return false;
176     });
177     return success;
178 }
179
180 static bool decodeEntryHeader(const Data& fileData, EntryMetaData& metaData, Data& data)
181 {
182     if (!decodeEntryMetaData(metaData, fileData))
183         return false;
184     if (metaData.cacheStorageVersion != Storage::version)
185         return false;
186     if (metaData.headerOffset + metaData.headerSize > metaData.bodyOffset)
187         return false;
188
189     auto headerData = fileData.subrange(metaData.headerOffset, metaData.headerSize);
190     if (metaData.headerChecksum != hashData(headerData)) {
191         LOG(NetworkCacheStorage, "(NetworkProcess) header checksum mismatch");
192         return false;
193     }
194     data = { headerData };
195     return true;
196 }
197
198 static std::unique_ptr<Storage::Entry> decodeEntry(const Data& fileData, int fd, const Key& key)
199 {
200     EntryMetaData metaData;
201     Data headerData;
202     if (!decodeEntryHeader(fileData, metaData, headerData))
203         return nullptr;
204
205     if (metaData.key != key)
206         return nullptr;
207     if (metaData.bodyOffset + metaData.bodySize != fileData.size())
208         return nullptr;
209
210     auto bodyData = mapFile(fd, metaData.bodyOffset, metaData.bodySize);
211     if (bodyData.isNull()) {
212         LOG(NetworkCacheStorage, "(NetworkProcess) map failed");
213         return nullptr;
214     }
215
216     if (metaData.bodyChecksum != hashData(bodyData)) {
217         LOG(NetworkCacheStorage, "(NetworkProcess) data checksum mismatch");
218         return nullptr;
219     }
220
221     return std::make_unique<Storage::Entry>(Storage::Entry {
222         metaData.key,
223         metaData.timeStamp,
224         headerData,
225         bodyData
226     });
227 }
228
229 static Data encodeEntryMetaData(const EntryMetaData& entry)
230 {
231     Encoder encoder;
232
233     encoder << entry.cacheStorageVersion;
234     encoder << entry.key;
235     encoder << entry.timeStamp;
236     encoder << entry.headerChecksum;
237     encoder << entry.headerSize;
238     encoder << entry.bodyChecksum;
239     encoder << entry.bodySize;
240
241     encoder.encodeChecksum();
242
243     return Data(encoder.buffer(), encoder.bufferSize());
244 }
245
246 static Data encodeEntryHeader(const Storage::Entry& entry)
247 {
248     EntryMetaData metaData(entry.key);
249     metaData.timeStamp = entry.timeStamp;
250     metaData.headerChecksum = hashData(entry.header);
251     metaData.headerSize = entry.header.size();
252     metaData.bodyChecksum = hashData(entry.body);
253     metaData.bodySize = entry.body.size();
254
255     auto encodedMetaData = encodeEntryMetaData(metaData);
256     auto headerData = concatenate(encodedMetaData, entry.header);
257     if (!entry.body.size())
258         return { headerData };
259
260     size_t dataOffset = WTF::roundUpToMultipleOf(pageSize(), headerData.size());
261     Vector<uint8_t, 4096> filler(dataOffset - headerData.size(), 0);
262     Data alignmentData(filler.data(), filler.size());
263
264     return concatenate(headerData, alignmentData);
265 }
266
267 void Storage::removeEntry(const Key& key)
268 {
269     ASSERT(RunLoop::isMain());
270
271     // For simplicity we don't reduce m_approximateSize on removals caused by load or decode errors.
272     // The next cache shrink will update the size.
273
274     if (m_contentsFilter.mayContain(key.shortHash()))
275         m_contentsFilter.remove(key.shortHash());
276
277     StringCapture filePathCapture(filePathForKey(key, m_directoryPath));
278     backgroundIOQueue().dispatch([this, filePathCapture] {
279         WebCore::deleteFile(filePathCapture.string());
280     });
281 }
282
283 void Storage::dispatchReadOperation(const ReadOperation& read)
284 {
285     ASSERT(RunLoop::isMain());
286     ASSERT(m_activeReadOperations.contains(&read));
287
288     StringCapture cachePathCapture(m_directoryPath);
289     ioQueue().dispatch([this, &read, cachePathCapture] {
290         auto channel = openFileForKey(read.key, IOChannel::Type::Read, cachePathCapture.string());
291         int fd = channel->fileDescriptor();
292         channel->read(0, std::numeric_limits<size_t>::max(), [this, &read, fd](Data& fileData, int error) {
293             if (error) {
294                 removeEntry(read.key);
295                 read.completionHandler(nullptr);
296             } else {
297                 auto entry = decodeEntry(fileData, fd, read.key);
298                 bool success = read.completionHandler(WTF::move(entry));
299                 if (!success)
300                     removeEntry(read.key);
301             }
302
303             ASSERT(m_activeReadOperations.contains(&read));
304             m_activeReadOperations.remove(&read);
305             dispatchPendingReadOperations();
306
307             LOG(NetworkCacheStorage, "(NetworkProcess) read complete error=%d", error);
308         });
309     });
310 }
311
312 void Storage::dispatchPendingReadOperations()
313 {
314     ASSERT(RunLoop::isMain());
315
316     const int maximumActiveReadOperationCount = 5;
317
318     for (int priority = maximumRetrievePriority; priority >= 0; --priority) {
319         if (m_activeReadOperations.size() > maximumActiveReadOperationCount) {
320             LOG(NetworkCacheStorage, "(NetworkProcess) limiting parallel retrieves");
321             return;
322         }
323         auto& pendingRetrieveQueue = m_pendingReadOperationsByPriority[priority];
324         if (pendingRetrieveQueue.isEmpty())
325             continue;
326         auto readOperation = pendingRetrieveQueue.takeFirst();
327         auto& read = *readOperation;
328         m_activeReadOperations.add(WTF::move(readOperation));
329         dispatchReadOperation(read);
330     }
331 }
332
333 template <class T> bool retrieveFromMemory(const T& operations, const Key& key, Storage::RetrieveCompletionHandler& completionHandler)
334 {
335     for (auto& operation : operations) {
336         if (operation->entry.key == key) {
337             LOG(NetworkCacheStorage, "(NetworkProcess) found write operation in progress");
338             auto entry = operation->entry;
339             RunLoop::main().dispatch([entry, completionHandler] {
340                 completionHandler(std::make_unique<Storage::Entry>(entry));
341             });
342             return true;
343         }
344     }
345     return false;
346 }
347
348 void Storage::retrieve(const Key& key, unsigned priority, RetrieveCompletionHandler&& completionHandler)
349 {
350     ASSERT(RunLoop::isMain());
351     ASSERT(priority <= maximumRetrievePriority);
352     ASSERT(!key.isNull());
353
354     if (!m_maximumSize) {
355         completionHandler(nullptr);
356         return;
357     }
358
359     if (!m_contentsFilter.mayContain(key.shortHash())) {
360         completionHandler(nullptr);
361         return;
362     }
363
364     if (retrieveFromMemory(m_pendingWriteOperations, key, completionHandler))
365         return;
366     if (retrieveFromMemory(m_activeWriteOperations, key, completionHandler))
367         return;
368
369     m_pendingReadOperationsByPriority[priority].append(new ReadOperation { key, WTF::move(completionHandler) });
370     dispatchPendingReadOperations();
371 }
372
373 void Storage::store(const Entry& entry, StoreCompletionHandler&& completionHandler)
374 {
375     ASSERT(RunLoop::isMain());
376     ASSERT(!entry.key.isNull());
377
378     if (!m_maximumSize) {
379         completionHandler(false, { });
380         return;
381     }
382
383     m_pendingWriteOperations.append(new WriteOperation { entry, { }, WTF::move(completionHandler) });
384
385     // Add key to the filter already here as we do lookups from the pending operations too.
386     m_contentsFilter.add(entry.key.shortHash());
387
388     dispatchPendingWriteOperations();
389 }
390
391 void Storage::update(const Entry& updateEntry, const Entry& existingEntry, StoreCompletionHandler&& completionHandler)
392 {
393     ASSERT(RunLoop::isMain());
394     ASSERT(!existingEntry.key.isNull());
395     ASSERT(existingEntry.key == updateEntry.key);
396
397     if (!m_maximumSize) {
398         completionHandler(false, { });
399         return;
400     }
401
402     m_pendingWriteOperations.append(new WriteOperation { updateEntry, existingEntry, WTF::move(completionHandler) });
403
404     dispatchPendingWriteOperations();
405 }
406
407 void Storage::traverse(std::function<void (const Entry*)>&& traverseHandler)
408 {
409     StringCapture cachePathCapture(m_directoryPath);
410     ioQueue().dispatch([this, cachePathCapture, traverseHandler] {
411         String cachePath = cachePathCapture.string();
412         auto semaphore = adoptDispatch(dispatch_semaphore_create(0));
413         traverseCacheFiles(cachePath, [this, &semaphore, &traverseHandler](const String& fileName, const String& partitionPath) {
414             auto filePath = WebCore::pathByAppendingComponent(partitionPath, fileName);
415             auto channel = IOChannel::open(filePath, IOChannel::Type::Read);
416             const size_t headerReadSize = 16 << 10;
417             channel->read(0, headerReadSize, [this, &semaphore, &traverseHandler](Data& fileData, int) {
418                 EntryMetaData metaData;
419                 Data headerData;
420                 if (decodeEntryHeader(fileData, metaData, headerData)) {
421                     Entry entry { metaData.key, metaData.timeStamp, headerData, { } };
422                     traverseHandler(&entry);
423                 }
424                 dispatch_semaphore_signal(semaphore.get());
425             });
426             dispatch_semaphore_wait(semaphore.get(), DISPATCH_TIME_FOREVER);
427         });
428         RunLoop::main().dispatch([this, traverseHandler] {
429             traverseHandler(nullptr);
430         });
431     });
432 }
433
434 void Storage::dispatchPendingWriteOperations()
435 {
436     ASSERT(RunLoop::isMain());
437
438     const int maximumActiveWriteOperationCount { 3 };
439
440     while (!m_pendingWriteOperations.isEmpty()) {
441         if (m_activeWriteOperations.size() >= maximumActiveWriteOperationCount) {
442             LOG(NetworkCacheStorage, "(NetworkProcess) limiting parallel writes");
443             return;
444         }
445         auto writeOperation = m_pendingWriteOperations.takeFirst();
446         auto& write = *writeOperation;
447         m_activeWriteOperations.add(WTF::move(writeOperation));
448
449         if (write.existingEntry && m_contentsFilter.mayContain(write.entry.key.shortHash())) {
450             dispatchHeaderWriteOperation(write);
451             continue;
452         }
453         dispatchFullWriteOperation(write);
454     }
455 }
456
457 void Storage::dispatchFullWriteOperation(const WriteOperation& write)
458 {
459     ASSERT(RunLoop::isMain());
460     ASSERT(m_activeWriteOperations.contains(&write));
461
462     if (!m_contentsFilter.mayContain(write.entry.key.shortHash()))
463         m_contentsFilter.add(write.entry.key.shortHash());
464
465     StringCapture cachePathCapture(m_directoryPath);
466     backgroundIOQueue().dispatch([this, &write, cachePathCapture] {
467         auto encodedHeader = encodeEntryHeader(write.entry);
468         auto headerAndBodyData = concatenate(encodedHeader, write.entry.body);
469
470         auto channel = openFileForKey(write.entry.key, IOChannel::Type::Create, cachePathCapture.string());
471         int fd = channel->fileDescriptor();
472         size_t bodyOffset = encodedHeader.size();
473
474         channel->write(0, headerAndBodyData, [this, &write, bodyOffset, fd](int error) {
475             LOG(NetworkCacheStorage, "(NetworkProcess) write complete error=%d", error);
476             if (error) {
477                 if (m_contentsFilter.mayContain(write.entry.key.shortHash()))
478                     m_contentsFilter.remove(write.entry.key.shortHash());
479             }
480             size_t bodySize = write.entry.body.size();
481             size_t totalSize = bodyOffset + bodySize;
482
483             m_approximateSize += totalSize;
484
485             bool shouldMapBody = !error && bodySize >= pageSize();
486             auto bodyMap = shouldMapBody ? mapFile(fd, bodyOffset, bodySize) : Data();
487
488             write.completionHandler(!error, bodyMap);
489
490             ASSERT(m_activeWriteOperations.contains(&write));
491             m_activeWriteOperations.remove(&write);
492             dispatchPendingWriteOperations();
493         });
494     });
495
496     shrinkIfNeeded();
497 }
498
499 void Storage::dispatchHeaderWriteOperation(const WriteOperation& write)
500 {
501     ASSERT(RunLoop::isMain());
502     ASSERT(write.existingEntry);
503     ASSERT(m_activeWriteOperations.contains(&write));
504     ASSERT(m_contentsFilter.mayContain(write.entry.key.shortHash()));
505
506     // Try to update the header of an existing entry.
507     StringCapture cachePathCapture(m_directoryPath);
508     backgroundIOQueue().dispatch([this, &write, cachePathCapture] {
509         auto headerData = encodeEntryHeader(write.entry);
510         auto existingHeaderData = encodeEntryHeader(write.existingEntry.value());
511
512         bool pageRoundedHeaderSizeChanged = headerData.size() != existingHeaderData.size();
513         if (pageRoundedHeaderSizeChanged) {
514             LOG(NetworkCacheStorage, "(NetworkProcess) page-rounded header size changed, storing full entry");
515             RunLoop::main().dispatch([this, &write] {
516                 dispatchFullWriteOperation(write);
517             });
518             return;
519         }
520
521         auto channel = openFileForKey(write.entry.key, IOChannel::Type::Write, cachePathCapture.string());
522         channel->write(0, headerData, [this, &write](int error) {
523             LOG(NetworkCacheStorage, "(NetworkProcess) update complete error=%d", error);
524
525             if (error)
526                 removeEntry(write.entry.key);
527
528             write.completionHandler(!error, { });
529
530             ASSERT(m_activeWriteOperations.contains(&write));
531             m_activeWriteOperations.remove(&write);
532             dispatchPendingWriteOperations();
533         });
534     });
535 }
536
537 void Storage::setMaximumSize(size_t size)
538 {
539     ASSERT(RunLoop::isMain());
540     m_maximumSize = size;
541
542     shrinkIfNeeded();
543 }
544
545 void Storage::clear()
546 {
547     ASSERT(RunLoop::isMain());
548     LOG(NetworkCacheStorage, "(NetworkProcess) clearing cache");
549
550     m_contentsFilter.clear();
551     m_approximateSize = 0;
552
553     StringCapture directoryPathCapture(m_directoryPath);
554
555     ioQueue().dispatch([directoryPathCapture] {
556         String directoryPath = directoryPathCapture.string();
557         traverseDirectory(directoryPath, DT_DIR, [&directoryPath](const String& subdirName) {
558             String subdirPath = WebCore::pathByAppendingComponent(directoryPath, subdirName);
559             traverseDirectory(subdirPath, DT_REG, [&subdirPath](const String& fileName) {
560                 WebCore::deleteFile(WebCore::pathByAppendingComponent(subdirPath, fileName));
561             });
562             WebCore::deleteEmptyDirectory(subdirPath);
563         });
564     });
565 }
566
567 void Storage::shrinkIfNeeded()
568 {
569     ASSERT(RunLoop::isMain());
570
571     static const double deletionProbability { 0.25 };
572
573     if (m_approximateSize <= m_maximumSize)
574         return;
575     if (m_shrinkInProgress)
576         return;
577     m_shrinkInProgress = true;
578
579     LOG(NetworkCacheStorage, "(NetworkProcess) shrinking cache approximateSize=%d, m_maximumSize=%d", static_cast<size_t>(m_approximateSize), m_maximumSize);
580
581     m_approximateSize = 0;
582
583     StringCapture cachePathCapture(m_directoryPath);
584     backgroundIOQueue().dispatch([this, cachePathCapture] {
585         String cachePath = cachePathCapture.string();
586         traverseCacheFiles(cachePath, [this](const String& fileName, const String& partitionPath) {
587             auto filePath = WebCore::pathByAppendingComponent(partitionPath, fileName);
588
589             bool shouldDelete = randomNumber() < deletionProbability;
590             if (!shouldDelete) {
591                 long long fileSize = 0;
592                 WebCore::getFileSize(filePath, fileSize);
593                 m_approximateSize += fileSize;
594                 return;
595             }
596
597             WebCore::deleteFile(filePath);
598             Key::HashType hash;
599             if (!Key::stringToHash(fileName, hash))
600                 return;
601             unsigned shortHash = Key::toShortHash(hash);
602             RunLoop::main().dispatch([this, shortHash] {
603                 if (m_contentsFilter.mayContain(shortHash))
604                     m_contentsFilter.remove(shortHash);
605             });
606         });
607
608         // Let system figure out if they are really empty.
609         traverseDirectory(cachePath, DT_DIR, [&cachePath](const String& subdirName) {
610             auto partitionPath = WebCore::pathByAppendingComponent(cachePath, subdirName);
611             WebCore::deleteEmptyDirectory(partitionPath);
612         });
613
614         m_shrinkInProgress = false;
615
616         LOG(NetworkCacheStorage, "(NetworkProcess) cache shrink completed approximateSize=%d", static_cast<size_t>(m_approximateSize));
617     });
618 }
619
620 void Storage::deleteOldVersions()
621 {
622     // Delete V1 cache.
623     StringCapture cachePathCapture(m_baseDirectoryPath);
624     backgroundIOQueue().dispatch([cachePathCapture] {
625         String cachePath = cachePathCapture.string();
626         traverseDirectory(cachePath, DT_DIR, [&cachePath](const String& subdirName) {
627             if (subdirName.startsWith(versionDirectoryPrefix))
628                 return;
629             String partitionPath = WebCore::pathByAppendingComponent(cachePath, subdirName);
630             traverseDirectory(partitionPath, DT_REG, [&partitionPath](const String& fileName) {
631                 WebCore::deleteFile(WebCore::pathByAppendingComponent(partitionPath, fileName));
632             });
633             WebCore::deleteEmptyDirectory(partitionPath);
634         });
635     });
636 }
637
638 }
639 }
640
641 #endif