24 #include <QElapsedTimer>
25 #include <QNetworkReply>
26 #include <QStandardPaths>
27 #include <QRegularExpression>
31 QgsTileDownloadManagerWorker::QgsTileDownloadManagerWorker(
QgsTileDownloadManager *manager, QObject *parent )
36 connect( &mIdleTimer, &QTimer::timeout,
this, &QgsTileDownloadManagerWorker::idleTimerTimeout );
39 void QgsTileDownloadManagerWorker::startIdleTimer()
41 if ( !mIdleTimer.isActive() )
43 mIdleTimer.start( mManager->mIdleThreadTimeoutMs );
47 void QgsTileDownloadManagerWorker::queueUpdated()
49 const QMutexLocker locker( &mManager->mMutex );
51 if ( mManager->mShuttingDown )
59 std::vector< QNetworkReply * > replies;
60 replies.reserve( mManager->mQueue.size() );
61 for (
auto it = mManager->mQueue.begin(); it != mManager->mQueue.end(); ++it )
63 replies.emplace_back( it->networkReply );
66 for ( QNetworkReply *reply : replies )
75 if ( mIdleTimer.isActive() && !mManager->mQueue.empty() )
86 mManager->mStageQueueRemovals =
true;
87 for (
auto it = mManager->mQueue.begin(); it != mManager->mQueue.end(); ++it )
89 if ( !it->networkReply )
91 QgsDebugMsgLevel( QStringLiteral(
"Tile download manager: starting request: " ) + it->request.url().toString(), 2 );
95 connect( it->networkReply, &QNetworkReply::finished, it->objWorker, &QgsTileDownloadManagerReplyWorkerObject::replyFinished );
97 ++mManager->mStats.networkRequestsStarted;
100 mManager->mStageQueueRemovals =
false;
101 mManager->processStagedEntryRemovals();
104 void QgsTileDownloadManagerWorker::quitThread()
106 QgsDebugMsgLevel( QStringLiteral(
"Tile download manager: stopping worker thread" ), 2 );
108 mManager->mWorker->deleteLater();
109 mManager->mWorker =
nullptr;
112 mManager->mWorkerThread->quit();
113 mManager->mWorkerThread =
nullptr;
114 mManager->mShuttingDown =
false;
117 void QgsTileDownloadManagerWorker::idleTimerTimeout()
119 const QMutexLocker locker( &mManager->mMutex );
120 Q_ASSERT( mManager->mQueue.empty() );
128 void QgsTileDownloadManagerReplyWorkerObject::replyFinished()
130 const QMutexLocker locker( &mManager->mMutex );
132 QgsDebugMsgLevel( QStringLiteral(
"Tile download manager: internal reply finished: " ) + mRequest.url().toString(), 2 );
134 QNetworkReply *reply = qobject_cast<QNetworkReply *>( sender() );
137 if ( reply->error() == QNetworkReply::NoError )
139 ++mManager->mStats.networkRequestsOk;
140 data = reply->readAll();
144 ++mManager->mStats.networkRequestsFailed;
145 const QString contentType = reply->header( QNetworkRequest::ContentTypeHeader ).toString();
146 if ( contentType.startsWith( QLatin1String(
"text/plain" ) ) )
147 data = reply->readAll();
150 QMap<QNetworkRequest::Attribute, QVariant> attributes;
151 attributes.insert( QNetworkRequest::SourceIsFromCacheAttribute, reply->attribute( QNetworkRequest::SourceIsFromCacheAttribute ) );
152 attributes.insert( QNetworkRequest::RedirectionTargetAttribute, reply->attribute( QNetworkRequest::RedirectionTargetAttribute ) );
153 attributes.insert( QNetworkRequest::HttpStatusCodeAttribute, reply->attribute( QNetworkRequest::HttpStatusCodeAttribute ) );
154 attributes.insert( QNetworkRequest::HttpReasonPhraseAttribute, reply->attribute( QNetworkRequest::HttpReasonPhraseAttribute ) );
156 QMap<QNetworkRequest::KnownHeaders, QVariant> headers;
157 headers.insert( QNetworkRequest::ContentTypeHeader, reply->header( QNetworkRequest::ContentTypeHeader ) );
160 int httpStatusCode = reply->attribute( QNetworkRequest::Attribute::HttpStatusCodeAttribute ).toInt();
161 if ( httpStatusCode == 206 && mManager->isRangeRequest( mRequest ) )
163 mManager->mRangesCache->registerEntry( mRequest, data );
166 emit finished( data, reply->url(), attributes, headers, reply->rawHeaderPairs(), reply->error(), reply->errorString() );
168 reply->deleteLater();
173 mManager->removeEntry( mRequest );
175 if ( mManager->mQueue.empty() )
178 mManager->mWorker->startIdleTimer();
188 #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
189 : mMutex( QMutex::Recursive )
195 QString cacheDirectory = settings.
value( QStringLiteral(
"cache/directory" ) ).toString();
196 if ( cacheDirectory.isEmpty() )
197 cacheDirectory = QStandardPaths::writableLocation( QStandardPaths::CacheLocation );
198 if ( !cacheDirectory.endsWith( QDir::separator() ) )
200 cacheDirectory.push_back( QDir::separator() );
202 cacheDirectory += QLatin1String(
"http-ranges" );
203 const qint64 cacheSize = settings.
value( QStringLiteral(
"cache/size" ), 256 * 1024 * 1024 ).toLongLong();
205 mRangesCache->setCacheDirectory( cacheDirectory );
206 mRangesCache->setCacheSize( cacheSize );
217 const QMutexLocker locker( &mMutex );
219 if ( isCachedRangeRequest( request ) )
222 QTimer::singleShot( 0, reply, &QgsTileDownloadManagerReply::cachedRangeRequestFinished );
228 QgsDebugMsgLevel( QStringLiteral(
"Tile download manager: starting worker thread" ), 2 );
229 mWorkerThread =
new QThread;
231 mWorker->moveToThread( mWorkerThread );
232 QObject::connect( mWorkerThread, &QThread::finished, mWorker, &QObject::deleteLater );
233 mWorkerThread->start();
240 QgsTileDownloadManager::QueueEntry entry = findEntryForRequest( request );
241 if ( !entry.isValid() )
243 QgsDebugMsgLevel( QStringLiteral(
"Tile download manager: get (new entry): " ) + request.url().toString(), 2 );
245 entry.request = request;
247 entry.objWorker->moveToThread( mWorkerThread );
249 QObject::connect( entry.objWorker, &QgsTileDownloadManagerReplyWorkerObject::finished, reply, &QgsTileDownloadManagerReply::requestFinished );
255 QgsDebugMsgLevel( QStringLiteral(
"Tile download manager: get (existing entry): " ) + request.url().toString(), 2 );
257 QObject::connect( entry.objWorker, &QgsTileDownloadManagerReplyWorkerObject::finished, reply, &QgsTileDownloadManagerReply::requestFinished );
262 signalQueueModified();
269 const QMutexLocker locker( &mMutex );
271 return !mQueue.empty();
279 while ( msec == -1 || t.elapsed() < msec )
282 const QMutexLocker locker( &mMutex );
283 if ( mQueue.empty() )
286 QThread::usleep( 1000 );
295 const QMutexLocker locker( &mMutex );
296 if ( !mWorkerThread )
300 mShuttingDown =
true;
301 signalQueueModified();
308 const QMutexLocker locker( &mMutex );
309 if ( !mWorkerThread )
313 QThread::usleep( 1000 );
319 return mWorkerThread && mWorkerThread->isRunning();
324 const QMutexLocker locker( &mMutex );
328 QgsTileDownloadManager::QueueEntry QgsTileDownloadManager::findEntryForRequest(
const QNetworkRequest &request )
330 for (
auto it = mQueue.begin(); it != mQueue.end(); ++it )
332 if ( it->request.url() == request.url() && it->request.rawHeader(
"Range" ) == request.rawHeader(
"Range" ) )
335 return QgsTileDownloadManager::QueueEntry();
338 void QgsTileDownloadManager::addEntry(
const QgsTileDownloadManager::QueueEntry &entry )
340 for (
auto it = mQueue.begin(); it != mQueue.end(); ++it )
342 Q_ASSERT( entry.request.url() != it->request.url() || entry.request.rawHeader(
"Range" ) != it->request.rawHeader(
"Range" ) );
345 mQueue.emplace_back( entry );
348 void QgsTileDownloadManager::updateEntry(
const QgsTileDownloadManager::QueueEntry &entry )
350 for (
auto it = mQueue.begin(); it != mQueue.end(); ++it )
352 if ( entry.request.url() == it->request.url() && entry.request.rawHeader(
"Range" ) == it->request.rawHeader(
"Range" ) )
361 void QgsTileDownloadManager::removeEntry(
const QNetworkRequest &request )
363 if ( mStageQueueRemovals )
365 mStagedQueueRemovals.emplace_back( request );
369 for (
auto it = mQueue.begin(); it != mQueue.end(); ++it )
371 if ( it->request.url() == request.url() && it->request.rawHeader(
"Range" ) == request.rawHeader(
"Range" ) )
381 void QgsTileDownloadManager::processStagedEntryRemovals()
383 Q_ASSERT( !mStageQueueRemovals );
384 for (
const QNetworkRequest &request : mStagedQueueRemovals )
386 removeEntry( request );
388 mStagedQueueRemovals.clear();
391 void QgsTileDownloadManager::signalQueueModified()
393 QMetaObject::invokeMethod( mWorker, &QgsTileDownloadManagerWorker::queueUpdated, Qt::QueuedConnection );
396 bool QgsTileDownloadManager::isRangeRequest(
const QNetworkRequest &request )
398 if ( request.rawHeader(
"Range" ).isEmpty() )
400 QRegularExpression regex(
"^bytes=\\d+-\\d+$" );
401 QRegularExpressionMatch match = regex.match( QString::fromUtf8( request.rawHeader(
"Range" ) ) );
402 return match.hasMatch();
405 bool QgsTileDownloadManager::isCachedRangeRequest(
const QNetworkRequest &request )
407 QNetworkRequest::CacheLoadControl loadControl = ( QNetworkRequest::CacheLoadControl ) request.attribute( QNetworkRequest::CacheLoadControlAttribute ).toInt();
408 bool saveControl = request.attribute( QNetworkRequest::CacheSaveControlAttribute ).toBool();
409 return isRangeRequest( request ) && saveControl && loadControl != QNetworkRequest::AlwaysNetwork && mRangesCache->hasEntry( request );
415 QgsTileDownloadManagerReply::QgsTileDownloadManagerReply(
QgsTileDownloadManager *manager,
const QNetworkRequest &request )
416 : mManager( manager )
417 , mRequest( request )
423 const QMutexLocker locker( &mManager->mMutex );
427 QgsDebugMsgLevel( QStringLiteral(
"Tile download manager: reply deleted before finished: " ) + mRequest.url().toString(), 2 );
433 void QgsTileDownloadManagerReply::requestFinished( QByteArray data, QUrl url,
const QMap<QNetworkRequest::Attribute, QVariant> &attributes,
const QMap<QNetworkRequest::KnownHeaders, QVariant> &headers,
const QList<QNetworkReply::RawHeaderPair> rawHeaderPairs, QNetworkReply::NetworkError error,
const QString &errorString )
435 QgsDebugMsgLevel( QStringLiteral(
"Tile download manager: reply finished: " ) + mRequest.url().toString(), 2 );
440 mAttributes = attributes;
448 void QgsTileDownloadManagerReply::cachedRangeRequestFinished()
450 QgsDebugMsgLevel( QStringLiteral(
"Tile download manager: internal range request reply loaded from cache: " ) + mRequest.url().toString(), 2 );
452 mData = mManager->mRangesCache->entry( mRequest );
453 mUrl = mRequest.url();
459 return mAttributes.contains( code ) ? mAttributes.value( code ) : QVariant();
464 return mHeaders.contains(
header ) ? mHeaders.value(
header ) : QVariant();