QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
qgsabstractcontentcache.h
Go to the documentation of this file.
1/***************************************************************************
2 qgsabstractcontentcache.h
3 ---------------
4 begin : December 2018
5 copyright : (C) 2018 by Nyall Dawson
6 email : nyall dot dawson at gmail dot com
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#ifndef QGSABSTRACTCONTENTCACHE_H
19#define QGSABSTRACTCONTENTCACHE_H
20
21#include "qgis_core.h"
22#include "qgis_sip.h"
23#include "qgslogger.h"
24#include "qgsmessagelog.h"
25#include "qgsapplication.h"
28#include "qgsvariantutils.h"
29
30#include <QObject>
31#include <QRecursiveMutex>
32#include <QCache>
33#include <QSet>
34#include <QDateTime>
35#include <QList>
36#include <QFile>
37#include <QNetworkReply>
38#include <QFileInfo>
39#include <QUrl>
40
52{
53 public:
54
58 QgsAbstractContentCacheEntry( const QString &path ) ;
59
60 virtual ~QgsAbstractContentCacheEntry() = default;
61
66
70 QString path;
71
73 QDateTime fileModified;
74
77
79 int mFileModifiedCheckTimeout = 30000;
80
85 QgsAbstractContentCacheEntry *nextEntry = nullptr;
86
91 QgsAbstractContentCacheEntry *previousEntry = nullptr;
92
93 bool operator==( const QgsAbstractContentCacheEntry &other ) const
94 {
95 return other.path == path;
96 }
97
101 virtual int dataSize() const = 0;
102
106 virtual void dump() const = 0;
107
108 protected:
109
115 virtual bool isEqual( const QgsAbstractContentCacheEntry *other ) const = 0;
116
117 private:
118#ifdef SIP_RUN
120#endif
121
122};
123
134class CORE_EXPORT QgsAbstractContentCacheBase: public QObject
135{
136 Q_OBJECT
137
138 public:
139
143 QgsAbstractContentCacheBase( QObject *parent );
144
145 signals:
146
150 void remoteContentFetched( const QString &url );
151
152 protected:
153
158 virtual bool checkReply( QNetworkReply *reply, const QString &path ) const
159 {
160 Q_UNUSED( reply )
161 Q_UNUSED( path )
162 return true;
163 }
164
165 protected slots:
166
173 virtual void onRemoteContentFetched( const QString &url, bool success );
174
175};
176
177#ifndef SIP_RUN
178
192template<class T>
194{
195
196 public:
197
209 QgsAbstractContentCache( QObject *parent SIP_TRANSFERTHIS = nullptr,
210 const QString &typeString = QString(),
211 long maxCacheSize = 20000000,
212 int fileModifiedCheckTimeout = 30000 )
214 , mMaxCacheSize( maxCacheSize )
215 , mFileModifiedCheckTimeout( fileModifiedCheckTimeout )
216 , mTypeString( typeString.isEmpty() ? QObject::tr( "Content" ) : typeString )
217 {
218 }
219
221 {
222 qDeleteAll( mEntryLookup );
223 }
224
225 protected:
226
231 {
232 //only one entry in cache
233 if ( mLeastRecentEntry == mMostRecentEntry )
234 {
235 return;
236 }
237 T *entry = mLeastRecentEntry;
238 while ( entry && ( mTotalSize > mMaxCacheSize ) )
239 {
240 T *bkEntry = entry;
241 entry = static_cast< T * >( entry->nextEntry );
242
243 takeEntryFromList( bkEntry );
244 mEntryLookup.remove( bkEntry->path, bkEntry );
245 mTotalSize -= bkEntry->dataSize();
246 delete bkEntry;
247 }
248 }
249
263 QByteArray getContent( const QString &path, const QByteArray &missingContent, const QByteArray &fetchingContent, bool blocking = false ) const;
264
265 void onRemoteContentFetched( const QString &url, bool success ) override
266 {
267 const QMutexLocker locker( &mMutex );
268 mPendingRemoteUrls.remove( url );
269
270 T *nextEntry = mLeastRecentEntry;
271 while ( T *entry = nextEntry )
272 {
273 nextEntry = static_cast< T * >( entry->nextEntry );
274 if ( entry->path == url )
275 {
276 takeEntryFromList( entry );
277 mEntryLookup.remove( entry->path, entry );
278 mTotalSize -= entry->dataSize();
279 delete entry;
280 }
281 }
282
283 if ( success )
284 emit remoteContentFetched( url );
285 }
286
298 {
299 // Wait up to timeout seconds for task finished
301 {
302 // The wait did not time out
303 // Third step, check status as complete
304 if ( task->status() == QgsTask::Complete )
305 {
306 // Fourth step, force the signal fetched to be sure reply has been checked
307
308 // ARGH this is BAD BAD BAD. The connection will get called twice as a result!!!
309 task->fetched();
310 return true;
311 }
312 }
313 return false;
314 }
315
325 T *findExistingEntry( T *entryTemplate )
326 {
327 //search entries in mEntryLookup
328 const QString path = entryTemplate->path;
329 T *currentEntry = nullptr;
330 const QList<T *> entries = mEntryLookup.values( path );
331 QDateTime modified;
332 for ( T *cacheEntry : entries )
333 {
334 if ( cacheEntry->isEqual( entryTemplate ) )
335 {
336 if ( mFileModifiedCheckTimeout <= 0 || cacheEntry->fileModifiedLastCheckTimer.hasExpired( mFileModifiedCheckTimeout ) )
337 {
338 if ( !modified.isValid() )
339 modified = QFileInfo( path ).lastModified();
340
341 if ( cacheEntry->fileModified != modified )
342 continue;
343 else
344 cacheEntry->fileModifiedLastCheckTimer.restart();
345 }
346 currentEntry = cacheEntry;
347 break;
348 }
349 }
350
351 //if not found: insert entryTemplate as a new entry
352 if ( !currentEntry )
353 {
354 currentEntry = insertCacheEntry( entryTemplate );
355 }
356 else
357 {
358 delete entryTemplate;
359 entryTemplate = nullptr;
360 ( void )entryTemplate;
361 takeEntryFromList( currentEntry );
362 if ( !mMostRecentEntry ) //list is empty
363 {
364 mMostRecentEntry = currentEntry;
365 mLeastRecentEntry = currentEntry;
366 }
367 else
368 {
369 mMostRecentEntry->nextEntry = currentEntry;
370 currentEntry->previousEntry = mMostRecentEntry;
371 currentEntry->nextEntry = nullptr;
372 mMostRecentEntry = currentEntry;
373 }
374 }
375
376 //debugging
377 //printEntryList();
378
379 return currentEntry;
380 }
381 mutable QRecursiveMutex mMutex;
382
384 long mTotalSize = 0;
385
387 long mMaxCacheSize = 20000000;
388
389 private:
390
396 T *insertCacheEntry( T *entry )
397 {
398 entry->mFileModifiedCheckTimeout = mFileModifiedCheckTimeout;
399
400 if ( !entry->path.startsWith( QLatin1String( "base64:" ) ) )
401 {
402 entry->fileModified = QFileInfo( entry->path ).lastModified();
403 entry->fileModifiedLastCheckTimer.start();
404 }
405
406 mEntryLookup.insert( entry->path, entry );
407
408 //insert to most recent place in entry list
409 if ( !mMostRecentEntry ) //inserting first entry
410 {
411 mLeastRecentEntry = entry;
412 mMostRecentEntry = entry;
413 entry->previousEntry = nullptr;
414 entry->nextEntry = nullptr;
415 }
416 else
417 {
418 entry->previousEntry = mMostRecentEntry;
419 entry->nextEntry = nullptr;
420 mMostRecentEntry->nextEntry = entry;
421 mMostRecentEntry = entry;
422 }
423
424 trimToMaximumSize();
425 return entry;
426 }
427
428
432 void takeEntryFromList( T *entry )
433 {
434 if ( !entry )
435 {
436 return;
437 }
438
439 if ( entry->previousEntry )
440 {
441 entry->previousEntry->nextEntry = entry->nextEntry;
442 }
443 else
444 {
445 mLeastRecentEntry = static_cast< T * >( entry->nextEntry );
446 }
447 if ( entry->nextEntry )
448 {
449 entry->nextEntry->previousEntry = entry->previousEntry;
450 }
451 else
452 {
453 mMostRecentEntry = static_cast< T * >( entry->previousEntry );
454 }
455 }
456
460 void printEntryList()
461 {
462 QgsDebugMsgLevel( QStringLiteral( "****************cache entry list*************************" ), 1 );
463 QgsDebugMsgLevel( "Cache size: " + QString::number( mTotalSize ), 1 );
464 T *entry = mLeastRecentEntry;
465 while ( entry )
466 {
467 QgsDebugMsgLevel( QStringLiteral( "***Entry:" ), 1 );
468 entry->dump();
469 entry = static_cast< T * >( entry->nextEntry );
470 }
471 }
472
474 QMultiHash< QString, T * > mEntryLookup;
475
477 int mFileModifiedCheckTimeout = 30000;
478
479 //The content cache keeps the entries on a double connected list, moving the current entry to the front.
480 //That way, removing entries for more space can start with the least used objects.
481 T *mLeastRecentEntry = nullptr;
482 T *mMostRecentEntry = nullptr;
483
484 mutable QCache< QString, QByteArray > mRemoteContentCache;
485 mutable QSet< QString > mPendingRemoteUrls;
486
487 QString mTypeString;
488
489 friend class TestQgsSvgCache;
490 friend class TestQgsImageCache;
491};
492
493#endif
494
495#endif // QGSABSTRACTCONTENTCACHE_H
A QObject derived base class for QgsAbstractContentCache.
void remoteContentFetched(const QString &url)
Emitted when the cache has finished retrieving content from a remote url.
virtual bool checkReply(QNetworkReply *reply, const QString &path) const
Runs additional checks on a network reply to ensure that the reply content is consistent with that re...
Base class for entries in a QgsAbstractContentCache.
virtual int dataSize() const =0
Returns the memory usage in bytes for the entry.
virtual void dump() const =0
Dumps debugging strings containing the item's properties.
virtual ~QgsAbstractContentCacheEntry()=default
QElapsedTimer fileModifiedLastCheckTimer
Time since last check of file modified date.
QgsAbstractContentCacheEntry(const QgsAbstractContentCacheEntry &rh)=delete
QgsAbstractContentCacheEntry cannot be copied.
QgsAbstractContentCacheEntry & operator=(const QgsAbstractContentCacheEntry &rh)=delete
QgsAbstractContentCacheEntry cannot be copied.
QString path
Represents the absolute path to a file, a remote URL, or a base64 encoded string.
virtual bool isEqual(const QgsAbstractContentCacheEntry *other) const =0
Tests whether this entry matches another entry.
QDateTime fileModified
Timestamp when file was last modified.
bool operator==(const QgsAbstractContentCacheEntry &other) const
Abstract base class for file content caches, such as SVG or raster image caches.
T * findExistingEntry(T *entryTemplate)
Returns the existing entry from the cache which matches entryTemplate (deleting entryTemplate when do...
void onRemoteContentFetched(const QString &url, bool success) override
Triggered after remote content (i.e.
QgsAbstractContentCache(QObject *parent=nullptr, const QString &typeString=QString(), long maxCacheSize=20000000, int fileModifiedCheckTimeout=30000)
Constructor for QgsAbstractContentCache, with the specified parent object.
void trimToMaximumSize()
Removes the least used cache entries until the maximum cache size is under the predefined size limit.
bool waitForTaskFinished(QgsNetworkContentFetcherTask *task) const
Blocks the current thread until the task finishes (or user's preset network timeout expires)
static int timeout()
Returns the network timeout length, in milliseconds.
Handles HTTP network content fetching in a background task.
void fetched()
Emitted when the network content has been fetched, regardless of whether the fetch was successful or ...
TaskStatus status() const
Returns the current task status.
@ Complete
Task successfully completed.
bool waitForFinished(int timeout=30000)
Blocks the current thread until the task finishes or a maximum of timeout milliseconds.
#define SIP_TRANSFERTHIS
Definition: qgis_sip.h:53
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39