1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtNetwork module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qnetworkaccesscache_p.h"
41#include "QtCore/qpointer.h"
42#include "QtCore/qdatetime.h"
43#include "qnetworkaccessmanager_p.h"
44#include "qnetworkreply_p.h"
45#include "qnetworkrequest.h"
46
47#include <vector>
48
49QT_BEGIN_NAMESPACE
50
51enum ExpiryTimeEnum {
52 ExpiryTime = 120
53};
54
55namespace {
56 struct Receiver
57 {
58 QPointer<QObject> object;
59 const char *member;
60 };
61}
62
63// idea copied from qcache.h
64struct QNetworkAccessCache::Node
65{
66 QDateTime timestamp;
67 std::vector<Receiver> receiverQueue;
68 QByteArray key;
69
70 Node *older, *newer;
71 CacheableObject *object;
72
73 int useCount;
74
75 Node()
76 : older(nullptr), newer(nullptr), object(nullptr), useCount(0)
77 { }
78};
79
80QNetworkAccessCache::CacheableObject::CacheableObject()
81{
82 // leave the members uninitialized
83 // they must be initialized by the derived class's constructor
84}
85
86QNetworkAccessCache::CacheableObject::~CacheableObject()
87{
88#if 0 //def QT_DEBUG
89 if (!key.isEmpty() && Ptr()->hasEntry(key))
90 qWarning() << "QNetworkAccessCache: object" << (void*)this << "key" << key
91 << "destroyed without being removed from cache first!";
92#endif
93}
94
95void QNetworkAccessCache::CacheableObject::setExpires(bool enable)
96{
97 expires = enable;
98}
99
100void QNetworkAccessCache::CacheableObject::setShareable(bool enable)
101{
102 shareable = enable;
103}
104
105QNetworkAccessCache::QNetworkAccessCache()
106 : oldest(nullptr), newest(nullptr)
107{
108}
109
110QNetworkAccessCache::~QNetworkAccessCache()
111{
112 clear();
113}
114
115void QNetworkAccessCache::clear()
116{
117 NodeHash hashCopy = hash;
118 hash.clear();
119
120 // remove all entries
121 NodeHash::Iterator it = hashCopy.begin();
122 NodeHash::Iterator end = hashCopy.end();
123 for ( ; it != end; ++it) {
124 it->object->key.clear();
125 it->object->dispose();
126 }
127
128 // now delete:
129 hashCopy.clear();
130
131 timer.stop();
132
133 oldest = newest = nullptr;
134}
135
136/*!
137 Appends the entry given by \a key to the end of the linked list.
138 (i.e., makes it the newest entry)
139 */
140void QNetworkAccessCache::linkEntry(const QByteArray &key)
141{
142 NodeHash::Iterator it = hash.find(akey: key);
143 if (it == hash.end())
144 return;
145
146 Node *const node = &it.value();
147 Q_ASSERT(node != oldest && node != newest);
148 Q_ASSERT(node->older == nullptr && node->newer == nullptr);
149 Q_ASSERT(node->useCount == 0);
150
151 if (newest) {
152 Q_ASSERT(newest->newer == nullptr);
153 newest->newer = node;
154 node->older = newest;
155 }
156 if (!oldest) {
157 // there are no entries, so this is the oldest one too
158 oldest = node;
159 }
160
161 node->timestamp = QDateTime::currentDateTimeUtc().addSecs(secs: ExpiryTime);
162 newest = node;
163}
164
165/*!
166 Removes the entry pointed by \a key from the linked list.
167 Returns \c true if the entry removed was the oldest one.
168 */
169bool QNetworkAccessCache::unlinkEntry(const QByteArray &key)
170{
171 NodeHash::Iterator it = hash.find(akey: key);
172 if (it == hash.end())
173 return false;
174
175 Node *const node = &it.value();
176
177 bool wasOldest = false;
178 if (node == oldest) {
179 oldest = node->newer;
180 wasOldest = true;
181 }
182 if (node == newest)
183 newest = node->older;
184 if (node->older)
185 node->older->newer = node->newer;
186 if (node->newer)
187 node->newer->older = node->older;
188
189 node->newer = node->older = nullptr;
190 return wasOldest;
191}
192
193void QNetworkAccessCache::updateTimer()
194{
195 timer.stop();
196
197 if (!oldest)
198 return;
199
200 int interval = QDateTime::currentDateTimeUtc().secsTo(oldest->timestamp);
201 if (interval <= 0) {
202 interval = 0;
203 } else {
204 // round up the interval
205 interval = (interval + 15) & ~16;
206 }
207
208 timer.start(msec: interval * 1000, obj: this);
209}
210
211bool QNetworkAccessCache::emitEntryReady(Node *node, QObject *target, const char *member)
212{
213 if (!connect(sender: this, SIGNAL(entryReady(QNetworkAccessCache::CacheableObject*)),
214 receiver: target, member, Qt::QueuedConnection))
215 return false;
216
217 emit entryReady(node->object);
218 disconnect(SIGNAL(entryReady(QNetworkAccessCache::CacheableObject*)));
219
220 return true;
221}
222
223void QNetworkAccessCache::timerEvent(QTimerEvent *)
224{
225 // expire old items
226 const QDateTime now = QDateTime::currentDateTimeUtc();
227
228 while (oldest && oldest->timestamp < now) {
229 Node *next = oldest->newer;
230 oldest->object->dispose();
231
232 hash.remove(akey: oldest->key); // oldest gets deleted
233 oldest = next;
234 }
235
236 // fixup the list
237 if (oldest)
238 oldest->older = nullptr;
239 else
240 newest = nullptr;
241
242 updateTimer();
243}
244
245void QNetworkAccessCache::addEntry(const QByteArray &key, CacheableObject *entry)
246{
247 Q_ASSERT(!key.isEmpty());
248
249 if (unlinkEntry(key))
250 updateTimer();
251
252 Node &node = hash[key]; // create the entry in the hash if it didn't exist
253 if (node.useCount)
254 qWarning(msg: "QNetworkAccessCache::addEntry: overriding active cache entry '%s'",
255 key.constData());
256 if (node.object)
257 node.object->dispose();
258 node.object = entry;
259 node.object->key = key;
260 node.key = key;
261 node.useCount = 1;
262}
263
264bool QNetworkAccessCache::hasEntry(const QByteArray &key) const
265{
266 return hash.contains(akey: key);
267}
268
269bool QNetworkAccessCache::requestEntry(const QByteArray &key, QObject *target, const char *member)
270{
271 NodeHash::Iterator it = hash.find(akey: key);
272 if (it == hash.end())
273 return false; // no such entry
274
275 Node *node = &it.value();
276
277 if (node->useCount > 0 && !node->object->shareable) {
278 // object is not shareable and is in use
279 // queue for later use
280 Q_ASSERT(node->older == nullptr && node->newer == nullptr);
281 node->receiverQueue.push_back(x: {.object: target, .member: member});
282
283 // request queued
284 return true;
285 } else {
286 // node not in use or is shareable
287 if (unlinkEntry(key))
288 updateTimer();
289
290 ++node->useCount;
291 return emitEntryReady(node, target, member);
292 }
293}
294
295QNetworkAccessCache::CacheableObject *QNetworkAccessCache::requestEntryNow(const QByteArray &key)
296{
297 NodeHash::Iterator it = hash.find(akey: key);
298 if (it == hash.end())
299 return nullptr;
300 if (it->useCount > 0) {
301 if (it->object->shareable) {
302 ++it->useCount;
303 return it->object;
304 }
305
306 // object in use and not shareable
307 return nullptr;
308 }
309
310 // entry not in use, let the caller have it
311 bool wasOldest = unlinkEntry(key);
312 ++it->useCount;
313
314 if (wasOldest)
315 updateTimer();
316 return it->object;
317}
318
319void QNetworkAccessCache::releaseEntry(const QByteArray &key)
320{
321 NodeHash::Iterator it = hash.find(akey: key);
322 if (it == hash.end()) {
323 qWarning(msg: "QNetworkAccessCache::releaseEntry: trying to release key '%s' that is not in cache",
324 key.constData());
325 return;
326 }
327
328 Node *node = &it.value();
329 Q_ASSERT(node->useCount > 0);
330
331 // are there other objects waiting?
332 const auto objectStillExists = [](const Receiver &r) { return !r.object.isNull(); };
333
334 auto &queue = node->receiverQueue;
335 auto qit = std::find_if(first: queue.begin(), last: queue.end(), pred: objectStillExists);
336
337 const Receiver receiver = qit == queue.end() ? Receiver{} : std::move(*qit++) ;
338
339 queue.erase(first: queue.begin(), last: qit);
340
341 if (receiver.object) {
342 // queue another activation
343 emitEntryReady(node, target: receiver.object, member: receiver.member);
344 return;
345 }
346
347 if (!--node->useCount) {
348 // no objects waiting; add it back to the expiry list
349 if (node->object->expires)
350 linkEntry(key);
351
352 if (oldest == node)
353 updateTimer();
354 }
355}
356
357void QNetworkAccessCache::removeEntry(const QByteArray &key)
358{
359 NodeHash::Iterator it = hash.find(akey: key);
360 if (it == hash.end()) {
361 qWarning(msg: "QNetworkAccessCache::removeEntry: trying to remove key '%s' that is not in cache",
362 key.constData());
363 return;
364 }
365
366 Node *node = &it.value();
367 if (unlinkEntry(key))
368 updateTimer();
369 if (node->useCount > 1)
370 qWarning(msg: "QNetworkAccessCache::removeEntry: removing active cache entry '%s'",
371 key.constData());
372
373 node->object->key.clear();
374 hash.remove(akey: node->key);
375}
376
377QT_END_NAMESPACE
378

source code of qtbase/src/network/access/qnetworkaccesscache.cpp