/**************************************************************************** | |
** | |
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). | |
** All rights reserved. | |
** Contact: Nokia Corporation (qt-info@nokia.com) | |
** | |
** This file is part of the QtNetwork module of the Qt Toolkit. | |
** | |
** $QT_BEGIN_LICENSE:LGPL$ | |
** GNU Lesser General Public License Usage | |
** This file may be used under the terms of the GNU Lesser General Public | |
** License version 2.1 as published by the Free Software Foundation and | |
** appearing in the file LICENSE.LGPL included in the packaging of this | |
** file. Please review the following information to ensure the GNU Lesser | |
** General Public License version 2.1 requirements will be met: | |
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. | |
** | |
** In addition, as a special exception, Nokia gives you certain additional | |
** rights. These rights are described in the Nokia Qt LGPL Exception | |
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. | |
** | |
** GNU General Public License Usage | |
** Alternatively, this file may be used under the terms of the GNU General | |
** Public License version 3.0 as published by the Free Software Foundation | |
** and appearing in the file LICENSE.GPL included in the packaging of this | |
** file. Please review the following information to ensure the GNU General | |
** Public License version 3.0 requirements will be met: | |
** http://www.gnu.org/copyleft/gpl.html. | |
** | |
** Other Usage | |
** Alternatively, this file may be used in accordance with the terms and | |
** conditions contained in a signed written agreement between you and Nokia. | |
** | |
** | |
** | |
** | |
** | |
** $QT_END_LICENSE$ | |
** | |
****************************************************************************/ | |
#include "qnetworkaccesscache_p.h" | |
#include "QtCore/qpointer.h" | |
#include "QtCore/qdatetime.h" | |
#include "QtCore/qqueue.h" | |
#include "qnetworkaccessmanager_p.h" | |
#include "qnetworkreply_p.h" | |
#include "qnetworkrequest.h" | |
QT_BEGIN_NAMESPACE | |
enum ExpiryTimeEnum { | |
ExpiryTime = 120 | |
}; | |
namespace { | |
struct Receiver | |
{ | |
QPointer<QObject> object; | |
const char *member; | |
}; | |
} | |
// idea copied from qcache.h | |
struct QNetworkAccessCache::Node | |
{ | |
QDateTime timestamp; | |
QQueue<Receiver> receiverQueue; | |
QByteArray key; | |
Node *older, *newer; | |
CacheableObject *object; | |
int useCount; | |
Node() | |
: older(0), newer(0), object(0), useCount(0) | |
{ } | |
}; | |
QNetworkAccessCache::CacheableObject::CacheableObject() | |
{ | |
// leave the members uninitialized | |
// they must be initialized by the derived class's constructor | |
} | |
QNetworkAccessCache::CacheableObject::~CacheableObject() | |
{ | |
#if 0 //def QT_DEBUG | |
if (!key.isEmpty() && Ptr()->hasEntry(key)) | |
qWarning() << "QNetworkAccessCache: object" << (void*)this << "key" << key | |
<< "destroyed without being removed from cache first!"; | |
#endif | |
} | |
void QNetworkAccessCache::CacheableObject::setExpires(bool enable) | |
{ | |
expires = enable; | |
} | |
void QNetworkAccessCache::CacheableObject::setShareable(bool enable) | |
{ | |
shareable = enable; | |
} | |
QNetworkAccessCache::QNetworkAccessCache() | |
: oldest(0), newest(0) | |
{ | |
} | |
QNetworkAccessCache::~QNetworkAccessCache() | |
{ | |
clear(); | |
} | |
void QNetworkAccessCache::clear() | |
{ | |
NodeHash hashCopy = hash; | |
hash.clear(); | |
// remove all entries | |
NodeHash::Iterator it = hashCopy.begin(); | |
NodeHash::Iterator end = hashCopy.end(); | |
for ( ; it != end; ++it) { | |
it->object->key.clear(); | |
it->object->dispose(); | |
} | |
// now delete: | |
hashCopy.clear(); | |
timer.stop(); | |
oldest = newest = 0; | |
} | |
/*! | |
Appens the entry given by @p key to the end of the linked list. | |
(i.e., makes it the newest entry) | |
*/ | |
void QNetworkAccessCache::linkEntry(const QByteArray &key) | |
{ | |
NodeHash::Iterator it = hash.find(key); | |
if (it == hash.end()) | |
return; | |
Node *const node = &it.value(); | |
Q_ASSERT(node != oldest && node != newest); | |
Q_ASSERT(node->older == 0 && node->newer == 0); | |
Q_ASSERT(node->useCount == 0); | |
if (newest) { | |
Q_ASSERT(newest->newer == 0); | |
newest->newer = node; | |
node->older = newest; | |
} | |
if (!oldest) { | |
// there are no entries, so this is the oldest one too | |
oldest = node; | |
} | |
node->timestamp = QDateTime::currentDateTime().addSecs(ExpiryTime); | |
newest = node; | |
} | |
/*! | |
Removes the entry pointed by @p key from the linked list. | |
Returns true if the entry removed was the oldest one. | |
*/ | |
bool QNetworkAccessCache::unlinkEntry(const QByteArray &key) | |
{ | |
NodeHash::Iterator it = hash.find(key); | |
if (it == hash.end()) | |
return false; | |
Node *const node = &it.value(); | |
bool wasOldest = false; | |
if (node == oldest) { | |
oldest = node->newer; | |
wasOldest = true; | |
} | |
if (node == newest) | |
newest = node->older; | |
if (node->older) | |
node->older->newer = node->newer; | |
if (node->newer) | |
node->newer->older = node->older; | |
node->newer = node->older = 0; | |
return wasOldest; | |
} | |
void QNetworkAccessCache::updateTimer() | |
{ | |
timer.stop(); | |
if (!oldest) | |
return; | |
int interval = QDateTime::currentDateTime().secsTo(oldest->timestamp); | |
if (interval <= 0) { | |
interval = 0; | |
} else { | |
// round up the interval | |
interval = (interval + 15) & ~16; | |
} | |
timer.start(interval * 1000, this); | |
} | |
bool QNetworkAccessCache::emitEntryReady(Node *node, QObject *target, const char *member) | |
{ | |
if (!connect(this, SIGNAL(entryReady(QNetworkAccessCache::CacheableObject*)), | |
target, member, Qt::QueuedConnection)) | |
return false; | |
emit entryReady(node->object); | |
disconnect(SIGNAL(entryReady(QNetworkAccessCache::CacheableObject*))); | |
return true; | |
} | |
void QNetworkAccessCache::timerEvent(QTimerEvent *) | |
{ | |
// expire old items | |
QDateTime now = QDateTime::currentDateTime(); | |
while (oldest && oldest->timestamp < now) { | |
Node *next = oldest->newer; | |
oldest->object->dispose(); | |
hash.remove(oldest->key); // oldest gets deleted | |
oldest = next; | |
} | |
// fixup the list | |
if (oldest) | |
oldest->older = 0; | |
else | |
newest = 0; | |
updateTimer(); | |
} | |
void QNetworkAccessCache::addEntry(const QByteArray &key, CacheableObject *entry) | |
{ | |
Q_ASSERT(!key.isEmpty()); | |
if (unlinkEntry(key)) | |
updateTimer(); | |
Node &node = hash[key]; // create the entry in the hash if it didn't exist | |
if (node.useCount) | |
qWarning("QNetworkAccessCache::addEntry: overriding active cache entry '%s'", | |
key.constData()); | |
if (node.object) | |
node.object->dispose(); | |
node.object = entry; | |
node.object->key = key; | |
node.key = key; | |
node.useCount = 1; | |
} | |
bool QNetworkAccessCache::hasEntry(const QByteArray &key) const | |
{ | |
return hash.contains(key); | |
} | |
bool QNetworkAccessCache::requestEntry(const QByteArray &key, QObject *target, const char *member) | |
{ | |
NodeHash::Iterator it = hash.find(key); | |
if (it == hash.end()) | |
return false; // no such entry | |
Node *node = &it.value(); | |
if (node->useCount > 0 && !node->object->shareable) { | |
// object is not shareable and is in use | |
// queue for later use | |
Q_ASSERT(node->older == 0 && node->newer == 0); | |
Receiver receiver; | |
receiver.object = target; | |
receiver.member = member; | |
node->receiverQueue.enqueue(receiver); | |
// request queued | |
return true; | |
} else { | |
// node not in use or is shareable | |
if (unlinkEntry(key)) | |
updateTimer(); | |
++node->useCount; | |
return emitEntryReady(node, target, member); | |
} | |
} | |
QNetworkAccessCache::CacheableObject *QNetworkAccessCache::requestEntryNow(const QByteArray &key) | |
{ | |
NodeHash::Iterator it = hash.find(key); | |
if (it == hash.end()) | |
return 0; | |
if (it->useCount > 0) { | |
if (it->object->shareable) { | |
++it->useCount; | |
return it->object; | |
} | |
// object in use and not shareable | |
return 0; | |
} | |
// entry not in use, let the caller have it | |
bool wasOldest = unlinkEntry(key); | |
++it->useCount; | |
if (wasOldest) | |
updateTimer(); | |
return it->object; | |
} | |
void QNetworkAccessCache::releaseEntry(const QByteArray &key) | |
{ | |
NodeHash::Iterator it = hash.find(key); | |
if (it == hash.end()) { | |
qWarning("QNetworkAccessCache::releaseEntry: trying to release key '%s' that is not in cache", | |
key.constData()); | |
return; | |
} | |
Node *node = &it.value(); | |
Q_ASSERT(node->useCount > 0); | |
// are there other objects waiting? | |
if (!node->receiverQueue.isEmpty()) { | |
// queue another activation | |
Receiver receiver; | |
do { | |
receiver = node->receiverQueue.dequeue(); | |
} while (receiver.object.isNull() && !node->receiverQueue.isEmpty()); | |
if (!receiver.object.isNull()) { | |
emitEntryReady(node, receiver.object, receiver.member); | |
return; | |
} | |
} | |
if (!--node->useCount) { | |
// no objects waiting; add it back to the expiry list | |
if (node->object->expires) | |
linkEntry(key); | |
if (oldest == node) | |
updateTimer(); | |
} | |
} | |
void QNetworkAccessCache::removeEntry(const QByteArray &key) | |
{ | |
NodeHash::Iterator it = hash.find(key); | |
if (it == hash.end()) { | |
qWarning("QNetworkAccessCache::removeEntry: trying to remove key '%s' that is not in cache", | |
key.constData()); | |
return; | |
} | |
Node *node = &it.value(); | |
if (unlinkEntry(key)) | |
updateTimer(); | |
if (node->useCount > 1) | |
qWarning("QNetworkAccessCache::removeEntry: removing active cache entry '%s'", | |
key.constData()); | |
node->object->key.clear(); | |
hash.remove(node->key); | |
} | |
QT_END_NAMESPACE |