QGIS API Documentation 3.41.0-Master (3440c17df1d)
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
164 static bool isBase64Data( const QString &path );
165
166 signals:
167
171 void remoteContentFetched( const QString &url );
172
173 protected:
174
179 virtual bool checkReply( QNetworkReply *reply, const QString &path ) const
180 {
181 Q_UNUSED( reply )
182 Q_UNUSED( path )
183 return true;
184 }
185
186 protected slots:
187
194 virtual void onRemoteContentFetched( const QString &url, bool success );
195
196};
197
198#ifndef SIP_RUN
199
213template<class T>
215{
216
217 public:
218
230 QgsAbstractContentCache( QObject *parent SIP_TRANSFERTHIS = nullptr,
231 const QString &typeString = QString(),
232 long maxCacheSize = 20000000,
233 int fileModifiedCheckTimeout = 30000 )
235 , mMaxCacheSize( maxCacheSize )
236 , mFileModifiedCheckTimeout( fileModifiedCheckTimeout )
237 , mTypeString( typeString.isEmpty() ? QObject::tr( "Content" ) : typeString )
238 {
239 }
240
242 {
243 qDeleteAll( mEntryLookup );
244 }
245
246 protected:
247
252 {
253 //only one entry in cache
254 if ( mLeastRecentEntry == mMostRecentEntry )
255 {
256 return;
257 }
258 T *entry = mLeastRecentEntry;
259 while ( entry && ( mTotalSize > mMaxCacheSize ) )
260 {
261 T *bkEntry = entry;
262 entry = static_cast< T * >( entry->nextEntry );
263
264 takeEntryFromList( bkEntry );
265 mEntryLookup.remove( bkEntry->path, bkEntry );
266 mTotalSize -= bkEntry->dataSize();
267 delete bkEntry;
268 }
269 }
270
284 QByteArray getContent( const QString &path, const QByteArray &missingContent, const QByteArray &fetchingContent, bool blocking = false ) const;
285
286 void onRemoteContentFetched( const QString &url, bool success ) override
287 {
288 const QMutexLocker locker( &mMutex );
289 mPendingRemoteUrls.remove( url );
290
291 T *nextEntry = mLeastRecentEntry;
292 while ( T *entry = nextEntry )
293 {
294 nextEntry = static_cast< T * >( entry->nextEntry );
295 if ( entry->path == url )
296 {
297 takeEntryFromList( entry );
298 mEntryLookup.remove( entry->path, entry );
299 mTotalSize -= entry->dataSize();
300 delete entry;
301 }
302 }
303
304 if ( success )
305 emit remoteContentFetched( url );
306 }
307
319 {
320 // Wait up to timeout seconds for task finished
322 {
323 // The wait did not time out
324 // Third step, check status as complete
325 if ( task->status() == QgsTask::Complete )
326 {
327 // Fourth step, force the signal fetched to be sure reply has been checked
328
329 // ARGH this is BAD BAD BAD. The connection will get called twice as a result!!!
330 task->fetched();
331 return true;
332 }
333 }
334 return false;
335 }
336
346 T *findExistingEntry( T *entryTemplate )
347 {
348 //search entries in mEntryLookup
349 const QString path = entryTemplate->path;
350 T *currentEntry = nullptr;
351 const QList<T *> entries = mEntryLookup.values( path );
352 QDateTime modified;
353 for ( T *cacheEntry : entries )
354 {
355 if ( cacheEntry->isEqual( entryTemplate ) )
356 {
357 if ( mFileModifiedCheckTimeout <= 0 || cacheEntry->fileModifiedLastCheckTimer.hasExpired( mFileModifiedCheckTimeout ) )
358 {
359 if ( !modified.isValid() )
360 modified = QFileInfo( path ).lastModified();
361
362 if ( cacheEntry->fileModified != modified )
363 continue;
364 else
365 cacheEntry->fileModifiedLastCheckTimer.restart();
366 }
367 currentEntry = cacheEntry;
368 break;
369 }
370 }
371
372 //if not found: insert entryTemplate as a new entry
373 if ( !currentEntry )
374 {
375 currentEntry = insertCacheEntry( entryTemplate );
376 }
377 else
378 {
379 delete entryTemplate;
380 entryTemplate = nullptr;
381 ( void )entryTemplate;
382 takeEntryFromList( currentEntry );
383 if ( !mMostRecentEntry ) //list is empty
384 {
385 mMostRecentEntry = currentEntry;
386 mLeastRecentEntry = currentEntry;
387 }
388 else
389 {
390 mMostRecentEntry->nextEntry = currentEntry;
391 currentEntry->previousEntry = mMostRecentEntry;
392 currentEntry->nextEntry = nullptr;
393 mMostRecentEntry = currentEntry;
394 }
395 }
396
397 //debugging
398 //printEntryList();
399
400 return currentEntry;
401 }
402 mutable QRecursiveMutex mMutex;
403
405 long mTotalSize = 0;
406
408 long mMaxCacheSize = 20000000;
409
410 private:
411
417 T *insertCacheEntry( T *entry )
418 {
419 entry->mFileModifiedCheckTimeout = mFileModifiedCheckTimeout;
420
421 if ( !entry->path.startsWith( QLatin1String( "base64:" ) ) )
422 {
423 entry->fileModified = QFileInfo( entry->path ).lastModified();
424 entry->fileModifiedLastCheckTimer.start();
425 }
426
427 mEntryLookup.insert( entry->path, entry );
428
429 //insert to most recent place in entry list
430 if ( !mMostRecentEntry ) //inserting first entry
431 {
432 mLeastRecentEntry = entry;
433 mMostRecentEntry = entry;
434 entry->previousEntry = nullptr;
435 entry->nextEntry = nullptr;
436 }
437 else
438 {
439 entry->previousEntry = mMostRecentEntry;
440 entry->nextEntry = nullptr;
441 mMostRecentEntry->nextEntry = entry;
442 mMostRecentEntry = entry;
443 }
444
445 trimToMaximumSize();
446 return entry;
447 }
448
449
453 void takeEntryFromList( T *entry )
454 {
455 if ( !entry )
456 {
457 return;
458 }
459
460 if ( entry->previousEntry )
461 {
462 entry->previousEntry->nextEntry = entry->nextEntry;
463 }
464 else
465 {
466 mLeastRecentEntry = static_cast< T * >( entry->nextEntry );
467 }
468 if ( entry->nextEntry )
469 {
470 entry->nextEntry->previousEntry = entry->previousEntry;
471 }
472 else
473 {
474 mMostRecentEntry = static_cast< T * >( entry->previousEntry );
475 }
476 }
477
481 void printEntryList()
482 {
483 QgsDebugMsgLevel( QStringLiteral( "****************cache entry list*************************" ), 1 );
484 QgsDebugMsgLevel( "Cache size: " + QString::number( mTotalSize ), 1 );
485 T *entry = mLeastRecentEntry;
486 while ( entry )
487 {
488 QgsDebugMsgLevel( QStringLiteral( "***Entry:" ), 1 );
489 entry->dump();
490 entry = static_cast< T * >( entry->nextEntry );
491 }
492 }
493
495 QMultiHash< QString, T * > mEntryLookup;
496
498 int mFileModifiedCheckTimeout = 30000;
499
500 //The content cache keeps the entries on a double connected list, moving the current entry to the front.
501 //That way, removing entries for more space can start with the least used objects.
502 T *mLeastRecentEntry = nullptr;
503 T *mMostRecentEntry = nullptr;
504
505 mutable QCache< QString, QByteArray > mRemoteContentCache;
506 mutable QSet< QString > mPendingRemoteUrls;
507
508 QString mTypeString;
509
510 friend class TestQgsSvgCache;
511 friend class TestQgsImageCache;
512};
513
514#endif
515
516#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:39