1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtNetwork module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 //#define QNETWORKDISKCACHE_DEBUG
41 
42 
43 #include "qnetworkdiskcache.h"
44 #include "qnetworkdiskcache_p.h"
45 #include "QtCore/qscopedpointer.h"
46 
47 #include <qfile.h>
48 #include <qdir.h>
49 #include <qdatastream.h>
50 #include <qdatetime.h>
51 #include <qdiriterator.h>
52 #include <qurl.h>
53 #include <qcryptographichash.h>
54 #include <qdebug.h>
55 
56 #define CACHE_POSTFIX QLatin1String(".d")
57 #define PREPARED_SLASH QLatin1String("prepared/")
58 #define CACHE_VERSION 8
59 #define DATA_DIR QLatin1String("data")
60 
61 #define MAX_COMPRESSION_SIZE (1024 * 1024 * 3)
62 
63 QT_BEGIN_NAMESPACE
64 
65 /*!
66     \class QNetworkDiskCache
67     \since 4.5
68     \inmodule QtNetwork
69 
70     \brief The QNetworkDiskCache class provides a very basic disk cache.
71 
72     QNetworkDiskCache stores each url in its own file inside of the
73     cacheDirectory using QDataStream.  Files with a text MimeType
74     are compressed using qCompress.  Data is written to disk only in insert()
75     and updateMetaData().
76 
77     Currently you cannot share the same cache files with more than
78     one disk cache.
79 
80     QNetworkDiskCache by default limits the amount of space that the cache will
81     use on the system to 50MB.
82 
83     Note you have to set the cache directory before it will work.
84 
85     A network disk cache can be enabled by:
86 
87     \snippet code/src_network_access_qnetworkdiskcache.cpp 0
88 
89     When sending requests, to control the preference of when to use the cache
90     and when to use the network, consider the following:
91 
92     \snippet code/src_network_access_qnetworkdiskcache.cpp 1
93 
94     To check whether the response came from the cache or from the network, the
95     following can be applied:
96 
97     \snippet code/src_network_access_qnetworkdiskcache.cpp 2
98 */
99 
100 /*!
101     Creates a new disk cache. The \a parent argument is passed to
102     QAbstractNetworkCache's constructor.
103  */
QNetworkDiskCache(QObject * parent)104 QNetworkDiskCache::QNetworkDiskCache(QObject *parent)
105     : QAbstractNetworkCache(*new QNetworkDiskCachePrivate, parent)
106 {
107 }
108 
109 /*!
110     Destroys the cache object.  This does not clear the disk cache.
111  */
~QNetworkDiskCache()112 QNetworkDiskCache::~QNetworkDiskCache()
113 {
114     Q_D(QNetworkDiskCache);
115     qDeleteAll(d->inserting);
116 }
117 
118 /*!
119     Returns the location where cached files will be stored.
120 */
cacheDirectory() const121 QString QNetworkDiskCache::cacheDirectory() const
122 {
123     Q_D(const QNetworkDiskCache);
124     return d->cacheDirectory;
125 }
126 
127 /*!
128     Sets the directory where cached files will be stored to \a cacheDir
129 
130     QNetworkDiskCache will create this directory if it does not exists.
131 
132     Prepared cache items will be stored in the new cache directory when
133     they are inserted.
134 
135     \sa QDesktopServices::CacheLocation
136 */
setCacheDirectory(const QString & cacheDir)137 void QNetworkDiskCache::setCacheDirectory(const QString &cacheDir)
138 {
139 #if defined(QNETWORKDISKCACHE_DEBUG)
140     qDebug() << "QNetworkDiskCache::setCacheDirectory()" << cacheDir;
141 #endif
142     Q_D(QNetworkDiskCache);
143     if (cacheDir.isEmpty())
144         return;
145     d->cacheDirectory = cacheDir;
146     QDir dir(d->cacheDirectory);
147     d->cacheDirectory = dir.absolutePath();
148     if (!d->cacheDirectory.endsWith(QLatin1Char('/')))
149         d->cacheDirectory += QLatin1Char('/');
150 
151     d->dataDirectory = d->cacheDirectory + DATA_DIR + QString::number(CACHE_VERSION) + QLatin1Char('/');
152     d->prepareLayout();
153 }
154 
155 /*!
156     \reimp
157 */
cacheSize() const158 qint64 QNetworkDiskCache::cacheSize() const
159 {
160 #if defined(QNETWORKDISKCACHE_DEBUG)
161     qDebug("QNetworkDiskCache::cacheSize()");
162 #endif
163     Q_D(const QNetworkDiskCache);
164     if (d->cacheDirectory.isEmpty())
165         return 0;
166     if (d->currentCacheSize < 0) {
167         QNetworkDiskCache *that = const_cast<QNetworkDiskCache*>(this);
168         that->d_func()->currentCacheSize = that->expire();
169     }
170     return d->currentCacheSize;
171 }
172 
173 /*!
174     \reimp
175 */
prepare(const QNetworkCacheMetaData & metaData)176 QIODevice *QNetworkDiskCache::prepare(const QNetworkCacheMetaData &metaData)
177 {
178 #if defined(QNETWORKDISKCACHE_DEBUG)
179     qDebug() << "QNetworkDiskCache::prepare()" << metaData.url();
180 #endif
181     Q_D(QNetworkDiskCache);
182     if (!metaData.isValid() || !metaData.url().isValid() || !metaData.saveToDisk())
183         return nullptr;
184 
185     if (d->cacheDirectory.isEmpty()) {
186         qWarning("QNetworkDiskCache::prepare() The cache directory is not set");
187         return nullptr;
188     }
189 
190     const auto headers = metaData.rawHeaders();
191     for (const auto &header : headers) {
192         if (header.first.compare("content-length", Qt::CaseInsensitive) == 0) {
193             const qint64 size = header.second.toLongLong();
194             if (size > (maximumCacheSize() * 3)/4)
195                 return nullptr;
196             break;
197         }
198     }
199     QScopedPointer<QCacheItem> cacheItem(new QCacheItem);
200     cacheItem->metaData = metaData;
201 
202     QIODevice *device = nullptr;
203     if (cacheItem->canCompress()) {
204         cacheItem->data.open(QBuffer::ReadWrite);
205         device = &(cacheItem->data);
206     } else {
207         QString templateName = d->tmpCacheFileName();
208         QT_TRY {
209             cacheItem->file = new QTemporaryFile(templateName, &cacheItem->data);
210         } QT_CATCH(...) {
211             cacheItem->file = nullptr;
212         }
213         if (!cacheItem->file || !cacheItem->file->open()) {
214             qWarning("QNetworkDiskCache::prepare() unable to open temporary file");
215             cacheItem.reset();
216             return nullptr;
217         }
218         cacheItem->writeHeader(cacheItem->file);
219         device = cacheItem->file;
220     }
221     d->inserting[device] = cacheItem.take();
222     return device;
223 }
224 
225 /*!
226     \reimp
227 */
insert(QIODevice * device)228 void QNetworkDiskCache::insert(QIODevice *device)
229 {
230 #if defined(QNETWORKDISKCACHE_DEBUG)
231     qDebug() << "QNetworkDiskCache::insert()" << device;
232 #endif
233     Q_D(QNetworkDiskCache);
234     const auto it = d->inserting.constFind(device);
235     if (Q_UNLIKELY(it == d->inserting.cend())) {
236         qWarning() << "QNetworkDiskCache::insert() called on a device we don't know about" << device;
237         return;
238     }
239 
240     d->storeItem(it.value());
241     delete it.value();
242     d->inserting.erase(it);
243 }
244 
245 
246 /*!
247     Create subdirectories and other housekeeping on the filesystem.
248     Prevents too many files from being present in any single directory.
249 */
prepareLayout()250 void QNetworkDiskCachePrivate::prepareLayout()
251 {
252     QDir helper;
253     helper.mkpath(cacheDirectory + PREPARED_SLASH);
254 
255     //Create directory and subdirectories 0-F
256     helper.mkpath(dataDirectory);
257     for (uint i = 0; i < 16 ; i++) {
258         QString str = QString::number(i, 16);
259         QString subdir = dataDirectory + str;
260         helper.mkdir(subdir);
261     }
262 }
263 
264 
storeItem(QCacheItem * cacheItem)265 void QNetworkDiskCachePrivate::storeItem(QCacheItem *cacheItem)
266 {
267     Q_Q(QNetworkDiskCache);
268     Q_ASSERT(cacheItem->metaData.saveToDisk());
269 
270     QString fileName = cacheFileName(cacheItem->metaData.url());
271     Q_ASSERT(!fileName.isEmpty());
272 
273     if (QFile::exists(fileName)) {
274         if (!QFile::remove(fileName)) {
275             qWarning() << "QNetworkDiskCache: couldn't remove the cache file " << fileName;
276             return;
277         }
278     }
279 
280     if (currentCacheSize > 0)
281         currentCacheSize += 1024 + cacheItem->size();
282     currentCacheSize = q->expire();
283     if (!cacheItem->file) {
284         QString templateName = tmpCacheFileName();
285         cacheItem->file = new QTemporaryFile(templateName, &cacheItem->data);
286         if (cacheItem->file->open()) {
287             cacheItem->writeHeader(cacheItem->file);
288             cacheItem->writeCompressedData(cacheItem->file);
289         }
290     }
291 
292     if (cacheItem->file
293         && cacheItem->file->isOpen()
294         && cacheItem->file->error() == QFile::NoError) {
295         cacheItem->file->setAutoRemove(false);
296         // ### use atomic rename rather then remove & rename
297         if (cacheItem->file->rename(fileName))
298             currentCacheSize += cacheItem->file->size();
299         else
300             cacheItem->file->setAutoRemove(true);
301     }
302     if (cacheItem->metaData.url() == lastItem.metaData.url())
303         lastItem.reset();
304 }
305 
306 /*!
307     \reimp
308 */
remove(const QUrl & url)309 bool QNetworkDiskCache::remove(const QUrl &url)
310 {
311 #if defined(QNETWORKDISKCACHE_DEBUG)
312     qDebug() << "QNetworkDiskCache::remove()" << url;
313 #endif
314     Q_D(QNetworkDiskCache);
315 
316     // remove is also used to cancel insertions, not a common operation
317     for (auto it = d->inserting.cbegin(), end = d->inserting.cend(); it != end; ++it) {
318         QCacheItem *item = it.value();
319         if (item && item->metaData.url() == url) {
320             delete item;
321             d->inserting.erase(it);
322             return true;
323         }
324     }
325 
326     if (d->lastItem.metaData.url() == url)
327         d->lastItem.reset();
328     return d->removeFile(d->cacheFileName(url));
329 }
330 
331 /*!
332     Put all of the misc file removing into one function to be extra safe
333  */
removeFile(const QString & file)334 bool QNetworkDiskCachePrivate::removeFile(const QString &file)
335 {
336 #if defined(QNETWORKDISKCACHE_DEBUG)
337     qDebug() << "QNetworkDiskCache::removFile()" << file;
338 #endif
339     if (file.isEmpty())
340         return false;
341     QFileInfo info(file);
342     QString fileName = info.fileName();
343     if (!fileName.endsWith(CACHE_POSTFIX))
344         return false;
345     qint64 size = info.size();
346     if (QFile::remove(file)) {
347         currentCacheSize -= size;
348         return true;
349     }
350     return false;
351 }
352 
353 /*!
354     \reimp
355 */
metaData(const QUrl & url)356 QNetworkCacheMetaData QNetworkDiskCache::metaData(const QUrl &url)
357 {
358 #if defined(QNETWORKDISKCACHE_DEBUG)
359     qDebug() << "QNetworkDiskCache::metaData()" << url;
360 #endif
361     Q_D(QNetworkDiskCache);
362     if (d->lastItem.metaData.url() == url)
363         return d->lastItem.metaData;
364     return fileMetaData(d->cacheFileName(url));
365 }
366 
367 /*!
368     Returns the QNetworkCacheMetaData for the cache file \a fileName.
369 
370     If \a fileName is not a cache file QNetworkCacheMetaData will be invalid.
371  */
fileMetaData(const QString & fileName) const372 QNetworkCacheMetaData QNetworkDiskCache::fileMetaData(const QString &fileName) const
373 {
374 #if defined(QNETWORKDISKCACHE_DEBUG)
375     qDebug() << "QNetworkDiskCache::fileMetaData()" << fileName;
376 #endif
377     Q_D(const QNetworkDiskCache);
378     QFile file(fileName);
379     if (!file.open(QFile::ReadOnly))
380         return QNetworkCacheMetaData();
381     if (!d->lastItem.read(&file, false)) {
382         file.close();
383         QNetworkDiskCachePrivate *that = const_cast<QNetworkDiskCachePrivate*>(d);
384         that->removeFile(fileName);
385     }
386     return d->lastItem.metaData;
387 }
388 
389 /*!
390     \reimp
391 */
data(const QUrl & url)392 QIODevice *QNetworkDiskCache::data(const QUrl &url)
393 {
394 #if defined(QNETWORKDISKCACHE_DEBUG)
395     qDebug() << "QNetworkDiskCache::data()" << url;
396 #endif
397     Q_D(QNetworkDiskCache);
398     QScopedPointer<QBuffer> buffer;
399     if (!url.isValid())
400         return nullptr;
401     if (d->lastItem.metaData.url() == url && d->lastItem.data.isOpen()) {
402         buffer.reset(new QBuffer);
403         buffer->setData(d->lastItem.data.data());
404     } else {
405         QScopedPointer<QFile> file(new QFile(d->cacheFileName(url)));
406         if (!file->open(QFile::ReadOnly | QIODevice::Unbuffered))
407             return nullptr;
408 
409         if (!d->lastItem.read(file.data(), true)) {
410             file->close();
411             remove(url);
412             return nullptr;
413         }
414         if (d->lastItem.data.isOpen()) {
415             // compressed
416             buffer.reset(new QBuffer);
417             buffer->setData(d->lastItem.data.data());
418         } else {
419             buffer.reset(new QBuffer);
420             // ### verify that QFile uses the fd size and not the file name
421             qint64 size = file->size() - file->pos();
422             const uchar *p = nullptr;
423 #if !defined(Q_OS_INTEGRITY)
424             p = file->map(file->pos(), size);
425 #endif
426             if (p) {
427                 buffer->setData((const char *)p, size);
428                 file.take()->setParent(buffer.data());
429             } else {
430                 buffer->setData(file->readAll());
431             }
432         }
433     }
434     buffer->open(QBuffer::ReadOnly);
435     return buffer.take();
436 }
437 
438 /*!
439     \reimp
440 */
updateMetaData(const QNetworkCacheMetaData & metaData)441 void QNetworkDiskCache::updateMetaData(const QNetworkCacheMetaData &metaData)
442 {
443 #if defined(QNETWORKDISKCACHE_DEBUG)
444     qDebug() << "QNetworkDiskCache::updateMetaData()" << metaData.url();
445 #endif
446     QUrl url = metaData.url();
447     QIODevice *oldDevice = data(url);
448     if (!oldDevice) {
449 #if defined(QNETWORKDISKCACHE_DEBUG)
450         qDebug("QNetworkDiskCache::updateMetaData(), no device!");
451 #endif
452         return;
453     }
454 
455     QIODevice *newDevice = prepare(metaData);
456     if (!newDevice) {
457 #if defined(QNETWORKDISKCACHE_DEBUG)
458         qDebug() << "QNetworkDiskCache::updateMetaData(), no new device!" << url;
459 #endif
460         return;
461     }
462     char data[1024];
463     while (!oldDevice->atEnd()) {
464         qint64 s = oldDevice->read(data, 1024);
465         newDevice->write(data, s);
466     }
467     delete oldDevice;
468     insert(newDevice);
469 }
470 
471 /*!
472     Returns the current maximum size for the disk cache.
473 
474     \sa setMaximumCacheSize()
475  */
maximumCacheSize() const476 qint64 QNetworkDiskCache::maximumCacheSize() const
477 {
478     Q_D(const QNetworkDiskCache);
479     return d->maximumCacheSize;
480 }
481 
482 /*!
483     Sets the maximum size of the disk cache to be \a size.
484 
485     If the new size is smaller then the current cache size then the cache will call expire().
486 
487     \sa maximumCacheSize()
488  */
setMaximumCacheSize(qint64 size)489 void QNetworkDiskCache::setMaximumCacheSize(qint64 size)
490 {
491     Q_D(QNetworkDiskCache);
492     bool expireCache = (size < d->maximumCacheSize);
493     d->maximumCacheSize = size;
494     if (expireCache)
495         d->currentCacheSize = expire();
496 }
497 
498 /*!
499     Cleans the cache so that its size is under the maximum cache size.
500     Returns the current size of the cache.
501 
502     When the current size of the cache is greater than the maximumCacheSize()
503     older cache files are removed until the total size is less then 90% of
504     maximumCacheSize() starting with the oldest ones first using the file
505     creation date to determine how old a cache file is.
506 
507     Subclasses can reimplement this function to change the order that cache
508     files are removed taking into account information in the application
509     knows about that QNetworkDiskCache does not, for example the number of times
510     a cache is accessed.
511 
512     \note cacheSize() calls expire if the current cache size is unknown.
513 
514     \sa maximumCacheSize(), fileMetaData()
515  */
expire()516 qint64 QNetworkDiskCache::expire()
517 {
518     Q_D(QNetworkDiskCache);
519     if (d->currentCacheSize >= 0 && d->currentCacheSize < maximumCacheSize())
520         return d->currentCacheSize;
521 
522     if (cacheDirectory().isEmpty()) {
523         qWarning("QNetworkDiskCache::expire() The cache directory is not set");
524         return 0;
525     }
526 
527     // close file handle to prevent "in use" error when QFile::remove() is called
528     d->lastItem.reset();
529 
530     QDir::Filters filters = QDir::AllDirs | QDir:: Files | QDir::NoDotAndDotDot;
531     QDirIterator it(cacheDirectory(), filters, QDirIterator::Subdirectories);
532 
533     QMultiMap<QDateTime, QString> cacheItems;
534     qint64 totalSize = 0;
535     while (it.hasNext()) {
536         QString path = it.next();
537         QFileInfo info = it.fileInfo();
538         QString fileName = info.fileName();
539         if (fileName.endsWith(CACHE_POSTFIX)) {
540             const QDateTime birthTime = info.fileTime(QFile::FileBirthTime);
541             cacheItems.insert(birthTime.isValid() ? birthTime
542                               : info.fileTime(QFile::FileMetadataChangeTime), path);
543             totalSize += info.size();
544         }
545     }
546 
547     int removedFiles = 0;
548     qint64 goal = (maximumCacheSize() * 9) / 10;
549     QMultiMap<QDateTime, QString>::const_iterator i = cacheItems.constBegin();
550     while (i != cacheItems.constEnd()) {
551         if (totalSize < goal)
552             break;
553         QString name = i.value();
554         QFile file(name);
555 
556         if (name.contains(PREPARED_SLASH)) {
557             for (QCacheItem *item : qAsConst(d->inserting)) {
558                 if (item && item->file && item->file->fileName() == name) {
559                     delete item->file;
560                     item->file = nullptr;
561                     break;
562                 }
563             }
564         }
565 
566         qint64 size = file.size();
567         file.remove();
568         totalSize -= size;
569         ++removedFiles;
570         ++i;
571     }
572 #if defined(QNETWORKDISKCACHE_DEBUG)
573     if (removedFiles > 0) {
574         qDebug() << "QNetworkDiskCache::expire()"
575                 << "Removed:" << removedFiles
576                 << "Kept:" << cacheItems.count() - removedFiles;
577     }
578 #endif
579     return totalSize;
580 }
581 
582 /*!
583     \reimp
584 */
clear()585 void QNetworkDiskCache::clear()
586 {
587 #if defined(QNETWORKDISKCACHE_DEBUG)
588     qDebug("QNetworkDiskCache::clear()");
589 #endif
590     Q_D(QNetworkDiskCache);
591     qint64 size = d->maximumCacheSize;
592     d->maximumCacheSize = 0;
593     d->currentCacheSize = expire();
594     d->maximumCacheSize = size;
595 }
596 
597 /*!
598     Given a URL, generates a unique enough filename (and subdirectory)
599  */
uniqueFileName(const QUrl & url)600 QString QNetworkDiskCachePrivate::uniqueFileName(const QUrl &url)
601 {
602     QUrl cleanUrl = url;
603     cleanUrl.setPassword(QString());
604     cleanUrl.setFragment(QString());
605 
606     QCryptographicHash hash(QCryptographicHash::Sha1);
607     hash.addData(cleanUrl.toEncoded());
608     // convert sha1 to base36 form and return first 8 bytes for use as string
609     const QByteArray id = QByteArray::number(*(qlonglong*)hash.result().constData(), 36).left(8);
610     // generates <one-char subdir>/<8-char filname.d>
611     uint code = (uint)id.at(id.length()-1) % 16;
612     QString pathFragment = QString::number(code, 16) + QLatin1Char('/')
613                              + QLatin1String(id) + CACHE_POSTFIX;
614 
615     return pathFragment;
616 }
617 
tmpCacheFileName() const618 QString QNetworkDiskCachePrivate::tmpCacheFileName() const
619 {
620     //The subdirectory is presumed to be already read for use.
621     return cacheDirectory + PREPARED_SLASH + QLatin1String("XXXXXX") + CACHE_POSTFIX;
622 }
623 
624 /*!
625     Generates fully qualified path of cached resource from a URL.
626  */
cacheFileName(const QUrl & url) const627 QString QNetworkDiskCachePrivate::cacheFileName(const QUrl &url) const
628 {
629     if (!url.isValid())
630         return QString();
631 
632     QString fullpath = dataDirectory + uniqueFileName(url);
633     return  fullpath;
634 }
635 
636 /*!
637     We compress small text and JavaScript files.
638  */
canCompress() const639 bool QCacheItem::canCompress() const
640 {
641     bool sizeOk = false;
642     bool typeOk = false;
643     const auto headers = metaData.rawHeaders();
644     for (const auto &header : headers) {
645         if (header.first.compare("content-length", Qt::CaseInsensitive) == 0) {
646             qint64 size = header.second.toLongLong();
647             if (size > MAX_COMPRESSION_SIZE)
648                 return false;
649             else
650                 sizeOk = true;
651         }
652 
653         if (header.first.compare("content-type", Qt::CaseInsensitive) == 0) {
654             QByteArray type = header.second;
655             if (type.startsWith("text/")
656                     || (type.startsWith("application/")
657                         && (type.endsWith("javascript") || type.endsWith("ecmascript"))))
658                 typeOk = true;
659             else
660                 return false;
661         }
662         if (sizeOk && typeOk)
663             return true;
664     }
665     return false;
666 }
667 
668 enum
669 {
670     CacheMagic = 0xe8,
671     CurrentCacheVersion = CACHE_VERSION
672 };
673 
writeHeader(QFile * device) const674 void QCacheItem::writeHeader(QFile *device) const
675 {
676     QDataStream out(device);
677 
678     out << qint32(CacheMagic);
679     out << qint32(CurrentCacheVersion);
680     out << static_cast<qint32>(out.version());
681     out << metaData;
682     bool compressed = canCompress();
683     out << compressed;
684 }
685 
writeCompressedData(QFile * device) const686 void QCacheItem::writeCompressedData(QFile *device) const
687 {
688     QDataStream out(device);
689 
690     out << qCompress(data.data());
691 }
692 
693 /*!
694     Returns \c false if the file is a cache file,
695     but is an older version and should be removed otherwise true.
696  */
read(QFile * device,bool readData)697 bool QCacheItem::read(QFile *device, bool readData)
698 {
699     reset();
700 
701     QDataStream in(device);
702 
703     qint32 marker;
704     qint32 v;
705     in >> marker;
706     in >> v;
707     if (marker != CacheMagic)
708         return true;
709 
710     // If the cache magic is correct, but the version is not we should remove it
711     if (v != CurrentCacheVersion)
712         return false;
713 
714     qint32 streamVersion;
715     in >> streamVersion;
716     // Default stream version is also the highest we can handle
717     if (streamVersion > in.version())
718         return false;
719     in.setVersion(streamVersion);
720 
721     bool compressed;
722     QByteArray dataBA;
723     in >> metaData;
724     in >> compressed;
725     if (readData && compressed) {
726         in >> dataBA;
727         data.setData(qUncompress(dataBA));
728         data.open(QBuffer::ReadOnly);
729     }
730 
731     // quick and dirty check if metadata's URL field and the file's name are in synch
732     QString expectedFilename = QNetworkDiskCachePrivate::uniqueFileName(metaData.url());
733     if (!device->fileName().endsWith(expectedFilename))
734         return false;
735 
736     return metaData.isValid();
737 }
738 
739 QT_END_NAMESPACE
740