18#ifndef QGSABSTRACTCONTENTCACHE_H
19#define QGSABSTRACTCONTENTCACHE_H
31#include <QRecursiveMutex>
37#include <QNetworkReply>
79 int mFileModifiedCheckTimeout = 30000;
95 return other.
path == path;
158 virtual bool checkReply( QNetworkReply *reply,
const QString &path )
const
173 virtual void onRemoteContentFetched(
const QString &url,
bool success );
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 )
222 qDeleteAll( mEntryLookup );
233 if ( mLeastRecentEntry == mMostRecentEntry )
237 T *entry = mLeastRecentEntry;
238 while ( entry && ( mTotalSize > mMaxCacheSize ) )
241 entry =
static_cast< T *
>( entry->nextEntry );
243 takeEntryFromList( bkEntry );
244 mEntryLookup.remove( bkEntry->path, bkEntry );
245 mTotalSize -= bkEntry->dataSize();
263 QByteArray
getContent(
const QString &path,
const QByteArray &missingContent,
const QByteArray &fetchingContent,
bool blocking =
false )
const
269 if ( file.open( QIODevice::ReadOnly ) )
271 return file.readAll();
275 return missingContent;
280 if ( path.startsWith( QLatin1String(
"base64:" ), Qt::CaseInsensitive ) )
282 const QByteArray base64 = path.mid( 7 ).toLocal8Bit();
283 return QByteArray::fromBase64( base64, QByteArray::OmitTrailingEquals );
287 if ( !path.contains( QLatin1String(
"://" ) ) )
289 return missingContent;
292 const QUrl url( path );
293 if ( !url.isValid() )
295 return missingContent;
299 if ( url.scheme().compare( QLatin1String(
"file" ), Qt::CaseInsensitive ) == 0 )
301 file.setFileName( url.toLocalFile() );
304 if ( file.open( QIODevice::ReadOnly ) )
306 return file.readAll();
311 return missingContent;
314 const QMutexLocker locker( &mMutex );
317 if ( mPendingRemoteUrls.contains( path ) )
322 return fetchingContent;
327 for (
QgsTask *task : constActiveTasks )
330 if ( !task->description().endsWith( path ) )
340 if ( waitForTaskFinished( ncfTask ) )
342 if ( mRemoteContentCache.contains( path ) )
345 return *mRemoteContentCache[ path ];
356 if ( mRemoteContentCache.contains( path ) )
359 return *mRemoteContentCache[ path ];
362 mPendingRemoteUrls.insert( path );
364 QNetworkRequest request( url );
366 request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
367 request.setAttribute( QNetworkRequest::CacheSaveControlAttribute,
true );
372 const QMutexLocker locker( &mMutex );
374 QNetworkReply *reply = task->
reply();
378 QMetaObject::invokeMethod(
const_cast< QgsAbstractContentCacheBase *
>( qobject_cast< const QgsAbstractContentCacheBase * >(
this ) ),
"onRemoteContentFetched", Qt::QueuedConnection, Q_ARG( QString, path ), Q_ARG(
bool,
false ) );
382 if ( reply->error() != QNetworkReply::NoError )
384 QgsMessageLog::logMessage( tr(
"%3 request failed [error: %1 - url: %2]" ).arg( reply->errorString(), path, mTypeString ), mTypeString );
390 const QVariant status = reply->attribute( QNetworkRequest::HttpStatusCodeAttribute );
393 const QVariant phrase = reply->attribute( QNetworkRequest::HttpReasonPhraseAttribute );
394 QgsMessageLog::logMessage( tr(
"%4 request error [status: %1 - reason phrase: %2] for %3" ).arg( status.toInt() ).arg( phrase.toString(), path, mTypeString ), mTypeString );
395 mRemoteContentCache.insert( path,
new QByteArray( missingContent ) );
399 if ( !checkReply( reply, path ) )
401 mRemoteContentCache.insert( path,
new QByteArray( missingContent ) );
408 const QByteArray ba = reply->readAll();
413 mRemoteContentCache.insert( path,
new QByteArray( ba ) );
415 QMetaObject::invokeMethod(
const_cast< QgsAbstractContentCacheBase *
>( qobject_cast< const QgsAbstractContentCacheBase * >(
this ) ),
"onRemoteContentFetched", Qt::QueuedConnection, Q_ARG( QString, path ), Q_ARG(
bool,
true ) );
423 if ( waitForTaskFinished( task ) )
425 if ( mRemoteContentCache.contains( path ) )
428 return *mRemoteContentCache[ path ];
432 return fetchingContent;
437 const QMutexLocker locker( &mMutex );
438 mPendingRemoteUrls.remove( url );
440 T *nextEntry = mLeastRecentEntry;
441 while ( T *entry = nextEntry )
443 nextEntry =
static_cast< T *
>( entry->nextEntry );
444 if ( entry->path == url )
446 takeEntryFromList( entry );
447 mEntryLookup.remove( entry->path, entry );
448 mTotalSize -= entry->dataSize();
454 emit remoteContentFetched( url );
498 const QString path = entryTemplate->path;
499 T *currentEntry =
nullptr;
500 const QList<T *> entries = mEntryLookup.values( path );
502 for ( T *cacheEntry : entries )
504 if ( cacheEntry->isEqual( entryTemplate ) )
506 if ( mFileModifiedCheckTimeout <= 0 || cacheEntry->fileModifiedLastCheckTimer.hasExpired( mFileModifiedCheckTimeout ) )
508 if ( !modified.isValid() )
509 modified = QFileInfo( path ).lastModified();
511 if ( cacheEntry->fileModified != modified )
514 cacheEntry->fileModifiedLastCheckTimer.restart();
516 currentEntry = cacheEntry;
524 currentEntry = insertCacheEntry( entryTemplate );
528 delete entryTemplate;
529 entryTemplate =
nullptr;
530 ( void )entryTemplate;
531 takeEntryFromList( currentEntry );
532 if ( !mMostRecentEntry )
534 mMostRecentEntry = currentEntry;
535 mLeastRecentEntry = currentEntry;
539 mMostRecentEntry->nextEntry = currentEntry;
540 currentEntry->previousEntry = mMostRecentEntry;
541 currentEntry->nextEntry =
nullptr;
542 mMostRecentEntry = currentEntry;
557 long mMaxCacheSize = 20000000;
566 T *insertCacheEntry( T *entry )
568 entry->mFileModifiedCheckTimeout = mFileModifiedCheckTimeout;
570 if ( !entry->path.startsWith( QLatin1String(
"base64:" ) ) )
572 entry->fileModified = QFileInfo( entry->path ).lastModified();
573 entry->fileModifiedLastCheckTimer.start();
576 mEntryLookup.insert( entry->path, entry );
579 if ( !mMostRecentEntry )
581 mLeastRecentEntry = entry;
582 mMostRecentEntry = entry;
583 entry->previousEntry =
nullptr;
584 entry->nextEntry =
nullptr;
588 entry->previousEntry = mMostRecentEntry;
589 entry->nextEntry =
nullptr;
590 mMostRecentEntry->nextEntry = entry;
591 mMostRecentEntry = entry;
602 void takeEntryFromList( T *entry )
609 if ( entry->previousEntry )
611 entry->previousEntry->nextEntry = entry->nextEntry;
615 mLeastRecentEntry =
static_cast< T *
>( entry->nextEntry );
617 if ( entry->nextEntry )
619 entry->nextEntry->previousEntry = entry->previousEntry;
623 mMostRecentEntry =
static_cast< T *
>( entry->previousEntry );
630 void printEntryList()
632 QgsDebugMsg( QStringLiteral(
"****************cache entry list*************************" ) );
633 QgsDebugMsg(
"Cache size: " + QString::number( mTotalSize ) );
634 T *entry = mLeastRecentEntry;
639 entry = entry->nextEntry;
644 QMultiHash< QString, T * > mEntryLookup;
647 int mFileModifiedCheckTimeout = 30000;
651 T *mLeastRecentEntry =
nullptr;
652 T *mMostRecentEntry =
nullptr;
654 mutable QCache< QString, QByteArray > mRemoteContentCache;
655 mutable QSet< QString > mPendingRemoteUrls;
659 friend class TestQgsSvgCache;
660 friend class TestQgsImageCache;
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.
QByteArray getContent(const QString &path, const QByteArray &missingContent, const QByteArray &fetchingContent, bool blocking=false) const
Gets the file content corresponding to the given path.
T * findExistingEntry(T *entryTemplate)
Returns the existing entry from the cache which matches entryTemplate (deleting entryTemplate when do...
~QgsAbstractContentCache() override
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 QgsTaskManager * taskManager()
Returns the application's task manager, used for managing application wide background task handling.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
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 ...
QNetworkReply * reply()
Returns the network reply.
QList< QgsTask * > activeTasks() const
Returns a list of the active (queued or running) tasks.
long addTask(QgsTask *task, int priority=0)
Adds a task to the manager.
Abstract base class for long running background tasks.
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.
static bool isNull(const QVariant &variant)
Returns true if the specified variant should be considered a NULL value.
#define QgsSetRequestInitiatorClass(request, _class)