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

Generated by: LCOV version 1.14