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