QGIS API Documentation 3.43.0-Master (69d1901085b)
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
64
68 QString path;
69
71 QDateTime fileModified;
72
75
77 int mFileModifiedCheckTimeout = 30000;
78
83 QgsAbstractContentCacheEntry *nextEntry = nullptr;
84
89 QgsAbstractContentCacheEntry *previousEntry = nullptr;
90
91 bool operator==( const QgsAbstractContentCacheEntry &other ) const
92 {
93 return other.path == path;
94 }
95
99 virtual int dataSize() const = 0;
100
104 virtual void dump() const = 0;
105
106 protected:
107
113 virtual bool isEqual( const QgsAbstractContentCacheEntry *other ) const = 0;
114
115 private:
116#ifdef SIP_RUN
118#endif
119
120};
121
132class CORE_EXPORT QgsAbstractContentCacheBase: public QObject
133{
134 Q_OBJECT
135
136 public:
137
141 QgsAbstractContentCacheBase( QObject *parent );
142
157 static bool parseBase64DataUrl( const QString &path, QString *mimeType SIP_OUT = nullptr, QString *data SIP_OUT = nullptr );
158
159
174 static bool parseEmbeddedStringData( const QString &path, QString *mimeType SIP_OUT = nullptr, QString *data SIP_OUT = nullptr );
175
181 static bool isBase64Data( const QString &path );
182
193 virtual bool invalidateCacheEntry( const QString &path );
194
195 signals:
196
200 void remoteContentFetched( const QString &url );
201
202 protected:
203
208 virtual bool checkReply( QNetworkReply *reply, const QString &path ) const
209 {
210 Q_UNUSED( reply )
211 Q_UNUSED( path )
212 return true;
213 }
214
215 protected slots:
216
223 virtual void onRemoteContentFetched( const QString &url, bool success );
224
225};
226
227#ifndef SIP_RUN
228
242template<class T>
244{
245
246 public:
247
259 QgsAbstractContentCache( QObject *parent SIP_TRANSFERTHIS = nullptr,
260 const QString &typeString = QString(),
261 long maxCacheSize = 20000000,
262 int fileModifiedCheckTimeout = 30000 )
264 , mMaxCacheSize( maxCacheSize )
265 , mFileModifiedCheckTimeout( fileModifiedCheckTimeout )
266 , mTypeString( typeString.isEmpty() ? QObject::tr( "Content" ) : typeString )
267 {
268 }
269
271 {
272 qDeleteAll( mEntryLookup );
273 }
274
275 bool invalidateCacheEntry( const QString &path ) override
276 {
277 const QMutexLocker locker( &mMutex );
278
279 const QList<T *> entries = mEntryLookup.values( path );
280 if ( entries.isEmpty() )
281 return false;
282
283 for ( T *entry : entries )
284 {
285 takeEntryFromList( entry );
286 mEntryLookup.remove( path, entry );
287 mTotalSize -= entry->dataSize();
288 delete entry;
289 }
290
291 return true;
292 }
293
294 protected:
295
300 {
301 //only one entry in cache
302 if ( mLeastRecentEntry == mMostRecentEntry )
303 {
304 return;
305 }
306 T *entry = mLeastRecentEntry;
307 while ( entry && ( mTotalSize > mMaxCacheSize ) )
308 {
309 T *bkEntry = entry;
310 entry = static_cast< T * >( entry->nextEntry );
311
312 takeEntryFromList( bkEntry );
313 mEntryLookup.remove( bkEntry->path, bkEntry );
314 mTotalSize -= bkEntry->dataSize();
315 delete bkEntry;
316 }
317 }
318
332 QByteArray getContent( const QString &path, const QByteArray &missingContent, const QByteArray &fetchingContent, bool blocking = false ) const;
333
334 void onRemoteContentFetched( const QString &url, bool success ) override
335 {
336 const QMutexLocker locker( &mMutex );
337 mPendingRemoteUrls.remove( url );
338
339 T *nextEntry = mLeastRecentEntry;
340 while ( T *entry = nextEntry )
341 {
342 nextEntry = static_cast< T * >( entry->nextEntry );
343 if ( entry->path == url )
344 {
345 takeEntryFromList( entry );
346 mEntryLookup.remove( entry->path, entry );
347 mTotalSize -= entry->dataSize();
348 delete entry;
349 }
350 }
351
352 if ( success )
353 emit remoteContentFetched( url );
354 }
355
367 {
368 // Wait up to timeout seconds for task finished
370 {
371 // The wait did not time out
372 // Third step, check status as complete
373 if ( task->status() == QgsTask::Complete )
374 {
375 // Fourth step, force the signal fetched to be sure reply has been checked
376
377 // ARGH this is BAD BAD BAD. The connection will get called twice as a result!!!
378 task->fetched();
379 return true;
380 }
381 }
382 return false;
383 }
384
394 T *findExistingEntry( T *entryTemplate )
395 {
396 //search entries in mEntryLookup
397 const QString path = entryTemplate->path;
398 T *currentEntry = nullptr;
399 const QList<T *> entries = mEntryLookup.values( path );
400 QDateTime modified;
401 for ( T *cacheEntry : entries )
402 {
403 if ( cacheEntry->isEqual( entryTemplate ) )
404 {
405 if ( mFileModifiedCheckTimeout <= 0 || cacheEntry->fileModifiedLastCheckTimer.hasExpired( mFileModifiedCheckTimeout ) )
406 {
407 if ( !modified.isValid() )
408 modified = QFileInfo( path ).lastModified();
409
410 if ( cacheEntry->fileModified != modified )
411 continue;
412 else
413 cacheEntry->fileModifiedLastCheckTimer.restart();
414 }
415 currentEntry = cacheEntry;
416 break;
417 }
418 }
419
420 //if not found: insert entryTemplate as a new entry
421 if ( !currentEntry )
422 {
423 currentEntry = insertCacheEntry( entryTemplate );
424 }
425 else
426 {
427 delete entryTemplate;
428 entryTemplate = nullptr;
429 ( void )entryTemplate;
430 takeEntryFromList( currentEntry );
431 if ( !mMostRecentEntry ) //list is empty
432 {
433 mMostRecentEntry = currentEntry;
434 mLeastRecentEntry = currentEntry;
435 }
436 else
437 {
438 mMostRecentEntry->nextEntry = currentEntry;
439 currentEntry->previousEntry = mMostRecentEntry;
440 currentEntry->nextEntry = nullptr;
441 mMostRecentEntry = currentEntry;
442 }
443 }
444
445 //debugging
446 //printEntryList();
447
448 return currentEntry;
449 }
450 mutable QRecursiveMutex mMutex;
451
453 long mTotalSize = 0;
454
456 long mMaxCacheSize = 20000000;
457
458 private:
459
465 T *insertCacheEntry( T *entry )
466 {
467 entry->mFileModifiedCheckTimeout = mFileModifiedCheckTimeout;
468
469 if ( !entry->path.startsWith( QLatin1String( "base64:" ) ) )
470 {
471 entry->fileModified = QFileInfo( entry->path ).lastModified();
472 entry->fileModifiedLastCheckTimer.start();
473 }
474
475 mEntryLookup.insert( entry->path, entry );
476
477 //insert to most recent place in entry list
478 if ( !mMostRecentEntry ) //inserting first entry
479 {
480 mLeastRecentEntry = entry;
481 mMostRecentEntry = entry;
482 entry->previousEntry = nullptr;
483 entry->nextEntry = nullptr;
484 }
485 else
486 {
487 entry->previousEntry = mMostRecentEntry;
488 entry->nextEntry = nullptr;
489 mMostRecentEntry->nextEntry = entry;
490 mMostRecentEntry = entry;
491 }
492
493 trimToMaximumSize();
494 return entry;
495 }
496
497
501 void takeEntryFromList( T *entry )
502 {
503 if ( !entry )
504 {
505 return;
506 }
507
508 if ( entry->previousEntry )
509 {
510 entry->previousEntry->nextEntry = entry->nextEntry;
511 }
512 else
513 {
514 mLeastRecentEntry = static_cast< T * >( entry->nextEntry );
515 }
516 if ( entry->nextEntry )
517 {
518 entry->nextEntry->previousEntry = entry->previousEntry;
519 }
520 else
521 {
522 mMostRecentEntry = static_cast< T * >( entry->previousEntry );
523 }
524 }
525
529 void printEntryList()
530 {
531 QgsDebugMsgLevel( QStringLiteral( "****************cache entry list*************************" ), 1 );
532 QgsDebugMsgLevel( "Cache size: " + QString::number( mTotalSize ), 1 );
533 T *entry = mLeastRecentEntry;
534 while ( entry )
535 {
536 QgsDebugMsgLevel( QStringLiteral( "***Entry:" ), 1 );
537 entry->dump();
538 entry = static_cast< T * >( entry->nextEntry );
539 }
540 }
541
543 QMultiHash< QString, T * > mEntryLookup;
544
546 int mFileModifiedCheckTimeout = 30000;
547
548 //The content cache keeps the entries on a double connected list, moving the current entry to the front.
549 //That way, removing entries for more space can start with the least used objects.
550 T *mLeastRecentEntry = nullptr;
551 T *mMostRecentEntry = nullptr;
552
553 mutable QCache< QString, QByteArray > mRemoteContentCache;
554 mutable QSet< QString > mPendingRemoteUrls;
555
556 QString mTypeString;
557
558 friend class TestQgsSvgCache;
559 friend class TestQgsImageCache;
560};
561
562#endif
563
564#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 & operator=(const QgsAbstractContentCacheEntry &rh)=delete
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.
bool invalidateCacheEntry(const QString &path) override
Invalidates a cache entry for the specified path.
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 SIP_OUT
Definition qgis_sip.h:58
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:41