src/network/access/qnetworkdiskcache.cpp
changeset 0 1918ee327afb
child 4 3b1da2848fc7
equal deleted inserted replaced
-1:000000000000 0:1918ee327afb
       
     1 /****************************************************************************
       
     2 **
       
     3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
       
     4 ** All rights reserved.
       
     5 ** Contact: Nokia Corporation (qt-info@nokia.com)
       
     6 **
       
     7 ** This file is part of the QtNetwork module of the Qt Toolkit.
       
     8 **
       
     9 ** $QT_BEGIN_LICENSE:LGPL$
       
    10 ** No Commercial Usage
       
    11 ** This file contains pre-release code and may not be distributed.
       
    12 ** You may use this file in accordance with the terms and conditions
       
    13 ** contained in the Technology Preview License Agreement accompanying
       
    14 ** this package.
       
    15 **
       
    16 ** GNU Lesser General Public License Usage
       
    17 ** Alternatively, this file may be used under the terms of the GNU Lesser
       
    18 ** General Public License version 2.1 as published by the Free Software
       
    19 ** Foundation and appearing in the file LICENSE.LGPL included in the
       
    20 ** packaging of this file.  Please review the following information to
       
    21 ** ensure the GNU Lesser General Public License version 2.1 requirements
       
    22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
       
    23 **
       
    24 ** In addition, as a special exception, Nokia gives you certain additional
       
    25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
       
    26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
       
    27 **
       
    28 ** If you have questions regarding the use of this file, please contact
       
    29 ** Nokia at qt-info@nokia.com.
       
    30 **
       
    31 **
       
    32 **
       
    33 **
       
    34 **
       
    35 **
       
    36 **
       
    37 **
       
    38 ** $QT_END_LICENSE$
       
    39 **
       
    40 ****************************************************************************/
       
    41 
       
    42 //#define QNETWORKDISKCACHE_DEBUG
       
    43 
       
    44 #ifndef QT_NO_NETWORKDISKCACHE
       
    45 
       
    46 #include "qnetworkdiskcache.h"
       
    47 #include "qnetworkdiskcache_p.h"
       
    48 #include "QtCore/qscopedpointer.h"
       
    49 
       
    50 #include <qfile.h>
       
    51 #include <qdir.h>
       
    52 #include <qdatetime.h>
       
    53 #include <qdiriterator.h>
       
    54 #include <qcryptographichash.h>
       
    55 #include <qurl.h>
       
    56 
       
    57 #include <qdebug.h>
       
    58 
       
    59 #define CACHE_PREFIX  QLatin1String("cache_")
       
    60 #define CACHE_POSTFIX QLatin1String(".cache")
       
    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.  Each cache file starts with "cache_"
       
    75     and ends in ".cache".  Data is written to disk only in insert()
       
    76     and updateMetaData().
       
    77 
       
    78     Currently you can not share the same cache files with more then
       
    79     one disk cache.
       
    80 
       
    81     QNetworkDiskCache by default limits the amount of space that the cache will
       
    82     use on the system to 50MB.
       
    83 
       
    84     Note you have to set the cache directory before it will work.
       
    85 
       
    86     A network disk cache can be enabled by:
       
    87 
       
    88     \snippet doc/src/snippets/code/src_network_access_qnetworkdiskcache.cpp 0
       
    89 
       
    90     When sending requests, to control the preference of when to use the cache
       
    91     and when to use the network, consider the following:
       
    92 
       
    93     \snippet doc/src/snippets/code/src_network_access_qnetworkdiskcache.cpp 1
       
    94 
       
    95     To check whether the response came from the cache or from the network, the
       
    96     following can be applied:
       
    97 
       
    98     \snippet doc/src/snippets/code/src_network_access_qnetworkdiskcache.cpp 2
       
    99 */
       
   100 
       
   101 /*!
       
   102     Creates a new disk cache. The \a parent argument is passed to
       
   103     QAbstractNetworkCache's constructor.
       
   104  */
       
   105 QNetworkDiskCache::QNetworkDiskCache(QObject *parent)
       
   106     : QAbstractNetworkCache(*new QNetworkDiskCachePrivate, parent)
       
   107 {
       
   108 }
       
   109 
       
   110 /*!
       
   111     Destroys the cache object.  This does not clear the disk cache.
       
   112  */
       
   113 QNetworkDiskCache::~QNetworkDiskCache()
       
   114 {
       
   115     Q_D(QNetworkDiskCache);
       
   116     QHashIterator<QIODevice*, QCacheItem*> it(d->inserting);
       
   117     while (it.hasNext()) {
       
   118         it.next();
       
   119         delete it.value();
       
   120     }
       
   121 }
       
   122 
       
   123 /*!
       
   124     Returns the location where cached files will be stored.
       
   125 */
       
   126 QString QNetworkDiskCache::cacheDirectory() const
       
   127 {
       
   128     Q_D(const QNetworkDiskCache);
       
   129     return d->cacheDirectory;
       
   130 }
       
   131 
       
   132 /*!
       
   133     Sets the directory where cached files will be stored to \a cacheDir
       
   134 
       
   135     QNetworkDiskCache will create this directory if it does not exists.
       
   136 
       
   137     Prepared cache items will be stored in the new cache directory when
       
   138     they are inserted.
       
   139 
       
   140     \sa QDesktopServices::CacheLocation
       
   141 */
       
   142 void QNetworkDiskCache::setCacheDirectory(const QString &cacheDir)
       
   143 {
       
   144 #if defined(QNETWORKDISKCACHE_DEBUG)
       
   145     qDebug() << "QNetworkDiskCache::setCacheDirectory()" << cacheDir;
       
   146 #endif
       
   147     Q_D(QNetworkDiskCache);
       
   148     if (cacheDir.isEmpty())
       
   149         return;
       
   150     d->cacheDirectory = cacheDir;
       
   151     QDir dir(d->cacheDirectory);
       
   152     d->cacheDirectory = dir.absolutePath();
       
   153     if (!d->cacheDirectory.endsWith(QLatin1Char('/')))
       
   154         d->cacheDirectory += QLatin1Char('/');
       
   155 }
       
   156 
       
   157 /*!
       
   158     \reimp
       
   159 */
       
   160 qint64 QNetworkDiskCache::cacheSize() const
       
   161 {
       
   162 #if defined(QNETWORKDISKCACHE_DEBUG)
       
   163     qDebug() << "QNetworkDiskCache::cacheSize()";
       
   164 #endif
       
   165     Q_D(const QNetworkDiskCache);
       
   166     if (d->cacheDirectory.isEmpty())
       
   167         return 0;
       
   168     if (d->currentCacheSize < 0) {
       
   169         QNetworkDiskCache *that = const_cast<QNetworkDiskCache*>(this);
       
   170         that->d_func()->currentCacheSize = that->expire();
       
   171     }
       
   172     return d->currentCacheSize;
       
   173 }
       
   174 
       
   175 /*!
       
   176     \reimp
       
   177 */
       
   178 QIODevice *QNetworkDiskCache::prepare(const QNetworkCacheMetaData &metaData)
       
   179 {
       
   180 #if defined(QNETWORKDISKCACHE_DEBUG)
       
   181     qDebug() << "QNetworkDiskCache::prepare()" << metaData.url();
       
   182 #endif
       
   183     Q_D(QNetworkDiskCache);
       
   184     if (!metaData.isValid() || !metaData.url().isValid() || !metaData.saveToDisk())
       
   185         return 0;
       
   186 
       
   187     if (d->cacheDirectory.isEmpty()) {
       
   188         qWarning() << "QNetworkDiskCache::prepare() The cache directory is not set";
       
   189         return 0;
       
   190     }
       
   191 
       
   192     foreach (QNetworkCacheMetaData::RawHeader header, metaData.rawHeaders()) {
       
   193         if (header.first.toLower() == "content-length") {
       
   194             qint64 size = header.second.toInt();
       
   195             if (size > (maximumCacheSize() * 3)/4)
       
   196                 return 0;
       
   197             break;
       
   198         }
       
   199     }
       
   200     QScopedPointer<QCacheItem> cacheItem(new QCacheItem);
       
   201     cacheItem->metaData = metaData;
       
   202 
       
   203     QIODevice *device = 0;
       
   204     if (cacheItem->canCompress()) {
       
   205         cacheItem->data.open(QBuffer::ReadWrite);
       
   206         device = &(cacheItem->data);
       
   207     } else {
       
   208         QString templateName = d->tmpCacheFileName();
       
   209         QT_TRY {
       
   210             cacheItem->file = new QTemporaryFile(templateName, &cacheItem->data);
       
   211         } QT_CATCH(...) {
       
   212             cacheItem->file = 0;
       
   213         }
       
   214         if (!cacheItem->file || !cacheItem->file->open()) {
       
   215             qWarning() << "QNetworkDiskCache::prepare() unable to open temporary file";
       
   216             cacheItem.reset();
       
   217             return 0;
       
   218         }
       
   219         cacheItem->writeHeader(cacheItem->file);
       
   220         device = cacheItem->file;
       
   221     }
       
   222     d->inserting[device] = cacheItem.take();
       
   223     return device;
       
   224 }
       
   225 
       
   226 /*!
       
   227     \reimp
       
   228 */
       
   229 void QNetworkDiskCache::insert(QIODevice *device)
       
   230 {
       
   231 #if defined(QNETWORKDISKCACHE_DEBUG)
       
   232     qDebug() << "QNetworkDiskCache::insert()" << device;
       
   233 #endif
       
   234     Q_D(QNetworkDiskCache);
       
   235     QHash<QIODevice*, QCacheItem*>::iterator it = d->inserting.find(device);
       
   236     if (it == d->inserting.end()) {
       
   237         qWarning() << "QNetworkDiskCache::insert() called on a device we don't know about" << device;
       
   238         return;
       
   239     }
       
   240 
       
   241     d->storeItem(it.value());
       
   242     delete it.value();
       
   243     d->inserting.erase(it);
       
   244 }
       
   245 
       
   246 void QNetworkDiskCachePrivate::storeItem(QCacheItem *cacheItem)
       
   247 {
       
   248     Q_Q(QNetworkDiskCache);
       
   249     Q_ASSERT(cacheItem->metaData.saveToDisk());
       
   250 
       
   251     QString fileName = cacheFileName(cacheItem->metaData.url());
       
   252     Q_ASSERT(!fileName.isEmpty());
       
   253 
       
   254     if (QFile::exists(fileName)) {
       
   255         if (!QFile::remove(fileName)) {
       
   256             qWarning() << "QNetworkDiskCache: couldn't remove the cache file " << fileName;
       
   257             return;
       
   258         }
       
   259     }
       
   260 
       
   261     if (currentCacheSize > 0)
       
   262         currentCacheSize += 1024 + cacheItem->size();
       
   263     currentCacheSize = q->expire();
       
   264     if (!cacheItem->file) {
       
   265         QString templateName = tmpCacheFileName();
       
   266         cacheItem->file = new QTemporaryFile(templateName, &cacheItem->data);
       
   267         if (cacheItem->file->open()) {
       
   268             cacheItem->writeHeader(cacheItem->file);
       
   269             cacheItem->writeCompressedData(cacheItem->file);
       
   270         }
       
   271     }
       
   272 
       
   273     if (cacheItem->file
       
   274         && cacheItem->file->isOpen()
       
   275         && cacheItem->file->error() == QFile::NoError) {
       
   276         cacheItem->file->setAutoRemove(false);
       
   277         // ### use atomic rename rather then remove & rename
       
   278         if (cacheItem->file->rename(fileName))
       
   279             currentCacheSize += cacheItem->file->size();
       
   280         else
       
   281             cacheItem->file->setAutoRemove(true);
       
   282     }
       
   283     if (cacheItem->metaData.url() == lastItem.metaData.url())
       
   284         lastItem.reset();
       
   285 }
       
   286 
       
   287 /*!
       
   288     \reimp
       
   289 */
       
   290 bool QNetworkDiskCache::remove(const QUrl &url)
       
   291 {
       
   292 #if defined(QNETWORKDISKCACHE_DEBUG)
       
   293     qDebug() << "QNetworkDiskCache::remove()" << url;
       
   294 #endif
       
   295     Q_D(QNetworkDiskCache);
       
   296 
       
   297     // remove is also used to cancel insertions, not a common operation
       
   298     QHashIterator<QIODevice*, QCacheItem*> it(d->inserting);
       
   299     while (it.hasNext()) {
       
   300         it.next();
       
   301         QCacheItem *item = it.value();
       
   302         if (item && item->metaData.url() == url) {
       
   303             delete item;
       
   304             d->inserting.remove(it.key());
       
   305             return true;
       
   306         }
       
   307     }
       
   308 
       
   309     if (d->lastItem.metaData.url() == url)
       
   310         d->lastItem.reset();
       
   311     return d->removeFile(d->cacheFileName(url));
       
   312 }
       
   313 
       
   314 /*!
       
   315     Put all of the misc file removing into one function to be extra safe
       
   316  */
       
   317 bool QNetworkDiskCachePrivate::removeFile(const QString &file)
       
   318 {
       
   319 #if defined(QNETWORKDISKCACHE_DEBUG)
       
   320     qDebug() << "QNetworkDiskCache::removFile()" << file;
       
   321 #endif
       
   322     if (file.isEmpty())
       
   323         return false;
       
   324     QFileInfo info(file);
       
   325     QString fileName = info.fileName();
       
   326     if (!fileName.endsWith(CACHE_POSTFIX) || !fileName.startsWith(CACHE_PREFIX))
       
   327         return false;
       
   328     qint64 size = info.size();
       
   329     if (QFile::remove(file)) {
       
   330         currentCacheSize -= size;
       
   331         return true;
       
   332     }
       
   333     return false;
       
   334 }
       
   335 
       
   336 /*!
       
   337     \reimp
       
   338 */
       
   339 QNetworkCacheMetaData QNetworkDiskCache::metaData(const QUrl &url)
       
   340 {
       
   341 #if defined(QNETWORKDISKCACHE_DEBUG)
       
   342     qDebug() << "QNetworkDiskCache::metaData()" << url;
       
   343 #endif
       
   344     Q_D(QNetworkDiskCache);
       
   345     if (d->lastItem.metaData.url() == url)
       
   346         return d->lastItem.metaData;
       
   347     return fileMetaData(d->cacheFileName(url));
       
   348 }
       
   349 
       
   350 /*!
       
   351     Returns the QNetworkCacheMetaData for the cache file \a fileName.
       
   352 
       
   353     If \a fileName is not a cache file QNetworkCacheMetaData will be invalid.
       
   354  */
       
   355 QNetworkCacheMetaData QNetworkDiskCache::fileMetaData(const QString &fileName) const
       
   356 {
       
   357 #if defined(QNETWORKDISKCACHE_DEBUG)
       
   358     qDebug() << "QNetworkDiskCache::fileMetaData()" << fileName;
       
   359 #endif
       
   360     Q_D(const QNetworkDiskCache);
       
   361     QFile file(fileName);
       
   362     if (!file.open(QFile::ReadOnly))
       
   363         return QNetworkCacheMetaData();
       
   364     if (!d->lastItem.read(&file, false)) {
       
   365         file.close();
       
   366         QNetworkDiskCachePrivate *that = const_cast<QNetworkDiskCachePrivate*>(d);
       
   367         that->removeFile(fileName);
       
   368     }
       
   369     return d->lastItem.metaData;
       
   370 }
       
   371 
       
   372 /*!
       
   373     \reimp
       
   374 */
       
   375 QIODevice *QNetworkDiskCache::data(const QUrl &url)
       
   376 {
       
   377 #if defined(QNETWORKDISKCACHE_DEBUG)
       
   378     qDebug() << "QNetworkDiskCache::data()" << url;
       
   379 #endif
       
   380     Q_D(QNetworkDiskCache);
       
   381     QScopedPointer<QBuffer> buffer;
       
   382     if (!url.isValid())
       
   383         return 0;
       
   384     if (d->lastItem.metaData.url() == url && d->lastItem.data.isOpen()) {
       
   385         buffer.reset(new QBuffer);
       
   386         buffer->setData(d->lastItem.data.data());
       
   387     } else {
       
   388         QScopedPointer<QFile> file(new QFile(d->cacheFileName(url)));
       
   389         if (!file->open(QFile::ReadOnly | QIODevice::Unbuffered))
       
   390             return 0;
       
   391 
       
   392         if (!d->lastItem.read(file.data(), true)) {
       
   393             file->close();
       
   394             remove(url);
       
   395             return 0;
       
   396         }
       
   397         if (d->lastItem.data.isOpen()) {
       
   398             // compressed
       
   399             buffer.reset(new QBuffer);
       
   400             buffer->setData(d->lastItem.data.data());
       
   401         } else {
       
   402             buffer.reset(new QBuffer);
       
   403             // ### verify that QFile uses the fd size and not the file name
       
   404             qint64 size = file->size() - file->pos();
       
   405             const uchar *p = 0;
       
   406 #ifndef Q_OS_WINCE
       
   407             p = file->map(file->pos(), size);
       
   408 #endif
       
   409             if (p) {
       
   410                 buffer->setData((const char *)p, size);
       
   411                 file.take()->setParent(buffer.data());
       
   412             } else {
       
   413                 buffer->setData(file->readAll());
       
   414             }
       
   415         }
       
   416     }
       
   417     buffer->open(QBuffer::ReadOnly);
       
   418     return buffer.take();
       
   419 }
       
   420 
       
   421 /*!
       
   422     \reimp
       
   423 */
       
   424 void QNetworkDiskCache::updateMetaData(const QNetworkCacheMetaData &metaData)
       
   425 {
       
   426 #if defined(QNETWORKDISKCACHE_DEBUG)
       
   427     qDebug() << "QNetworkDiskCache::updateMetaData()" << metaData.url();
       
   428 #endif
       
   429     QUrl url = metaData.url();
       
   430     QIODevice *oldDevice = data(url);
       
   431     if (!oldDevice) {
       
   432 #if defined(QNETWORKDISKCACHE_DEBUG)
       
   433         qDebug() << "QNetworkDiskCache::updateMetaData(), no device!";
       
   434 #endif
       
   435         return;
       
   436     }
       
   437 
       
   438     QIODevice *newDevice = prepare(metaData);
       
   439     if (!newDevice) {
       
   440 #if defined(QNETWORKDISKCACHE_DEBUG)
       
   441         qDebug() << "QNetworkDiskCache::updateMetaData(), no new device!" << url;
       
   442 #endif
       
   443         return;
       
   444     }
       
   445     char data[1024];
       
   446     while (!oldDevice->atEnd()) {
       
   447         qint64 s = oldDevice->read(data, 1024);
       
   448         newDevice->write(data, s);
       
   449     }
       
   450     delete oldDevice;
       
   451     insert(newDevice);
       
   452 }
       
   453 
       
   454 /*!
       
   455     Returns the current maximum size for the disk cache.
       
   456 
       
   457     \sa setMaximumCacheSize()
       
   458  */
       
   459 qint64 QNetworkDiskCache::maximumCacheSize() const
       
   460 {
       
   461     Q_D(const QNetworkDiskCache);
       
   462     return d->maximumCacheSize;
       
   463 }
       
   464 
       
   465 /*!
       
   466     Sets the maximum size of the disk cache to be \a size.
       
   467 
       
   468     If the new size is smaller then the current cache size then the cache will call expire().
       
   469 
       
   470     \sa maximumCacheSize()
       
   471  */
       
   472 void QNetworkDiskCache::setMaximumCacheSize(qint64 size)
       
   473 {
       
   474     Q_D(QNetworkDiskCache);
       
   475     bool expireCache = (size < d->maximumCacheSize);
       
   476     d->maximumCacheSize = size;
       
   477     if (expireCache)
       
   478         d->currentCacheSize = expire();
       
   479 }
       
   480 
       
   481 /*!
       
   482     Cleans the cache so that its size is under the maximum cache size.
       
   483     Returns the current size of the cache.
       
   484 
       
   485     When the current size of the cache is greater than the maximumCacheSize()
       
   486     older cache files are removed until the total size is less then 90% of
       
   487     maximumCacheSize() starting with the oldest ones first using the file
       
   488     creation date to determine how old a cache file is.
       
   489 
       
   490     Subclasses can reimplement this function to change the order that cache
       
   491     files are removed taking into account information in the application
       
   492     knows about that QNetworkDiskCache does not, for example the number of times
       
   493     a cache is accessed.
       
   494 
       
   495     Note: cacheSize() calls expire if the current cache size is unknown.
       
   496 
       
   497     \sa maximumCacheSize(), fileMetaData()
       
   498  */
       
   499 qint64 QNetworkDiskCache::expire()
       
   500 {
       
   501     Q_D(QNetworkDiskCache);
       
   502     if (d->currentCacheSize >= 0 && d->currentCacheSize < maximumCacheSize())
       
   503         return d->currentCacheSize;
       
   504 
       
   505     if (cacheDirectory().isEmpty()) {
       
   506         qWarning() << "QNetworkDiskCache::expire() The cache directory is not set";
       
   507         return 0;
       
   508     }
       
   509 
       
   510     QDir::Filters filters = QDir::AllDirs | QDir:: Files | QDir::NoDotAndDotDot;
       
   511     QDirIterator it(cacheDirectory(), filters, QDirIterator::Subdirectories);
       
   512 
       
   513     QMultiMap<QDateTime, QString> cacheItems;
       
   514     qint64 totalSize = 0;
       
   515     while (it.hasNext()) {
       
   516         QString path = it.next();
       
   517         QFileInfo info = it.fileInfo();
       
   518         QString fileName = info.fileName();
       
   519         if (fileName.endsWith(CACHE_POSTFIX) && fileName.startsWith(CACHE_PREFIX)) {
       
   520             cacheItems.insert(info.created(), path);
       
   521             totalSize += info.size();
       
   522         }
       
   523     }
       
   524 
       
   525     int removedFiles = 0;
       
   526     qint64 goal = (maximumCacheSize() * 9) / 10;
       
   527     QMultiMap<QDateTime, QString>::const_iterator i = cacheItems.constBegin();
       
   528     while (i != cacheItems.constEnd()) {
       
   529         if (totalSize < goal)
       
   530             break;
       
   531         QString name = i.value();
       
   532         QFile file(name);
       
   533         qint64 size = file.size();
       
   534         file.remove();
       
   535         totalSize -= size;
       
   536         ++removedFiles;
       
   537         ++i;
       
   538     }
       
   539 #if defined(QNETWORKDISKCACHE_DEBUG)
       
   540     if (removedFiles > 0) {
       
   541         qDebug() << "QNetworkDiskCache::expire()"
       
   542                 << "Removed:" << removedFiles
       
   543                 << "Kept:" << cacheItems.count() - removedFiles;
       
   544     }
       
   545 #endif
       
   546     if (removedFiles > 0)
       
   547         d->lastItem.reset();
       
   548     return totalSize;
       
   549 }
       
   550 
       
   551 /*!
       
   552     \reimp
       
   553 */
       
   554 void QNetworkDiskCache::clear()
       
   555 {
       
   556 #if defined(QNETWORKDISKCACHE_DEBUG)
       
   557     qDebug() << "QNetworkDiskCache::clear()";
       
   558 #endif
       
   559     Q_D(QNetworkDiskCache);
       
   560     qint64 size = d->maximumCacheSize;
       
   561     d->maximumCacheSize = 0;
       
   562     d->currentCacheSize = expire();
       
   563     d->maximumCacheSize = size;
       
   564 }
       
   565 
       
   566 QByteArray QNetworkDiskCachePrivate::generateId(const QUrl &url) const
       
   567 {
       
   568     QUrl cleanUrl = url;
       
   569     cleanUrl.setPassword(QString());
       
   570     cleanUrl.setFragment(QString());
       
   571 
       
   572     QCryptographicHash hash(QCryptographicHash::Sha1);
       
   573     hash.addData(cleanUrl.toEncoded());
       
   574     return hash.result().toHex();
       
   575 }
       
   576 
       
   577 QString QNetworkDiskCachePrivate::tmpCacheFileName() const
       
   578 {
       
   579     QDir dir;
       
   580     dir.mkpath(cacheDirectory + QLatin1String("prepared/"));
       
   581     return cacheDirectory + QLatin1String("prepared/") + CACHE_PREFIX + QLatin1String("XXXXXX") + CACHE_POSTFIX;
       
   582 }
       
   583 
       
   584 QString QNetworkDiskCachePrivate::cacheFileName(const QUrl &url) const
       
   585 {
       
   586     if (!url.isValid())
       
   587         return QString();
       
   588     QString directory = cacheDirectory + url.scheme() + QLatin1Char('/');
       
   589     if (!QFile::exists(directory)) {
       
   590         // ### make a static QDir function for this...
       
   591         QDir dir;
       
   592         dir.mkpath(directory);
       
   593     }
       
   594 
       
   595     QString fileName = CACHE_PREFIX + QLatin1String(generateId(url)) + CACHE_POSTFIX;
       
   596     return  directory + fileName;
       
   597 }
       
   598 
       
   599 /*!
       
   600     We compress small text and JavaScript files.
       
   601  */
       
   602 bool QCacheItem::canCompress() const
       
   603 {
       
   604     bool sizeOk = false;
       
   605     bool typeOk = false;
       
   606     foreach (QNetworkCacheMetaData::RawHeader header, metaData.rawHeaders()) {
       
   607         if (header.first.toLower() == "content-length") {
       
   608             qint64 size = header.second.toLongLong();
       
   609             if (size > MAX_COMPRESSION_SIZE)
       
   610                 return false;
       
   611             else
       
   612                 sizeOk = true;
       
   613         }
       
   614 
       
   615         if (header.first.toLower() == "content-type") {
       
   616             QByteArray type = header.second;
       
   617             if (type.startsWith("text/")
       
   618                     || (type.startsWith("application/")
       
   619                         && (type.endsWith("javascript") || type.endsWith("ecmascript"))))
       
   620                 typeOk = true;
       
   621             else
       
   622                 return false;
       
   623         }
       
   624         if (sizeOk && typeOk)
       
   625             return true;
       
   626     }
       
   627     return false;
       
   628 }
       
   629 
       
   630 enum
       
   631 {
       
   632     CacheMagic = 0xe8,
       
   633     CurrentCacheVersion = 7
       
   634 };
       
   635 
       
   636 void QCacheItem::writeHeader(QFile *device) const
       
   637 {
       
   638     QDataStream out(device);
       
   639 
       
   640     out << qint32(CacheMagic);
       
   641     out << qint32(CurrentCacheVersion);
       
   642     out << metaData;
       
   643     bool compressed = canCompress();
       
   644     out << compressed;
       
   645 }
       
   646 
       
   647 void QCacheItem::writeCompressedData(QFile *device) const
       
   648 {
       
   649     QDataStream out(device);
       
   650 
       
   651     out << qCompress(data.data());
       
   652 }
       
   653 
       
   654 /*!
       
   655     Returns false if the file is a cache file,
       
   656     but is an older version and should be removed otherwise true.
       
   657  */
       
   658 bool QCacheItem::read(QFile *device, bool readData)
       
   659 {
       
   660     reset();
       
   661 
       
   662     QDataStream in(device);
       
   663 
       
   664     qint32 marker;
       
   665     qint32 v;
       
   666     in >> marker;
       
   667     in >> v;
       
   668     if (marker != CacheMagic)
       
   669         return true;
       
   670 
       
   671     // If the cache magic is correct, but the version is not we should remove it
       
   672     if (v != CurrentCacheVersion)
       
   673         return false;
       
   674 
       
   675     bool compressed;
       
   676     QByteArray dataBA;
       
   677     in >> metaData;
       
   678     in >> compressed;
       
   679     if (readData && compressed) {
       
   680         in >> dataBA;
       
   681         data.setData(qUncompress(dataBA));
       
   682         data.open(QBuffer::ReadOnly);
       
   683     }
       
   684     return metaData.isValid();
       
   685 }
       
   686 
       
   687 QT_END_NAMESPACE
       
   688 
       
   689 #endif // QT_NO_NETWORKDISKCACHE