QGIS API Documentation 3.41.0-Master (1deb1daf037)
Loading...
Searching...
No Matches
qgsabstractcontentcache.h
Go to the documentation of this file.
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"
28#include "qgsvariantutils.h"
29
30#include <QObject>
31#include <QRecursiveMutex>
32#include <QCache>
33#include <QSet>
34#include <QDateTime>
35#include <QList>
36#include <QFile>
37#include <QNetworkReply>
38#include <QFileInfo>
39#include <QUrl>
40
52{
53 public:
54
58 QgsAbstractContentCacheEntry( const QString &path ) ;
59
60 virtual ~QgsAbstractContentCacheEntry() = default;
61
64
68 QString path;
69
71 QDateTime fileModified;
72
75
77 int mFileModifiedCheckTimeout = 30000;
78
83 QgsAbstractContentCacheEntry *nextEntry = nullptr;
84
89 QgsAbstractContentCacheEntry *previousEntry = nullptr;
90
91 bool operator==( const QgsAbstractContentCacheEntry &other ) const
92 {
93 return other.path == path;
94 }
95
99 virtual int dataSize() const = 0;
100
104 virtual void dump() const = 0;
105
106 protected:
107
113 virtual bool isEqual( const QgsAbstractContentCacheEntry *other ) const = 0;
114
115 private:
116#ifdef SIP_RUN
118#endif
119
120};
121
132class CORE_EXPORT QgsAbstractContentCacheBase: public QObject
133{
134 Q_OBJECT
135
136 public:
137
141 QgsAbstractContentCacheBase( QObject *parent );
142
157 static bool parseBase64DataUrl( const QString &path, QString *mimeType SIP_OUT = nullptr, QString *data SIP_OUT = nullptr );
158
159
174 static bool parseEmbeddedStringData( const QString &path, QString *mimeType SIP_OUT = nullptr, QString *data SIP_OUT = nullptr );
175
181 static bool isBase64Data( const QString &path );
182
183 signals:
184
188 void remoteContentFetched( const QString &url );
189
190 protected:
191
196 virtual bool checkReply( QNetworkReply *reply, const QString &path ) const
197 {
198 Q_UNUSED( reply )
199 Q_UNUSED( path )
200 return true;
201 }
202
203 protected slots:
204
211 virtual void onRemoteContentFetched( const QString &url, bool success );
212
213};
214
215#ifndef SIP_RUN
216
230template<class T>
232{
233
234 public:
235
247 QgsAbstractContentCache( QObject *parent SIP_TRANSFERTHIS = nullptr,
248 const QString &typeString = QString(),
249 long maxCacheSize = 20000000,
250 int fileModifiedCheckTimeout = 30000 )
252 , mMaxCacheSize( maxCacheSize )
253 , mFileModifiedCheckTimeout( fileModifiedCheckTimeout )
254 , mTypeString( typeString.isEmpty() ? QObject::tr( "Content" ) : typeString )
255 {
256 }
257
259 {
260 qDeleteAll( mEntryLookup );
261 }
262
263 protected:
264
269 {
270 //only one entry in cache
271 if ( mLeastRecentEntry == mMostRecentEntry )
272 {
273 return;
274 }
275 T *entry = mLeastRecentEntry;
276 while ( entry && ( mTotalSize > mMaxCacheSize ) )
277 {
278 T *bkEntry = entry;
279 entry = static_cast< T * >( entry->nextEntry );
280
281 takeEntryFromList( bkEntry );
282 mEntryLookup.remove( bkEntry->path, bkEntry );
283 mTotalSize -= bkEntry->dataSize();
284 delete bkEntry;
285 }
286 }
287
301 QByteArray getContent( const QString &path, const QByteArray &missingContent, const QByteArray &fetchingContent, bool blocking = false ) const;
302
303 void onRemoteContentFetched( const QString &url, bool success ) override
304 {
305 const QMutexLocker locker( &mMutex );
306 mPendingRemoteUrls.remove( url );
307
308 T *nextEntry = mLeastRecentEntry;
309 while ( T *entry = nextEntry )
310 {
311 nextEntry = static_cast< T * >( entry->nextEntry );
312 if ( entry->path == url )
313 {
314 takeEntryFromList( entry );
315 mEntryLookup.remove( entry->path, entry );
316 mTotalSize -= entry->dataSize();
317 delete entry;
318 }
319 }
320
321 if ( success )
322 emit remoteContentFetched( url );
323 }
324
336 {
337 // Wait up to timeout seconds for task finished
339 {
340 // The wait did not time out
341 // Third step, check status as complete
342 if ( task->status() == QgsTask::Complete )
343 {
344 // Fourth step, force the signal fetched to be sure reply has been checked
345
346 // ARGH this is BAD BAD BAD. The connection will get called twice as a result!!!
347 task->fetched();
348 return true;
349 }
350 }
351 return false;
352 }
353
363 T *findExistingEntry( T *entryTemplate )
364 {
365 //search entries in mEntryLookup
366 const QString path = entryTemplate->path;
367 T *currentEntry = nullptr;
368 const QList<T *> entries = mEntryLookup.values( path );
369 QDateTime modified;
370 for ( T *cacheEntry : entries )
371 {
372 if ( cacheEntry->isEqual( entryTemplate ) )
373 {
374 if ( mFileModifiedCheckTimeout <= 0 || cacheEntry->fileModifiedLastCheckTimer.hasExpired( mFileModifiedCheckTimeout ) )
375 {
376 if ( !modified.isValid() )
377 modified = QFileInfo( path ).lastModified();
378
379 if ( cacheEntry->fileModified != modified )
380 continue;
381 else
382 cacheEntry->fileModifiedLastCheckTimer.restart();
383 }
384 currentEntry = cacheEntry;
385 break;
386 }
387 }
388
389 //if not found: insert entryTemplate as a new entry
390 if ( !currentEntry )
391 {
392 currentEntry = insertCacheEntry( entryTemplate );
393 }
394 else
395 {
396 delete entryTemplate;
397 entryTemplate = nullptr;
398 ( void )entryTemplate;
399 takeEntryFromList( currentEntry );
400 if ( !mMostRecentEntry ) //list is empty
401 {
402 mMostRecentEntry = currentEntry;
403 mLeastRecentEntry = currentEntry;
404 }
405 else
406 {
407 mMostRecentEntry->nextEntry = currentEntry;
408 currentEntry->previousEntry = mMostRecentEntry;
409 currentEntry->nextEntry = nullptr;
410 mMostRecentEntry = currentEntry;
411 }
412 }
413
414 //debugging
415 //printEntryList();
416
417 return currentEntry;
418 }
419 mutable QRecursiveMutex mMutex;
420
422 long mTotalSize = 0;
423
425 long mMaxCacheSize = 20000000;
426
427 private:
428
434 T *insertCacheEntry( T *entry )
435 {
436 entry->mFileModifiedCheckTimeout = mFileModifiedCheckTimeout;
437
438 if ( !entry->path.startsWith( QLatin1String( "base64:" ) ) )
439 {
440 entry->fileModified = QFileInfo( entry->path ).lastModified();
441 entry->fileModifiedLastCheckTimer.start();
442 }
443
444 mEntryLookup.insert( entry->path, entry );
445
446 //insert to most recent place in entry list
447 if ( !mMostRecentEntry ) //inserting first entry
448 {
449 mLeastRecentEntry = entry;
450 mMostRecentEntry = entry;
451 entry->previousEntry = nullptr;
452 entry->nextEntry = nullptr;
453 }
454 else
455 {
456 entry->previousEntry = mMostRecentEntry;
457 entry->nextEntry = nullptr;
458 mMostRecentEntry->nextEntry = entry;
459 mMostRecentEntry = entry;
460 }
461
462 trimToMaximumSize();
463 return entry;
464 }
465
466
470 void takeEntryFromList( T *entry )
471 {
472 if ( !entry )
473 {
474 return;
475 }
476
477 if ( entry->previousEntry )
478 {
479 entry->previousEntry->nextEntry = entry->nextEntry;
480 }
481 else
482 {
483 mLeastRecentEntry = static_cast< T * >( entry->nextEntry );
484 }
485 if ( entry->nextEntry )
486 {
487 entry->nextEntry->previousEntry = entry->previousEntry;
488 }
489 else
490 {
491 mMostRecentEntry = static_cast< T * >( entry->previousEntry );
492 }
493 }
494
498 void printEntryList()
499 {
500 QgsDebugMsgLevel( QStringLiteral( "****************cache entry list*************************" ), 1 );
501 QgsDebugMsgLevel( "Cache size: " + QString::number( mTotalSize ), 1 );
502 T *entry = mLeastRecentEntry;
503 while ( entry )
504 {
505 QgsDebugMsgLevel( QStringLiteral( "***Entry:" ), 1 );
506 entry->dump();
507 entry = static_cast< T * >( entry->nextEntry );
508 }
509 }
510
512 QMultiHash< QString, T * > mEntryLookup;
513
515 int mFileModifiedCheckTimeout = 30000;
516
517 //The content cache keeps the entries on a double connected list, moving the current entry to the front.
518 //That way, removing entries for more space can start with the least used objects.
519 T *mLeastRecentEntry = nullptr;
520 T *mMostRecentEntry = nullptr;
521
522 mutable QCache< QString, QByteArray > mRemoteContentCache;
523 mutable QSet< QString > mPendingRemoteUrls;
524
525 QString mTypeString;
526
527 friend class TestQgsSvgCache;
528 friend class TestQgsImageCache;
529};
530
531#endif
532
533#endif // QGSABSTRACTCONTENTCACHE_H
A QObject derived base class for QgsAbstractContentCache.
void remoteContentFetched(const QString &url)
Emitted when the cache has finished retrieving content from a remote url.
virtual bool checkReply(QNetworkReply *reply, const QString &path) const
Runs additional checks on a network reply to ensure that the reply content is consistent with that re...
Base class for entries in a QgsAbstractContentCache.
virtual int dataSize() const =0
Returns the memory usage in bytes for the entry.
virtual void dump() const =0
Dumps debugging strings containing the item's properties.
virtual ~QgsAbstractContentCacheEntry()=default
QElapsedTimer fileModifiedLastCheckTimer
Time since last check of file modified date.
QgsAbstractContentCacheEntry(const QgsAbstractContentCacheEntry &rh)=delete
QgsAbstractContentCacheEntry & operator=(const QgsAbstractContentCacheEntry &rh)=delete
QString path
Represents the absolute path to a file, a remote URL, or a base64 encoded string.
virtual bool isEqual(const QgsAbstractContentCacheEntry *other) const =0
Tests whether this entry matches another entry.
QDateTime fileModified
Timestamp when file was last modified.
bool operator==(const QgsAbstractContentCacheEntry &other) const
Abstract base class for file content caches, such as SVG or raster image caches.
T * findExistingEntry(T *entryTemplate)
Returns the existing entry from the cache which matches entryTemplate (deleting entryTemplate when do...
void onRemoteContentFetched(const QString &url, bool success) override
Triggered after remote content (i.e.
QgsAbstractContentCache(QObject *parent=nullptr, const QString &typeString=QString(), long maxCacheSize=20000000, int fileModifiedCheckTimeout=30000)
Constructor for QgsAbstractContentCache, with the specified parent object.
void trimToMaximumSize()
Removes the least used cache entries until the maximum cache size is under the predefined size limit.
bool waitForTaskFinished(QgsNetworkContentFetcherTask *task) const
Blocks the current thread until the task finishes (or user's preset network timeout expires)
static int timeout()
Returns the network timeout length, in milliseconds.
Handles HTTP network content fetching in a background task.
void fetched()
Emitted when the network content has been fetched, regardless of whether the fetch was successful or ...
TaskStatus status() const
Returns the current task status.
@ Complete
Task successfully completed.
bool waitForFinished(int timeout=30000)
Blocks the current thread until the task finishes or a maximum of timeout milliseconds.
#define SIP_TRANSFERTHIS
Definition qgis_sip.h:53
#define SIP_OUT
Definition qgis_sip.h:58
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:41