Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qnetworkdiskcache.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4//#define QNETWORKDISKCACHE_DEBUG
5
6
7#include "qnetworkdiskcache.h"
9#include "QtCore/qscopedpointer.h"
10
11#include <qfile.h>
12#include <qdir.h>
13#include <qdatastream.h>
14#include <qdatetime.h>
15#include <qdiriterator.h>
16#include <qurl.h>
17#include <qcryptographichash.h>
18#include <qdebug.h>
19
20#include <memory>
21
22#define CACHE_POSTFIX ".d"_L1
23#define CACHE_VERSION 8
24#define DATA_DIR "data"_L1
25
26#define MAX_COMPRESSION_SIZE (1024 * 1024 * 3)
27
29
30using namespace Qt::StringLiterals;
31
73{
74}
75
80{
82 qDeleteAll(d->inserting);
83}
84
89{
90 Q_D(const QNetworkDiskCache);
91 return d->cacheDirectory;
92}
93
105{
106#if defined(QNETWORKDISKCACHE_DEBUG)
107 qDebug() << "QNetworkDiskCache::setCacheDirectory()" << cacheDir;
108#endif
110 if (cacheDir.isEmpty())
111 return;
112 d->cacheDirectory = cacheDir;
113 QDir dir(d->cacheDirectory);
114 d->cacheDirectory = dir.absolutePath();
115 if (!d->cacheDirectory.endsWith(u'/'))
116 d->cacheDirectory += u'/';
117
118 d->dataDirectory = d->cacheDirectory + DATA_DIR + QString::number(CACHE_VERSION) + u'/';
119 d->prepareLayout();
120}
121
126{
127#if defined(QNETWORKDISKCACHE_DEBUG)
128 qDebug("QNetworkDiskCache::cacheSize()");
129#endif
130 Q_D(const QNetworkDiskCache);
131 if (d->cacheDirectory.isEmpty())
132 return 0;
133 if (d->currentCacheSize < 0) {
134 QNetworkDiskCache *that = const_cast<QNetworkDiskCache*>(this);
135 that->d_func()->currentCacheSize = that->expire();
136 }
137 return d->currentCacheSize;
138}
139
144{
145#if defined(QNETWORKDISKCACHE_DEBUG)
146 qDebug() << "QNetworkDiskCache::prepare()" << metaData.url();
147#endif
150 return nullptr;
151
152 if (d->cacheDirectory.isEmpty()) {
153 qWarning("QNetworkDiskCache::prepare() The cache directory is not set");
154 return nullptr;
155 }
156
157 const auto headers = metaData.rawHeaders();
158 for (const auto &header : headers) {
159 if (header.first.compare("content-length", Qt::CaseInsensitive) == 0) {
160 const qint64 size = header.second.toLongLong();
161 if (size > (maximumCacheSize() * 3)/4)
162 return nullptr;
163 break;
164 }
165 }
166 std::unique_ptr<QCacheItem> cacheItem = std::make_unique<QCacheItem>();
167 cacheItem->metaData = metaData;
168
169 QIODevice *device = nullptr;
170 if (cacheItem->canCompress()) {
171 cacheItem->data.open(QBuffer::ReadWrite);
172 device = &(cacheItem->data);
173 } else {
174 QString fileName = d->cacheFileName(cacheItem->metaData.url());
175 QT_TRY {
176 cacheItem->file = new QSaveFile(fileName, &cacheItem->data);
177 } QT_CATCH(...) {
178 cacheItem->file = nullptr;
179 }
180 if (!cacheItem->file || !cacheItem->file->open(QFileDevice::WriteOnly)) {
181 qWarning("QNetworkDiskCache::prepare() unable to open temporary file");
182 cacheItem.reset();
183 return nullptr;
184 }
185 cacheItem->writeHeader(cacheItem->file);
186 device = cacheItem->file;
187 }
188 d->inserting[device] = cacheItem.release();
189 return device;
190}
191
196{
197#if defined(QNETWORKDISKCACHE_DEBUG)
198 qDebug() << "QNetworkDiskCache::insert()" << device;
199#endif
201 const auto it = d->inserting.constFind(device);
202 if (Q_UNLIKELY(it == d->inserting.cend())) {
203 qWarning() << "QNetworkDiskCache::insert() called on a device we don't know about" << device;
204 return;
205 }
206
207 d->storeItem(it.value());
208 delete it.value();
209 d->inserting.erase(it);
210}
211
212
218{
219 QDir helper;
220
221 //Create directory and subdirectories 0-F
222 helper.mkpath(dataDirectory);
223 for (uint i = 0; i < 16 ; i++) {
225 QString subdir = dataDirectory + str;
226 helper.mkdir(subdir);
227 }
228}
229
230
232{
234 Q_ASSERT(cacheItem->metaData.saveToDisk());
235
236 QString fileName = cacheFileName(cacheItem->metaData.url());
237 Q_ASSERT(!fileName.isEmpty());
238
239 if (QFile::exists(fileName)) {
240 if (!removeFile(fileName)) {
241 qWarning() << "QNetworkDiskCache: couldn't remove the cache file " << fileName;
242 return;
243 }
244 }
245
246 currentCacheSize = q->expire();
247 if (!cacheItem->file) {
248 cacheItem->file = new QSaveFile(fileName, &cacheItem->data);
249 if (cacheItem->file->open(QFileDevice::WriteOnly)) {
250 cacheItem->writeHeader(cacheItem->file);
251 cacheItem->writeCompressedData(cacheItem->file);
252 }
253 }
254
255 if (cacheItem->file
256 && cacheItem->file->isOpen()
257 && cacheItem->file->error() == QFileDevice::NoError) {
258 // We have to call size() here instead of inside the if-body because
259 // commit() invalidates the file-engine, and size() will create a new
260 // one, pointing at an empty filename.
261 qint64 size = cacheItem->file->size();
262 if (cacheItem->file->commit())
264 // Delete and unset the QSaveFile, it's invalid now.
265 delete std::exchange(cacheItem->file, nullptr);
266 }
267 if (cacheItem->metaData.url() == lastItem.metaData.url())
268 lastItem.reset();
269}
270
275{
276#if defined(QNETWORKDISKCACHE_DEBUG)
277 qDebug() << "QNetworkDiskCache::remove()" << url;
278#endif
280
281 // remove is also used to cancel insertions, not a common operation
282 for (auto it = d->inserting.cbegin(), end = d->inserting.cend(); it != end; ++it) {
283 QCacheItem *item = it.value();
284 if (item && item->metaData.url() == url) {
285 delete item;
286 d->inserting.erase(it);
287 return true;
288 }
289 }
290
291 if (d->lastItem.metaData.url() == url)
292 d->lastItem.reset();
293 return d->removeFile(d->cacheFileName(url));
294}
295
300{
301#if defined(QNETWORKDISKCACHE_DEBUG)
302 qDebug() << "QNetworkDiskCache::removFile()" << file;
303#endif
304 if (file.isEmpty())
305 return false;
308 if (!fileName.endsWith(CACHE_POSTFIX))
309 return false;
310 qint64 size = info.size();
311 if (QFile::remove(file)) {
313 return true;
314 }
315 return false;
316}
317
322{
323#if defined(QNETWORKDISKCACHE_DEBUG)
324 qDebug() << "QNetworkDiskCache::metaData()" << url;
325#endif
327 if (d->lastItem.metaData.url() == url)
328 return d->lastItem.metaData;
329 return fileMetaData(d->cacheFileName(url));
330}
331
338{
339#if defined(QNETWORKDISKCACHE_DEBUG)
340 qDebug() << "QNetworkDiskCache::fileMetaData()" << fileName;
341#endif
342 Q_D(const QNetworkDiskCache);
345 return QNetworkCacheMetaData();
346 if (!d->lastItem.read(&file, false)) {
347 file.close();
349 that->removeFile(fileName);
350 }
351 return d->lastItem.metaData;
352}
353
358{
359#if defined(QNETWORKDISKCACHE_DEBUG)
360 qDebug() << "QNetworkDiskCache::data()" << url;
361#endif
363 std::unique_ptr<QBuffer> buffer;
364 if (!url.isValid())
365 return nullptr;
366 if (d->lastItem.metaData.url() == url && d->lastItem.data.isOpen()) {
367 buffer.reset(new QBuffer);
368 buffer->setData(d->lastItem.data.data());
369 } else {
370 QScopedPointer<QFile> file(new QFile(d->cacheFileName(url)));
372 return nullptr;
373
374 if (!d->lastItem.read(file.data(), true)) {
375 file->close();
376 remove(url);
377 return nullptr;
378 }
379 if (d->lastItem.data.isOpen()) {
380 // compressed
381 buffer.reset(new QBuffer);
382 buffer->setData(d->lastItem.data.data());
383 } else {
384 buffer.reset(new QBuffer);
385 buffer->setData(file->readAll());
386 }
387 }
389 return buffer.release();
390}
391
396{
397#if defined(QNETWORKDISKCACHE_DEBUG)
398 qDebug() << "QNetworkDiskCache::updateMetaData()" << metaData.url();
399#endif
400 QUrl url = metaData.url();
401 QIODevice *oldDevice = data(url);
402 if (!oldDevice) {
403#if defined(QNETWORKDISKCACHE_DEBUG)
404 qDebug("QNetworkDiskCache::updateMetaData(), no device!");
405#endif
406 return;
407 }
408
409 QIODevice *newDevice = prepare(metaData);
410 if (!newDevice) {
411#if defined(QNETWORKDISKCACHE_DEBUG)
412 qDebug() << "QNetworkDiskCache::updateMetaData(), no new device!" << url;
413#endif
414 return;
415 }
416 char data[1024];
417 while (!oldDevice->atEnd()) {
418 qint64 s = oldDevice->read(data, 1024);
419 newDevice->write(data, s);
420 }
421 delete oldDevice;
422 insert(newDevice);
423}
424
431{
432 Q_D(const QNetworkDiskCache);
433 return d->maximumCacheSize;
434}
435
444{
446 bool expireCache = (size < d->maximumCacheSize);
447 d->maximumCacheSize = size;
448 if (expireCache)
449 d->currentCacheSize = expire();
450}
451
471{
473 if (d->currentCacheSize >= 0 && d->currentCacheSize < maximumCacheSize())
474 return d->currentCacheSize;
475
476 if (cacheDirectory().isEmpty()) {
477 qWarning("QNetworkDiskCache::expire() The cache directory is not set");
478 return 0;
479 }
480
481 // close file handle to prevent "in use" error when QFile::remove() is called
482 d->lastItem.reset();
483
484 const QDir::Filters filters = QDir::AllDirs | QDir:: Files | QDir::NoDotAndDotDot;
486
487 struct CacheItem
488 {
489 std::chrono::milliseconds msecs;
491 qint64 size = 0;
492 };
493 std::vector<CacheItem> cacheItems;
494 qint64 totalSize = 0;
495 while (it.hasNext()) {
496 QFileInfo info = it.nextFileInfo();
498 continue;
499
501 if (!fileTime.isValid())
503 const std::chrono::milliseconds msecs{fileTime.toMSecsSinceEpoch()};
504 const qint64 size = info.size();
505 cacheItems.push_back(CacheItem{msecs, info.filePath(), size});
506 totalSize += size;
507 }
508
509 const qint64 goal = (maximumCacheSize() * 9) / 10;
510 if (totalSize < goal)
511 return totalSize; // Nothing to do
512
513 auto byFileTime = [&](const auto &a, const auto &b) { return a.msecs < b.msecs; };
514 std::sort(cacheItems.begin(), cacheItems.end(), byFileTime);
515
516 [[maybe_unused]] int removedFiles = 0; // used under QNETWORKDISKCACHE_DEBUG
517 for (const CacheItem &cached : cacheItems) {
518 QFile::remove(cached.path);
519 ++removedFiles;
520 totalSize -= cached.size;
521 if (totalSize < goal)
522 break;
523 }
524#if defined(QNETWORKDISKCACHE_DEBUG)
525 if (removedFiles > 0) {
526 qDebug() << "QNetworkDiskCache::expire()"
527 << "Removed:" << removedFiles
528 << "Kept:" << cacheItems.count() - removedFiles;
529 }
530#endif
531 return totalSize;
532}
533
538{
539#if defined(QNETWORKDISKCACHE_DEBUG)
540 qDebug("QNetworkDiskCache::clear()");
541#endif
543 qint64 size = d->maximumCacheSize;
544 d->maximumCacheSize = 0;
545 d->currentCacheSize = expire();
546 d->maximumCacheSize = size;
547}
548
553{
554 QUrl cleanUrl = url;
555 cleanUrl.setPassword(QString());
556 cleanUrl.setFragment(QString());
557
559 // convert sha1 to base36 form and return first 8 bytes for use as string
560 const QByteArray id = QByteArray::number(*(qlonglong*)hash.data(), 36).left(8);
561 // generates <one-char subdir>/<8-char filename.d>
562 uint code = (uint)id.at(id.size()-1) % 16;
563 QString pathFragment = QString::number(code, 16) + u'/' + QLatin1StringView(id) + CACHE_POSTFIX;
564
565 return pathFragment;
566}
567
572{
573 if (!url.isValid())
574 return QString();
575
577 return fullpath;
578}
579
584{
585 bool sizeOk = false;
586 bool typeOk = false;
587 const auto headers = metaData.rawHeaders();
588 for (const auto &header : headers) {
589 if (header.first.compare("content-length", Qt::CaseInsensitive) == 0) {
590 qint64 size = header.second.toLongLong();
592 return false;
593 else
594 sizeOk = true;
595 }
596
597 if (header.first.compare("content-type", Qt::CaseInsensitive) == 0) {
598 QByteArray type = header.second;
599 if (type.startsWith("text/")
600 || (type.startsWith("application/")
601 && (type.endsWith("javascript") || type.endsWith("ecmascript"))))
602 typeOk = true;
603 else
604 return false;
605 }
606 if (sizeOk && typeOk)
607 return true;
608 }
609 return false;
610}
611
612enum
613{
617
619{
621
624 out << static_cast<qint32>(out.version());
625 out << metaData;
626 bool compressed = canCompress();
627 out << compressed;
628}
629
631{
633
634 out << qCompress(data.data());
635}
636
642{
643 reset();
644
646
648 qint32 v;
649 in >> marker;
650 in >> v;
651 if (marker != CacheMagic)
652 return true;
653
654 // If the cache magic is correct, but the version is not we should remove it
655 if (v != CurrentCacheVersion)
656 return false;
657
658 qint32 streamVersion;
659 in >> streamVersion;
660 // Default stream version is also the highest we can handle
661 if (streamVersion > in.version())
662 return false;
663 in.setVersion(streamVersion);
664
665 bool compressed;
666 QByteArray dataBA;
667 in >> metaData;
668 in >> compressed;
669 if (readData && compressed) {
670 in >> dataBA;
671 data.setData(qUncompress(dataBA));
673 }
674
675 // quick and dirty check if metadata's URL field and the file's name are in synch
677 if (!device->fileName().endsWith(expectedFilename))
678 return false;
679
680 return metaData.isValid() && !metaData.rawHeaders().isEmpty();
681}
682
684
685#include "moc_qnetworkdiskcache.cpp"
IOBluetoothDevice * device
The QAbstractNetworkCache class provides the interface for cache implementations.
\inmodule QtCore \reentrant
Definition qbuffer.h:16
\inmodule QtCore
Definition qbytearray.h:57
static QByteArray number(int, int base=10)
Returns a byte-array representing the whole number n as text.
QByteArray left(qsizetype len) const
Returns a byte array that contains the first len bytes of this byte array.
void writeCompressedData(QFileDevice *device) const
bool canCompress() const
We compress small text and JavaScript files.
bool read(QFileDevice *device, bool readData)
Returns false if the file is a cache file, but is an older version and should be removed otherwise tr...
QNetworkCacheMetaData metaData
QSaveFile * file
void writeHeader(QFileDevice *device) const
static QByteArray hash(QByteArrayView data, Algorithm method)
Returns the hash of data using method.
\inmodule QtCore\reentrant
Definition qdatastream.h:30
\inmodule QtCore\reentrant
Definition qdatetime.h:257
qint64 toMSecsSinceEpoch() const
bool isValid() const
Returns true if this datetime represents a definite moment, otherwise false.
The QDirIterator class provides an iterator for directory entrylists.
\inmodule QtCore
Definition qdir.h:19
bool mkdir(const QString &dirName) const
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qdir.cpp:1527
bool mkpath(const QString &dirPath) const
Creates the directory path dirPath.
Definition qdir.cpp:1579
@ Files
Definition qdir.h:22
@ AllDirs
Definition qdir.h:39
@ NoDotAndDotDot
Definition qdir.h:43
\inmodule QtCore
Definition qfiledevice.h:16
qint64 size() const override
Returns the size of the file.
FileError error() const
Returns the file error status.
void close() override
Calls QFileDevice::flush() and closes the file.
\inmodule QtCore \reentrant
Definition qfileinfo.h:22
QDateTime birthTime() const
Returns the date and time when the file was created (born), in local time.
Definition qfileinfo.h:154
QString fileName() const
Returns the name of the file, excluding the path.
QDateTime metadataChangeTime() const
Returns the date and time when the file's metadata was last changed, in local time.
Definition qfileinfo.h:155
qint64 size() const
Returns the file size in bytes.
QString filePath() const
Returns the file name, including the path (which may be absolute or relative).
\inmodule QtCore
Definition qfile.h:93
bool open(OpenMode flags) override
Opens the file using OpenMode mode, returning true if successful; otherwise false.
Definition qfile.cpp:881
bool remove()
Removes the file specified by fileName().
Definition qfile.cpp:419
bool exists() const
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qfile.cpp:351
\inmodule QtCore \reentrant
Definition qiodevice.h:34
bool isOpen() const
Returns true if the device is open; otherwise returns false.
QByteArray readAll()
Reads all remaining data from the device, and returns it as a byte array.
qint64 write(const char *data, qint64 len)
Writes at most maxSize bytes of data from data to the device.
virtual bool atEnd() const
Returns true if the current read and write position is at the end of the device (i....
qint64 read(char *data, qint64 maxlen)
Reads at most maxSize bytes from the device into data, and returns the number of bytes read.
bool isEmpty() const noexcept
Definition qlist.h:390
The QNetworkCacheMetaData class provides cache information.
bool saveToDisk() const
Returns is this cache should be allowed to be stored on disk.
bool isValid() const
Returns true if this network cache meta data has attributes that have been set otherwise false.
QUrl url() const
Returns the URL this network cache meta data is referring to.
RawHeaderList rawHeaders() const
Returns a list of all raw headers that are set in this meta data.
bool removeFile(const QString &file)
Put all of the misc file removing into one function to be extra safe.
QString cacheFileName(const QUrl &url) const
Generates fully qualified path of cached resource from a URL.
static QString uniqueFileName(const QUrl &url)
Given a URL, generates a unique enough filename (and subdirectory)
void prepareLayout()
Create subdirectories and other housekeeping on the filesystem.
void storeItem(QCacheItem *item)
The QNetworkDiskCache class provides a very basic disk cache.
void insert(QIODevice *device) override
\reimp
QNetworkCacheMetaData metaData(const QUrl &url) override
\reimp
void updateMetaData(const QNetworkCacheMetaData &metaData) override
\reimp
QIODevice * prepare(const QNetworkCacheMetaData &metaData) override
\reimp
void setCacheDirectory(const QString &cacheDir)
Sets the directory where cached files will be stored to cacheDir.
virtual qint64 expire()
Cleans the cache so that its size is under the maximum cache size.
void clear() override
\reimp
QString cacheDirectory() const
Returns the location where cached files will be stored.
QNetworkCacheMetaData fileMetaData(const QString &fileName) const
Returns the QNetworkCacheMetaData for the cache file fileName.
QNetworkDiskCache(QObject *parent=nullptr)
Creates a new disk cache.
~QNetworkDiskCache()
Destroys the cache object.
QIODevice * data(const QUrl &url) override
\reimp
void setMaximumCacheSize(qint64 size)
Sets the maximum size of the disk cache to be size.
qint64 maximumCacheSize() const
Returns the current maximum size for the disk cache.
bool remove(const QUrl &url) override
\reimp
qint64 cacheSize() const override
\reimp
\inmodule QtCore
Definition qobject.h:90
\inmodule QtCore
Definition qsavefile.h:24
bool open(OpenMode flags) override
Opens the file using OpenMode mode, returning true if successful; otherwise false.
bool commit()
Commits the changes to disk, if all previous writes were successful.
\inmodule QtCore
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
QString first(qsizetype n) const
Definition qstring.h:337
qlonglong toLongLong(bool *ok=nullptr, int base=10) const
Returns the string converted to a {long long} using base base, which is 10 by default and must be bet...
bool endsWith(const QString &s, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Returns true if the string ends with s; otherwise returns false.
Definition qstring.cpp:5350
bool isEmpty() const
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:1083
int compare(const QString &s, Qt::CaseSensitivity cs=Qt::CaseSensitive) const noexcept
Definition qstring.cpp:6498
static QString number(int, int base=10)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:7822
\inmodule QtCore
Definition qurl.h:94
void setPassword(const QString &password, ParsingMode mode=DecodedMode)
Sets the URL's password to password.
Definition qurl.cpp:2224
void setFragment(const QString &fragment, ParsingMode mode=TolerantMode)
Sets the fragment of the URL to fragment.
Definition qurl.cpp:2645
bool isValid() const
Returns true if the URL is non-empty and valid; otherwise returns false.
Definition qurl.cpp:1874
QByteArray toEncoded(FormattingOptions options=FullyEncoded) const
Returns the encoded representation of the URL if it's valid; otherwise an empty QByteArray is returne...
Definition qurl.cpp:2964
QHash< int, QWidget * > hash
[35multi]
QString str
[2]
qDeleteAll(list.begin(), list.end())
QSet< QString >::iterator it
Combined button and popup list for selecting options.
@ CaseInsensitive
QByteArray qCompress(const uchar *data, qsizetype nbytes, int compressionLevel)
Q_CORE_EXPORT QByteArray qUncompress(const uchar *data, qsizetype nbytes)
#define Q_UNLIKELY(x)
static QString header(const QString &name)
#define QT_CATCH(A)
#define QT_TRY
#define qDebug
[1]
Definition qlogging.h:160
#define qWarning
Definition qlogging.h:162
#define CACHE_VERSION
#define DATA_DIR
#define CACHE_POSTFIX
@ CurrentCacheVersion
#define MAX_COMPRESSION_SIZE
GLboolean GLboolean GLboolean b
GLsizei const GLfloat * v
[13]
GLboolean GLboolean GLboolean GLboolean a
[7]
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLuint GLuint end
GLenum GLuint buffer
GLenum type
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
const GLchar * marker
GLuint in
GLdouble GLdouble GLdouble GLdouble q
Definition qopenglext.h:259
GLsizei const GLchar *const * path
GLdouble s
[6]
Definition qopenglext.h:235
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
int qint32
Definition qtypes.h:44
unsigned int uint
Definition qtypes.h:29
long long qint64
Definition qtypes.h:55
qint64 qlonglong
Definition qtypes.h:58
QFile file
[0]
QFileInfo info(fileName)
[8]
QByteArray compressed
QTextStream out(stdout)
[7]
QUrl url("example.com")
[constructor-url-reference]
QByteArray readData()
QString dir
[11]
const QStringList filters({"Image files (*.png *.xpm *.jpg)", "Text files (*.txt)", "Any files (*)" })
[6]
QGraphicsItem * item
QAction * at
IUIAutomationTreeWalker __RPC__deref_out_opt IUIAutomationElement ** parent