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
49 QT_BEGIN_NAMESPACE
50
51 enum ExpiryTimeEnum {
52 ExpiryTime = 120
53 };
54
55 namespace {
56 struct Receiver
57 {
58 QPointer<QObject> object;
59 const char *member;
60 };
61 }
62
63 // idea copied from qcache.h
64 struct 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
NodeQNetworkAccessCache::Node75 Node()
76 : older(nullptr), newer(nullptr), object(nullptr), useCount(0)
77 { }
78 };
79
CacheableObject()80 QNetworkAccessCache::CacheableObject::CacheableObject()
81 {
82 // leave the members uninitialized
83 // they must be initialized by the derived class's constructor
84 }
85
~CacheableObject()86 QNetworkAccessCache::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
setExpires(bool enable)95 void QNetworkAccessCache::CacheableObject::setExpires(bool enable)
96 {
97 expires = enable;
98 }
99
setShareable(bool enable)100 void QNetworkAccessCache::CacheableObject::setShareable(bool enable)
101 {
102 shareable = enable;
103 }
104
QNetworkAccessCache()105 QNetworkAccessCache::QNetworkAccessCache()
106 : oldest(nullptr), newest(nullptr)
107 {
108 }
109
~QNetworkAccessCache()110 QNetworkAccessCache::~QNetworkAccessCache()
111 {
112 clear();
113 }
114
clear()115 void 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 */
linkEntry(const QByteArray & key)140 void QNetworkAccessCache::linkEntry(const QByteArray &key)
141 {
142 NodeHash::Iterator it = hash.find(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(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 */
unlinkEntry(const QByteArray & key)169 bool QNetworkAccessCache::unlinkEntry(const QByteArray &key)
170 {
171 NodeHash::Iterator it = hash.find(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
updateTimer()193 void 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(interval * 1000, this);
209 }
210
emitEntryReady(Node * node,QObject * target,const char * member)211 bool QNetworkAccessCache::emitEntryReady(Node *node, QObject *target, const char *member)
212 {
213 if (!connect(this, SIGNAL(entryReady(QNetworkAccessCache::CacheableObject*)),
214 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
timerEvent(QTimerEvent *)223 void 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(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
addEntry(const QByteArray & key,CacheableObject * entry)245 void 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("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
hasEntry(const QByteArray & key) const264 bool QNetworkAccessCache::hasEntry(const QByteArray &key) const
265 {
266 return hash.contains(key);
267 }
268
requestEntry(const QByteArray & key,QObject * target,const char * member)269 bool QNetworkAccessCache::requestEntry(const QByteArray &key, QObject *target, const char *member)
270 {
271 NodeHash::Iterator it = hash.find(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({target, 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
requestEntryNow(const QByteArray & key)295 QNetworkAccessCache::CacheableObject *QNetworkAccessCache::requestEntryNow(const QByteArray &key)
296 {
297 NodeHash::Iterator it = hash.find(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
releaseEntry(const QByteArray & key)319 void QNetworkAccessCache::releaseEntry(const QByteArray &key)
320 {
321 NodeHash::Iterator it = hash.find(key);
322 if (it == hash.end()) {
323 qWarning("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(queue.begin(), queue.end(), objectStillExists);
336
337 const Receiver receiver = qit == queue.end() ? Receiver{} : std::move(*qit++) ;
338
339 queue.erase(queue.begin(), qit);
340
341 if (receiver.object) {
342 // queue another activation
343 emitEntryReady(node, receiver.object, 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
removeEntry(const QByteArray & key)357 void QNetworkAccessCache::removeEntry(const QByteArray &key)
358 {
359 NodeHash::Iterator it = hash.find(key);
360 if (it == hash.end()) {
361 qWarning("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("QNetworkAccessCache::removeEntry: removing active cache entry '%s'",
371 key.constData());
372
373 node->object->key.clear();
374 hash.remove(node->key);
375 }
376
377 QT_END_NAMESPACE
378