QGIS API Documentation 3.99.0-Master (d270888f95f)
Loading...
Searching...
No Matches
qgsabstractcontentcache_p.h
Go to the documentation of this file.
1/***************************************************************************
2 qgsabstractcontentcache_p.h
3 ---------------
4 begin : February 2024
5 copyright : (C) 2024 by Matthias Kuhn
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_P_H
19#define QGSABSTRACTCONTENTCACHE_P_H
20
23
24#include <QRegularExpression>
25#include <QString>
26
27using namespace Qt::StringLiterals;
28
29template<class T>
30QByteArray QgsAbstractContentCache<T>::getContent( const QString &path, const QByteArray &missingContent, const QByteArray &fetchingContent, bool blocking ) const
31{
32 // is it a path to local file?
33 QFile file( path );
34 if ( file.exists() )
35 {
36 if ( file.open( QIODevice::ReadOnly ) )
37 {
38 return file.readAll();
39 }
40 else
41 {
42 return missingContent;
43 }
44 }
45
46 // maybe it's an embedded base64 string
47 if ( path.startsWith( "base64:"_L1, Qt::CaseInsensitive ) )
48 {
49 const QByteArray base64 = path.mid( 7 ).toLocal8Bit(); // strip 'base64:' prefix
50 return QByteArray::fromBase64( base64, QByteArray::OmitTrailingEquals );
51 }
52 else
53 {
54 // maybe a HTML data URL
55 QString base64String;
56 if ( parseBase64DataUrl( path, nullptr, &base64String ) )
57 {
58 return QByteArray::fromBase64( base64String.toLocal8Bit(), QByteArray::OmitTrailingEquals );
59 }
60 // maybe embedded string data
61 QString embeddedString;
62 if ( parseEmbeddedStringData( path, nullptr, &embeddedString ) )
63 {
64 return embeddedString.toUtf8();
65 }
66 }
67
68 // maybe it's a url...
69 if ( !path.contains( "://"_L1 ) ) // otherwise short, relative SVG paths might be considered URLs
70 {
71 return missingContent;
72 }
73
74 const QUrl url( path );
75 if ( !url.isValid() )
76 {
77 return missingContent;
78 }
79
80 // check whether it's a url pointing to a local file
81 if ( url.scheme().compare( "file"_L1, Qt::CaseInsensitive ) == 0 )
82 {
83 file.setFileName( url.toLocalFile() );
84 if ( file.exists() )
85 {
86 if ( file.open( QIODevice::ReadOnly ) )
87 {
88 return file.readAll();
89 }
90 }
91
92 // not found...
93 return missingContent;
94 }
95
96 const QMutexLocker locker( &mMutex );
97
98 // already a request in progress for this url
99 if ( mPendingRemoteUrls.contains( path ) )
100 {
101 // it's a non blocking request so return fetching content
102 if ( !blocking )
103 {
104 return fetchingContent;
105 }
106
107 // it's a blocking request so try to find the task and wait for task finished
108 const auto constActiveTasks = QgsApplication::taskManager()->activeTasks();
109 for ( QgsTask *task : constActiveTasks )
110 {
111 // the network content fetcher task's description ends with the path
112 if ( !task->description().endsWith( path ) )
113 {
114 continue;
115 }
116
117 // cast task to network content fetcher task
118 QgsNetworkContentFetcherTask *ncfTask = qobject_cast<QgsNetworkContentFetcherTask *>( task );
119 if ( ncfTask )
120 {
121 // wait for task finished
122 if ( waitForTaskFinished( ncfTask ) )
123 {
124 if ( mRemoteContentCache.contains( path ) )
125 {
126 // We got the file!
127 return *mRemoteContentCache[ path ];
128 }
129 }
130 }
131 // task found, no needs to continue
132 break;
133 }
134 // if no content returns the content is probably in remote content cache
135 // or a new task will be created
136 }
137
138 if ( mRemoteContentCache.contains( path ) )
139 {
140 // already fetched this content - phew. Just return what we already got.
141 return *mRemoteContentCache[ path ];
142 }
143
144 mPendingRemoteUrls.insert( path );
145 //fire up task to fetch content in background
146 QNetworkRequest request( url );
147 QgsSetRequestInitiatorClass( request, u"QgsAbstractContentCache<%1>"_s.arg( mTypeString ) );
148 request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
149 request.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true );
150
152 connect( task, &QgsNetworkContentFetcherTask::fetched, this, [this, task, path, missingContent]
153 {
154 const QMutexLocker locker( &mMutex );
155
156 QNetworkReply *reply = task->reply();
157 if ( !reply )
158 {
159 // canceled
160 QMetaObject::invokeMethod( const_cast< QgsAbstractContentCacheBase * >( qobject_cast< const QgsAbstractContentCacheBase * >( this ) ), "onRemoteContentFetched", Qt::QueuedConnection, Q_ARG( QString, path ), Q_ARG( bool, false ) );
161 return;
162 }
163
164 if ( reply->error() != QNetworkReply::NoError )
165 {
166 QgsMessageLog::logMessage( tr( "%3 request failed [error: %1 - url: %2]" ).arg( reply->errorString(), path, mTypeString ), mTypeString );
167 return;
168 }
169
170 bool ok = true;
171
172 const QVariant status = reply->attribute( QNetworkRequest::HttpStatusCodeAttribute );
173 if ( !QgsVariantUtils::isNull( status ) && status.toInt() >= 400 )
174 {
175 const QVariant phrase = reply->attribute( QNetworkRequest::HttpReasonPhraseAttribute );
176 QgsMessageLog::logMessage( tr( "%4 request error [status: %1 - reason phrase: %2] for %3" ).arg( status.toInt() ).arg( phrase.toString(), path, mTypeString ), mTypeString );
177 mRemoteContentCache.insert( path, new QByteArray( missingContent ) );
178 ok = false;
179 }
180
181 if ( !checkReply( reply, path ) )
182 {
183 mRemoteContentCache.insert( path, new QByteArray( missingContent ) );
184 ok = false;
185 }
186
187 if ( ok )
188 {
189 // read the content data
190 const QByteArray ba = reply->readAll();
191
192 // because of the fragility listed below in waitForTaskFinished, this slot may get called twice. In that case
193 // the second time will have an empty reply (we've already read it all...)
194 if ( !ba.isEmpty() )
195 mRemoteContentCache.insert( path, new QByteArray( ba ) );
196 }
197 QMetaObject::invokeMethod( const_cast< QgsAbstractContentCacheBase * >( qobject_cast< const QgsAbstractContentCacheBase * >( this ) ), "onRemoteContentFetched", Qt::QueuedConnection, Q_ARG( QString, path ), Q_ARG( bool, true ) );
198 } );
199
201
202 // if blocking, wait for finished
203 if ( blocking )
204 {
205 if ( waitForTaskFinished( task ) )
206 {
207 if ( mRemoteContentCache.contains( path ) )
208 {
209 // We got the file!
210 return *mRemoteContentCache[ path ];
211 }
212 }
213 }
214 return fetchingContent;
215}
216
217#endif // QGSABSTRACTCONTENTCACHE_P_H
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.
QByteArray getContent(const QString &path, const QByteArray &missingContent, const QByteArray &fetchingContent, bool blocking=false) const
Gets the file content corresponding to the given path.
bool waitForTaskFinished(QgsNetworkContentFetcherTask *task) const
Blocks the current thread until the task finishes (or user's preset network timeout expires).
static QgsTaskManager * taskManager()
Returns the application's task manager, used for managing application wide background task handling.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE())
Adds a message to the log instance (and creates it if necessary).
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 ...
QNetworkReply * reply()
Returns the network reply.
QList< QgsTask * > activeTasks() const
Returns a list of the active (queued or running) tasks.
long addTask(QgsTask *task, int priority=0)
Adds a task to the manager.
Abstract base class for long running background tasks.
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
#define QgsSetRequestInitiatorClass(request, _class)