diff options
Diffstat (limited to 'src/network/access/qnetworkdiskcache.cpp')
-rw-r--r-- | src/network/access/qnetworkdiskcache.cpp | 176 |
1 files changed, 77 insertions, 99 deletions
diff --git a/src/network/access/qnetworkdiskcache.cpp b/src/network/access/qnetworkdiskcache.cpp index fe145a323d..b39924025e 100644 --- a/src/network/access/qnetworkdiskcache.cpp +++ b/src/network/access/qnetworkdiskcache.cpp @@ -12,16 +12,14 @@ #include <qdir.h> #include <qdatastream.h> #include <qdatetime.h> -#include <qdiriterator.h> +#include <qdirlisting.h> #include <qurl.h> #include <qcryptographichash.h> #include <qdebug.h> -#include <QMultiMap> #include <memory> #define CACHE_POSTFIX ".d"_L1 -#define PREPARED_SLASH "prepared/"_L1 #define CACHE_VERSION 8 #define DATA_DIR "data"_L1 @@ -156,15 +154,11 @@ QIODevice *QNetworkDiskCache::prepare(const QNetworkCacheMetaData &metaData) return nullptr; } - const auto headers = metaData.rawHeaders(); - for (const auto &header : headers) { - if (header.first.compare("content-length", Qt::CaseInsensitive) == 0) { - const qint64 size = header.second.toLongLong(); - if (size > (maximumCacheSize() * 3)/4) - return nullptr; - break; - } - } + const auto sizeValue = metaData.headers().value(QHttpHeaders::WellKnownHeader::ContentLength); + const qint64 size = sizeValue.toLongLong(); + if (size > (maximumCacheSize() * 3)/4) + return nullptr; + std::unique_ptr<QCacheItem> cacheItem = std::make_unique<QCacheItem>(); cacheItem->metaData = metaData; @@ -173,13 +167,9 @@ QIODevice *QNetworkDiskCache::prepare(const QNetworkCacheMetaData &metaData) cacheItem->data.open(QBuffer::ReadWrite); device = &(cacheItem->data); } else { - QString templateName = d->tmpCacheFileName(); - QT_TRY { - cacheItem->file = new QTemporaryFile(templateName, &cacheItem->data); - } QT_CATCH(...) { - cacheItem->file = nullptr; - } - if (!cacheItem->file || !cacheItem->file->open()) { + QString fileName = d->cacheFileName(cacheItem->metaData.url()); + cacheItem->file = new(std::nothrow) QSaveFile(fileName, &cacheItem->data); + if (!cacheItem->file || !cacheItem->file->open(QFileDevice::WriteOnly)) { qWarning("QNetworkDiskCache::prepare() unable to open temporary file"); cacheItem.reset(); return nullptr; @@ -219,7 +209,6 @@ void QNetworkDiskCache::insert(QIODevice *device) void QNetworkDiskCachePrivate::prepareLayout() { QDir helper; - helper.mkpath(cacheDirectory + PREPARED_SLASH); //Create directory and subdirectories 0-F helper.mkpath(dataDirectory); @@ -248,9 +237,8 @@ void QNetworkDiskCachePrivate::storeItem(QCacheItem *cacheItem) currentCacheSize = q->expire(); if (!cacheItem->file) { - QString templateName = tmpCacheFileName(); - cacheItem->file = new QTemporaryFile(templateName, &cacheItem->data); - if (cacheItem->file->open()) { + cacheItem->file = new QSaveFile(fileName, &cacheItem->data); + if (cacheItem->file->open(QFileDevice::WriteOnly)) { cacheItem->writeHeader(cacheItem->file); cacheItem->writeCompressedData(cacheItem->file); } @@ -258,13 +246,15 @@ void QNetworkDiskCachePrivate::storeItem(QCacheItem *cacheItem) if (cacheItem->file && cacheItem->file->isOpen() - && cacheItem->file->error() == QFile::NoError) { - cacheItem->file->setAutoRemove(false); - // ### use atomic rename rather then remove & rename - if (cacheItem->file->rename(fileName)) - currentCacheSize += cacheItem->file->size(); - else - cacheItem->file->setAutoRemove(true); + && cacheItem->file->error() == QFileDevice::NoError) { + // We have to call size() here instead of inside the if-body because + // commit() invalidates the file-engine, and size() will create a new + // one, pointing at an empty filename. + qint64 size = cacheItem->file->size(); + if (cacheItem->file->commit()) + currentCacheSize += size; + // Delete and unset the QSaveFile, it's invalid now. + delete std::exchange(cacheItem->file, nullptr); } if (cacheItem->metaData.url() == lastItem.metaData.url()) lastItem.reset(); @@ -483,47 +473,45 @@ qint64 QNetworkDiskCache::expire() // close file handle to prevent "in use" error when QFile::remove() is called d->lastItem.reset(); - QDir::Filters filters = QDir::AllDirs | QDir:: Files | QDir::NoDotAndDotDot; - QDirIterator it(cacheDirectory(), filters, QDirIterator::Subdirectories); + const QDir::Filters filters = QDir::AllDirs | QDir:: Files | QDir::NoDotAndDotDot; - QMultiMap<QDateTime, QString> cacheItems; + struct CacheItem + { + std::chrono::milliseconds msecs; + QString path; + qint64 size = 0; + }; + std::vector<CacheItem> cacheItems; qint64 totalSize = 0; - while (it.hasNext()) { - QFileInfo info = it.nextFileInfo(); - QString path = info.filePath(); - QString fileName = info.fileName(); - if (fileName.endsWith(CACHE_POSTFIX)) { - const QDateTime birthTime = info.fileTime(QFile::FileBirthTime); - cacheItems.insert(birthTime.isValid() ? birthTime - : info.fileTime(QFile::FileMetadataChangeTime), path); - totalSize += info.size(); - } + using F = QDirListing::IteratorFlag; + for (const auto &dirEntry : QDirListing(cacheDirectory(), filters, F::Recursive)) { + if (!dirEntry.fileName().endsWith(CACHE_POSTFIX)) + continue; + + const QFileInfo &info = dirEntry.fileInfo(); + QDateTime fileTime = info.birthTime(QTimeZone::UTC); + if (!fileTime.isValid()) + fileTime = info.metadataChangeTime(QTimeZone::UTC); + const std::chrono::milliseconds msecs{fileTime.toMSecsSinceEpoch()}; + const qint64 size = info.size(); + cacheItems.push_back(CacheItem{msecs, info.filePath(), size}); + totalSize += size; } + const qint64 goal = (maximumCacheSize() * 9) / 10; + if (totalSize < goal) + return totalSize; // Nothing to do + + auto byFileTime = [&](const auto &a, const auto &b) { return a.msecs < b.msecs; }; + std::sort(cacheItems.begin(), cacheItems.end(), byFileTime); + [[maybe_unused]] int removedFiles = 0; // used under QNETWORKDISKCACHE_DEBUG - qint64 goal = (maximumCacheSize() * 9) / 10; - QMultiMap<QDateTime, QString>::const_iterator i = cacheItems.constBegin(); - while (i != cacheItems.constEnd()) { + for (const CacheItem &cached : cacheItems) { + QFile::remove(cached.path); + ++removedFiles; + totalSize -= cached.size; if (totalSize < goal) break; - QString name = i.value(); - QFile file(name); - - if (name.contains(PREPARED_SLASH)) { - for (QCacheItem *item : qAsConst(d->inserting)) { - if (item && item->file && item->file->fileName() == name) { - delete item->file; - item->file = nullptr; - break; - } - } - } - - qint64 size = file.size(); - file.remove(); - totalSize -= size; - ++removedFiles; - ++i; } #if defined(QNETWORKDISKCACHE_DEBUG) if (removedFiles > 0) { @@ -562,19 +550,13 @@ QString QNetworkDiskCachePrivate::uniqueFileName(const QUrl &url) const QByteArray hash = QCryptographicHash::hash(cleanUrl.toEncoded(), QCryptographicHash::Sha1); // convert sha1 to base36 form and return first 8 bytes for use as string const QByteArray id = QByteArray::number(*(qlonglong*)hash.data(), 36).left(8); - // generates <one-char subdir>/<8-char filname.d> - uint code = (uint)id.at(id.length()-1) % 16; + // generates <one-char subdir>/<8-char filename.d> + uint code = (uint)id.at(id.size()-1) % 16; QString pathFragment = QString::number(code, 16) + u'/' + QLatin1StringView(id) + CACHE_POSTFIX; return pathFragment; } -QString QNetworkDiskCachePrivate::tmpCacheFileName() const -{ - //The subdirectory is presumed to be already read for use. - return cacheDirectory + PREPARED_SLASH + "XXXXXX"_L1 + CACHE_POSTFIX; -} - /*! Generates fully qualified path of cached resource from a URL. */ @@ -592,31 +574,27 @@ QString QNetworkDiskCachePrivate::cacheFileName(const QUrl &url) const */ bool QCacheItem::canCompress() const { - bool sizeOk = false; - bool typeOk = false; - const auto headers = metaData.rawHeaders(); - for (const auto &header : headers) { - if (header.first.compare("content-length", Qt::CaseInsensitive) == 0) { - qint64 size = header.second.toLongLong(); - if (size > MAX_COMPRESSION_SIZE) - return false; - else - sizeOk = true; - } + const auto h = metaData.headers(); - if (header.first.compare("content-type", Qt::CaseInsensitive) == 0) { - QByteArray type = header.second; - if (type.startsWith("text/") - || (type.startsWith("application/") - && (type.endsWith("javascript") || type.endsWith("ecmascript")))) - typeOk = true; - else - return false; - } - if (sizeOk && typeOk) - return true; + const auto sizeValue = h.value(QHttpHeaders::WellKnownHeader::ContentLength); + if (sizeValue.empty()) + return false; + + qint64 size = sizeValue.toLongLong(); + if (size > MAX_COMPRESSION_SIZE) + return false; + + const auto type = h.value(QHttpHeaders::WellKnownHeader::ContentType); + if (!type.empty()) + return false; + + if (!type.startsWith("text/") + && !(type.startsWith("application/") + && (type.endsWith("javascript") || type.endsWith("ecmascript")))) { + return false; } - return false; + + return true; } enum @@ -625,7 +603,7 @@ enum CurrentCacheVersion = CACHE_VERSION }; -void QCacheItem::writeHeader(QFile *device) const +void QCacheItem::writeHeader(QFileDevice *device) const { QDataStream out(device); @@ -637,7 +615,7 @@ void QCacheItem::writeHeader(QFile *device) const out << compressed; } -void QCacheItem::writeCompressedData(QFile *device) const +void QCacheItem::writeCompressedData(QFileDevice *device) const { QDataStream out(device); @@ -648,7 +626,7 @@ void QCacheItem::writeCompressedData(QFile *device) const Returns \c false if the file is a cache file, but is an older version and should be removed otherwise true. */ -bool QCacheItem::read(QFile *device, bool readData) +bool QCacheItem::read(QFileDevice *device, bool readData) { reset(); @@ -687,7 +665,7 @@ bool QCacheItem::read(QFile *device, bool readData) if (!device->fileName().endsWith(expectedFilename)) return false; - return metaData.isValid(); + return metaData.isValid() && !metaData.headers().isEmpty(); } QT_END_NAMESPACE |