1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qnetworkaccesscache_p.h"
5#include "QtCore/qpointer.h"
6#include "QtCore/qdeadlinetimer.h"
7#include "qnetworkaccessmanager_p.h"
8#include "qnetworkreply_p.h"
9#include "qnetworkrequest.h"
10
11#include <vector>
12
13//#define DEBUG_ACCESSCACHE
14
15QT_BEGIN_NAMESPACE
16
17enum ExpiryTimeEnum {
18 ExpiryTime = 120
19};
20
21// idea copied from qcache.h
22struct QNetworkAccessCache::Node
23{
24 QDeadlineTimer timer;
25 QByteArray key;
26
27 Node *previous = nullptr; // "previous" nodes expire "previous"ly (before us)
28 Node *next = nullptr; // "next" nodes expire "next" (after us)
29 CacheableObject *object = nullptr;
30
31 int useCount = 0;
32};
33
34QNetworkAccessCache::CacheableObject::CacheableObject(Options options)
35 : expires(options & Option::Expires),
36 shareable(options & Option::Shareable)
37{
38
39}
40
41QNetworkAccessCache::CacheableObject::~CacheableObject()
42{
43#if 0 //def QT_DEBUG
44 if (!key.isEmpty() && Ptr()->hasEntry(key))
45 qWarning() << "QNetworkAccessCache: object" << (void*)this << "key" << key
46 << "destroyed without being removed from cache first!";
47#endif
48}
49
50QNetworkAccessCache::~QNetworkAccessCache()
51{
52 clear();
53}
54
55void QNetworkAccessCache::clear()
56{
57 NodeHash hashCopy = hash;
58 hash.clear();
59
60 // remove all entries
61 NodeHash::Iterator it = hashCopy.begin();
62 NodeHash::Iterator end = hashCopy.end();
63 for ( ; it != end; ++it) {
64 (*it)->object->key.clear();
65 (*it)->object->dispose();
66 delete (*it);
67 }
68
69 // now delete:
70 hashCopy.clear();
71
72 timer.stop();
73
74 firstExpiringNode = lastExpiringNode = nullptr;
75}
76
77/*!
78 Appends the entry given by \a key to the end of the linked list.
79 (i.e., makes it the newest entry)
80 */
81void QNetworkAccessCache::linkEntry(const QByteArray &key)
82{
83 Node * const node = hash.value(key);
84 if (!node)
85 return;
86
87 Q_ASSERT(node != firstExpiringNode && node != lastExpiringNode);
88 Q_ASSERT(node->previous == nullptr && node->next == nullptr);
89 Q_ASSERT(node->useCount == 0);
90
91
92 node->timer.setPreciseRemainingTime(secs: node->object->expiryTimeoutSeconds);
93#ifdef DEBUG_ACCESSCACHE
94 qDebug() << "QNetworkAccessCache case trying to insert=" << QString::fromUtf8(key)
95 << node->timer.remainingTime() << "milliseconds";
96 Node *current = lastExpiringNode;
97 while (current) {
98 qDebug() << "QNetworkAccessCache item=" << QString::fromUtf8(current->key)
99 << current->timer.remainingTime() << "milliseconds"
100 << (current == lastExpiringNode ? "[last to expire]" : "")
101 << (current == firstExpiringNode ? "[first to expire]" : "");
102 current = current->previous;
103 }
104#endif
105
106 if (lastExpiringNode) {
107 Q_ASSERT(lastExpiringNode->next == nullptr);
108 if (lastExpiringNode->timer < node->timer) {
109 // Insert as new last-to-expire node.
110 node->previous = lastExpiringNode;
111 lastExpiringNode->next = node;
112 lastExpiringNode = node;
113 } else {
114 // Insert in a sorted way, as different nodes might have had different expiryTimeoutSeconds set.
115 Node *current = lastExpiringNode;
116 while (current->previous != nullptr && current->previous->timer >= node->timer)
117 current = current->previous;
118 node->previous = current->previous;
119 if (node->previous)
120 node->previous->next = node;
121 node->next = current;
122 current->previous = node;
123 if (node->previous == nullptr)
124 firstExpiringNode = node;
125 }
126 } else {
127 // no current last-to-expire node
128 lastExpiringNode = node;
129 }
130 if (!firstExpiringNode) {
131 // there are no entries, so this is the next-to-expire too
132 firstExpiringNode = node;
133 }
134 Q_ASSERT(firstExpiringNode->previous == nullptr);
135 Q_ASSERT(lastExpiringNode->next == nullptr);
136}
137
138/*!
139 Removes the entry pointed by \a key from the linked list.
140 Returns \c true if the entry removed was the next to expire.
141 */
142bool QNetworkAccessCache::unlinkEntry(const QByteArray &key)
143{
144 Node * const node = hash.value(key);
145 if (!node)
146 return false;
147
148 bool wasFirst = false;
149 if (node == firstExpiringNode) {
150 firstExpiringNode = node->next;
151 wasFirst = true;
152 }
153 if (node == lastExpiringNode)
154 lastExpiringNode = node->previous;
155 if (node->previous)
156 node->previous->next = node->next;
157 if (node->next)
158 node->next->previous = node->previous;
159
160 node->next = node->previous = nullptr;
161 return wasFirst;
162}
163
164void QNetworkAccessCache::updateTimer()
165{
166 timer.stop();
167
168 if (!firstExpiringNode)
169 return;
170
171 qint64 interval = firstExpiringNode->timer.remainingTime();
172 if (interval <= 0) {
173 interval = 0;
174 }
175
176 // Plus 10 msec so we don't spam timer events if date comparisons are too fuzzy.
177 // This code used to do (broken) rounding, but for ConnectionCacheExpiryTimeoutSecondsAttribute
178 // to work we cannot do this.
179 // See discussion in https://codereview.qt-project.org/c/qt/qtbase/+/337464
180 timer.start(msec: interval + 10, obj: this);
181}
182
183bool QNetworkAccessCache::emitEntryReady(Node *node, QObject *target, const char *member)
184{
185 if (!connect(sender: this, SIGNAL(entryReady(QNetworkAccessCache::CacheableObject*)),
186 receiver: target, member, Qt::QueuedConnection))
187 return false;
188
189 emit entryReady(node->object);
190 disconnect(SIGNAL(entryReady(QNetworkAccessCache::CacheableObject*)));
191
192 return true;
193}
194
195void QNetworkAccessCache::timerEvent(QTimerEvent *)
196{
197 while (firstExpiringNode && firstExpiringNode->timer.hasExpired()) {
198 Node *next = firstExpiringNode->next;
199 firstExpiringNode->object->dispose();
200 hash.remove(key: firstExpiringNode->key); // `firstExpiringNode` gets deleted
201 delete firstExpiringNode;
202 firstExpiringNode = next;
203 }
204
205 // fixup the list
206 if (firstExpiringNode)
207 firstExpiringNode->previous = nullptr;
208 else
209 lastExpiringNode = nullptr;
210
211 updateTimer();
212}
213
214void QNetworkAccessCache::addEntry(const QByteArray &key, CacheableObject *entry, qint64 connectionCacheExpiryTimeoutSeconds)
215{
216 Q_ASSERT(!key.isEmpty());
217
218 if (unlinkEntry(key))
219 updateTimer();
220
221 Node *node = hash.value(key);
222 if (!node) {
223 node = new Node;
224 hash.insert(key, value: node);
225 }
226
227 if (node->useCount)
228 qWarning(msg: "QNetworkAccessCache::addEntry: overriding active cache entry '%s'", key.constData());
229 if (node->object)
230 node->object->dispose();
231 node->object = entry;
232 node->object->key = key;
233 if (connectionCacheExpiryTimeoutSeconds > -1) {
234 node->object->expiryTimeoutSeconds = connectionCacheExpiryTimeoutSeconds; // via ConnectionCacheExpiryTimeoutSecondsAttribute
235 } else {
236 node->object->expiryTimeoutSeconds = ExpiryTime;
237 }
238 node->key = key;
239 node->useCount = 1;
240
241 // It gets only put into the expiry list in linkEntry (from releaseEntry), when it is not used anymore.
242}
243
244bool QNetworkAccessCache::hasEntry(const QByteArray &key) const
245{
246 return hash.contains(key);
247}
248
249QNetworkAccessCache::CacheableObject *QNetworkAccessCache::requestEntryNow(const QByteArray &key)
250{
251 Node *node = hash.value(key);
252 if (!node)
253 return nullptr;
254
255 if (node->useCount > 0) {
256 if (node->object->shareable) {
257 ++node->useCount;
258 return node->object;
259 }
260
261 // object in use and not shareable
262 return nullptr;
263 }
264
265 // entry not in use, let the caller have it
266 bool wasNext = unlinkEntry(key);
267 ++node->useCount;
268
269 if (wasNext)
270 updateTimer();
271 return node->object;
272}
273
274void QNetworkAccessCache::releaseEntry(const QByteArray &key)
275{
276 Node *node = hash.value(key);
277 if (!node) {
278 qWarning(msg: "QNetworkAccessCache::releaseEntry: trying to release key '%s' that is not in cache", key.constData());
279 return;
280 }
281
282 Q_ASSERT(node->useCount > 0);
283
284 if (!--node->useCount) {
285 // no objects waiting; add it back to the expiry list
286 if (node->object->expires)
287 linkEntry(key);
288
289 if (firstExpiringNode == node)
290 updateTimer();
291 }
292}
293
294void QNetworkAccessCache::removeEntry(const QByteArray &key)
295{
296 Node *node = hash.value(key);
297 if (!node) {
298 qWarning(msg: "QNetworkAccessCache::removeEntry: trying to remove key '%s' that is not in cache", key.constData());
299 return;
300 }
301
302 if (unlinkEntry(key))
303 updateTimer();
304 if (node->useCount > 1)
305 qWarning(msg: "QNetworkAccessCache::removeEntry: removing active cache entry '%s'",
306 key.constData());
307
308 node->object->key.clear();
309 hash.remove(key: node->key);
310 delete node;
311}
312
313QT_END_NAMESPACE
314
315#include "moc_qnetworkaccesscache_p.cpp"
316

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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