/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtNetwork module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qnetworkaccesscache_p.h" #include "QtCore/qpointer.h" #include "QtCore/qdatetime.h" #include "qnetworkaccessmanager_p.h" #include "qnetworkreply_p.h" #include "qnetworkrequest.h" #include 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; std::vector 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; } /*! Appends the entry given by \a 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::currentDateTimeUtc().addSecs(ExpiryTime); newest = node; } /*! Removes the entry pointed by \a key from the linked list. Returns \c 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::currentDateTimeUtc().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 const QDateTime now = QDateTime::currentDateTimeUtc(); 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); node->receiverQueue.push_back({target, member}); // 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? const auto objectStillExists = [](const Receiver &r) { return !r.object.isNull(); }; auto &queue = node->receiverQueue; auto qit = std::find_if(queue.begin(), queue.end(), objectStillExists); const Receiver receiver = qit == queue.end() ? Receiver{} : std::move(*qit++) ; queue.erase(queue.begin(), qit); if (receiver.object) { // queue another activation 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