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