/**************************************************************************** ** ** Copyright (C) 2012 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 object; const char *member; }; } // idea copied from qcache.h struct QNetworkAccessCache::Node { QDateTime timestamp; QQueue 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