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)