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