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 : 5 : : mMutex( QMutex::Recursive )
148 : : {
149 : 5 : }
150 : :
151 : 5 : QgsTileDownloadManager::~QgsTileDownloadManager()
152 : : {
153 : : // make sure the worker thread is gone and any pending requests are canceled
154 : 5 : shutdown();
155 : 5 : }
156 : :
157 : 0 : QgsTileDownloadManagerReply *QgsTileDownloadManager::get( const QNetworkRequest &request )
158 : : {
159 : 0 : QMutexLocker locker( &mMutex );
160 : :
161 : 0 : if ( !mWorker )
162 : : {
163 : 0 : QgsDebugMsgLevel( QStringLiteral( "Tile download manager: starting worker thread" ), 2 );
164 : 0 : mWorkerThread = new QThread;
165 : 0 : mWorker = new QgsTileDownloadManagerWorker( this );
166 : 0 : mWorker->moveToThread( mWorkerThread );
167 : 0 : QObject::connect( mWorkerThread, &QThread::finished, mWorker, &QObject::deleteLater );
168 : 0 : mWorkerThread->start();
169 : 0 : }
170 : :
171 : 0 : QgsTileDownloadManagerReply *reply = new QgsTileDownloadManagerReply( this, request ); // lives in the same thread as the caller
172 : :
173 : 0 : ++mStats.requestsTotal;
174 : :
175 : 0 : QgsTileDownloadManager::QueueEntry entry = findEntryForRequest( request );
176 : 0 : if ( !entry.isValid() )
177 : : {
178 : 0 : QgsDebugMsgLevel( QStringLiteral( "Tile download manager: get (new entry): " ) + request.url().toString(), 2 );
179 : : // create a new entry and add it to queue
180 : 0 : entry.request = request;
181 : 0 : entry.objWorker = new QgsTileDownloadManagerReplyWorkerObject( this, request );
182 : 0 : entry.objWorker->moveToThread( mWorkerThread );
183 : :
184 : 0 : QObject::connect( entry.objWorker, &QgsTileDownloadManagerReplyWorkerObject::finished, reply, &QgsTileDownloadManagerReply::requestFinished ); // should be queued connection
185 : :
186 : 0 : addEntry( entry );
187 : 0 : }
188 : : else
189 : : {
190 : 0 : QgsDebugMsgLevel( QStringLiteral( "Tile download manager: get (existing entry): " ) + request.url().toString(), 2 );
191 : :
192 : 0 : QObject::connect( entry.objWorker, &QgsTileDownloadManagerReplyWorkerObject::finished, reply, &QgsTileDownloadManagerReply::requestFinished ); // should be queued connection
193 : :
194 : 0 : ++mStats.requestsMerged;
195 : : }
196 : :
197 : 0 : signalQueueModified();
198 : :
199 : 0 : return reply;
200 : 0 : }
201 : :
202 : 0 : bool QgsTileDownloadManager::hasPendingRequests() const
203 : : {
204 : 0 : QMutexLocker locker( &mMutex );
205 : :
206 : 0 : return !mQueue.isEmpty();
207 : 0 : }
208 : :
209 : 0 : bool QgsTileDownloadManager::waitForPendingRequests( int msec )
210 : : {
211 : 0 : QElapsedTimer t;
212 : 0 : t.start();
213 : :
214 : 0 : while ( msec == -1 || t.elapsed() < msec )
215 : : {
216 : : {
217 : 0 : QMutexLocker locker( &mMutex );
218 : 0 : if ( mQueue.isEmpty() )
219 : 0 : return true;
220 : 0 : }
221 : 0 : QThread::currentThread()->usleep( 1000 );
222 : : }
223 : :
224 : 0 : return false;
225 : 0 : }
226 : :
227 : 5 : void QgsTileDownloadManager::shutdown()
228 : : {
229 : : {
230 : 5 : QMutexLocker locker( &mMutex );
231 : 5 : if ( !mWorkerThread )
232 : 5 : return; // nothing to stop
233 : :
234 : : // let's signal to the thread
235 : 0 : mShuttingDown = true;
236 : 0 : signalQueueModified();
237 : 5 : }
238 : :
239 : : // wait until the thread is gone
240 : 0 : while ( 1 )
241 : : {
242 : : {
243 : 0 : QMutexLocker locker( &mMutex );
244 : 0 : if ( !mWorkerThread )
245 : 0 : return; // the thread has stopped
246 : 0 : }
247 : :
248 : 0 : QThread::currentThread()->usleep( 1000 );
249 : : }
250 : 5 : }
251 : :
252 : 0 : bool QgsTileDownloadManager::hasWorkerThreadRunning() const
253 : : {
254 : 0 : return mWorkerThread && mWorkerThread->isRunning();
255 : : }
256 : :
257 : 0 : void QgsTileDownloadManager::resetStatistics()
258 : : {
259 : 0 : QMutexLocker locker( &mMutex );
260 : 0 : mStats = QgsTileDownloadManager::Stats();
261 : 0 : }
262 : :
263 : 0 : QgsTileDownloadManager::QueueEntry QgsTileDownloadManager::findEntryForRequest( const QNetworkRequest &request )
264 : : {
265 : 0 : for ( auto it = mQueue.constBegin(); it != mQueue.constEnd(); ++it )
266 : : {
267 : 0 : if ( it->request.url() == request.url() )
268 : 0 : return *it;
269 : 0 : }
270 : 0 : return QgsTileDownloadManager::QueueEntry();
271 : 0 : }
272 : :
273 : 0 : void QgsTileDownloadManager::addEntry( const QgsTileDownloadManager::QueueEntry &entry )
274 : : {
275 : 0 : for ( auto it = mQueue.constBegin(); it != mQueue.constEnd(); ++it )
276 : : {
277 : : Q_ASSERT( entry.request.url() != it->request.url() );
278 : 0 : }
279 : :
280 : 0 : mQueue.append( entry );
281 : 0 : }
282 : :
283 : 5 : void QgsTileDownloadManager::updateEntry( const QgsTileDownloadManager::QueueEntry &entry )
284 : : {
285 : 5 : for ( auto it = mQueue.begin(); it != mQueue.end(); ++it )
286 : 5 : {
287 : 0 : if ( entry.request.url() == it->request.url() )
288 : : {
289 : 5 : *it = entry;
290 : 0 : return;
291 : : }
292 : 0 : }
293 : : Q_ASSERT( false );
294 : 0 : }
295 : :
296 : 0 : void QgsTileDownloadManager::removeEntry( const QNetworkRequest &request )
297 : : {
298 : 0 : int i = 0;
299 : 0 : for ( auto it = mQueue.constBegin(); it != mQueue.constEnd(); ++it, ++i )
300 : : {
301 : 0 : if ( it->request.url() == request.url() )
302 : : {
303 : 0 : mQueue.removeAt( i );
304 : 0 : return;
305 : : }
306 : 0 : }
307 : : Q_ASSERT( false );
308 : 0 : }
309 : :
310 : 0 : void QgsTileDownloadManager::signalQueueModified()
311 : : {
312 : 0 : QMetaObject::invokeMethod( mWorker, &QgsTileDownloadManagerWorker::queueUpdated, Qt::QueuedConnection );
313 : 0 : }
314 : :
315 : :
316 : : ///
317 : :
318 : :
319 : 0 : QgsTileDownloadManagerReply::QgsTileDownloadManagerReply( QgsTileDownloadManager *manager, const QNetworkRequest &request )
320 : 0 : : mManager( manager )
321 : 0 : , mRequest( request )
322 : 0 : {
323 : 0 : }
324 : :
325 : 0 : QgsTileDownloadManagerReply::~QgsTileDownloadManagerReply()
326 : 0 : {
327 : 0 : QMutexLocker locker( &mManager->mMutex );
328 : :
329 : 0 : if ( !mHasFinished )
330 : : {
331 : 0 : QgsDebugMsgLevel( QStringLiteral( "Tile download manager: reply deleted before finished: " ) + mRequest.url().toString(), 2 );
332 : :
333 : 0 : ++mManager->mStats.requestsEarlyDeleted;
334 : 0 : }
335 : 0 : }
336 : :
337 : 0 : void QgsTileDownloadManagerReply::requestFinished( QByteArray data, QNetworkReply::NetworkError error, const QString &errorString )
338 : : {
339 : 0 : QgsDebugMsgLevel( QStringLiteral( "Tile download manager: reply finished: " ) + mRequest.url().toString(), 2 );
340 : :
341 : 0 : mHasFinished = true;
342 : 0 : mData = data;
343 : 0 : mError = error;
344 : 0 : mErrorString = errorString;
345 : 0 : emit finished();
346 : 0 : }
|