18 #ifndef QGSABSTRACTCONTENTCACHE_H 
   19 #define QGSABSTRACTCONTENTCACHE_H 
   21 #include "qgis_core.h" 
   30 #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) 
   33 #include <QRecursiveMutex> 
   40 #include <QNetworkReply> 
   82     int mFileModifiedCheckTimeout = 30000;
 
   98       return other.
path == path;
 
  161     virtual bool checkReply( QNetworkReply *reply, 
const QString &path )
 const 
  176     virtual void onRemoteContentFetched( 
const QString &url, 
bool success );
 
  213                              const QString &typeString = QString(),
 
  214                              long maxCacheSize = 20000000,
 
  215                              int fileModifiedCheckTimeout = 30000 )
 
  217 #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
 
  218       , mMutex( QMutex::Recursive )
 
  220       , mMaxCacheSize( maxCacheSize )
 
  221       , mFileModifiedCheckTimeout( fileModifiedCheckTimeout )
 
  222       , mTypeString( typeString.isEmpty() ? QObject::tr( 
"Content" ) : typeString )
 
  228       qDeleteAll( mEntryLookup );
 
  239       if ( mLeastRecentEntry == mMostRecentEntry )
 
  243       T *entry = mLeastRecentEntry;
 
  244       while ( entry && ( mTotalSize > mMaxCacheSize ) )
 
  247         entry = 
static_cast< T * 
>( entry->nextEntry );
 
  249         takeEntryFromList( bkEntry );
 
  250         mEntryLookup.remove( bkEntry->path, bkEntry );
 
  251         mTotalSize -= bkEntry->dataSize();
 
  269     QByteArray 
getContent( 
const QString &path, 
const QByteArray &missingContent, 
const QByteArray &fetchingContent, 
bool blocking = 
false )
 const 
  275         if ( file.open( QIODevice::ReadOnly ) )
 
  277           return file.readAll();
 
  281           return missingContent;
 
  286       if ( path.startsWith( QLatin1String( 
"base64:" ), Qt::CaseInsensitive ) )
 
  288         const QByteArray base64 = path.mid( 7 ).toLocal8Bit(); 
 
  289         return QByteArray::fromBase64( base64, QByteArray::OmitTrailingEquals );
 
  293       if ( !path.contains( QLatin1String( 
"://" ) ) ) 
 
  295         return missingContent;
 
  298       const QUrl url( path );
 
  299       if ( !url.isValid() )
 
  301         return missingContent;
 
  305       if ( url.scheme().compare( QLatin1String( 
"file" ), Qt::CaseInsensitive ) == 0 )
 
  307         file.setFileName( url.toLocalFile() );
 
  310           if ( file.open( QIODevice::ReadOnly ) )
 
  312             return file.readAll();
 
  317         return missingContent;
 
  320       const QMutexLocker locker( &mMutex );
 
  323       if ( mPendingRemoteUrls.contains( path ) )
 
  328           return fetchingContent;
 
  333         for ( 
QgsTask *task : constActiveTasks )
 
  336           if ( !task->description().endsWith( path ) )
 
  346             if ( waitForTaskFinished( ncfTask ) )
 
  348               if ( mRemoteContentCache.contains( path ) )
 
  351                 return *mRemoteContentCache[ path ];
 
  362       if ( mRemoteContentCache.contains( path ) )
 
  365         return *mRemoteContentCache[ path ];
 
  368       mPendingRemoteUrls.insert( path );
 
  370       QNetworkRequest request( url );
 
  372       request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
 
  373       request.setAttribute( QNetworkRequest::CacheSaveControlAttribute, 
true );
 
  378         const QMutexLocker locker( &mMutex );
 
  380         QNetworkReply *reply = task->
reply();
 
  384           QMetaObject::invokeMethod( 
const_cast< QgsAbstractContentCacheBase * 
>( qobject_cast< const QgsAbstractContentCacheBase * >( 
this ) ), 
"onRemoteContentFetched", Qt::QueuedConnection, Q_ARG( QString, path ), Q_ARG( 
bool, 
false ) );
 
  388         if ( reply->error() != QNetworkReply::NoError )
 
  390           QgsMessageLog::logMessage( tr( 
"%3 request failed [error: %1 - url: %2]" ).arg( reply->errorString(), path, mTypeString ), mTypeString );
 
  396         const QVariant status = reply->attribute( QNetworkRequest::HttpStatusCodeAttribute );
 
  397         if ( !status.isNull() && status.toInt() >= 400 )
 
  399           const QVariant phrase = reply->attribute( QNetworkRequest::HttpReasonPhraseAttribute );
 
  400           QgsMessageLog::logMessage( tr( 
"%4 request error [status: %1 - reason phrase: %2] for %3" ).arg( status.toInt() ).arg( phrase.toString(), path, mTypeString ), mTypeString );
 
  401           mRemoteContentCache.insert( path, 
new QByteArray( missingContent ) );
 
  405         if ( !checkReply( reply, path ) )
 
  407           mRemoteContentCache.insert( path, 
new QByteArray( missingContent ) );
 
  414           const QByteArray ba = reply->readAll();
 
  419             mRemoteContentCache.insert( path, 
new QByteArray( ba ) );
 
  421         QMetaObject::invokeMethod( 
const_cast< QgsAbstractContentCacheBase * 
>( qobject_cast< const QgsAbstractContentCacheBase * >( 
this ) ), 
"onRemoteContentFetched", Qt::QueuedConnection, Q_ARG( QString, path ), Q_ARG( 
bool, 
true ) );
 
  429         if ( waitForTaskFinished( task ) )
 
  431           if ( mRemoteContentCache.contains( path ) )
 
  434             return *mRemoteContentCache[ path ];
 
  438       return fetchingContent;
 
  443       const QMutexLocker locker( &mMutex );
 
  444       mPendingRemoteUrls.remove( url );
 
  446       T *nextEntry = mLeastRecentEntry;
 
  447       while ( T *entry = nextEntry )
 
  449         nextEntry = 
