LCOV - code coverage report
Current view: top level - core - qgstiledownloadmanager.cpp (source / functions) Hit Total Coverage
Test: coverage.info.cleaned Lines: 15 184 8.2 %
Date: 2021-04-10 08:29:14 Functions: 0 0 -
Branches: 0 0 -

           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 : }

Generated by: LCOV version 1.14