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 | |
15 | QT_BEGIN_NAMESPACE |
16 | |
17 | enum ExpiryTimeEnum { |
18 | ExpiryTime = 120 |
19 | }; |
20 | |
21 | // idea copied from qcache.h |
22 | struct 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 | |
34 | QNetworkAccessCache::CacheableObject::CacheableObject(Options options) |
35 | : expires(options & Option::Expires), |
36 | shareable(options & Option::Shareable) |
37 | { |
38 | |
39 | } |
40 | |
41 | QNetworkAccessCache::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 | |
50 | QNetworkAccessCache::~QNetworkAccessCache() |
51 | { |
52 | clear(); |
53 | } |
54 | |
55 | void 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 | */ |
81 | void 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 | */ |
142 | bool 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 | |
164 | void 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 | |
183 | bool 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 | |
195 | void 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 | |
214 | void 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 | |
244 | bool QNetworkAccessCache::hasEntry(const QByteArray &key) const |
245 | { |
246 | return hash.contains(key); |
247 | } |
248 | |
249 | QNetworkAccessCache::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 | |
274 | void 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 | |
294 | void 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 | |
313 | QT_END_NAMESPACE |
314 | |
315 | #include "moc_qnetworkaccesscache_p.cpp" |
316 | |