QGIS API Documentation 4.1.0-Master (5bf3c20f3c9)
Loading...
Searching...
No Matches
qgsnetworkdiskcache.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsnetworkdiskcache.cpp - Thread-safe interface for QNetworkDiskCache
3 -------------------
4 begin : 2016-03-05
5 copyright : (C) 2016 by Juergen E. Fischer
6 email : jef at norbit dot de
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
18
19#include "qgsnetworkdiskcache.h"
20
21#include <mutex>
22
24
25#include <QStorageInfo>
26
27#include "moc_qgsnetworkdiskcache.cpp"
28
30ExpirableNetworkDiskCache QgsNetworkDiskCache::sDiskCache;
32QMutex QgsNetworkDiskCache::sDiskCacheMutex;
33
34QHash<QUrl, QVariantMap> QgsNetworkDiskCache::sPendingRequestHeaders;
35
36QgsNetworkDiskCache::QgsNetworkDiskCache( QObject *parent )
37 : QNetworkDiskCache( parent )
38{}
39
40void QgsNetworkDiskCache::insertPendingRequestHeaders( const QUrl &url, const QVariantMap &headers )
41{
42 const QMutexLocker lock( &sDiskCacheMutex );
43 sPendingRequestHeaders.insert( url, headers );
44}
45
47{
48 const QMutexLocker lock( &sDiskCacheMutex );
49 return sPendingRequestHeaders.contains( url );
50}
51
53{
54 const QMutexLocker lock( &sDiskCacheMutex );
55 sPendingRequestHeaders.remove( url );
56}
57
58bool QgsNetworkDiskCache::hasInvalidMatchForRequest( const QNetworkRequest &request )
59{
60 // metaData is protected by mutex, so thread-safe to call here
61 const QNetworkCacheMetaData cachedMetadata = metaData( request.url() );
62 if ( !cachedMetadata.isValid() )
63 return false;
64
65 // iterate through the cached response headers looking for "Vary"
66 const QNetworkCacheMetaData::RawHeaderList rawHeaders = cachedMetadata.rawHeaders();
67 for ( const QNetworkCacheMetaData::RawHeader &cachedHeader : rawHeaders )
68 {
69 if ( cachedHeader.first.compare( "vary", Qt::CaseInsensitive ) == 0 )
70 {
71 const QString varyValue = QString::fromUtf8( cachedHeader.second ).trimmed();
72
73 // vary: * indicates we should NEVER use this cached response
74 if ( varyValue == '*' )
75 {
76 return true;
77 }
78
79 // retrieve the original headers that generated this cached response
80 const QVariantMap originalCachedHeaders = cachedMetadata.attributes().value( static_cast< QNetworkRequest::Attribute >( QgsNetworkRequestParameters::AttributeOriginalHeaders ) ).toMap();
81 const QStringList varyHeaderNames = varyValue.split( ',' );
82 for ( const QString &headerName : varyHeaderNames )
83 {
84 const QString normalizedHeaderName = headerName.trimmed().toLower();
85 const QByteArray currentHeaderValue = request.rawHeader( normalizedHeaderName.toUtf8() );
86 const QByteArray originalCachedHeaderValue = originalCachedHeaders.value( normalizedHeaderName ).toByteArray();
87 if ( currentHeaderValue != originalCachedHeaderValue )
88 {
89 // we can't use the previously cached response, the header corresponding to the Vary value doesn't match
90 // what was used when the cached response was stored
91 return true;
92 }
93 }
94 }
95 }
96
97 return false;
98}
99
101{
102 const QMutexLocker lock( &sDiskCacheMutex );
103 return sDiskCache.cacheDirectory();
104}
105
106void QgsNetworkDiskCache::setCacheDirectory( const QString &cacheDir )
107{
108 const QMutexLocker lock( &sDiskCacheMutex );
109 sDiskCache.setCacheDirectory( cacheDir );
110}
111
113{
114 const QMutexLocker lock( &sDiskCacheMutex );
115 return sDiskCache.maximumCacheSize();
116}
117
119{
120 const QMutexLocker lock( &sDiskCacheMutex );
121
122 if ( size == 0 )
123 {
124 // Calculate maximum cache size based on available free space
125 size = smartCacheSize( sDiskCache.cacheDirectory() );
126 }
127
128 sDiskCache.setMaximumCacheSize( size );
129}
130
132{
133 const QMutexLocker lock( &sDiskCacheMutex );
134 return sDiskCache.cacheSize();
135}
136
137QNetworkCacheMetaData QgsNetworkDiskCache::metaData( const QUrl &url )
138{
139 const QMutexLocker lock( &sDiskCacheMutex );
140 return sDiskCache.metaData( url );
141}
142
143void QgsNetworkDiskCache::updateMetaData( const QNetworkCacheMetaData &metaData )
144{
145 const QMutexLocker lock( &sDiskCacheMutex );
146 sDiskCache.updateMetaData( metaData );
147}
148
149QIODevice *QgsNetworkDiskCache::data( const QUrl &url )
150{
151 const QMutexLocker lock( &sDiskCacheMutex );
152 return sDiskCache.data( url );
153}
154
155bool QgsNetworkDiskCache::remove( const QUrl &url )
156{
157 const QMutexLocker lock( &sDiskCacheMutex );
158 return sDiskCache.remove( url );
159}
160
161QIODevice *QgsNetworkDiskCache::prepare( const QNetworkCacheMetaData &metaData )
162{
163 const QMutexLocker lock( &sDiskCacheMutex );
164
165 // explicitly drop responses with "Vary: *" -- these should never be cached!
166 for ( const QNetworkCacheMetaData::RawHeader &header : metaData.rawHeaders() )
167 {
168 if ( header.first.compare( "vary", Qt::CaseInsensitive ) == 0 && QString::fromUtf8( header.second ).trimmed() == "*" )
169 {
170 return nullptr;
171 }
172 }
173
174 QNetworkCacheMetaData modifiedMeta = metaData;
175 // inject the original request headers, so that these are stored in the cache files and we can later retrieve them
176 if ( sPendingRequestHeaders.contains( metaData.url() ) )
177 {
178 QNetworkCacheMetaData::AttributesMap attributes = modifiedMeta.attributes();
179 attributes.insert( static_cast< QNetworkRequest::Attribute >( QgsNetworkRequestParameters::AttributeOriginalHeaders ), sPendingRequestHeaders.value( metaData.url() ) );
180 modifiedMeta.setAttributes( attributes );
181 }
182
183 return sDiskCache.prepare( modifiedMeta );
184}
185
186void QgsNetworkDiskCache::insert( QIODevice *device )
187{
188 const QMutexLocker lock( &sDiskCacheMutex );
189 sDiskCache.insert( device );
190}
191
192QNetworkCacheMetaData QgsNetworkDiskCache::fileMetaData( const QString &fileName ) const
193{
194 const QMutexLocker lock( &sDiskCacheMutex );
195 return sDiskCache.fileMetaData( fileName );
196}
197
199{
200 const QMutexLocker lock( &sDiskCacheMutex );
201 return sDiskCache.runExpire();
202}
203
205{
206 const QMutexLocker lock( &sDiskCacheMutex );
207 return sDiskCache.clear();
208}
209
210void determineSmartCacheSize( const QString &cacheDir, qint64 &cacheSize )
211{
212 std::function<qint64( const QString & )> dirSize;
213 dirSize = [&dirSize]( const QString &dirPath ) -> qint64 {
214 qint64 size = 0;
215 QDir dir( dirPath );
216
217 const QStringList filePaths = dir.entryList( QDir::Files | QDir::System | QDir::Hidden );
218 for ( const QString &filePath : filePaths )
219 {
220 QFileInfo fi( dir, filePath );
221 size += fi.size();
222 }
223
224 const QStringList childDirPaths = dir.entryList( QDir::Dirs | QDir::NoDotAndDotDot | QDir::System | QDir::Hidden | QDir::NoSymLinks );
225 for ( const QString &childDirPath : childDirPaths )
226 {
227 size += dirSize( dirPath + QDir::separator() + childDirPath );
228 }
229
230 return size;
231 };
232
233 qint64 bytesFree;
234 QStorageInfo storageInfo( cacheDir );
235 bytesFree = storageInfo.bytesFree() + dirSize( cacheDir );
236
237 // NOLINTBEGIN(bugprone-narrowing-conversions)
238 // Logic taken from Firefox's smart cache size handling
239 qint64 available10MB = bytesFree / 1024 / ( 1024LL * 10 );
240 qint64 cacheSize10MB = 0;
241 if ( available10MB > 2500 )
242 {
243 // Cap the cache size to 1GB
244 cacheSize10MB = 100;
245 }
246 else
247 {
248 if ( available10MB > 700 )
249 {
250 // Add 2.5% of the free space above 7GB
251 cacheSize10MB += ( available10MB - 700 ) * 0.025;
252 available10MB = 700;
253 }
254 if ( available10MB > 50 )
255 {
256 // Add 7.5% of free space between 500MB to 7GB
257 cacheSize10MB += ( available10MB - 50 ) * 0.075;
258 available10MB = 50;
259 }
260
261#if defined( Q_OS_ANDROID )
262 // On Android, smaller/older devices may have very little storage
263
264 // Add 16% of free space up to 500 MB
265 cacheSize10MB += std::max( 2LL, static_cast<qint64>( available10MB * 0.16 ) );
266#else
267 // Add 30% of free space up to 500 MB
268 cacheSize10MB += std::max( 5LL, static_cast<qint64>( available10MB * 0.30 ) );
269#endif
270 }
271 cacheSize = cacheSize10MB * 10 * 1024 * 1024;
272 // NOLINTEND(bugprone-narrowing-conversions)
273}
274
275qint64 QgsNetworkDiskCache::smartCacheSize( const QString &cacheDir )
276{
277 static qint64 sCacheSize = 0;
278 static std::once_flag initialized;
279 std::call_once( initialized, determineSmartCacheSize, cacheDir, sCacheSize );
280 return sCacheSize;
281}
void setCacheDirectory(const QString &cacheDir)
void removePendingRequestForUrl(const QUrl &url) const
Removes a pending (ongoing) request in place for the specified url.
QIODevice * data(const QUrl &url) override
void updateMetaData(const QNetworkCacheMetaData &metaData) override
QNetworkCacheMetaData metaData(const QUrl &url) override
bool hasInvalidMatchForRequest(const QNetworkRequest &request)
Returns true if the cache has a matching but invalid entry for a request.
QNetworkCacheMetaData fileMetaData(const QString &fileName) const
bool hasPendingRequestForUrl(const QUrl &url) const
Returns true if there is a pending (ongoing) request in place for the specified url.
void insertPendingRequestHeaders(const QUrl &url, const QVariantMap &headers)
Registers the original request headers for a pending request to the specified url.
void insert(QIODevice *device) override
QIODevice * prepare(const QNetworkCacheMetaData &metaData) override
bool remove(const QUrl &url) override
void setMaximumCacheSize(qint64 size)
qint64 cacheSize() const override
static qint64 smartCacheSize(const QString &path)
Returns a smart cache size, in bytes, based on available free space.
@ AttributeOriginalHeaders
Internal ID used to store original request headers, used when checking against previously cached resp...
void determineSmartCacheSize(const QString &cacheDir, qint64 &cacheSize)