static_cast< T * 
>( entry->nextEntry );
 
  450         if ( entry->path == url )
 
  452           takeEntryFromList( entry );
 
  453           mEntryLookup.remove( entry->path, entry );
 
  454           mTotalSize -= entry->dataSize();
 
  460         emit remoteContentFetched( url );
 
  504       const QString path = entryTemplate->path;
 
  505       T *currentEntry = 
nullptr;
 
  506       const QList<T *> entries = mEntryLookup.values( path );
 
  508       for ( T *cacheEntry : entries )
 
  510         if ( cacheEntry->isEqual( entryTemplate ) )
 
  512           if ( mFileModifiedCheckTimeout <= 0 || cacheEntry->fileModifiedLastCheckTimer.hasExpired( mFileModifiedCheckTimeout ) )
 
  514             if ( !modified.isValid() )
 
  515               modified = QFileInfo( path ).lastModified();
 
  517             if ( cacheEntry->fileModified != modified )
 
  520               cacheEntry->fileModifiedLastCheckTimer.restart();
 
  522           currentEntry = cacheEntry;
 
  530         currentEntry = insertCacheEntry( entryTemplate );
 
  534         delete entryTemplate;
 
  535         entryTemplate = 
nullptr;
 
  536         ( void )entryTemplate;
 
  537         takeEntryFromList( currentEntry );
 
  538         if ( !mMostRecentEntry ) 
 
  540           mMostRecentEntry = currentEntry;
 
  541           mLeastRecentEntry = currentEntry;
 
  545           mMostRecentEntry->nextEntry = currentEntry;
 
  546           currentEntry->previousEntry = mMostRecentEntry;
 
  547           currentEntry->nextEntry = 
nullptr;
 
  548           mMostRecentEntry = currentEntry;
 
  557 #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) 
  558     mutable QMutex mMutex;
 
  566     long mMaxCacheSize = 20000000;
 
  575     T *insertCacheEntry( T *entry )
 
  577       entry->mFileModifiedCheckTimeout = mFileModifiedCheckTimeout;
 
  579       if ( !entry->path.startsWith( QLatin1String( 
"base64:" ) ) )
 
  581         entry->fileModified = QFileInfo( entry->path ).lastModified();
 
  582         entry->fileModifiedLastCheckTimer.start();
 
  585       mEntryLookup.insert( entry->path, entry );
 
  588       if ( !mMostRecentEntry ) 
 
  590         mLeastRecentEntry = entry;
 
  591         mMostRecentEntry = entry;
 
  592         entry->previousEntry = 
nullptr;
 
  593         entry->nextEntry = 
nullptr;
 
  597         entry->previousEntry = mMostRecentEntry;
 
  598         entry->nextEntry = 
nullptr;
 
  599         mMostRecentEntry->nextEntry = entry;
 
  600         mMostRecentEntry = entry;
 
  611     void takeEntryFromList( T *entry )
 
  618       if ( entry->previousEntry )
 
  620         entry->previousEntry->nextEntry = entry->nextEntry;
 
  624         mLeastRecentEntry = 
static_cast< T * 
>( entry->nextEntry );
 
  626       if ( entry->nextEntry )
 
  628         entry->nextEntry->previousEntry = entry->previousEntry;
 
  632         mMostRecentEntry = 
static_cast< T * 
>( entry->previousEntry );
 
  639     void printEntryList()
 
  641       QgsDebugMsg( QStringLiteral( 
"****************cache entry list*************************" ) );
 
  642       QgsDebugMsg( 
"Cache size: " + QString::number( mTotalSize ) );
 
  643       T *entry = mLeastRecentEntry;
 
  648         entry = entry->nextEntry;
 
  653     QMultiHash< QString, T * > mEntryLookup;
 
  656     int mFileModifiedCheckTimeout = 30000;
 
  660     T *mLeastRecentEntry = 
nullptr;
 
  661     T *mMostRecentEntry = 
nullptr;
 
  663     mutable QCache< QString, QByteArray > mRemoteContentCache;
 
  664     mutable QSet< QString > mPendingRemoteUrls;
 
  668     friend class TestQgsSvgCache;
 
  669     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.
T * findExistingEntry(T *entryTemplate)
Returns the existing entry from the cache which matches entryTemplate (deleting entryTemplate when do...
QByteArray getContent(const QString &path, const QByteArray &missingContent, const QByteArray &fetchingContent, bool blocking=false) const
Gets the file content corresponding to the given path.
~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.
#define QgsSetRequestInitiatorClass(request, _class)