browsercore/core/network/networkdiskcache.cpp
changeset 9 b39122337a00
parent 7 a1f515018ac1
child 11 786160610b4d
equal deleted inserted replaced
7:a1f515018ac1 9:b39122337a00
     1 /****************************************************************************
       
     2 **
       
     3 ** Copyright (C) 2010 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 ** Commercial Usage
       
    11 ** Licensees holding valid Qt Commercial licenses may use this file in
       
    12 ** accordance with the Qt Commercial License Agreement provided with the
       
    13 ** Software or, alternatively, in accordance with the terms contained in
       
    14 ** a written agreement between you and Nokia.
       
    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 ** GNU General Public License Usage
       
    29 ** Alternatively, this file may be used under the terms of the GNU
       
    30 ** General Public License version 3.0 as published by the Free Software
       
    31 ** Foundation and appearing in the file LICENSE.GPL included in the
       
    32 ** packaging of this file.  Please review the following information to
       
    33 ** ensure the GNU General Public License version 3.0 requirements will be
       
    34 ** met: http://www.gnu.org/copyleft/gpl.html.
       
    35 **
       
    36 ** If you have questions regarding the use of this file, please contact
       
    37 ** Nokia at qt-info@nokia.com.
       
    38 ** $QT_END_LICENSE$
       
    39 **
       
    40 ****************************************************************************/
       
    41 
       
    42 //#define QNETWORKDISKCACHE_DEBUG
       
    43 
       
    44 #ifndef QT_NO_NETWORKDISKCACHE
       
    45 
       
    46 #include "networkdiskcache.h"
       
    47 #include "networkdiskcache_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 <qurl.h>
       
    55 
       
    56 #include <qdebug.h>
       
    57 
       
    58 
       
    59 #define MAX_COMPRESSION_SIZE (1024 * 1024 * 3)
       
    60 #define CACHE_SUBDIR_COUNT 16
       
    61 
       
    62 QT_BEGIN_NAMESPACE
       
    63 
       
    64 /*!
       
    65     \class QNetworkDiskCache
       
    66     \since 4.5
       
    67     \inmodule QtNetwork
       
    68 
       
    69     \brief The QNetworkDiskCache class provides a very basic disk cache.
       
    70 
       
    71     QNetworkDiskCache stores each url in its own file inside of the
       
    72     cacheDirectory using QDataStream.  Files with a text MimeType
       
    73     are compressed using qCompress.  Each cache file starts with "cache_"
       
    74     and ends in ".cache".  Data is written to disk only in insert()
       
    75     and updateMetaData().
       
    76 
       
    77     Currently you can not share the same cache files with more then
       
    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 doc/src/snippets/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 doc/src/snippets/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 doc/src/snippets/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  */
       
   104 NetworkDiskCache::NetworkDiskCache(QObject *parent)
       
   105     : QAbstractNetworkCache(*new NetworkDiskCachePrivate, parent)
       
   106 {
       
   107 }
       
   108 
       
   109 /*!
       
   110     Destroys the cache object.  This does not clear the disk cache.
       
   111  */
       
   112 NetworkDiskCache::~NetworkDiskCache()
       
   113 {
       
   114     Q_D(NetworkDiskCache);
       
   115     QHashIterator<QIODevice*, QCacheItem*> it(d->inserting);
       
   116     while (it.hasNext()) {
       
   117         it.next();
       
   118         delete it.value();
       
   119     }
       
   120 }
       
   121 
       
   122 /*!
       
   123     Returns the location where cached files will be stored.
       
   124 */
       
   125 QString NetworkDiskCache::cacheDirectory() const
       
   126 {
       
   127     Q_D(const NetworkDiskCache);
       
   128     return d->cacheDirectory;
       
   129 }
       
   130 
       
   131 /*!
       
   132     Sets the directory where cached files will be stored to \a cacheDir
       
   133 
       
   134     QNetworkDiskCache will create this directory if it does not exists.
       
   135 
       
   136     Prepared cache items will be stored in the new cache directory when
       
   137     they are inserted.
       
   138 
       
   139     \sa QDesktopServices::CacheLocation
       
   140 */
       
   141 void NetworkDiskCache::setCacheDirectory(const QString &cacheDir)
       
   142 {
       
   143 #if defined(QNETWORKDISKCACHE_DEBUG)
       
   144     qDebug() << "NetworkDiskCache::setCacheDirectory()" << cacheDir;
       
   145 #endif
       
   146     Q_D(NetworkDiskCache);
       
   147     if (cacheDir.isEmpty())
       
   148         return;
       
   149     d->cacheDirectory = cacheDir;
       
   150     QDir cDir(d->cacheDirectory);
       
   151     d->cacheDirectory = cDir.absolutePath();
       
   152     if (!d->cacheDirectory.endsWith(QLatin1Char('/')))
       
   153         d->cacheDirectory += QLatin1Char('/');
       
   154 
       
   155     QDir dir;
       
   156     // Setup and create directories
       
   157     if (!QFile::exists(d->cacheDirectory)) 
       
   158     {
       
   159         // ### make a static QDir function for this...
       
   160         dir.mkpath(d->cacheDirectory);
       
   161     }
       
   162 
       
   163     QString subDirectory;
       
   164     for (int i = 0; i < CACHE_SUBDIR_COUNT; i++)
       
   165     {
       
   166         subDirectory = d->cacheDirectory + QString("%1").arg(i, 0, 16) + QLatin1Char('/');
       
   167         if (!QFile::exists(subDirectory)) 
       
   168         {
       
   169     	    dir.mkpath(subDirectory);
       
   170         }
       
   171     }
       
   172 
       
   173     // For Temporary Prepared Directory
       
   174     dir.mkpath(d->cacheDirectory + QLatin1String("prepared/"));
       
   175 }
       
   176 
       
   177 /*!
       
   178     \reimp
       
   179 */
       
   180 qint64 NetworkDiskCache::cacheSize() const
       
   181 {
       
   182 #if defined(QNETWORKDISKCACHE_DEBUG)
       
   183     qDebug() << "NetworkDiskCache::cacheSize()";
       
   184 #endif
       
   185     Q_D(const NetworkDiskCache);
       
   186     if (d->cacheDirectory.isEmpty())
       
   187         return 0;
       
   188     if (d->currentCacheSize < 0) {
       
   189         NetworkDiskCache *that = const_cast<NetworkDiskCache*>(this);
       
   190         that->d_func()->currentCacheSize = that->expire();
       
   191     }
       
   192     return d->currentCacheSize;
       
   193 }
       
   194 
       
   195 /*!
       
   196     \reimp
       
   197 */
       
   198 QIODevice *NetworkDiskCache::prepare(const QNetworkCacheMetaData &metaData)
       
   199 {
       
   200 #if defined(QNETWORKDISKCACHE_DEBUG)
       
   201     qDebug() << "NetworkDiskCache::prepare()" << metaData.url();
       
   202 #endif
       
   203     Q_D(NetworkDiskCache);
       
   204     if (!metaData.isValid() || !metaData.url().isValid() || !metaData.saveToDisk())
       
   205         return 0;
       
   206 
       
   207     if (d->cacheDirectory.isEmpty()) {
       
   208         qWarning() << "NetworkDiskCache::prepare() The cache directory is not set";
       
   209         return 0;
       
   210     }
       
   211 
       
   212     foreach (QNetworkCacheMetaData::RawHeader header, metaData.rawHeaders()) {
       
   213         if (header.first.toLower() == "content-length") {
       
   214             qint64 size = header.second.toInt();
       
   215             if (size > (maximumCacheSize() * 3)/4)
       
   216                 return 0;
       
   217             break;
       
   218         }
       
   219     }
       
   220     QScopedPointer<QCacheItem> cacheItem(new QCacheItem);
       
   221     cacheItem->metaData = metaData;
       
   222 
       
   223     QIODevice *device = 0;
       
   224     if (cacheItem->canCompress()) {
       
   225         cacheItem->data.open(QBuffer::ReadWrite);
       
   226         device = &(cacheItem->data);
       
   227     } else {
       
   228         QString templateName = d->tmpCacheFileName();
       
   229         QT_TRY {
       
   230             cacheItem->file = new QTemporaryFile(templateName, &cacheItem->data);
       
   231         } QT_CATCH(...) {
       
   232             cacheItem->file = 0;
       
   233         }
       
   234         if (!cacheItem->file || !cacheItem->file->open()) {
       
   235             qWarning() << "NetworkDiskCache::prepare() unable to open temporary file";
       
   236             cacheItem.reset();
       
   237             return 0;
       
   238         }
       
   239         cacheItem->writeHeader(cacheItem->file);
       
   240         device = cacheItem->file;
       
   241     }
       
   242     d->inserting[device] = cacheItem.take();
       
   243     return device;
       
   244 }
       
   245 
       
   246 /*!
       
   247     \reimp
       
   248 */
       
   249 void NetworkDiskCache::insert(QIODevice *device)
       
   250 {
       
   251 #if defined(QNETWORKDISKCACHE_DEBUG)
       
   252     qDebug() << "NetworkDiskCache::insert()" << device;
       
   253 #endif
       
   254     Q_D(NetworkDiskCache);
       
   255     QHash<QIODevice*, QCacheItem*>::iterator it = d->inserting.find(device);
       
   256     if (it == d->inserting.end()) {
       
   257         qWarning() << "NetworkDiskCache::insert() called on a device we don't know about" << device;
       
   258         return;
       
   259     }
       
   260 
       
   261     d->storeItem(it.value());
       
   262     delete it.value();
       
   263     d->inserting.erase(it);
       
   264 }
       
   265 
       
   266 // CRC32 implementation.
       
   267 // Could be made into new API QByteArray:qChecksum32()
       
   268 static const quint32 crc_tbl32[256] = {
       
   269 	0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
       
   270 	0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
       
   271 	0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
       
   272 	0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
       
   273 	0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
       
   274 	0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
       
   275 	0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
       
   276 	0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
       
   277 	0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
       
   278 	0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
       
   279 	0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,
       
   280 	0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
       
   281 	0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,
       
   282 	0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
       
   283 	0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
       
   284 	0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
       
   285 	0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,
       
   286 	0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
       
   287 	0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,
       
   288 	0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
       
   289 	0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
       
   290 	0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
       
   291 	0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,
       
   292 	0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
       
   293 	0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
       
   294 	0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
       
   295 	0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
       
   296 	0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
       
   297 	0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,
       
   298 	0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
       
   299 	0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,
       
   300 	0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
       
   301 	0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
       
   302 	0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
       
   303 	0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
       
   304 	0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
       
   305 	0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,
       
   306 	0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
       
   307 	0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
       
   308 	0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
       
   309 	0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,
       
   310 	0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
       
   311 	0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,
       
   312 	0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
       
   313 	0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
       
   314 	0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
       
   315 	0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,
       
   316 	0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
       
   317 	0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
       
   318 	0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
       
   319 	0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
       
   320 	0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
       
   321 	0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,
       
   322 	0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
       
   323 	0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
       
   324 	0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
       
   325 	0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
       
   326 	0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
       
   327 	0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,
       
   328 	0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
       
   329 	0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,
       
   330 	0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
       
   331 	0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
       
   332 	0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
       
   333 };
       
   334 	
       
   335 quint32 NetworkDiskCachePrivate::crc32(const char *data, uint len)
       
   336 {
       
   337 	const uchar *p = reinterpret_cast<const uchar *>(data);
       
   338 	const uchar *q = p + len;
       
   339 	const quint32 init = 0xFFFFFFFFL;
       
   340 	
       
   341 	quint32 crc32 = init;
       
   342 	while (p < q) {
       
   343 	    crc32 = (crc32 >> 8) ^ crc_tbl32[(crc32 ^ *p++) & 0xffL];
       
   344 	}
       
   345 	return crc32 ^ init ;
       
   346 }
       
   347 
       
   348 void NetworkDiskCachePrivate::storeItem(QCacheItem *cacheItem)
       
   349 {
       
   350     Q_Q(NetworkDiskCache);
       
   351     
       
   352     // just an idea of not caching anything more than 15k
       
   353     //if (cacheItem->size() > (15*1024)) return;
       
   354     
       
   355     Q_ASSERT(cacheItem->metaData.saveToDisk());
       
   356 
       
   357     QString fileName = cacheFileName(cacheItem->metaData.url());
       
   358     Q_ASSERT(!fileName.isEmpty());
       
   359 
       
   360     if (QFile::exists(fileName)) {
       
   361         if (!QFile::remove(fileName)) {
       
   362             qWarning() << "NetworkDiskCache: couldn't remove the cache file " << fileName;
       
   363             return;
       
   364         }
       
   365     }
       
   366 
       
   367     if (currentCacheSize > 0)
       
   368         currentCacheSize += 1024 + cacheItem->size();
       
   369     currentCacheSize = q->expire();
       
   370     if (!cacheItem->file) {
       
   371         QString templateName = tmpCacheFileName();
       
   372         cacheItem->file = new QTemporaryFile(templateName, &cacheItem->data);
       
   373         if (cacheItem->file->open()) {
       
   374             cacheItem->writeHeader(cacheItem->file);
       
   375             cacheItem->writeCompressedData(cacheItem->file);
       
   376         }
       
   377     }
       
   378 
       
   379     if (cacheItem->file
       
   380         && cacheItem->file->isOpen()
       
   381         && cacheItem->file->error() == QFile::NoError) {
       
   382         cacheItem->file->setAutoRemove(false);
       
   383         // ### use atomic rename rather then remove & rename
       
   384         if (cacheItem->file->rename(fileName))
       
   385             currentCacheSize += cacheItem->file->size();
       
   386         else
       
   387             cacheItem->file->setAutoRemove(true);
       
   388     }
       
   389     if (cacheItem->metaData.url() == lastItem.metaData.url())
       
   390         lastItem.reset();
       
   391 }
       
   392 
       
   393 /*!
       
   394     \reimp
       
   395 */
       
   396 bool NetworkDiskCache::remove(const QUrl &url)
       
   397 {
       
   398 #if defined(QNETWORKDISKCACHE_DEBUG)
       
   399     qDebug() << "NetworkDiskCache::remove()" << url;
       
   400 #endif
       
   401     Q_D(NetworkDiskCache);
       
   402 
       
   403     // remove is also used to cancel insertions, not a common operation
       
   404     QHashIterator<QIODevice*, QCacheItem*> it(d->inserting);
       
   405     while (it.hasNext()) {
       
   406         it.next();
       
   407         QCacheItem *item = it.value();
       
   408         if (item && item->metaData.url() == url) {
       
   409             delete item;
       
   410             d->inserting.remove(it.key());
       
   411             return true;
       
   412         }
       
   413     }
       
   414 
       
   415     if (d->lastItem.metaData.url() == url)
       
   416         d->lastItem.reset();
       
   417     return d->removeFile(d->cacheFileName(url));
       
   418 }
       
   419 
       
   420 /*!
       
   421     Put all of the misc file removing into one function to be extra safe
       
   422  */
       
   423 bool NetworkDiskCachePrivate::removeFile(const QString &file)
       
   424 {
       
   425 #if defined(QNETWORKDISKCACHE_DEBUG)
       
   426     qDebug() << "NetworkDiskCache::removeFile()" << file;
       
   427 #endif
       
   428     if (file.isEmpty())
       
   429         return false;
       
   430     QFileInfo info(file);
       
   431     QString fileName = info.fileName();
       
   432     qint64 size = info.size();
       
   433     if (QFile::remove(file)) {
       
   434         currentCacheSize -= size;
       
   435         return true;
       
   436     }
       
   437     return false;
       
   438 }
       
   439 
       
   440 /*!
       
   441     \reimp
       
   442 */
       
   443 QNetworkCacheMetaData NetworkDiskCache::metaData(const QUrl &url)
       
   444 {
       
   445 #if defined(QNETWORKDISKCACHE_DEBUG)
       
   446     qDebug() << "NetworkDiskCache::metaData()" << url;
       
   447 #endif
       
   448     Q_D(NetworkDiskCache);
       
   449     if (d->lastItem.metaData.url() == url)
       
   450         return d->lastItem.metaData;
       
   451     return fileMetaData(d->cacheFileName(url));
       
   452 }
       
   453 
       
   454 /*!
       
   455     Returns the QNetworkCacheMetaData for the cache file \a fileName.
       
   456 
       
   457     If \a fileName is not a cache file QNetworkCacheMetaData will be invalid.
       
   458  */
       
   459 QNetworkCacheMetaData NetworkDiskCache::fileMetaData(const QString &fileName) const
       
   460 {
       
   461 #if defined(QNETWORKDISKCACHE_DEBUG)
       
   462     qDebug() << "NetworkDiskCache::fileMetaData()" << fileName;
       
   463 #endif
       
   464     Q_D(const NetworkDiskCache);
       
   465     QFile file(fileName);
       
   466     if (!file.open(QFile::ReadOnly))
       
   467         return QNetworkCacheMetaData();
       
   468     if (!d->lastItem.read(&file, false)) {
       
   469         file.close();
       
   470         NetworkDiskCachePrivate *that = const_cast<NetworkDiskCachePrivate*>(d);
       
   471         that->removeFile(fileName);
       
   472     }
       
   473     return d->lastItem.metaData;
       
   474 }
       
   475 
       
   476 /*!
       
   477     \reimp
       
   478 */
       
   479 QIODevice *NetworkDiskCache::data(const QUrl &url)
       
   480 {
       
   481 #if defined(QNETWORKDISKCACHE_DEBUG)
       
   482     qDebug() << "NetworkDiskCache::data()" << url;
       
   483 #endif
       
   484     Q_D(NetworkDiskCache);
       
   485     QScopedPointer<QBuffer> buffer;
       
   486     if (!url.isValid())
       
   487         return 0;
       
   488     if (d->lastItem.metaData.url() == url && d->lastItem.data.isOpen()) {
       
   489         buffer.reset(new QBuffer);
       
   490         buffer->setData(d->lastItem.data.data());
       
   491     } else {
       
   492         QScopedPointer<QFile> file(new QFile(d->cacheFileName(url)));
       
   493         if (!file->open(QFile::ReadOnly | QIODevice::Unbuffered))
       
   494             return 0;
       
   495 
       
   496         if (!d->lastItem.read(file.data(), true)) {
       
   497             file->close();
       
   498             remove(url);
       
   499             return 0;
       
   500         }
       
   501         if (d->lastItem.data.isOpen()) {
       
   502             // compressed
       
   503             buffer.reset(new QBuffer);
       
   504             buffer->setData(d->lastItem.data.data());
       
   505         } else {
       
   506             buffer.reset(new QBuffer);
       
   507             // ### verify that QFile uses the fd size and not the file name
       
   508             qint64 size = file->size() - file->pos();
       
   509             const uchar *p = 0;
       
   510 #ifndef Q_OS_WINCE
       
   511             p = file->map(file->pos(), size);
       
   512 #endif
       
   513             if (p) {
       
   514                 buffer->setData((const char *)p, size);
       
   515                 file.take()->setParent(buffer.data());
       
   516             } else {
       
   517                 buffer->setData(file->readAll());
       
   518             }
       
   519         }
       
   520     }
       
   521     buffer->open(QBuffer::ReadOnly);
       
   522     return buffer.take();
       
   523 }
       
   524 
       
   525 /*!
       
   526     \reimp
       
   527 */
       
   528 void NetworkDiskCache::updateMetaData(const QNetworkCacheMetaData &metaData)
       
   529 {
       
   530 #if defined(QNETWORKDISKCACHE_DEBUG)
       
   531     qDebug() << "NetworkDiskCache::updateMetaData()" << metaData.url();
       
   532 #endif
       
   533     QUrl url = metaData.url();
       
   534     QIODevice *oldDevice = data(url);
       
   535     if (!oldDevice) {
       
   536 #if defined(QNETWORKDISKCACHE_DEBUG)
       
   537         qDebug() << "NetworkDiskCache::updateMetaData(), no device!";
       
   538 #endif
       
   539         return;
       
   540     }
       
   541 
       
   542     QIODevice *newDevice = prepare(metaData);
       
   543     if (!newDevice) {
       
   544 #if defined(QNETWORKDISKCACHE_DEBUG)
       
   545         qDebug() << "NetworkDiskCache::updateMetaData(), no new device!" << url;
       
   546 #endif
       
   547         return;
       
   548     }
       
   549     char data[1024];
       
   550     while (!oldDevice->atEnd()) {
       
   551         qint64 s = oldDevice->read(data, 1024);
       
   552         newDevice->write(data, s);
       
   553     }
       
   554     delete oldDevice;
       
   555     insert(newDevice);
       
   556 }
       
   557 
       
   558 /*!
       
   559     Returns the current maximum size for the disk cache.
       
   560 
       
   561     \sa setMaximumCacheSize()
       
   562  */
       
   563 qint64 NetworkDiskCache::maximumCacheSize() const
       
   564 {
       
   565     Q_D(const NetworkDiskCache);
       
   566     return d->maximumCacheSize;
       
   567 }
       
   568 
       
   569 /*!
       
   570     Sets the maximum size of the disk cache to be \a size.
       
   571 
       
   572     If the new size is smaller then the current cache size then the cache will call expire().
       
   573 
       
   574     \sa maximumCacheSize()
       
   575  */
       
   576 void NetworkDiskCache::setMaximumCacheSize(qint64 size)
       
   577 {
       
   578     Q_D(NetworkDiskCache);
       
   579     bool expireCache = (size < d->maximumCacheSize);
       
   580     d->maximumCacheSize = size;
       
   581     if (expireCache)
       
   582         d->currentCacheSize = expire();
       
   583 }
       
   584 
       
   585 /*!
       
   586     Cleans the cache so that its size is under the maximum cache size.
       
   587     Returns the current size of the cache.
       
   588 
       
   589     When the current size of the cache is greater than the maximumCacheSize()
       
   590     older cache files are removed until the total size is less then 90% of
       
   591     maximumCacheSize() starting with the oldest ones first using the file
       
   592     creation date to determine how old a cache file is.
       
   593 
       
   594     Subclasses can reimplement this function to change the order that cache
       
   595     files are removed taking into account information in the application
       
   596     knows about that QNetworkDiskCache does not, for example the number of times
       
   597     a cache is accessed.
       
   598 
       
   599     Note: cacheSize() calls expire if the current cache size is unknown.
       
   600 
       
   601     \sa maximumCacheSize(), fileMetaData()
       
   602  */
       
   603 qint64 NetworkDiskCache::expire()
       
   604 {
       
   605     Q_D(NetworkDiskCache);
       
   606     if (d->currentCacheSize >= 0 && d->currentCacheSize < maximumCacheSize())
       
   607         return d->currentCacheSize;
       
   608 
       
   609     if (cacheDirectory().isEmpty()) {
       
   610         qWarning() << "NetworkDiskCache::expire() The cache directory is not set";
       
   611         return 0;
       
   612     }
       
   613 
       
   614     QDir::Filters filters = QDir::AllDirs | QDir:: Files | QDir::NoDotAndDotDot;
       
   615     QDirIterator it(cacheDirectory(), filters, QDirIterator::Subdirectories);
       
   616 
       
   617     QMultiMap<QDateTime, QString> cacheItems;
       
   618     qint64 totalSize = 0;
       
   619     while (it.hasNext()) {
       
   620         QString path = it.next();
       
   621         QFileInfo info = it.fileInfo();
       
   622         QString fileName = info.fileName();
       
   623         cacheItems.insert(info.created(), path);
       
   624         totalSize += info.size();
       
   625     }
       
   626 
       
   627     int removedFiles = 0;
       
   628     qint64 goal = (maximumCacheSize() * 9) / 10;
       
   629     QMultiMap<QDateTime, QString>::const_iterator i = cacheItems.constBegin();
       
   630     while (i != cacheItems.constEnd()) {
       
   631         if (totalSize < goal)
       
   632             break;
       
   633         QString name = i.value();
       
   634         QFile file(name);
       
   635         qint64 size = file.size();
       
   636         file.remove();
       
   637         totalSize -= size;
       
   638         ++removedFiles;
       
   639         ++i;
       
   640     }
       
   641 #if defined(QNETWORKDISKCACHE_DEBUG)
       
   642     if (removedFiles > 0) {
       
   643         qDebug() << "NetworkDiskCache::expire()"
       
   644                 << "Removed:" << removedFiles
       
   645                 << "Kept:" << cacheItems.count() - removedFiles;
       
   646     }
       
   647 #endif
       
   648     if (removedFiles > 0)
       
   649         d->lastItem.reset();
       
   650     return totalSize;
       
   651 }
       
   652 
       
   653 /*!
       
   654     \reimp
       
   655 */
       
   656 void NetworkDiskCache::clear()
       
   657 {
       
   658 #if defined(QNETWORKDISKCACHE_DEBUG)
       
   659     qDebug() << "NetworkDiskCache::clear()";
       
   660 #endif
       
   661     Q_D(NetworkDiskCache);
       
   662     qint64 size = d->maximumCacheSize;
       
   663     d->maximumCacheSize = 0;
       
   664     d->currentCacheSize = expire();
       
   665     d->maximumCacheSize = size;
       
   666 }
       
   667 
       
   668 QByteArray NetworkDiskCachePrivate::generateId(const QUrl &url) const
       
   669 {
       
   670     QUrl cleanUrl = url;
       
   671     cleanUrl.setPassword(QString());
       
   672     cleanUrl.setFragment(QString());
       
   673     QByteArray blob = cleanUrl.toEncoded();
       
   674 
       
   675     QByteArray hash;
       
   676     hash.setNum(crc32(blob.data(), blob.length()), 16);
       
   677     return hash;
       
   678 }
       
   679 
       
   680 QString NetworkDiskCachePrivate::tmpCacheFileName() const
       
   681 {
       
   682     return cacheDirectory + QLatin1String("prepared/") + QLatin1String("XXXXXX");
       
   683 }
       
   684 
       
   685 QString NetworkDiskCachePrivate::cacheFileName(const QUrl &url) const
       
   686 {
       
   687     if (!url.isValid())
       
   688         return QString();
       
   689 
       
   690     // Directories were already created during setup phase.
       
   691     QString subDirectory;
       
   692     QByteArray filenameID;
       
   693     filenameID = generateId(url);
       
   694     subDirectory = cacheDirectory + QLatin1Char(filenameID.at(0)) + QLatin1Char('/');
       
   695 
       
   696     return  subDirectory + QLatin1String(filenameID);
       
   697 }
       
   698 
       
   699 /*!
       
   700     We compress small text and JavaScript files.
       
   701  */
       
   702 bool QCacheItem::canCompress() const
       
   703 {
       
   704     bool sizeOk = false;
       
   705     bool typeOk = false;
       
   706     foreach (QNetworkCacheMetaData::RawHeader header, metaData.rawHeaders()) {
       
   707         if (header.first.toLower() == "content-length") {
       
   708             qint64 size = header.second.toLongLong();
       
   709             if (size > MAX_COMPRESSION_SIZE)
       
   710                 return false;
       
   711             else
       
   712                 sizeOk = true;
       
   713         }
       
   714 
       
   715         if (header.first.toLower() == "content-type") {
       
   716             QByteArray type = header.second;
       
   717             if (type.startsWith("text/")
       
   718                     || (type.startsWith("application/")
       
   719                         && (type.endsWith("javascript") || type.endsWith("ecmascript"))))
       
   720                 typeOk = true;
       
   721             else
       
   722                 return false;
       
   723         }
       
   724         if (sizeOk && typeOk)
       
   725             return true;
       
   726     }
       
   727     return false;
       
   728 }
       
   729 
       
   730 enum
       
   731 {
       
   732     CacheMagic = 0xe8,
       
   733     CurrentCacheVersion = 7
       
   734 };
       
   735 
       
   736 void QCacheItem::writeHeader(QFile *device) const
       
   737 {
       
   738     QDataStream out(device);
       
   739 
       
   740     out << qint32(CacheMagic);
       
   741     out << qint32(CurrentCacheVersion);
       
   742     out << metaData;
       
   743     bool compressed = canCompress();
       
   744     out << compressed;
       
   745 }
       
   746 
       
   747 void QCacheItem::writeCompressedData(QFile *device) const
       
   748 {
       
   749     QDataStream out(device);
       
   750 
       
   751     out << qCompress(data.data());
       
   752 }
       
   753 
       
   754 /*!
       
   755     Returns false if the file is a cache file,
       
   756     but is an older version and should be removed otherwise true.
       
   757  */
       
   758 bool QCacheItem::read(QFile *device, bool readData)
       
   759 {
       
   760     reset();
       
   761 
       
   762     QDataStream in(device);
       
   763 
       
   764     qint32 marker;
       
   765     qint32 v;
       
   766     in >> marker;
       
   767     in >> v;
       
   768     if (marker != CacheMagic)
       
   769         return true;
       
   770 
       
   771     // If the cache magic is correct, but the version is not we should remove it
       
   772     if (v != CurrentCacheVersion)
       
   773         return false;
       
   774 
       
   775     bool compressed;
       
   776     QByteArray dataBA;
       
   777     in >> metaData;
       
   778     in >> compressed;
       
   779     if (readData && compressed) {
       
   780         in >> dataBA;
       
   781         data.setData(qUncompress(dataBA));
       
   782         data.open(QBuffer::ReadOnly);
       
   783     }
       
   784     return metaData.isValid();
       
   785 }
       
   786 
       
   787 QT_END_NAMESPACE
       
   788 
       
   789 #endif // QT_NO_NETWORKDISKCACHE