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 delete (*it);
127 }
128
129 // now delete:
130 hashCopy.clear();
131
132 timer.stop();
133
134 oldest = newest = nullptr;
135}
136
137/*!
138 Appends the entry given by \a key to the end of the linked list.
139 (i.e., makes it the newest entry)
140 */
141void QNetworkAccessCache::linkEntry(const QByteArray &key)
142{
143 Node * const node = hash.value(key);
144 if (!node)
145 return;
146
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 */
169bool QNetworkAccessCache::unlinkEntry(const QByteArray &key)
170{
171 Node * const node = hash.value(key);
172 if (!node)
173 return false;
174
175 bool wasOldest = false;
176 if (node == oldest) {
177 oldest = node->newer;
178 wasOldest = true;
179 }
180 if (node == newest)
181 newest = node->older;
182 if (node->older)
183 node->older->newer = node->newer;
184 if (node->newer)
185 node->newer->older = node->older;
186
187 node->newer = node->older = nullptr;
188 return wasOldest;
189}
190
191void QNetworkAccessCache::updateTimer()
192{
193 timer.stop();
194
195 if (!oldest)
196 return;
197
198 int interval = QDateTime::currentDateTimeUtc().secsTo(oldest->timestamp);
199 if (interval <= 0) {
200 interval = 0;
201 } else {
202 // round up the interval
203 interval = (interval + 15) & ~16;
204 }
205
206 timer.start(interval * 1000, this);
207}
208
209bool QNetworkAccessCache::emitEntryReady(Node *node, QObject *target, const char *member)
210{
211 if (!connect(this, SIGNAL(entryReady(QNetworkAccessCache::CacheableObject*)),
212 target, member, Qt::QueuedConnection))
213 return false;
214
215 emit entryReady(node->object);
216 disconnect(SIGNAL(entryReady(QNetworkAccessCache::CacheableObject*)));
217
218 return true;
219}
220
221void QNetworkAccessCache::timerEvent(QTimerEvent *)
222{
223 // expire old items
224 const QDateTime now = QDateTime::currentDateTimeUtc();
225
226 while (oldest && oldest->timestamp < now) {
227 Node *next = oldest->newer;
228 oldest->object->dispose();
229
230 hash.remove(oldest->key); // oldest gets deleted
231 delete oldest;
232 oldest = next;
233 }
234
235 // fixup the list
236 if (oldest)
237 oldest->older = nullptr;
238 else
239 newest = nullptr;
240
241 updateTimer();
242}
243
244void QNetworkAccessCache::addEntry(const QByteArray &key, CacheableObject *entry)
245{
246 Q_ASSERT(!key.isEmpty());
247
248 if (unlinkEntry(key))
249 updateTimer();
250
251 Node *node = hash.value(key);
252 if (!node) {
253 node = new Node;
254 hash.insert(key, node);
255 }
256
257 if (node->useCount)
258 qWarning("QNetworkAccessCache::addEntry: overriding active cache entry '%s'", key.constData());
259 if (node->object)
260 node->object->dispose();
261 node->object = entry;
262 node->object->key = key;
263 node->key = key;
264 node->useCount = 1;
265}
266
267bool QNetworkAccessCache::hasEntry(const QByteArray &key) const
268{
269 return hash.contains(key);
270}
271
272bool QNetworkAccessCache::requestEntry(const QByteArray &key, QObject *target, const char *member)
273{
274 Node *node = hash.value(key);
275 if (!node)
276 return false;
277
278 if (node->useCount > 0 && !node->object->shareable) {
279 // object is not shareable and is in use
280 // queue for later use
281 Q_ASSERT(node->older == nullptr && node->newer == nullptr);
282 node->receiverQueue.push_back({target, member});
283
284 // request queued
285 return true;
286 } else {
287 // node not in use or is shareable
288 if (unlinkEntry(key))
289 updateTimer();
290
291 ++node->useCount;
292 return emitEntryReady(node, target, member);
293 }
294}
295
296QNetworkAccessCache::CacheableObject *QNetworkAccessCache::requestEntryNow(const QByteArray &key)
297{
298 Node *node = hash.value(key);
299 if (!node)
300 return nullptr;
301
302 if (node->useCount > 0) {
303 if (node->object->shareable) {
304 ++node->useCount;
305 return node->object;
306 }
307
308 // object in use and not shareable
309 return nullptr;
310 }
311
312 // entry not in use, let the caller have it
313 bool wasOldest = unlinkEntry(key);
314 ++node->useCount;
315
316 if (wasOldest)
317 updateTimer();
318 return node->object;
319}
320
321void QNetworkAccessCache::releaseEntry(const QByteArray &key)
322{
323 Node *node = hash.value(key);
324 if (!node) {
325 qWarning("QNetworkAccessCache::releaseEntry: trying to release key '%s' that is not in cache", key.constData());
326 return;
327 }
328
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
357void QNetworkAccessCache::removeEntry(const QByteArray &key)
358{
359 Node *node = hash.value(key);
360 if (!node) {
361 qWarning("QNetworkAccessCache::removeEntry: trying to remove key '%s' that is not in cache", key.constData());
362 return;
363 }
364
365 if (unlinkEntry(key))
366 updateTimer();
367 if (node->useCount > 1)
368 qWarning("QNetworkAccessCache::removeEntry: removing active cache entry '%s'",
369 key.constData());
370
371 node->object->key.clear();
372 hash.remove(node->key);
373 delete node;
374}
375
376QT_END_NAMESPACE
377