Branch data Line data Source code
1 : : /***************************************************************************
2 : : qgstiledownloadmanager.cpp
3 : : --------------------------
4 : : begin : January 2021
5 : : copyright : (C) 2021 by Martin Dobias
6 : : email : wonder dot sk at gmail dot com
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 : : #include "qgstiledownloadmanager.h"
19 : :
20 : : #include "qgslogger.h"
21 : : #include "qgsnetworkaccessmanager.h"
22 : :
23 : : #include <QElapsedTimer>
24 : : #include <QNetworkReply>
25 : :
26 : :
27 : : /// @cond PRIVATE
28 : :
29 : 0 : QgsTileDownloadManagerWorker::QgsTileDownloadManagerWorker( QgsTileDownloadManager *manager, QObject *parent )
30 : 0 : : QObject( parent )
31 : 0 : , mManager( manager )
32 : 0 : , mIdleTimer( this )
33 : 0 : {
34 : 0 : connect( &mIdleTimer, &QTimer::timeout, this, &QgsTileDownloadManagerWorker::idleTimerTimeout );
35 : 0 : }
36 : :
37 : 0 : void QgsTileDownloadManagerWorker::startIdleTimer()
38 : : {
39 : 0 : if ( !mIdleTimer.isActive() )
40 : : {
41 : 0 : mIdleTimer.start( mManager->mIdleThreadTimeoutMs );
42 : 0 : }
43 : 0 : }
44 : :
45 : 0 : void QgsTileDownloadManagerWorker::queueUpdated()
46 : : {
47 : 0 : QMutexLocker locker( &mManager->mMutex );
48 : :
49 : 0 : if ( mManager->mShuttingDown )
50 : : {
51 : 0 : for ( auto it = mManager->mQueue.begin(); it != mManager->mQueue.end(); ++it )
52 : : {
53 : 0 : it->networkReply->abort();
54 : 0 : }
55 : :
56 : 0 : quitThread();
57 : 0 : return;
58 : : }
59 : :
60 : 0 : if ( mIdleTimer.isActive() && !mManager->mQueue.isEmpty() )
61 : : {
62 : : // if timer to kill thread is running: stop the timer, we have work to do
63 : 0 : mIdleTimer.stop();
64 : 0 : }
65 : :
66 : 0 : for ( auto it = mManager->mQueue.begin(); it != mManager->mQueue.end(); ++it )
67 : : {
68 : 0 : if ( !it->networkReply )
69 : : {
70 : 0 : QgsDebugMsgLevel( QStringLiteral( "Tile download manager: starting request: " ) + it->request.url().toString(), 2 );
71 : : // start entries which are not in progress
72 : :
73 : 0 : it->networkReply = QgsNetworkAccessManager::instance()->get( it->request );
74 : 0 : connect( it->networkReply, &QNetworkReply::finished, it->objWorker, &QgsTileDownloadManagerReplyWorkerObject::replyFinished );
75 : :
76 : 0 : ++mManager->mStats.networkRequestsStarted;
77 : 0 : }
78 : 0 : }
79 : 0 : }
80 : 0 :
81 : 0 : void QgsTileDownloadManagerWorker::quitThread()
82 : 0 : {
83 : 0 : QgsDebugMsgLevel( QStringLiteral( "Tile download manager: stopping worker thread" ), 2 );
84 : :
85 : 0 : mManager->mWorker->deleteLater();
86 : 0 : mManager->mWorker = nullptr;
87 : : // we signal to our worker thread it's time to go. Its finished() signal is connected
88 : : // to deleteLater() call, so it will get deleted automatically
89 : 0 : mManager->mWorkerThread->quit();
90 : 0 : mManager->mWorkerThread = nullptr;
91 : 0 : mManager->mShuttingDown = false;
92 : 0 : }
93 : :
94 : 0 : void QgsTileDownloadManagerWorker::idleTimerTimeout()
95 : : {
96 : 0 : QMutexLocker locker( &mManager->mMutex );
97 : : Q_ASSERT( mManager->mQueue.isEmpty() );
98 : 0 : quitThread();
99 : 0 : }
100 : :
101 : :
102 : : ///
103 : :
104 : :
105 : 0 : void QgsTileDownloadManagerReplyWorkerObject::replyFinished()
106 : : {
107 : 0 : QMutexLocker locker( &mManager->mMutex );
108 : :
109 : 0 : QgsDebugMsgLevel( QStringLiteral( "Tile download manager: internal reply finished: " ) + mRequest.url().toString(), 2 );
110 : :
111 : 0 : QNetworkReply *reply = qobject_cast<QNetworkReply *>( sender() );
112 : 0 : QByteArray data;
113 : :
114 : 0 : if ( reply->error() == QNetworkReply::NoError )
115 : : {
116 : 0 : ++mManager->mStats.networkRequestsOk;
117 : :
118 : 0 : data = reply->readAll();
119 : 0 : }
120 : : else
121 : : {
122 : 0 : ++mManager->mStats.networkRequestsFailed;
123 : : }
124 : :
125 : 0 : emit finished( data, reply->error(), reply->errorString() );
126 : :
127 : 0 : reply->deleteLater();
128 : :
129 : : // kill the worker obj
130 : 0 : deleteLater();
131 : :
132 : 0 : mManager->removeEntry( mRequest );
133 : :
134 : 0 : if ( mManager->mQueue.isEmpty() )
135 : : {
136 : : // if this was the last thing in the queue, start a timer to kill thread after X seconds
137 : 0 : mManager->mWorker->startIdleTimer();
138 : 0 : }
139 : 0 : }
140 : :
141 : : /// @endcond
142 : :
143 : : ///
144 : :
145 : :
146 : 5 : QgsTileDownloadManager::QgsTileDownloadManager()
147 : : #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
148 : : : mMutex( QMutex::Recursive )
149 : : #endif
150 : : {
151 : 5 : }
152 : :
153 : 5 : QgsTileDownloadManager::~QgsTileDownloadManager()
154 : : {
155 : : // make sure the worker thread is gone and any pending requests are canceled
156 : 5 : shutdown();
157 : 5 : }
158 : :
159 : 0 : QgsTileDownloadManagerReply *QgsTileDownloadManager::get( const QNetworkRequest &request )
160 : : {
161 : 0 : QMutexLocker locker( &mMutex );
162 : :
163 : 0 : if ( !mWorker )
164 : : {
165 : 0 : QgsDebugMsgLevel( QStringLiteral( "Tile download manager: starting worker thread" ), 2 );
166 : 0 : mWorkerThread = new QThread;
167 : 0 : mWorker = new QgsTileDownloadManagerWorker( this );
168 : 0 : mWorker->moveToThread( mWorkerThread );
169 : 0 : QObject::connect( mWorkerThread, &QThread::finished, mWorker, &QObject::deleteLater );
170 : 0 : mWorkerThread->start();
171 : 0 : }
172 : :
173 : 0 : QgsTileDownloadManagerReply *reply = new QgsTileDownloadManagerReply( this, request ); // lives in the same thread as the caller
174 : :
175 : 0 : ++mStats.requestsTotal;
176 : :
177 : 0 : QgsTileDownloadManager::QueueEntry entry = findEntryForRequest( request );
178 : 0 : if ( !entry.isValid() )
179 : : {
180 : 0 : QgsDebugMsgLevel( QStringLiteral( "Tile download manager: get (new entry): " ) + request.url().toString(), 2 );
181 : : // create a new entry and add it to queue
182 : 0 : entry.request = request;
183 : 0 : entry.objWorker = new QgsTileDownloadManagerReplyWorkerObject( this, request );
184 : 0 : entry.objWorker->moveToThread( mWorkerThread );
185 : :
186 : 0 : QObject::connect( entry.objWorker, &QgsTileDownloadManagerReplyWorkerObject::finished, reply, &QgsTileDownloadManagerReply::requestFinished ); // should be queued connection
187 : :
188 : 0 : addEntry( entry );
189 : 0 : }
190 : : else
191 : : {
192 : 0 : QgsDebugMsgLevel( QStringLiteral( "Tile download manager: get (existing entry): " ) + request.url().toString(), 2 );
193 : :
194 : 0 : QObject::connect( entry.objWorker, &QgsTileDownloadManagerReplyWorkerObject::finished, reply, &QgsTileDownloadManagerReply::requestFinished ); // should be queued connection
195 : :
196 : 0 : ++mStats.requestsMerged;
197 : : }
198 : :
199 : 0 : signalQueueModified();
200 : :
201 : 0 : return reply;
202 : 0 : }
203 : :
204 : 0 : bool QgsTileDownloadManager::hasPendingRequests() const
205 : : {
206 : 0 : QMutexLocker locker( &mMutex );
207 : :
208 : 0 : return !mQueue.isEmpty();
209 : 0 : }
210 : :
211 : 0 : bool QgsTileDownloadManager::waitForPendingRequests( int msec )
212 : : {
213 : 0 : QElapsedTimer t;
214 : 0 : t.start();
215 : :
216 : 0 : while ( msec == -1 || t.elapsed() < msec )
217 : : {
218 : : {
219 : 0 : QMutexLocker locker( &mMutex );
220 : 0 : if ( mQueue.isEmpty() )
221 : 0 : return true;
222 : 0 : }
223 : 0 : QThread::currentThread()->usleep( 1000 );
224 : : }
225 : :
226 : 0 : return false;
227 : 0 : }
228 : :
229 : 5 : void QgsTileDownloadManager::shutdown()
230 : : {
231 : : {
232 : 5 : QMutexLocker locker( &mMutex );
233 : 5 : if ( !mWorkerThread )
234 : 5 : return; // nothing to stop
235 : :
236 : : // let's signal to the thread
237 : 0 : mShuttingDown = true;
238 : 0 : signalQueueModified();
239 : 5 : }
240 : :
241 : : // wait until the thread is gone
242 : 0 : while ( 1 )
243 : : {
244 : : {
245 : 0 : QMutexLocker locker( &mMutex );
246 : 0 : if ( !mWorkerThread )
247 : 0 : return; // the thread has stopped
248 : 0 : }
249 : :
250 : 0 : QThread::currentThread()->usleep( 1000 );
251 : : }
252 : 5 : }
253 : :
254 : 0 : bool QgsTileDownloadManager::hasWorkerThreadRunning() const
255 : : {
256 : 0 : return mWorkerThread && mWorkerThread->isRunning();
257 : : }
258 : :
259 : 0 : void QgsTileDownloadManager::resetStatistics()
260 : : {
261 : 0 : QMutexLocker locker( &mMutex );
262 : 0 : mStats = QgsTileDownloadManager::Stats();
263 : 0 : }
264 : :
265 : 0 : QgsTileDownloadManager::QueueEntry QgsTileDownloadManager::findEntryForRequest( const QNetworkRequest &request )
266 : : {
267 : 0 : for ( auto it = mQueue.constBegin(); it != mQueue.constEnd(); ++it )
268 : : {
269 : 0 : if ( it->request.url() == request.url() )
270 : 0 : return *it;
271 : 0 : }
272 : 0 : return QgsTileDownloadManager::QueueEntry();
273 : 0 : }
274 : :
275 : 0 : void QgsTileDownloadManager::addEntry( const QgsTileDownloadManager::QueueEntry &entry )
276 : : {
277 : 0 : for ( auto it = mQueue.constBegin(); it != mQueue.constEnd(); ++it )
278 : : {
279 : : Q_ASSERT( entry.request.url() != it->request.url() );
280 : 0 : }
281 : :
282 : 0 : mQueue.append( entry );
283 : 5 : }
284 : :
285 : 0 : void QgsTileDownloadManager::updateEntry( const QgsTileDownloadManager::QueueEntry &entry )
286 : : {
287 : 0 : for ( auto it = mQueue.begin(); it != mQueue.end(); ++it )
288 : : {
289 : 5 : if ( entry.request.url() == it->request.url() )
290 : 5 : {
291 : 0 : *it = entry;
292 : 0 : return;
293 : 5 : }
294 : 0 : }
295 : : Q_ASSERT( false );
296 : 0 : }
297 : :
298 : 0 : void QgsTileDownloadManager::removeEntry( const QNetworkRequest &request )
299 : : {
300 : 0 : int i = 0;
301 : 0 : for ( auto it = mQueue.constBegin(); it != mQueue.constEnd(); ++it, ++i )
302 : : {
303 : 0 : if ( it->request.url() == request.url() )
304 : : {
305 : 0 : mQueue.removeAt( i );
306 : 0 : return;
307 : : }
308 : 0 : }
309 : : Q_ASSERT( false );
310 : 0 : }
311 : :
312 : 0 : void QgsTileDownloadManager::signalQueueModified()
313 : : {
314 : 0 : QMetaObject::invokeMethod( mWorker, &QgsTileDownloadManagerWorker::queueUpdated, Qt::QueuedConnection );
315 : 0 : }
316 : :
317 : :
318 : : ///
319 : :
320 : :
321 : 0 : QgsTileDownloadManagerReply::QgsTileDownloadManagerReply( QgsTileDownloadManager *manager, const QNetworkRequest &request )
322 : 0 : : mManager( manager )
323 : 0 : , mRequest( request )
324 : 0 : {
325 : 0 : }
326 : :
327 : 0 : QgsTileDownloadManagerReply::~QgsTileDownloadManagerReply()
328 : 0 : {
329 : 0 : QMutexLocker locker( &mManager->mMutex );
330 : :
331 : 0 : if ( !mHasFinished )
332 : : {
333 : 0 : QgsDebugMsgLevel( QStringLiteral( "Tile download manager: reply deleted before finished: " ) + mRequest.url().toString(), 2 );
334 : :
335 : 0 : ++mManager->mStats.requestsEarlyDeleted;
336 : 0 : }
337 : 0 : }
338 : :
339 : 0 : void QgsTileDownloadManagerReply::requestFinished( QByteArray data, QNetworkReply::NetworkError error, const QString &errorString )
340 : : {
341 : 0 : QgsDebugMsgLevel( QStringLiteral( "Tile download manager: reply finished: " ) + mRequest.url().toString(), 2 );
342 : :
343 : 0 : mHasFinished = true;
344 : 0 : mData = data;
345 : 0 : mError = error;
346 : 0 : mErrorString = errorString;
347 : 0 : emit finished();
348 : 0 : }
|