LCOV - code coverage report
Current view: top level - core - qgsabstractcontentcache.h (source / functions) Hit Total Coverage
Test: coverage.info.cleaned Lines: 83 203 40.9 %
Date: 2021-04-10 08:29:14 Functions: 0 0 -
Branches: 0 0 -

           Branch data     Line data    Source code
       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"
      26                 :            : #include "qgsnetworkaccessmanager.h"
      27                 :            : #include "qgsnetworkcontentfetchertask.h"
      28                 :            : 
      29                 :            : #include <QObject>
      30                 :            : #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
      31                 :            : #include <QMutex>
      32                 :            : #else
      33                 :            : #include <QRecursiveMutex>
      34                 :            : #endif
      35                 :            : #include <QCache>
      36                 :            : #include <QSet>
      37                 :            : #include <QDateTime>
      38                 :            : #include <QList>
      39                 :            : #include <QFile>
      40                 :            : #include <QNetworkReply>
      41                 :            : #include <QFileInfo>
      42                 :            : #include <QUrl>
      43                 :            : 
      44                 :            : /**
      45                 :            :  * \class QgsAbstractContentCacheEntry
      46                 :            :  * \ingroup core
      47                 :            :  * \brief Base class for entries in a QgsAbstractContentCache.
      48                 :            :  *
      49                 :            :  * Subclasses must take care to correctly implement the isEqual() method, applying their
      50                 :            :  * own logic for testing extra cache properties (e.g. image size for an image-based cache).
      51                 :            :  *
      52                 :            :  * \since QGIS 3.6
      53                 :            :  */
      54                 :            : class CORE_EXPORT QgsAbstractContentCacheEntry
      55                 :            : {
      56                 :            :   public:
      57                 :            : 
      58                 :            :     /**
      59                 :            :      * Constructor for QgsAbstractContentCacheEntry for an entry relating to the specified \a path.
      60                 :            :      */
      61                 :            :     QgsAbstractContentCacheEntry( const QString &path ) ;
      62                 :            : 
      63                 :         40 :     virtual ~QgsAbstractContentCacheEntry() = default;
      64                 :            : 
      65                 :            :     //! QgsAbstractContentCacheEntry cannot be copied.
      66                 :            :     QgsAbstractContentCacheEntry( const QgsAbstractContentCacheEntry &rh ) = delete;
      67                 :            :     //! QgsAbstractContentCacheEntry cannot be copied.
      68                 :            :     QgsAbstractContentCacheEntry &operator=( const QgsAbstractContentCacheEntry &rh ) = delete;
      69                 :            : 
      70                 :            :     /**
      71                 :            :      * Represents the absolute path to a file, a remote URL, or a base64 encoded string.
      72                 :            :      */
      73                 :            :     QString path;
      74                 :            : 
      75                 :            :     //! Timestamp when file was last modified
      76                 :            :     QDateTime fileModified;
      77                 :            : 
      78                 :            :     //! Time since last check of file modified date
      79                 :            :     QElapsedTimer fileModifiedLastCheckTimer;
      80                 :            : 
      81                 :            :     //! Timeout before re-checking whether the file modified date has changed.
      82                 :            :     int mFileModifiedCheckTimeout = 30000;
      83                 :            : 
      84                 :            :     /**
      85                 :            :      * Entries are kept on a linked list, sorted by last access. This point refers
      86                 :            :      * to the next entry in the cache.
      87                 :            :      */
      88                 :            :     QgsAbstractContentCacheEntry *nextEntry = nullptr;
      89                 :            : 
      90                 :            :     /**
      91                 :            :      * Entries are kept on a linked list, sorted by last access. This point refers
      92                 :            :      * to the previous entry in the cache.
      93                 :            :      */
      94                 :            :     QgsAbstractContentCacheEntry *previousEntry = nullptr;
      95                 :            : 
      96                 :            :     bool operator==( const QgsAbstractContentCacheEntry &other ) const
      97                 :            :     {
      98                 :            :       return other.path == path;
      99                 :            :     }
     100                 :            : 
     101                 :            :     /**
     102                 :            :      * Returns the memory usage in bytes for the entry.
     103                 :            :      */
     104                 :            :     virtual int dataSize() const = 0;
     105                 :            : 
     106                 :            :     /**
     107                 :            :      * Dumps debugging strings containing the item's properties. For testing purposes only.
     108                 :            :      */
     109                 :            :     virtual void dump() const = 0;
     110                 :            : 
     111                 :            :   protected:
     112                 :            : 
     113                 :            :     /**
     114                 :            :      * Tests whether this entry matches another entry. Subclasses must take care to check
     115                 :            :      * that the type of \a other is of a matching class, and then test extra cache-specific
     116                 :            :      * properties, such as image size.
     117                 :            :      */
     118                 :            :     virtual bool isEqual( const QgsAbstractContentCacheEntry *other ) const = 0;
     119                 :            : 
     120                 :            :   private:
     121                 :            : #ifdef SIP_RUN
     122                 :            :     QgsAbstractContentCacheEntry( const QgsAbstractContentCacheEntry &rh );
     123                 :            : #endif
     124                 :            : 
     125                 :            : };
     126                 :            : 
     127                 :            : /**
     128                 :            :  * \class QgsAbstractContentCacheBase
     129                 :            :  * \ingroup core
     130                 :            :  *
     131                 :            :  * \brief A QObject derived base class for QgsAbstractContentCache.
     132                 :            :  *
     133                 :            :  * Required because template based class (such as QgsAbstractContentCache) cannot use the Q_OBJECT macro.
     134                 :            :  *
     135                 :            :  * \since QGIS 3.6
     136                 :            :  */
     137                 :         15 : class CORE_EXPORT QgsAbstractContentCacheBase: public QObject
     138                 :            : {
     139                 :          0 :     Q_OBJECT
     140                 :            : 
     141                 :            :   public:
     142                 :            : 
     143                 :            :     /**
     144                 :            :      * Constructor for QgsAbstractContentCacheBase, with the specified \a parent object.
     145                 :            :      */
     146                 :            :     QgsAbstractContentCacheBase( QObject *parent );
     147                 :            : 
     148                 :            :   signals:
     149                 :            : 
     150                 :            :     /**
     151                 :            :      * Emitted when the cache has finished retrieving content from a remote \a url.
     152                 :            :      */
     153                 :            :     void remoteContentFetched( const QString &url );
     154                 :            : 
     155                 :            :   protected:
     156                 :            : 
     157                 :            :     /**
     158                 :            :      * Runs additional checks on a network \a reply to ensure that the reply content is
     159                 :            :      * consistent with that required by the cache.
     160                 :            :      */
     161                 :          0 :     virtual bool checkReply( QNetworkReply *reply, const QString &path ) const
     162                 :            :     {
     163                 :            :       Q_UNUSED( reply )
     164                 :          0 :       Q_UNUSED( path )
     165                 :          0 :       return true;
     166                 :            :     }
     167                 :            : 
     168                 :            :   protected slots:
     169                 :            : 
     170                 :            :     /**
     171                 :            :      * Triggered after remote content (i.e. HTTP linked content at the given \a url) has been fetched.
     172                 :            :      *
     173                 :            :      * The \a success argument will be TRUE if the content was successfully fetched, or FALSE if
     174                 :            :      * it was not fetched successfully.
     175                 :            :      */
     176                 :            :     virtual void onRemoteContentFetched( const QString &url, bool success );
     177                 :            : 
     178                 :            : };
     179                 :            : 
     180                 :            : #ifndef SIP_RUN
     181                 :            : 
     182                 :            : /**
     183                 :            :  * \class QgsAbstractContentCache
     184                 :            :  * \ingroup core
     185                 :            :  *
     186                 :            :  * \brief Abstract base class for file content caches, such as SVG or raster image caches.
     187                 :            :  *
     188                 :            :  * Handles trimming the maximum cached content size to a desired limit, fetching remote
     189                 :            :  * content (via HTTP), and automatically invalidating cached content when the corresponding
     190                 :            :  * file is changed.
     191                 :            :  *
     192                 :            :  * \note Not available in Python bindings.
     193                 :            :  * \since QGIS 3.6
     194                 :            :  */
     195                 :            : template<class T>
     196                 :            : class CORE_EXPORT QgsAbstractContentCache : public QgsAbstractContentCacheBase
     197                 :            : {
     198                 :            : 
     199                 :            :   public:
     200                 :            : 
     201                 :            :     /**
     202                 :            :      * Constructor for QgsAbstractContentCache, with the specified \a parent object.
     203                 :            :      *
     204                 :            :      * The \a maxCacheSize argument dictates the maximum allowable total size of the cache,
     205                 :            :      * in bytes. This in turn dictates the maximum allowable size for caching individual
     206                 :            :      * entries.
     207                 :            :      *
     208                 :            :      * The \a fileModifiedCheckTimeout dictates the minimum time (in milliseconds) between
     209                 :            :      * consecutive checks of whether a file's content has been modified (and existing
     210                 :            :      * cache entries should be discarded).
     211                 :            :      */
     212                 :         15 :     QgsAbstractContentCache( QObject *parent SIP_TRANSFERTHIS = nullptr,
     213                 :            :                              const QString &typeString = QString(),
     214                 :            :                              long maxCacheSize = 20000000,
     215                 :            :                              int fileModifiedCheckTimeout = 30000 )
     216                 :         15 :       : QgsAbstractContentCacheBase( parent )
     217                 :            : #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
     218                 :            :       , mMutex( QMutex::Recursive )
     219                 :            : #endif
     220                 :         15 :       , mMaxCacheSize( maxCacheSize )
     221                 :         15 :       , mFileModifiedCheckTimeout( fileModifiedCheckTimeout )
     222                 :         15 :       , mTypeString( typeString.isEmpty() ? QObject::tr( "Content" ) : typeString )
     223                 :         30 :     {
     224                 :         15 :     }
     225                 :            : 
     226                 :         15 :     ~QgsAbstractContentCache() override
     227                 :         15 :     {
     228                 :         15 :       qDeleteAll( mEntryLookup );
     229                 :         15 :     }
     230                 :            : 
     231                 :            :   protected:
     232                 :            : 
     233                 :            :     /**
     234                 :            :      * Removes the least used cache entries until the maximum cache size is under the predefined size limit.
     235                 :            :      */
     236                 :         30 :     void trimToMaximumSize()
     237                 :            :     {
     238                 :            :       //only one entry in cache
     239                 :         30 :       if ( mLeastRecentEntry == mMostRecentEntry )
     240                 :            :       {
     241                 :          5 :         return;
     242                 :            :       }
     243                 :         25 :       T *entry = mLeastRecentEntry;
     244                 :         25 :       while ( entry && ( mTotalSize > mMaxCacheSize ) )
     245                 :            :       {
     246                 :          0 :         T *bkEntry = entry;
     247                 :          0 :         entry = static_cast< T * >( entry->nextEntry );
     248                 :            : 
     249                 :          0 :         takeEntryFromList( bkEntry );
     250                 :          0 :         mEntryLookup.remove( bkEntry->path, bkEntry );
     251                 :          0 :         mTotalSize -= bkEntry->dataSize();
     252                 :          0 :         delete bkEntry;
     253                 :            :       }
     254                 :         30 :     }
     255                 :            : 
     256                 :            :     /**
     257                 :            :      * Gets the file content corresponding to the given \a path.
     258                 :            :      *
     259                 :            :      * \a path may be a local file, remote (HTTP) url, or a base 64 encoded string (with a "base64:" prefix).
     260                 :            :      *
     261                 :            :      * The \a missingContent byte array is returned if the \a path could not be resolved or is broken. If
     262                 :            :      * the \a path corresponds to a remote URL, then \a fetchingContent will be returned while the content
     263                 :            :      * is in the process of being fetched.
     264                 :            :      * The \a blocking boolean forces to wait for loading before returning result. The content is loaded
     265                 :            :      * in the same thread to ensure provided the remote content. WARNING: the \a blocking parameter must NEVER
     266                 :            :      * be TRUE from GUI based applications (like the main QGIS application) or crashes will result. Only for
     267                 :            :      * use in external scripts or QGIS server.
     268                 :            :      */
     269                 :         70 :     QByteArray getContent( const QString &path, const QByteArray &missingContent, const QByteArray &fetchingContent, bool blocking = false ) const
     270                 :            :     {
     271                 :            :       // is it a path to local file?
     272                 :         70 :       QFile file( path );
     273                 :         70 :       if ( file.exists() )
     274                 :            :       {
     275                 :          0 :         if ( file.open( QIODevice::ReadOnly ) )
     276                 :            :         {
     277                 :          0 :           return file.readAll();
     278                 :            :         }
     279                 :            :         else
     280                 :            :         {
     281                 :          0 :           return missingContent;
     282                 :            :         }
     283                 :            :       }
     284                 :            : 
     285                 :            :       // maybe it's an embedded base64 string
     286                 :         70 :       if ( path.startsWith( QLatin1String( "base64:" ), Qt::CaseInsensitive ) )
     287                 :            :       {
     288                 :          0 :         QByteArray base64 = path.mid( 7 ).toLocal8Bit(); // strip 'base64:' prefix
     289                 :          0 :         return QByteArray::fromBase64( base64, QByteArray::OmitTrailingEquals );
     290                 :          0 :       }
     291                 :            : 
     292                 :            :       // maybe it's a url...
     293                 :         70 :       if ( !path.contains( QLatin1String( "://" ) ) ) // otherwise short, relative SVG paths might be considered URLs
     294                 :            :       {
     295                 :         70 :         return missingContent;
     296                 :            :       }
     297                 :            : 
     298                 :          0 :       QUrl url( path );
     299                 :          0 :       if ( !url.isValid() )
     300                 :            :       {
     301                 :          0 :         return missingContent;
     302                 :            :       }
     303                 :            : 
     304                 :            :       // check whether it's a url pointing to a local file
     305                 :          0 :       if ( url.scheme().compare( QLatin1String( "file" ), Qt::CaseInsensitive ) == 0 )
     306                 :            :       {
     307                 :          0 :         file.setFileName( url.toLocalFile() );
     308                 :          0 :         if ( file.exists() )
     309                 :            :         {
     310                 :          0 :           if ( file.open( QIODevice::ReadOnly ) )
     311                 :            :           {
     312                 :          0 :             return file.readAll();
     313                 :            :           }
     314                 :          0 :         }
     315                 :            : 
     316                 :            :         // not found...
     317                 :          0 :         return missingContent;
     318                 :            :       }
     319                 :            : 
     320                 :          0 :       QMutexLocker locker( &mMutex );
     321                 :            : 
     322                 :            :       // already a request in progress for this url
     323                 :          0 :       if ( mPendingRemoteUrls.contains( path ) )
     324                 :            :       {
     325                 :            :         // it's a non blocking request so return fetching content
     326                 :          0 :         if ( !blocking )
     327                 :            :         {
     328                 :          0 :           return fetchingContent;
     329                 :            :         }
     330                 :            : 
     331                 :            :         // it's a blocking request so try to find the task and wait for task finished
     332                 :          0 :         const auto constActiveTasks = QgsApplication::taskManager()->activeTasks();
     333                 :          0 :         for ( QgsTask *task : constActiveTasks )
     334                 :            :         {
     335                 :            :           // the network content fetcher task's description ends with the path
     336                 :          0 :           if ( !task->description().endsWith( path ) )
     337                 :            :           {
     338                 :          0 :             continue;
     339                 :            :           }
     340                 :            : 
     341                 :            :           // cast task to network content fetcher task
     342                 :          0 :           QgsNetworkContentFetcherTask *ncfTask = qobject_cast<QgsNetworkContentFetcherTask *>( task );
     343                 :          0 :           if ( ncfTask )
     344                 :            :           {
     345                 :            :             // wait for task finished
     346                 :          0 :             if ( waitForTaskFinished( ncfTask ) )
     347                 :            :             {
     348                 :          0 :               if ( mRemoteContentCache.contains( path ) )
     349                 :            :               {
     350                 :            :                 // We got the file!
     351                 :          0 :                 return *mRemoteContentCache[ path ];
     352                 :            :               }
     353                 :          0 :             }
     354                 :          0 :           }
     355                 :            :           // task found, no needs to continue
     356                 :          0 :           break;
     357                 :            :         }
     358                 :            :         // if no content returns the content is probably in remote content cache
     359                 :            :         // or a new task will be created
     360                 :          0 :       }
     361                 :            : 
     362                 :          0 :       if ( mRemoteContentCache.contains( path ) )
     363                 :            :       {
     364                 :            :         // already fetched this content - phew. Just return what we already got.
     365                 :          0 :         return *mRemoteContentCache[ path ];
     366                 :            :       }
     367                 :            : 
     368                 :          0 :       mPendingRemoteUrls.insert( path );
     369                 :            :       //fire up task to fetch content in background
     370                 :          0 :       QNetworkRequest request( url );
     371                 :          0 :       QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsAbstractContentCache<%1>" ).arg( mTypeString ) );
     372                 :          0 :       request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
     373                 :          0 :       request.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true );
     374                 :            : 
     375                 :          0 :       QgsNetworkContentFetcherTask *task = new QgsNetworkContentFetcherTask( request );
     376                 :          0 :       connect( task, &QgsNetworkContentFetcherTask::fetched, this, [this, task, path, missingContent]
     377                 :            :       {
     378                 :          0 :         QMutexLocker locker( &mMutex );
     379                 :            : 
     380                 :          0 :         QNetworkReply *reply = task->reply();
     381                 :          0 :         if ( !reply )
     382                 :            :         {
     383                 :            :           // canceled
     384                 :          0 :           QMetaObject::invokeMethod( const_cast< QgsAbstractContentCacheBase * >( qobject_cast< const QgsAbstractContentCacheBase * >( this ) ), "onRemoteContentFetched", Qt::QueuedConnection, Q_ARG( QString, path ), Q_ARG( bool, false ) );
     385                 :          0 :           return;
     386                 :            :         }
     387                 :            : 
     388                 :          0 :         if ( reply->error() != QNetworkReply::NoError )
     389                 :            :         {
     390                 :          0 :           QgsMessageLog::logMessage( tr( "%3 request failed [error: %1 - url: %2]" ).arg( reply->errorString(), path, mTypeString ), mTypeString );
     391                 :          0 :           return;
     392                 :            :         }
     393                 :            : 
     394                 :          0 :         bool ok = true;
     395                 :            : 
     396                 :          0 :         QVariant status = reply->attribute( QNetworkRequest::HttpStatusCodeAttribute );
     397                 :          0 :         if ( !status.isNull() && status.toInt() >= 400 )
     398                 :            :         {
     399                 :          0 :           QVariant phrase = reply->attribute( QNetworkRequest::HttpReasonPhraseAttribute );
     400                 :          0 :           QgsMessageLog::logMessage( tr( "%4 request error [status: %1 - reason phrase: %2] for %3" ).arg( status.toInt() ).arg( phrase.toString(), path, mTypeString ), mTypeString );
     401                 :          0 :           mRemoteContentCache.insert( path, new QByteArray( missingContent ) );
     402                 :          0 :           ok = false;
     403                 :          0 :         }
     404                 :            : 
     405                 :          0 :         if ( !checkReply( reply, path ) )
     406                 :            :         {
     407                 :          0 :           mRemoteContentCache.insert( path, new QByteArray( missingContent ) );
     408                 :          0 :           ok = false;
     409                 :          0 :         }
     410                 :            : 
     411                 :          0 :         if ( ok )
     412                 :            :         {
     413                 :            :           // read the content data
     414                 :          0 :           const QByteArray ba = reply->readAll();
     415                 :            : 
     416                 :            :           // because of the fragility listed below in waitForTaskFinished, this slot may get called twice. In that case
     417                 :            :           // the second time will have an empty reply (we've already read it all...)
     418                 :          0 :           if ( !ba.isEmpty() )
     419                 :          0 :             mRemoteContentCache.insert( path, new QByteArray( ba ) );
     420                 :          0 :         }
     421                 :          0 :         QMetaObject::invokeMethod( const_cast< QgsAbstractContentCacheBase * >( qobject_cast< const QgsAbstractContentCacheBase * >( this ) ), "onRemoteContentFetched", Qt::QueuedConnection, Q_ARG( QString, path ), Q_ARG( bool, true ) );
     422                 :          0 :       } );
     423                 :            : 
     424                 :          0 :       QgsApplication::taskManager()->addTask( task );
     425                 :            : 
     426                 :            :       // if blocking, wait for finished
     427                 :          0 :       if ( blocking )
     428                 :            :       {
     429                 :          0 :         if ( waitForTaskFinished( task ) )
     430                 :            :         {
     431                 :          0 :           if ( mRemoteContentCache.contains( path ) )
     432                 :            :           {
     433                 :            :             // We got the file!
     434                 :          0 :             return *mRemoteContentCache[ path ];
     435                 :            :           }
     436                 :          0 :         }
     437                 :          0 :       }
     438                 :          0 :       return fetchingContent;
     439                 :         70 :     }
     440                 :            : 
     441                 :          0 :     void onRemoteContentFetched( const QString &url, bool success ) override
     442                 :            :     {
     443                 :          0 :       QMutexLocker locker( &mMutex );
     444                 :          0 :       mPendingRemoteUrls.remove( url );
     445                 :            : 
     446                 :          0 :       T *nextEntry = mLeastRecentEntry;
     447                 :          0 :       while ( T *entry = nextEntry )
     448                 :            :       {
     449                 :          0 :         nextEntry = static_cast< T * >( entry->nextEntry );
     450                 :          0 :         if ( entry->path == url )
     451                 :            :         {
     452                 :          0 :           takeEntryFromList( entry );
     453                 :          0 :           mEntryLookup.remove( entry->path, entry );
     454                 :          0 :           mTotalSize -= entry->dataSize();
     455                 :          0 :           delete entry;
     456                 :          0 :         }
     457                 :            :       }
     458                 :            : 
     459                 :          0 :       if ( success )
     460                 :          0 :         emit remoteContentFetched( url );
     461                 :          0 :     }
     462                 :            : 
     463                 :            :     /**
     464                 :            :      * Blocks the current thread until the \a task finishes (or user's preset network timeout expires)
     465                 :            :      *
     466                 :            :      * \warning this method must NEVER be used from GUI based applications (like the main QGIS application)
     467                 :            :      * or crashes will result. Only for use in external scripts or QGIS server.
     468                 :            :      *
     469                 :            :      * The result will be FALSE if the wait timed out and TRUE in any other case.
     470                 :            :      *
     471                 :            :      * \since QGIS 3.10
     472                 :            :      */
     473                 :          0 :     bool waitForTaskFinished( QgsNetworkContentFetcherTask *task ) const
     474                 :            :     {
     475                 :            :       // Wait up to timeout seconds for task finished
     476                 :          0 :       if ( task->waitForFinished( QgsNetworkAccessManager::timeout() ) )
     477                 :            :       {
     478                 :            :         // The wait did not time out
     479                 :            :         // Third step, check status as complete
     480                 :          0 :         if ( task->status() == QgsTask::Complete )
     481                 :            :         {
     482                 :            :           // Fourth step, force the signal fetched to be sure reply has been checked
     483                 :            : 
     484                 :            :           // ARGH this is BAD BAD BAD. The connection will get called twice as a result!!!
     485                 :          0 :           task->fetched();
     486                 :          0 :           return true;
     487                 :            :         }
     488                 :          0 :       }
     489                 :          0 :       return false;
     490                 :          0 :     }
     491                 :            : 
     492                 :            :     /**
     493                 :            :      * Returns the existing entry from the cache which matches \a entryTemplate (deleting entryTemplate when done), or
     494                 :            :      * if no existing entry is found then \a entryTemplate is transferred to the cache and returned.
     495                 :            :      *
     496                 :            :      * I.e. either way ownership of \a entryTemplate is transferred by calling this method.
     497                 :            :      *
     498                 :            :      * If an existing entry was found, then the corresponding file MAY be rechecked for changes (only if a suitable
     499                 :            :      * time has occurred since the last check).
     500                 :            :      */
     501                 :         40 :     T *findExistingEntry( T *entryTemplate )
     502                 :            :     {
     503                 :            :       //search entries in mEntryLookup
     504                 :         40 :       const QString path = entryTemplate->path;
     505                 :         40 :       T *currentEntry = nullptr;
     506                 :         40 :       const QList<T *> entries = mEntryLookup.values( path );
     507                 :         40 :       QDateTime modified;
     508                 :         45 :       for ( T *cacheEntry : entries )
     509                 :            :       {
     510                 :         15 :         if ( cacheEntry->isEqual( entryTemplate ) )
     511                 :            :         {
     512                 :         10 :           if ( mFileModifiedCheckTimeout <= 0 || cacheEntry->fileModifiedLastCheckTimer.hasExpired( mFileModifiedCheckTimeout ) )
     513                 :            :           {
     514                 :          0 :             if ( !modified.isValid() )
     515                 :          0 :               modified = QFileInfo( path ).lastModified();
     516                 :            : 
     517                 :          0 :             if ( cacheEntry->fileModified != modified )
     518                 :          0 :               continue;
     519                 :            :             else
     520                 :          0 :               cacheEntry->fileModifiedLastCheckTimer.restart();
     521                 :          0 :           }
     522                 :         10 :           currentEntry = cacheEntry;
     523                 :         10 :           break;
     524                 :            :         }
     525                 :            :       }
     526                 :            : 
     527                 :            :       //if not found: insert entryTemplate as a new entry
     528                 :         40 :       if ( !currentEntry )
     529                 :            :       {
     530                 :         30 :         currentEntry = insertCacheEntry( entryTemplate );
     531                 :         30 :       }
     532                 :            :       else
     533                 :            :       {
     534                 :         10 :         delete entryTemplate;
     535                 :         10 :         entryTemplate = nullptr;
     536                 :            :         ( void )entryTemplate;
     537                 :         10 :         takeEntryFromList( currentEntry );
     538                 :         10 :         if ( !mMostRecentEntry ) //list is empty
     539                 :            :         {
     540                 :          5 :           mMostRecentEntry = currentEntry;
     541                 :          5 :           mLeastRecentEntry = currentEntry;
     542                 :          5 :         }
     543                 :            :         else
     544                 :            :         {
     545                 :          5 :           mMostRecentEntry->nextEntry = currentEntry;
     546                 :          5 :           currentEntry->previousEntry = mMostRecentEntry;
     547                 :          5 :           currentEntry->nextEntry = nullptr;
     548                 :          5 :           mMostRecentEntry = currentEntry;
     549                 :            :         }
     550                 :            :       }
     551                 :            : 
     552                 :            :       //debugging
     553                 :            :       //printEntryList();
     554                 :            : 
     555                 :         40 :       return currentEntry;
     556                 :         40 :     }
     557                 :            : #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
     558                 :            :     mutable QMutex mMutex;
     559                 :            : #else
     560                 :            :     mutable QRecursiveMutex mMutex;
     561                 :            : #endif
     562                 :            :     //! Estimated total size of all cached content
     563                 :         15 :     long mTotalSize = 0;
     564                 :            : 
     565                 :            :     //! Maximum cache size
     566                 :            :     long mMaxCacheSize = 20000000;
     567                 :            : 
     568                 :            :   private:
     569                 :            : 
     570                 :            :     /**
     571                 :            :      * Inserts a new \a entry into the cache.
     572                 :            :      *
     573                 :            :      * Ownership of \a entry is transferred to the cache.
     574                 :            :      */
     575                 :         30 :     T *insertCacheEntry( T *entry )
     576                 :            :     {
     577                 :         30 :       entry->mFileModifiedCheckTimeout = mFileModifiedCheckTimeout;
     578                 :            : 
     579                 :         30 :       if ( !entry->path.startsWith( QLatin1String( "base64:" ) ) )
     580                 :            :       {
     581                 :         30 :         entry->fileModified = QFileInfo( entry->path ).lastModified();
     582                 :         30 :         entry->fileModifiedLastCheckTimer.start();
     583                 :         30 :       }
     584                 :            : 
     585                 :         30 :       mEntryLookup.insert( entry->path, entry );
     586                 :            : 
     587                 :            :       //insert to most recent place in entry list
     588                 :         30 :       if ( !mMostRecentEntry ) //inserting first entry
     589                 :            :       {
     590                 :          5 :         mLeastRecentEntry = entry;
     591                 :          5 :         mMostRecentEntry = entry;
     592                 :          5 :         entry->previousEntry = nullptr;
     593                 :          5 :         entry->nextEntry = nullptr;
     594                 :          5 :       }
     595                 :            :       else
     596                 :            :       {
     597                 :         25 :         entry->previousEntry = mMostRecentEntry;
     598                 :         25 :         entry->nextEntry = nullptr;
     599                 :         25 :         mMostRecentEntry->nextEntry = entry;
     600                 :         25 :         mMostRecentEntry = entry;
     601                 :            :       }
     602                 :            : 
     603                 :         30 :       trimToMaximumSize();
     604                 :         30 :       return entry;
     605                 :          0 :     }
     606                 :            : 
     607                 :            : 
     608                 :            :     /**
     609                 :            :      * Removes an \a entry from the ordered list (but does not delete the entry itself).
     610                 :            :      */
     611                 :         10 :     void takeEntryFromList( T *entry )
     612                 :            :     {
     613                 :         10 :       if ( !entry )
     614                 :            :       {
     615                 :          0 :         return;
     616                 :            :       }
     617                 :            : 
     618                 :         10 :       if ( entry->previousEntry )
     619                 :            :       {
     620                 :          5 :         entry->previousEntry->nextEntry = entry->nextEntry;
     621                 :          5 :       }
     622                 :            :       else
     623                 :            :       {
     624                 :          5 :         mLeastRecentEntry = static_cast< T * >( entry->nextEntry );
     625                 :            :       }
     626                 :         10 :       if ( entry->nextEntry )
     627                 :            :       {
     628                 :          0 :         entry->nextEntry->previousEntry = entry->previousEntry;
     629                 :          0 :       }
     630                 :            :       else
     631                 :            :       {
     632                 :         10 :         mMostRecentEntry = static_cast< T * >( entry->previousEntry );
     633                 :            :       }
     634                 :         10 :     }
     635                 :            : 
     636                 :            :     /**
     637                 :            :      * Prints a list of all entries in the cache. For debugging purposes only.
     638                 :            :      */
     639                 :            :     void printEntryList()
     640                 :            :     {
     641                 :            :       QgsDebugMsg( QStringLiteral( "****************cache entry list*************************" ) );
     642                 :            :       QgsDebugMsg( "Cache size: " + QString::number( mTotalSize ) );
     643                 :            :       T *entry = mLeastRecentEntry;
     644                 :            :       while ( entry )
     645                 :            :       {
     646                 :            :         QgsDebugMsg( QStringLiteral( "***Entry:" ) );
     647                 :            :         entry->dump();
     648                 :            :         entry = entry->nextEntry;
     649                 :            :       }
     650                 :            :     }
     651                 :            : 
     652                 :            :     //! Entry pointers accessible by file name
     653                 :            :     QMultiHash< QString, T * > mEntryLookup;
     654                 :            : 
     655                 :            :     //! Minimum time (in ms) between consecutive file modified time checks
     656                 :            :     int mFileModifiedCheckTimeout = 30000;
     657                 :            : 
     658                 :            :     //The content cache keeps the entries on a double connected list, moving the current entry to the front.
     659                 :            :     //That way, removing entries for more space can start with the least used objects.
     660                 :         15 :     T *mLeastRecentEntry = nullptr;
     661                 :         15 :     T *mMostRecentEntry = nullptr;
     662                 :            : 
     663                 :            :     mutable QCache< QString, QByteArray > mRemoteContentCache;
     664                 :            :     mutable QSet< QString > mPendingRemoteUrls;
     665                 :            : 
     666                 :            :     QString mTypeString;
     667                 :            : 
     668                 :            :     friend class TestQgsSvgCache;
     669                 :            :     friend class TestQgsImageCache;
     670                 :            : };
     671                 :            : 
     672                 :            : #endif
     673                 :            : 
     674                 :            : #endif // QGSABSTRACTCONTENTCACHE_H

Generated by: LCOV version 1.14