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