Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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 <qdirlisting.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
75
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
158 const qint64 size = sizeValue.toLongLong();
159 if (size > (maximumCacheSize() * 3)/4)
160 return nullptr;
161
162 std::unique_ptr<QCacheItem> cacheItem = std::make_unique<QCacheItem>();
163 cacheItem->metaData = metaData;
164
165 QIODevice *device = nullptr;
166 if (cacheItem->canCompress()) {
167 cacheItem->data.open(QBuffer::ReadWrite);
168 device = &(cacheItem->data);
169 } else {
170 QString fileName = d->cacheFileName(cacheItem->metaData.url());
171 cacheItem->file = new(std::nothrow) QSaveFile(fileName, &cacheItem->data);
172 if (!cacheItem->file || !cacheItem->file->open(QFileDevice::WriteOnly)) {
173 qWarning("QNetworkDiskCache::prepare() unable to open temporary file");
174 cacheItem.reset();
175 return nullptr;
176 }
177 cacheItem->writeHeader(cacheItem->file);
178 device = cacheItem->file;
179 }
180 d->inserting[device] = cacheItem.release();
181 return device;
182}
183
188{
189#if defined(QNETWORKDISKCACHE_DEBUG)
190 qDebug() << "QNetworkDiskCache::insert()" << device;
191#endif
193 const auto it = d->inserting.constFind(device);
194 if (Q_UNLIKELY(it == d->inserting.cend())) {
195 qWarning() << "QNetworkDiskCache::insert() called on a device we don't know about" << device;
196 return;
197 }
198
199 d->storeItem(it.value());
200 delete it.value();
201 d->inserting.erase(it);
202}
203
204
210{
211 QDir helper;
212
213 //Create directory and subdirectories 0-F
214 helper.mkpath(dataDirectory);
215 for (uint i = 0; i < 16 ; i++) {
217 QString subdir = dataDirectory + str;
218 helper.mkdir(subdir);
219 }
220}
221
222
224{
226 Q_ASSERT(cacheItem->metaData.saveToDisk());
227
228 QString fileName = cacheFileName(cacheItem->metaData.url());
229 Q_ASSERT(!fileName.isEmpty());
230
231 if (QFile::exists(fileName)) {
232 if (!removeFile(fileName)) {
233 qWarning() << "QNetworkDiskCache: couldn't remove the cache file " << fileName;
234 return;
235 }
236 }
237
238 currentCacheSize = q->expire();
239 if (!cacheItem->file) {
240 cacheItem->file = new QSaveFile(fileName, &cacheItem->data);
241 if (cacheItem->file->open(QFileDevice::WriteOnly)) {
242 cacheItem->writeHeader(cacheItem->file);
243 cacheItem->writeCompressedData(cacheItem->file);
244 }
245 }
246
247 if (cacheItem->file
248 && cacheItem->file->isOpen()
249 && cacheItem->file->error() == QFileDevice::NoError) {
250 // We have to call size() here instead of inside the if-body because
251 // commit() invalidates the file-engine, and size() will create a new
252 // one, pointing at an empty filename.
253 qint64 size = cacheItem->file->size();
254 if (cacheItem->file->commit())
256 // Delete and unset the QSaveFile, it's invalid now.
257 delete std::exchange(cacheItem->file, nullptr);
258 }
259 if (cacheItem->metaData.url() == lastItem.metaData.url())
260 lastItem.reset();
261}
262
267{
268#if defined(QNETWORKDISKCACHE_DEBUG)
269 qDebug() << "QNetworkDiskCache::remove()" << url;
270#endif
272
273 // remove is also used to cancel insertions, not a common operation
274 for (auto it = d->inserting.cbegin(), end = d->inserting.cend(); it != end; ++it) {
275 QCacheItem *item = it.value();
276 if (item && item->metaData.url() == url) {
277 delete item;
278 d->inserting.erase(it);
279 return true;
280 }
281 }
282
283 if (d->lastItem.metaData.url() == url)
284 d->lastItem.reset();
285 return d->removeFile(d->cacheFileName(url));
286}
287
292{
293#if defined(QNETWORKDISKCACHE_DEBUG)
294 qDebug() << "QNetworkDiskCache::removFile()" << file;
295#endif
296 if (file.isEmpty())
297 return false;
299 QString fileName = info.fileName();
300 if (!fileName.endsWith(CACHE_POSTFIX))
301 return false;
302 qint64 size = info.size();
303 if (QFile::remove(file)) {
305 return true;
306 }
307 return false;
308}
309
314{
315#if defined(QNETWORKDISKCACHE_DEBUG)
316 qDebug() << "QNetworkDiskCache::metaData()" << url;
317#endif
319 if (d->lastItem.metaData.url() == url)
320 return d->lastItem.metaData;
321 return fileMetaData(d->cacheFileName(url));
322}
323
330{
331#if defined(QNETWORKDISKCACHE_DEBUG)
332 qDebug() << "QNetworkDiskCache::fileMetaData()" << fileName;
333#endif
334 Q_D(const QNetworkDiskCache);
337 return QNetworkCacheMetaData();
338 if (!d->lastItem.read(&file, false)) {
339 file.close();
341 that->removeFile(fileName);
342 }
343 return d->lastItem.metaData;
344}
345
350{
351#if defined(QNETWORKDISKCACHE_DEBUG)
352 qDebug() << "QNetworkDiskCache::data()" << url;
353#endif
355 std::unique_ptr<QBuffer> buffer;
356 if (!url.isValid())
357 return nullptr;
358 if (d->lastItem.metaData.url() == url && d->lastItem.data.isOpen()) {
359 buffer.reset(new QBuffer);
360 buffer->setData(d->lastItem.data.data());
361 } else {
362 QScopedPointer<QFile> file(new QFile(d->cacheFileName(url)));
364 return nullptr;
365
366 if (!d->lastItem.read(file.data(), true)) {
367 file->close();
368 remove(url);
369 return nullptr;
370 }
371 if (d->lastItem.data.isOpen()) {
372 // compressed
373 buffer.reset(new QBuffer);
374 buffer->setData(d->lastItem.data.data());
375 } else {
376 buffer.reset(new QBuffer);
377 buffer->setData(file->readAll());
378 }
379 }
381 return buffer.release();
382}
383
388{
389#if defined(QNETWORKDISKCACHE_DEBUG)
390 qDebug() << "QNetworkDiskCache::updateMetaData()" << metaData.url();
391#endif
392 QUrl url = metaData.url();
393 QIODevice *oldDevice = data(url);
394 if (!oldDevice) {
395#if defined(QNETWORKDISKCACHE_DEBUG)
396 qDebug("QNetworkDiskCache::updateMetaData(), no device!");
397#endif
398 return;
399 }
400
401 QIODevice *newDevice = prepare(metaData);
402 if (!newDevice) {
403#if defined(QNETWORKDISKCACHE_DEBUG)
404 qDebug() << "QNetworkDiskCache::updateMetaData(), no new device!" << url;
405#endif
406 return;
407 }
408 char data[1024];
409 while (!oldDevice->atEnd()) {
410 qint64 s = oldDevice->read(data, 1024);
411 newDevice->write(data, s);
412 }
413 delete oldDevice;
414 insert(newDevice);
415}
416
423{
424 Q_D(const QNetworkDiskCache);
425 return d->maximumCacheSize;
426}
427
436{
438 bool expireCache = (size < d->maximumCacheSize);
439 d->maximumCacheSize = size;
440 if (expireCache)
441 d->currentCacheSize = expire();
442}
443
463{
465 if (d->currentCacheSize >= 0 && d->currentCacheSize < maximumCacheSize())
466 return d->currentCacheSize;
467
468 if (cacheDirectory().isEmpty()) {
469 qWarning("QNetworkDiskCache::expire() The cache directory is not set");
470 return 0;
471 }
472
473 // close file handle to prevent "in use" error when QFile::remove() is called
474 d->lastItem.reset();
475
476 const QDir::Filters filters = QDir::AllDirs | QDir:: Files | QDir::NoDotAndDotDot;
477
478 struct CacheItem
479 {
480 std::chrono::milliseconds msecs;
482 qint64 size = 0;
483 };
484 std::vector<CacheItem> cacheItems;
485 qint64 totalSize = 0;
487 for (const auto &dirEntry : QDirListing(cacheDirectory(), filters, F::Recursive)) {
488 if (!dirEntry.fileName().endsWith(CACHE_POSTFIX))
489 continue;
490
491 const QFileInfo &info = dirEntry.fileInfo();
492 QDateTime fileTime = info.birthTime(QTimeZone::UTC);
493 if (!fileTime.isValid())
494 fileTime = info.metadataChangeTime(QTimeZone::UTC);
495 const std::chrono::milliseconds msecs{fileTime.toMSecsSinceEpoch()};
496 const qint64 size = info.size();
497 cacheItems.push_back(CacheItem{msecs, info.filePath(), size});
498 totalSize += size;
499 }
500
501 const qint64 goal = (maximumCacheSize() * 9) / 10;
502 if (totalSize < goal)
503 return totalSize; // Nothing to do
504
505 auto byFileTime = [&](const auto &a, const auto &b) { return a.msecs < b.msecs; };
506 std::sort(cacheItems.begin(), cacheItems.end(), byFileTime);
507
508 [[maybe_unused]] int removedFiles = 0; // used under QNETWORKDISKCACHE_DEBUG
509 for (const CacheItem &cached : cacheItems) {
510 QFile::remove(cached.path);
511 ++removedFiles;
512 totalSize -= cached.size;
513 if (totalSize < goal)
514 break;
515 }
516#if defined(QNETWORKDISKCACHE_DEBUG)
517 if (removedFiles > 0) {
518 qDebug() << "QNetworkDiskCache::expire()"
519 << "Removed:" << removedFiles
520 << "Kept:" << cacheItems.count() - removedFiles;
521 }
522#endif
523 return totalSize;
524}
525
530{
531#if defined(QNETWORKDISKCACHE_DEBUG)
532 qDebug("QNetworkDiskCache::clear()");
533#endif
535 qint64 size = d->maximumCacheSize;
536 d->maximumCacheSize = 0;
537 d->currentCacheSize = expire();
538 d->maximumCacheSize = size;
539}
540
545{
546 QUrl cleanUrl = url;
547 cleanUrl.setPassword(QString());
548 cleanUrl.setFragment(QString());
549
551 // convert sha1 to base36 form and return first 8 bytes for use as string
552 const QByteArray id = QByteArray::number(*(qlonglong*)hash.data(), 36).left(8);
553 // generates <one-char subdir>/<8-char filename.d>
554 uint code = (uint)id.at(id.size()-1) % 16;
555 QString pathFragment = QString::number(code, 16) + u'/' + QLatin1StringView(id) + CACHE_POSTFIX;
556
557 return pathFragment;
558}
559
564{
565 if (!url.isValid())
566 return QString();
567
569 return fullpath;
570}
571
576{
577 const auto h = metaData.headers();
578
580 if (sizeValue.empty())
581 return false;
582
583 qint64 size = sizeValue.toLongLong();
585 return false;
586
588 if (!type.empty())
589 return false;
590
591 if (!type.startsWith("text/")
592 && !(type.startsWith("application/")
593 && (type.endsWith("javascript") || type.endsWith("ecmascript")))) {
594 return false;
595 }
596
597 return true;
598}
599
600enum
601{
605
607{
609
612 out << static_cast<qint32>(out.version());
613 out << metaData;
614 bool compressed = canCompress();
615 out << compressed;
616}
617
624
630{
631 reset();
632
634
636 qint32 v;
637 in >> marker;
638 in >> v;
639 if (marker != CacheMagic)
640 return true;
641
642 // If the cache magic is correct, but the version is not we should remove it
643 if (v != CurrentCacheVersion)
644 return false;
645
646 qint32 streamVersion;
647 in >> streamVersion;
648 // Default stream version is also the highest we can handle
649 if (streamVersion > in.version())
650 return false;
651 in.setVersion(streamVersion);
652
653 bool compressed;
654 QByteArray dataBA;
655 in >> metaData;
656 in >> compressed;
657 if (readData && compressed) {
658 in >> dataBA;
659 data.setData(qUncompress(dataBA));
661 }
662
663 // quick and dirty check if metadata's URL field and the file's name are in synch
665 if (!device->fileName().endsWith(expectedFilename))
666 return false;
667
668 return metaData.isValid() && !metaData.headers().isEmpty();
669}
670
672
673#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.
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
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:46
\inmodule QtCore\reentrant
Definition qdatetime.h:283
qint64 toMSecsSinceEpoch() const
bool isValid() const
Returns true if this datetime represents a definite moment, otherwise false.
The QDirListing class provides an STL-style iterator for directory entries.
Definition qdirlisting.h:18
IteratorFlag
This enum class describes flags can be used to configure the behavior of QDirListing.
Definition qdirlisting.h:20
\inmodule QtCore
Definition qdir.h:20
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:1526
bool mkpath(const QString &dirPath) const
Creates the directory path dirPath.
Definition qdir.cpp:1578
@ Files
Definition qdir.h:23
@ AllDirs
Definition qdir.h:40
@ NoDotAndDotDot
Definition qdir.h:44
\inmodule QtCore
Definition qfiledevice.h:32
void close() override
Calls QFileDevice::flush() and closes the file.
\inmodule QtCore
Definition qfile.h:93
QFILE_MAYBE_NODISCARD bool open(OpenMode flags) override
Opens the file using OpenMode mode, returning true if successful; otherwise false.
Definition qfile.cpp:904
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
bool isEmpty() const noexcept
Returns true if the headers have size 0; otherwise returns false.
Q_NETWORK_EXPORT QByteArrayView value(QAnyStringView name, QByteArrayView defaultValue={}) const noexcept
Returns the value of the (first) header name, or defaultValue if it doesn't exist.
\inmodule QtCore \reentrant
Definition qiodevice.h:34
QByteArray readAll()
Reads all remaining data from the device, and returns it as a byte array.
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.
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:103
const_iterator cend() const noexcept
Definition qset.h:142
iterator erase(const_iterator i)
Definition qset.h:145
const_iterator constFind(const T &value) const
Definition qset.h:161
const_iterator cbegin() const noexcept
Definition qset.h:138
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
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:8084
\inmodule QtCore
Definition qurl.h:94
void setPassword(const QString &password, ParsingMode mode=DecodedMode)
Sets the URL's password to password.
Definition qurl.cpp:2227
bool isValid() const
Returns true if the URL is non-empty and valid; otherwise returns false.
Definition qurl.cpp:1882
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.
QByteArray qCompress(const uchar *data, qsizetype nbytes, int compressionLevel)
Q_CORE_EXPORT QByteArray qUncompress(const uchar *data, qsizetype nbytes)
#define Q_UNLIKELY(x)
#define qDebug
[1]
Definition qlogging.h:164
#define qWarning
Definition qlogging.h:166
@ CurrentCacheVersion
#define CACHE_VERSION
#define DATA_DIR
#define CACHE_POSTFIX
#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
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLenum GLuint buffer
GLenum type
const GLchar * marker
GLfloat GLfloat GLfloat GLfloat h
GLdouble s
[6]
Definition qopenglext.h:235
GLuint in
GLdouble GLdouble GLdouble GLdouble q
Definition qopenglext.h:259
GLsizei const GLchar *const * path
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
int qint32
Definition qtypes.h:49
unsigned int uint
Definition qtypes.h:34
long long qint64
Definition qtypes.h:60
qint64 qlonglong
Definition qtypes.h:63
QFile file
[0]
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
QHostInfo info
[0]