QGIS API Documentation 4.0.0-Norrköping (1ddcee3d0e4)
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> QByteArray QgsAbstractContentCache<T>::getContent( const QString &path, const QByteArray &missingContent, const QByteArray &fetchingContent, bool blocking ) const
30{
31 // is it a path to local file?
32 QFile file( path );
33 if ( file.exists() )
34 {
35 if ( file.open( QIODevice::ReadOnly ) )
36 {
37 return file.readAll();
38 }
39 else
40 {
41 return missingContent;
42 }
43 }
44
45 // maybe it's an embedded base64 string
46 if ( path.startsWith( "base64:"_L1, Qt::CaseInsensitive ) )
47 {
48 const QByteArray base64 = path.mid( 7 ).toLocal8Bit(); // strip 'base64:' prefix
49 return QByteArray::fromBase64( base64, QByteArray::OmitTrailingEquals );
50 }
51 else
52 {
53 // maybe a HTML data URL
54 QString base64String;
55 if ( parseBase64DataUrl( path, nullptr, &base64String ) )
56 {
57 return QByteArray::fromBase64( base64String.toLocal8Bit(), QByteArray::OmitTrailingEquals );
58 }
59 // maybe embedded string data
60 QString embeddedString;
61 if ( parseEmbeddedStringData( path, nullptr, &embeddedString ) )
62 {
63 return embeddedString.toUtf8();
64 }
65 }
66
67 // maybe it's a url...
68 if ( !path.contains( "://"_L1 ) ) // otherwise short, relative SVG paths might be considered URLs
69 {
70 return missingContent;
71 }
72
73 const QUrl url( path );
74 if ( !url.isValid() )
75 {
76 return missingContent;
77 }
78
79 // check whether it's a url pointing to a local file
80 if ( url.scheme().compare( "file"_L1, Qt::CaseInsensitive ) == 0 )
81 {
82 file.setFileName( url.toLocalFile() );
83 if ( file.exists() )
84 {
85 if ( file.open( QIODevice::ReadOnly ) )
86 {
87 return file.readAll();
88 }
89 }
90
91 // not found...
92 return missingContent;
93 }
94
95 const QMutexLocker locker( &mMutex );
96
97 // already a request in progress for this url
98 if ( mPendingRemoteUrls.contains( path ) )
99 {
100 // it's a non blocking request so return fetching content
101 if ( !blocking )
102 {
103 return fetchingContent;
104 }
105
106 // it's a blocking request so try to find the task and wait for task finished
107 const auto constActiveTasks = QgsApplication::taskManager()->activeTasks();
108 for ( QgsTask *task : constActiveTasks )
109 {
110 // the network content fetcher task's description ends with the path
111 if ( !task->description().endsWith( path ) )
112 {
113 continue;
114 }
115
116 // cast task to network content fetcher task
117 QgsNetworkContentFetcherTask *ncfTask = qobject_cast<QgsNetworkContentFetcherTask *>( task );
118 if ( ncfTask )
119 {
120 // wait for task finished
121 if ( waitForTaskFinished( ncfTask ) )
122 {
123 if ( mRemoteContentCache.contains( path ) )
124 {
125 // We got the file!
126 return *mRemoteContentCache[path];
127 }
128 }
129 }
130 // task found, no needs to continue
131 break;
132 }
133 // if no content returns the content is probably in remote content cache
134 // or a new task will be created
135 }
136
137 if ( mRemoteContentCache.contains( path ) )
138 {
139 // already fetched this content - phew. Just return what we already got.
140 return *mRemoteContentCache[path];
141 }
142
143 mPendingRemoteUrls.insert( path );
144 //fire up task to fetch content in background
145 QNetworkRequest request( url );
146 QgsSetRequestInitiatorClass( request, u"QgsAbstractContentCache<%1>"_s.arg( mTypeString ) );
147 request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
148 request.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true );
149
151 connect( task, &QgsNetworkContentFetcherTask::fetched, this, [this, task, path, missingContent] {
152 const QMutexLocker locker( &mMutex );
153
154 QNetworkReply *reply = task->reply();
155 if ( !reply )
156 {
157 // canceled
158 QMetaObject::
159 invokeMethod( const_cast< QgsAbstractContentCacheBase * >( qobject_cast< const QgsAbstractContentCacheBase * >( this ) ), "onRemoteContentFetched", Qt::QueuedConnection, Q_ARG( QString, path ), Q_ARG( bool, false ) );
160 return;
161 }
162
163 if ( reply->error() != QNetworkReply::NoError )
164 {
165 QgsMessageLog::logMessage( tr( "%3 request failed [error: %1 - url: %2]" ).arg( reply->errorString(), path, mTypeString ), mTypeString );
166 return;
167 }
168
169 bool ok = true;
170
171 const QVariant status = reply->attribute( QNetworkRequest::HttpStatusCodeAttribute );
172 if ( !QgsVariantUtils::isNull( status ) && status.toInt() >= 400 )
173 {
174 const QVariant phrase = reply->attribute( QNetworkRequest::HttpReasonPhraseAttribute );
175 QgsMessageLog::logMessage( tr( "%4 request error [status: %1 - reason phrase: %2] for %3" ).arg( status.toInt() ).arg( phrase.toString(), path, mTypeString ), mTypeString );
176 mRemoteContentCache.insert( path, new QByteArray( missingContent ) );
177 ok = false;
178 }
179
180 if ( !checkReply( reply, path ) )
181 {
182 mRemoteContentCache.insert( path, new QByteArray( missingContent ) );
183 ok = false;
184 }
185
186 if ( ok )
187 {
188 // read the content data
189 const QByteArray ba = reply->readAll();
190
191 // because of the fragility listed below in waitForTaskFinished, this slot may get called twice. In that case
192 // the second time will have an empty reply (we've already read it all...)
193 if ( !ba.isEmpty() )
194 mRemoteContentCache.insert( path, new QByteArray( ba ) );
195 }
196 QMetaObject::
197 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(), Qgis::StringFormat format=Qgis::StringFormat::PlainText)
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)