summaryrefslogtreecommitdiffstats
path: root/src/network/access/qnetworkaccesscache.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/network/access/qnetworkaccesscache.cpp')
-rw-r--r--src/network/access/qnetworkaccesscache.cpp379
1 files changed, 379 insertions, 0 deletions
diff --git a/src/network/access/qnetworkaccesscache.cpp b/src/network/access/qnetworkaccesscache.cpp
new file mode 100644
index 0000000000..fd16591c3e
--- /dev/null
+++ b/src/network/access/qnetworkaccesscache.cpp
@@ -0,0 +1,379 @@
+/****************************************************************************
+**
+** 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$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $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