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
|