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