QGIS API Documentation 3.99.0-Master (d270888f95f)
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 "qgsapplication.h"
24#include "qgslogger.h"
25#include "qgsmessagelog.h"
28#include "qgsvariantutils.h"
29
30#include <QCache>
31#include <QDateTime>
32#include <QFile>
33#include <QFileInfo>
34#include <QList>
35#include <QNetworkReply>
36#include <QObject>
37#include <QRecursiveMutex>
38#include <QSet>
39#include <QString>
40#include <QUrl>
41
42using namespace Qt::StringLiterals;
43
55{
56 public:
57
61 QgsAbstractContentCacheEntry( const QString &path ) ;
62
63 virtual ~QgsAbstractContentCacheEntry() = default;
64
67
71 QString path;
72
74 QDateTime fileModified;
75
78
81
87
93
94 bool operator==( const QgsAbstractContentCacheEntry &other ) const
95 {
96 return other.path == path;
97 }
98
102 virtual int dataSize() const = 0;
103
107 virtual void dump() const = 0;
108
109 protected:
110
116 virtual bool isEqual( const QgsAbstractContentCacheEntry *other ) const = 0;
117
118 private:
119#ifdef SIP_RUN
121#endif
122
123};
124
135class CORE_EXPORT QgsAbstractContentCacheBase: public QObject
136{
137 Q_OBJECT
138
139 public:
140
144 QgsAbstractContentCacheBase( QObject *parent );
145
160 static bool parseBase64DataUrl( const QString &path, QString *mimeType SIP_OUT = nullptr, QString *data SIP_OUT = nullptr );
161
162
177 static bool parseEmbeddedStringData( const QString &path, QString *mimeType SIP_OUT = nullptr, QString *data SIP_OUT = nullptr );
178
184 static bool isBase64Data( const QString &path );
185
196 virtual bool invalidateCacheEntry( const QString &path );
197
198 signals:
199
203 void remoteContentFetched( const QString &url );
204
205 protected:
206
211 virtual bool checkReply( QNetworkReply *reply, const QString &path ) const
212 {
213 Q_UNUSED( reply )
214 Q_UNUSED( path )
215 return true;
216 }
217
218 protected slots:
219
226 virtual void onRemoteContentFetched( const QString &url, bool success );
227
228};
229
230#ifndef SIP_RUN
231
245template<class T>
247{
248
249 public:
250
262 QgsAbstractContentCache( QObject *parent SIP_TRANSFERTHIS = nullptr,
263 const QString &typeString = QString(),
264 long maxCacheSize = 20000000,
265 int fileModifiedCheckTimeout = 30000 )
267 , mMaxCacheSize( maxCacheSize )
268 , mFileModifiedCheckTimeout( fileModifiedCheckTimeout )
269 , mTypeString( typeString.isEmpty() ? QObject::tr( "Content" ) : typeString )
270 {
271 }
272
274 {
275 qDeleteAll( mEntryLookup );
276 }
277
278 bool invalidateCacheEntry( const QString &path ) override
279 {
280 const QMutexLocker locker( &mMutex );
281
282 const QList<T *> entries = mEntryLookup.values( path );
283 if ( entries.isEmpty() )
284 return false;
285
286 for ( T *entry : entries )
287 {
288 takeEntryFromList( entry );
289 mEntryLookup.remove( path, entry );
290 mTotalSize -= entry->dataSize();
291 delete entry;
292 }
293
294 return true;
295 }
296
297 protected:
298
303 {
304 //only one entry in cache
305 if ( mLeastRecentEntry == mMostRecentEntry )
306 {
307 return;
308 }
309 T *entry = mLeastRecentEntry;
310 while ( entry && ( mTotalSize > mMaxCacheSize ) )
311 {
312 T *bkEntry = entry;
313 entry = static_cast< T * >( entry->nextEntry );
314
315 takeEntryFromList( bkEntry );
316 mEntryLookup.remove( bkEntry->path, bkEntry );
317 mTotalSize -= bkEntry->dataSize();
318 delete bkEntry;
319 }
320 }
321
335 QByteArray getContent( const QString &path, const QByteArray &missingContent, const QByteArray &fetchingContent, bool blocking = false ) const;
336
337 void onRemoteContentFetched( const QString &url, bool success ) override
338 {
339 const QMutexLocker locker( &mMutex );
340 mPendingRemoteUrls.remove( url );
341
342 T *nextEntry = mLeastRecentEntry;
343 while ( T *entry = nextEntry )
344 {
345 nextEntry = static_cast< T * >( entry->nextEntry );
346 if ( entry->path == url )
347 {
348 takeEntryFromList( entry );
349 mEntryLookup.remove( entry->path, entry );
350 mTotalSize -= entry->dataSize();
351 delete entry;
352 }
353 }
354
355 if ( success )
356 emit remoteContentFetched( url );
357 }
358
370 {
371 // Wait up to timeout seconds for task finished
373 {
374 // The wait did not time out
375 // Third step, check status as complete
376 if ( task->status() == QgsTask::Complete )
377 {
378 // Fourth step, force the signal fetched to be sure reply has been checked
379
380 // ARGH this is BAD BAD BAD. The connection will get called twice as a result!!!
381 task->fetched();
382 return true;
383 }
384 }
385 return false;
386 }
387
397 T *findExistingEntry( T *entryTemplate )
398 {
399 //search entries in mEntryLookup
400 const QString path = entryTemplate->path;
401 T *currentEntry = nullptr;
402 const QList<T *> entries = mEntryLookup.values( path );
403 QDateTime modified;
404 for ( T *cacheEntry : entries )
405 {
406 if ( cacheEntry->isEqual( entryTemplate ) )
407 {
408 if ( mFileModifiedCheckTimeout <= 0 || cacheEntry->fileModifiedLastCheckTimer.hasExpired( mFileModifiedCheckTimeout ) )
409 {
410 if ( !modified.isValid() )
411 modified = QFileInfo( path ).lastModified();
412
413 if ( cacheEntry->fileModified != modified )
414 continue;
415 else
416 cacheEntry->fileModifiedLastCheckTimer.restart();
417 }
418 currentEntry = cacheEntry;
419 break;
420 }
421 }
422
423 //if not found: insert entryTemplate as a new entry
424 if ( !currentEntry )
425 {
426 currentEntry = insertCacheEntry( entryTemplate );
427 }
428 else
429 {
430 delete entryTemplate;
431 entryTemplate = nullptr;
432 ( void )entryTemplate;
433 takeEntryFromList( currentEntry );
434 if ( !mMostRecentEntry ) //list is empty
435 {
436 mMostRecentEntry = currentEntry;
437 mLeastRecentEntry = currentEntry;
438 }
439 else
440 {
441 mMostRecentEntry->nextEntry = currentEntry;
442 currentEntry->previousEntry = mMostRecentEntry;
443 currentEntry->nextEntry = nullptr;
444 mMostRecentEntry = currentEntry;
445 }
446 }
447
448 //debugging
449 //printEntryList();
450
451 return currentEntry;
452 }
453 mutable QRecursiveMutex mMutex;
454
456 long mTotalSize = 0;
457
459 long mMaxCacheSize = 20000000;
460
461 private:
462
468 T *insertCacheEntry( T *entry )
469 {
470 entry->mFileModifiedCheckTimeout = mFileModifiedCheckTimeout;
471
472 if ( !entry->path.startsWith( "base64:"_L1 ) )
473 {
474 entry->fileModified = QFileInfo( entry->path ).lastModified();
475 entry->fileModifiedLastCheckTimer.start();
476 }
477
478 mEntryLookup.insert( entry->path, entry );
479
480 //insert to most recent place in entry list
481 if ( !mMostRecentEntry ) //inserting first entry
482 {
483 mLeastRecentEntry = entry;
484 mMostRecentEntry = entry;
485 entry->previousEntry = nullptr;
486 entry->nextEntry = nullptr;
487 }
488 else
489 {
490 entry->previousEntry = mMostRecentEntry;
491 entry->nextEntry = nullptr;
492 mMostRecentEntry->nextEntry = entry;
493 mMostRecentEntry = entry;
494 }
495
497 return entry;
498 }
499
500
504 void takeEntryFromList( T *entry )
505 {
506 if ( !entry )
507 {
508 return;
509 }
510
511 if ( entry->previousEntry )
512 {
513 entry->previousEntry->nextEntry = entry->nextEntry;
514 }
515 else
516 {
517 mLeastRecentEntry = static_cast< T * >( entry->nextEntry );
518 }
519 if ( entry->nextEntry )
520 {
521 entry->nextEntry->previousEntry = entry->previousEntry;
522 }
523 else
524 {
525 mMostRecentEntry = static_cast< T * >( entry->previousEntry );
526 }
527 }
528
532 void printEntryList()
533 {
534 QgsDebugMsgLevel( u"****************cache entry list*************************"_s, 1 );
535 QgsDebugMsgLevel( "Cache size: " + QString::number( mTotalSize ), 1 );
536 T *entry = mLeastRecentEntry;
537 while ( entry )
538 {
539 QgsDebugMsgLevel( u"***Entry:"_s, 1 );
540 entry->dump();
541 entry = static_cast< T * >( entry->nextEntry );
542 }
543 }
544
546 QMultiHash< QString, T * > mEntryLookup;
547
549 int mFileModifiedCheckTimeout = 30000;
550
551 //The content cache keeps the entries on a double connected list, moving the current entry to the front.
552 //That way, removing entries for more space can start with the least used objects.
553 T *mLeastRecentEntry = nullptr;
554 T *mMostRecentEntry = nullptr;
555
556 mutable QCache< QString, QByteArray > mRemoteContentCache;
557 mutable QSet< QString > mPendingRemoteUrls;
558
559 QString mTypeString;
560
561 friend class TestQgsSvgCache;
562 friend class TestQgsImageCache;
563};
564
565#endif
566
567#endif // QGSABSTRACTCONTENTCACHE_H
void remoteContentFetched(const QString &url)
Emitted when the cache has finished retrieving content from a remote url.
static bool parseEmbeddedStringData(const QString &path, QString *mimeType=nullptr, QString *data=nullptr)
Parses a path to determine if it represents a embedded string data, and if so, extracts the component...
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...
static bool parseBase64DataUrl(const QString &path, QString *mimeType=nullptr, QString *data=nullptr)
Parses a path to determine if it represents a base 64 encoded HTML data URL, and if so,...
QgsAbstractContentCacheBase(QObject *parent)
Constructor for QgsAbstractContentCacheBase, with the specified parent object.
virtual bool invalidateCacheEntry(const QString &path)
Invalidates a cache entry for the specified path.
static bool isBase64Data(const QString &path)
Returns true if path represents base64 encoded data.
virtual int dataSize() const =0
Returns the memory usage in bytes for the entry.
QgsAbstractContentCacheEntry * nextEntry
Entries are kept on a linked list, sorted by last access.
virtual void dump() const =0
Dumps debugging strings containing the item's properties.
int mFileModifiedCheckTimeout
Timeout before re-checking whether the file modified date has changed.
virtual ~QgsAbstractContentCacheEntry()=default
QElapsedTimer fileModifiedLastCheckTimer
Time since last check of file modified date.
QgsAbstractContentCacheEntry(const QgsAbstractContentCacheEntry &rh)=delete
QgsAbstractContentCacheEntry & operator=(const QgsAbstractContentCacheEntry &rh)=delete
QgsAbstractContentCacheEntry(const QString &path)
Constructor for QgsAbstractContentCacheEntry for an entry relating to the specified path.
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.
QgsAbstractContentCacheEntry * previousEntry
Entries are kept on a linked list, sorted by last access.
QDateTime fileModified
Timestamp when file was last modified.
bool operator==(const QgsAbstractContentCacheEntry &other) const
long mMaxCacheSize
Maximum cache size.
bool invalidateCacheEntry(const QString &path) override
Invalidates a cache entry for the specified path.
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.
long mTotalSize
Estimated total size of all cached content.
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:63