18 #ifndef QGSABSTRACTCONTENTCACHE_H 19 #define QGSABSTRACTCONTENTCACHE_H 21 #include "qgis_core.h" 35 #include <QNetworkReply> 75 int mFileModifiedCheckTimeout = 30000;
91 return other.
path == path;
97 virtual int dataSize()
const = 0;
102 virtual void dump()
const = 0;
146 void remoteContentFetched(
const QString &url );
154 virtual bool checkReply( QNetworkReply *reply,
const QString &path )
const 169 virtual void onRemoteContentFetched(
const QString &url,
bool success );
206 const QString &typeString = QString(),
207 long maxCacheSize = 20000000,
208 int fileModifiedCheckTimeout = 30000 )
210 , mMutex( QMutex::Recursive )
211 , mMaxCacheSize( maxCacheSize )
212 , mFileModifiedCheckTimeout( fileModifiedCheckTimeout )
213 , mTypeString( typeString.isEmpty() ? QObject::tr(
"Content" ) : typeString )
219 qDeleteAll( mEntryLookup );
230 if ( mLeastRecentEntry == mMostRecentEntry )
234 T *entry = mLeastRecentEntry;
235 while ( entry && ( mTotalSize > mMaxCacheSize ) )
238 entry =
static_cast< T *
>( entry->nextEntry );
240 takeEntryFromList( bkEntry );
241 mEntryLookup.remove( bkEntry->path, bkEntry );
242 mTotalSize -= bkEntry->dataSize();
260 QByteArray
getContent(
const QString &path,
const QByteArray &missingContent,
const QByteArray &fetchingContent,
bool blocking =
false )
const 266 if ( file.open( QIODevice::ReadOnly ) )
268 return file.readAll();
272 return missingContent;
277 if ( path.startsWith( QLatin1String(
"base64:" ), Qt::CaseInsensitive ) )
279 QByteArray base64 = path.mid( 7 ).toLocal8Bit();
280 return QByteArray::fromBase64( base64, QByteArray::OmitTrailingEquals );
284 if ( !path.contains( QLatin1String(
"://" ) ) )
286 return missingContent;
290 if ( !url.isValid() )
292 return missingContent;
296 if ( url.scheme().compare( QLatin1String(
"file" ), Qt::CaseInsensitive ) == 0 )
298 file.setFileName( url.toLocalFile() );
301 if ( file.open( QIODevice::ReadOnly ) )
303 return file.readAll();
308 return missingContent;
311 QMutexLocker locker( &mMutex );
314 if ( mPendingRemoteUrls.contains( path ) )
319 return fetchingContent;
324 for (
QgsTask *task : constActiveTasks )
327 if ( !task->description().endsWith( path ) )
337 if ( waitForTaskFinished( ncfTask ) )
339 if ( mRemoteContentCache.contains( path ) )
342 return *mRemoteContentCache[ path ];
353 if ( mRemoteContentCache.contains( path ) )
356 return *mRemoteContentCache[ path ];
359 mPendingRemoteUrls.insert( path );
361 QNetworkRequest request( url );
363 request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
364 request.setAttribute( QNetworkRequest::CacheSaveControlAttribute,
true );
369 QMutexLocker locker( &mMutex );
371 QNetworkReply *reply = task->
reply();
375 QMetaObject::invokeMethod( const_cast< QgsAbstractContentCacheBase * >( qobject_cast< const QgsAbstractContentCacheBase * >(
this ) ),
"onRemoteContentFetched", Qt::QueuedConnection, Q_ARG( QString, path ), Q_ARG(
bool,
false ) );
379 if ( reply->error() != QNetworkReply::NoError )
381 QgsMessageLog::logMessage( tr(
"%3 request failed [error: %1 - url: %2]" ).arg( reply->errorString(), path, mTypeString ), mTypeString );
387 QVariant status = reply->attribute( QNetworkRequest::HttpStatusCodeAttribute );
388 if ( !status.isNull() && status.toInt() >= 400 )
390 QVariant phrase = reply->attribute( QNetworkRequest::HttpReasonPhraseAttribute );
391 QgsMessageLog::logMessage( tr(
"%4 request error [status: %1 - reason phrase: %2] for %3" ).arg( status.toInt() ).arg( phrase.toString(), path, mTypeString ), mTypeString );
392 mRemoteContentCache.insert( path,
new QByteArray( missingContent ) );
398 mRemoteContentCache.insert( path,
new QByteArray( missingContent ) );
405 mRemoteContentCache.insert( path,
new QByteArray( reply->readAll() ) );
407 QMetaObject::invokeMethod( const_cast< QgsAbstractContentCacheBase * >( qobject_cast< const QgsAbstractContentCacheBase * >(
this ) ),
"onRemoteContentFetched", Qt::QueuedConnection, Q_ARG( QString, path ), Q_ARG(
bool,
true ) );
415 if ( waitForTaskFinished( task ) )
417 if ( mRemoteContentCache.contains( path ) )
420 return *mRemoteContentCache[ path ];
424 return fetchingContent;
429 QMutexLocker locker( &mMutex );
430 mPendingRemoteUrls.remove( url );
432 T *nextEntry = mLeastRecentEntry;
433 while ( T *entry = nextEntry )
435 nextEntry =
static_cast< T *
>( entry->nextEntry );
436 if ( entry->path == url )
438 takeEntryFromList( entry );
439 mEntryLookup.remove( entry->path, entry );
440 mTotalSize -= entry->dataSize();
497 const QString path = entryTemplate->path;
498 T *currentEntry =
nullptr;
499 const QList<T *> entries = mEntryLookup.values( path );
501 for ( T *cacheEntry : entries )
503 if ( cacheEntry->isEqual( entryTemplate ) )
505 if ( mFileModifiedCheckTimeout <= 0 || cacheEntry->fileModifiedLastCheckTimer.hasExpired( mFileModifiedCheckTimeout ) )
507 if ( !modified.isValid() )
508 modified = QFileInfo( path ).lastModified();
510 if ( cacheEntry->fileModified != modified )
513 cacheEntry->fileModifiedLastCheckTimer.restart();
515 currentEntry = cacheEntry;
523 currentEntry = insertCacheEntry( entryTemplate );
527 delete entryTemplate;
528 entryTemplate =
nullptr;
529 takeEntryFromList( currentEntry );
530 if ( !mMostRecentEntry )
532 mMostRecentEntry = currentEntry;
533 mLeastRecentEntry = currentEntry;
537 mMostRecentEntry->nextEntry = currentEntry;
538 currentEntry->previousEntry = mMostRecentEntry;
539 currentEntry->nextEntry =
nullptr;
540 mMostRecentEntry = currentEntry;
555 long mMaxCacheSize = 20000000;
564 T *insertCacheEntry( T *entry )
566 entry->mFileModifiedCheckTimeout = mFileModifiedCheckTimeout;
568 if ( !entry->path.startsWith( QStringLiteral(
"base64:" ) ) )
570 entry->fileModified = QFileInfo( entry->path ).lastModified();
571 entry->fileModifiedLastCheckTimer.start();
574 mEntryLookup.insert( entry->path, entry );
577 if ( !mMostRecentEntry )
579 mLeastRecentEntry = entry;
580 mMostRecentEntry = entry;
581 entry->previousEntry =
nullptr;
582 entry->nextEntry =
nullptr;
586 entry->previousEntry = mMostRecentEntry;
587 entry->nextEntry =
nullptr;
588 mMostRecentEntry->nextEntry = entry;
589 mMostRecentEntry = entry;
600 void takeEntryFromList( T *entry )
607 if ( entry->previousEntry )
609 entry->previousEntry->nextEntry = entry->nextEntry;
613 mLeastRecentEntry =
static_cast< T *
>( entry->nextEntry );
615 if ( entry->nextEntry )
617 entry->nextEntry->previousEntry = entry->previousEntry;
621 mMostRecentEntry =
static_cast< T *
>( entry->previousEntry );
628 void printEntryList()
630 QgsDebugMsg( QStringLiteral(
"****************cache entry list*************************" ) );
631 QgsDebugMsg(
"Cache size: " + QString::number( mTotalSize ) );
632 T *entry = mLeastRecentEntry;
637 entry = entry->nextEntry;
642 QMultiHash< QString, T * > mEntryLookup;
645 int mFileModifiedCheckTimeout = 30000;
649 T *mLeastRecentEntry =
nullptr;
650 T *mMostRecentEntry =
nullptr;
652 mutable QCache< QString, QByteArray > mRemoteContentCache;
653 mutable QSet< QString > mPendingRemoteUrls;
657 friend class TestQgsSvgCache;
658 friend class TestQgsImageCache;
663 #endif // QGSABSTRACTCONTENTCACHE_H QByteArray getContent(const QString &path, const QByteArray &missingContent, const QByteArray &fetchingContent, bool blocking=false) const
Gets the file content corresponding to the given path.
#define QgsSetRequestInitiatorClass(request, _class)
Abstract base class for file content caches, such as SVG or raster image caches.
bool waitForTaskFinished(QgsNetworkContentFetcherTask *task) const
Blocks the current thread until the task finishes or an arbitrary setting maximum wait to 5 seconds...
void fetched()
Emitted when the network content has been fetched, regardless of whether the fetch was successful or ...
QgsAbstractContentCache(QObject *parent=nullptr, const QString &typeString=QString(), long maxCacheSize=20000000, int fileModifiedCheckTimeout=30000)
Constructor for QgsAbstractContentCache, with the specified parent object.
Base class for entries in a QgsAbstractContentCache.
~QgsAbstractContentCache() override
bool waitForFinished(int timeout=30000)
Blocks the current thread until the task finishes or a maximum of timeout milliseconds.
void begun()
Will be emitted by task to indicate its commencement.
void remoteContentFetched(const QString &url)
Emitted when the cache has finished retrieving content from a remote url.
QNetworkReply * reply()
Returns the network reply.
Handles HTTP network content fetching in a background task.
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::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
long addTask(QgsTask *task, int priority=0)
Adds a task to the manager.
Abstract base class for long running background tasks.
Task successfully completed.
void onRemoteContentFetched(const QString &url, bool success) override
Triggered after remote content (i.e.
QList< QgsTask *> activeTasks() const
Returns a list of the active (queued or running) tasks.
Task is currently running.
T * findExistingEntry(T *entryTemplate)
Returns the existing entry from the cache which matches entryTemplate (deleting entryTemplate when do...
QDateTime fileModified
Timestamp when file was last modified.
TaskStatus status() const
Returns the current task status.
A QObject derived base class for QgsAbstractContentCache.
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...
QElapsedTimer fileModifiedLastCheckTimer
Time since last check of file modified date.
bool operator==(const QgsAbstractContentCacheEntry &other) const
void trimToMaximumSize()
Removes the least used cache entries until the maximum cache size is under the predefined size limit...
QString path
Represents the absolute path to a file, a remote URL, or a base64 encoded string. ...