QGIS API Documentation 4.0.0-Norrköping (1ddcee3d0e4)
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:
60 QgsAbstractContentCacheEntry( const QString &path );
61
62 virtual ~QgsAbstractContentCacheEntry() = default;
63
66
70 QString path;
71
73 QDateTime fileModified;
74
77
80
86
92
93 bool operator==( const QgsAbstractContentCacheEntry &other ) const { return other.path == path; }
94
98 virtual int dataSize() const = 0;
99
103 virtual void dump() const = 0;
104
105 protected:
111 virtual bool isEqual( const QgsAbstractContentCacheEntry *other ) const = 0;
112
113 private:
114#ifdef SIP_RUN
116#endif
117};
118
129class CORE_EXPORT QgsAbstractContentCacheBase : public QObject
130{
131 Q_OBJECT
132
133 public:
137 QgsAbstractContentCacheBase( QObject *parent );
138
153 static bool parseBase64DataUrl( const QString &path, QString *mimeType SIP_OUT = nullptr, QString *data SIP_OUT = nullptr );
154
155
170 static bool parseEmbeddedStringData( const QString &path, QString *mimeType SIP_OUT = nullptr, QString *data SIP_OUT = nullptr );
171
177 static bool isBase64Data( const QString &path );
178
189 virtual bool invalidateCacheEntry( const QString &path );
190
191 signals:
192
196 void remoteContentFetched( const QString &url );
197
198 protected:
203 virtual bool checkReply( QNetworkReply *reply, const QString &path ) const
204 {
205 Q_UNUSED( reply )
206 Q_UNUSED( path )
207 return true;
208 }
209
210 protected slots:
211
218 virtual void onRemoteContentFetched( const QString &url, bool success );
219};
220
221#ifndef SIP_RUN
222
236template<class T> class CORE_EXPORT QgsAbstractContentCache : public QgsAbstractContentCacheBase
237{
238 public:
250 QgsAbstractContentCache( QObject *parent SIP_TRANSFERTHIS = nullptr, const QString &typeString = QString(), long maxCacheSize = 20000000, int fileModifiedCheckTimeout = 30000 )
252 , mMaxCacheSize( maxCacheSize )
253 , mFileModifiedCheckTimeout( fileModifiedCheckTimeout )
254 , mTypeString( typeString.isEmpty() ? QObject::tr( "Content" ) : typeString )
255 {}
256
257 ~QgsAbstractContentCache() override { qDeleteAll( mEntryLookup ); }
258
259 bool invalidateCacheEntry( const QString &path ) override
260 {
261 const QMutexLocker locker( &mMutex );
262
263 const QList<T *> entries = mEntryLookup.values( path );
264 if ( entries.isEmpty() )
265 return false;
266
267 for ( T *entry : entries )
268 {
269 takeEntryFromList( entry );
270 mEntryLookup.remove( path, entry );
271 mTotalSize -= entry->dataSize();
272 delete entry;
273 }
274
275 return true;
276 }
277
278 protected:
283 {
284 //only one entry in cache
285 if ( mLeastRecentEntry == mMostRecentEntry )
286 {
287 return;
288 }
289 T *entry = mLeastRecentEntry;
290 while ( entry && ( mTotalSize > mMaxCacheSize ) )
291 {
292 T *bkEntry = entry;
293 entry = static_cast< T * >( entry->nextEntry );
294
295 takeEntryFromList( bkEntry );
296 mEntryLookup.remove( bkEntry->path, bkEntry );
297 mTotalSize -= bkEntry->dataSize();
298 delete bkEntry;
299 }
300 }
301
315 QByteArray getContent( const QString &path, const QByteArray &missingContent, const QByteArray &fetchingContent, bool blocking = false ) const;
316
317 void onRemoteContentFetched( const QString &url, bool success ) override
318 {
319 const QMutexLocker locker( &mMutex );
320 mPendingRemoteUrls.remove( url );
321
322 T *nextEntry = mLeastRecentEntry;
323 while ( T *entry = nextEntry )
324 {
325 nextEntry = static_cast< T * >( entry->nextEntry );
326 if ( entry->path == url )
327 {
328 takeEntryFromList( entry );
329 mEntryLookup.remove( entry->path, entry );
330 mTotalSize -= entry->dataSize();
331 delete entry;
332 }
333 }
334
335 if ( success )
336 emit remoteContentFetched( url );
337 }
338
350 {
351 // Wait up to timeout seconds for task finished
353 {
354 // The wait did not time out
355 // Third step, check status as complete
356 if ( task->status() == QgsTask::Complete )
357 {
358 // Fourth step, force the signal fetched to be sure reply has been checked
359
360 // ARGH this is BAD BAD BAD. The connection will get called twice as a result!!!
361 task->fetched();
362 return true;
363 }
364 }
365 return false;
366 }
367
377 T *findExistingEntry( T *entryTemplate )
378 {
379 //search entries in mEntryLookup
380 const QString path = entryTemplate->path;
381 T *currentEntry = nullptr;
382 const QList<T *> entries = mEntryLookup.values( path );
383 QDateTime modified;
384 for ( T *cacheEntry : entries )
385 {
386 if ( cacheEntry->isEqual( entryTemplate ) )
387 {
388 if ( mFileModifiedCheckTimeout <= 0 || cacheEntry->fileModifiedLastCheckTimer.hasExpired( mFileModifiedCheckTimeout ) )
389 {
390 if ( !modified.isValid() )
391 modified = QFileInfo( path ).lastModified();
392
393 if ( cacheEntry->fileModified != modified )
394 continue;
395 else
396 cacheEntry->fileModifiedLastCheckTimer.restart();
397 }
398 currentEntry = cacheEntry;
399 break;
400 }
401 }
402
403 //if not found: insert entryTemplate as a new entry
404 if ( !currentEntry )
405 {
406 currentEntry = insertCacheEntry( entryTemplate );
407 }
408 else
409 {
410 delete entryTemplate;
411 entryTemplate = nullptr;
412 ( void ) entryTemplate;
413 takeEntryFromList( currentEntry );
414 if ( !mMostRecentEntry ) //list is empty
415 {
416 mMostRecentEntry = currentEntry;
417 mLeastRecentEntry = currentEntry;
418 }
419 else
420 {
421 mMostRecentEntry->nextEntry = currentEntry;
422 currentEntry->previousEntry = mMostRecentEntry;
423 currentEntry->nextEntry = nullptr;
424 mMostRecentEntry = currentEntry;
425 }
426 }
427
428 //debugging
429 //printEntryList();
430
431 return currentEntry;
432 }
433 mutable QRecursiveMutex mMutex;
434
436 long mTotalSize = 0;
437
439 long mMaxCacheSize = 20000000;
440
441 private:
447 T *insertCacheEntry( T *entry )
448 {
449 entry->mFileModifiedCheckTimeout = mFileModifiedCheckTimeout;
450
451 if ( !entry->path.startsWith( "base64:"_L1 ) )
452 {
453 entry->fileModified = QFileInfo( entry->path ).lastModified();
454 entry->fileModifiedLastCheckTimer.start();
455 }
456
457 mEntryLookup.insert( entry->path, entry );
458
459 //insert to most recent place in entry list
460 if ( !mMostRecentEntry ) //inserting first entry
461 {
462 mLeastRecentEntry = entry;
463 mMostRecentEntry = entry;
464 entry->previousEntry = nullptr;
465 entry->nextEntry = nullptr;
466 }
467 else
468 {
469 entry->previousEntry = mMostRecentEntry;
470 entry->nextEntry = nullptr;
471 mMostRecentEntry->nextEntry = entry;
472 mMostRecentEntry = entry;
473 }
474
476 return entry;
477 }
478
479
483 void takeEntryFromList( T *entry )
484 {
485 if ( !entry )
486 {
487 return;
488 }
489
490 if ( entry->previousEntry )
491 {
492 entry->previousEntry->nextEntry = entry->nextEntry;
493 }
494 else
495 {
496 mLeastRecentEntry = static_cast< T * >( entry->nextEntry );
497 }
498 if ( entry->nextEntry )
499 {
500 entry->nextEntry->previousEntry = entry->previousEntry;
501 }
502 else
503 {
504 mMostRecentEntry = static_cast< T * >( entry->previousEntry );
505 }
506 }
507
511 void printEntryList()
512 {
513 QgsDebugMsgLevel( u"****************cache entry list*************************"_s, 1 );
514 QgsDebugMsgLevel( "Cache size: " + QString::number( mTotalSize ), 1 );
515 T *entry = mLeastRecentEntry;
516 while ( entry )
517 {
518 QgsDebugMsgLevel( u"***Entry:"_s, 1 );
519 entry->dump();
520 entry = static_cast< T * >( entry->nextEntry );
521 }
522 }
523
525 QMultiHash< QString, T * > mEntryLookup;
526
528 int mFileModifiedCheckTimeout = 30000;
529
530 //The content cache keeps the entries on a double connected list, moving the current entry to the front.
531 //That way, removing entries for more space can start with the least used objects.
532 T *mLeastRecentEntry = nullptr;
533 T *mMostRecentEntry = nullptr;
534
535 mutable QCache< QString, QByteArray > mRemoteContentCache;
536 mutable QSet< QString > mPendingRemoteUrls;
537
538 QString mTypeString;
539
540 friend class TestQgsSvgCache;
541 friend class TestQgsImageCache;
542};
543
544#endif
545
546#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:52
#define SIP_OUT
Definition qgis_sip.h:57
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:63