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