LCOV - code coverage report
Current view: top level - core - qgstiledownloadmanager.h (source / functions) Hit Total Coverage
Test: coverage.info.cleaned Lines: 7 16 43.8 %
Date: 2021-03-26 12:19:53 Functions: 0 0 -
Branches: 0 0 -

           Branch data     Line data    Source code
       1                 :            : /***************************************************************************
       2                 :            :                          qgstiledownloadmanager.h
       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                 :            : #ifndef QGSTILEDOWNLOADMANAGER_H
      19                 :            : #define QGSTILEDOWNLOADMANAGER_H
      20                 :            : 
      21                 :            : #define SIP_NO_FILE
      22                 :            : 
      23                 :            : #include <QTimer>
      24                 :            : #include <QThread>
      25                 :            : #include <QMutex>
      26                 :            : 
      27                 :            : #include <QNetworkAccessManager>
      28                 :            : #include <QNetworkReply>
      29                 :            : 
      30                 :            : #include "qgis_core.h"
      31                 :            : 
      32                 :            : class QgsTileDownloadManager;
      33                 :            : 
      34                 :            : /**
      35                 :            :  * \ingroup core
      36                 :            :  * \brief Reply object for tile download manager requests returned from calls to QgsTileDownloadManager::get().
      37                 :            :  *
      38                 :            :  * When the underlying network request has finished (with success or failure), the finished() signal
      39                 :            :  * gets emitted.
      40                 :            :  *
      41                 :            :  * It is OK to delete this object before the request has finished - the request will not be aborted,
      42                 :            :  * the download manager will finish the download (as it may be needed soon afterwards).
      43                 :            :  *
      44                 :            :  * \since QGIS 3.18
      45                 :            :  */
      46                 :            : class CORE_EXPORT QgsTileDownloadManagerReply : public QObject
      47                 :            : {
      48                 :            :     Q_OBJECT
      49                 :            :   public:
      50                 :            :     ~QgsTileDownloadManagerReply();
      51                 :            : 
      52                 :            :     //! Returns whether the reply has already finished (with success/failure)
      53                 :            :     bool hasFinished() const { return mHasFinished; }
      54                 :            :     //! Returns binary data returned in the reply (only valid when already finished)
      55                 :          0 :     QByteArray data() const { return mData; }
      56                 :            :     //! Returns error code (only valid when already finished)
      57                 :          0 :     QNetworkReply::NetworkError error() const { return mError; }
      58                 :            :     //! Returns error string (only valid when already finished)
      59                 :            :     QString errorString() const { return mErrorString; }
      60                 :            : 
      61                 :            :     //! Returns the original request for this reply object
      62                 :          0 :     QNetworkRequest request() const { return mRequest; }
      63                 :            : 
      64                 :            :   signals:
      65                 :            :     //! Emitted when the reply has finished (either with a success or with a failure)
      66                 :            :     void finished();
      67                 :            : 
      68                 :            :   private slots:
      69                 :            :     void requestFinished( QByteArray data, QNetworkReply::NetworkError error, const QString &errorString );
      70                 :            : 
      71                 :            :   private:
      72                 :            :     QgsTileDownloadManagerReply( QgsTileDownloadManager *manager, const QNetworkRequest &request );
      73                 :            : 
      74                 :            :     friend class QgsTileDownloadManager;  // allows creation of new instances from the manager
      75                 :            : 
      76                 :            :   private:
      77                 :            :     //! "parent" download manager of this reply
      78                 :            :     QgsTileDownloadManager *mManager = nullptr;
      79                 :            :     QNetworkRequest mRequest;
      80                 :            :     bool mHasFinished = false;
      81                 :            :     QByteArray mData;
      82                 :            :     QNetworkReply::NetworkError mError = QNetworkReply::NoError;
      83                 :            :     QString mErrorString;
      84                 :            : };
      85                 :            : 
      86                 :            : 
      87                 :            : /// @cond PRIVATE
      88                 :            : 
      89                 :            : /**
      90                 :            :  * \ingroup core
      91                 :            :  * \brief Reply object that is used in the worker thread of the tile download manager.
      92                 :            :  * \note This class is not a part of public API
      93                 :            :  */
      94                 :            : class QgsTileDownloadManagerReplyWorkerObject : public QObject
      95                 :            : {
      96                 :            :     Q_OBJECT
      97                 :            :   public:
      98                 :          0 :     QgsTileDownloadManagerReplyWorkerObject( QgsTileDownloadManager *manager, const QNetworkRequest &request )
      99                 :          0 :       : mManager( manager ), mRequest( request ) {}
     100                 :            : 
     101                 :            :   public slots:
     102                 :            :     void replyFinished();
     103                 :            : 
     104                 :            :   signals:
     105                 :            :     void finished( QByteArray data, QNetworkReply::NetworkError error, const QString &errorString );
     106                 :            : 
     107                 :            :   private:
     108                 :            :     //! "parent" download manager of this worker object
     109                 :            :     QgsTileDownloadManager *mManager = nullptr;
     110                 :            :     QNetworkRequest mRequest;
     111                 :            : };
     112                 :            : 
     113                 :            : 
     114                 :            : /**
     115                 :            :  * \ingroup core
     116                 :            :  * \brief Worker object that is used in the worker thread of the tile download manager.
     117                 :            :  * \note This class is not a part of public API
     118                 :            :  */
     119                 :            : class QgsTileDownloadManagerWorker : public QObject
     120                 :            : {
     121                 :            :     Q_OBJECT
     122                 :            : 
     123                 :            :   public:
     124                 :            :     //! Creates the worker
     125                 :            :     QgsTileDownloadManagerWorker( QgsTileDownloadManager *manager, QObject *parent = nullptr );
     126                 :            : 
     127                 :            :     void startIdleTimer();
     128                 :            : 
     129                 :            :   public slots:
     130                 :            :     void queueUpdated();
     131                 :            :     void idleTimerTimeout();
     132                 :            : 
     133                 :            :   signals:
     134                 :            :     void requestFinished( QString url, QByteArray data );
     135                 :            : 
     136                 :            :   private:
     137                 :            :     void quitThread();
     138                 :            : 
     139                 :            :   private:
     140                 :            :     //! "parent" download manager of this worker
     141                 :            :     QgsTileDownloadManager *mManager = nullptr;
     142                 :            :     //! Timer used to delete the worker thread if thread is not doing anything for some time
     143                 :            :     QTimer mIdleTimer;
     144                 :            : };
     145                 :            : 
     146                 :            : /// @endcond
     147                 :            : 
     148                 :            : 
     149                 :            : 
     150                 :            : /**
     151                 :            :  * \ingroup core
     152                 :            :  *
     153                 :            :  * \brief Tile download manager handles downloads of map tiles for the purpose of map rendering.
     154                 :            :  * The purpose of this class is to handle a couple of situations that may happen:
     155                 :            :  *
     156                 :            :  * - a map rendering job starts which requests tiles from a remote server, then in a short
     157                 :            :  *   while user zooms/pans map, which would normally mean that all pending requests get
     158                 :            :  *   aborted and then restarted soon afterwards. The download manager allows the requests
     159                 :            :  *   to finish to avoid excessive load on servers and needless aborts and repeated requests.
     160                 :            :  * - multiple map rendering jobs start at a similar time, requesting the same map tiles.
     161                 :            :  *   Normally they could be requested multiple times from the server - the download manager
     162                 :            :  *   groups these requests and only does a single request at a time.
     163                 :            :  *
     164                 :            :  * At this point, it is not recommended to use this class for other scenarios than map
     165                 :            :  * rendering: using it elsewhere could slow down map rendering or have some unexpected
     166                 :            :  * negative effects.
     167                 :            :  *
     168                 :            :  * How do things work:
     169                 :            :  *
     170                 :            :  * - Upon a request, a QgsTileDownloadManagerReply object (based on QObject) is returned,
     171                 :            :  *   encapsulating the pending reply. Client can wait for its finished() signal - when
     172                 :            :  *   it gets emitted, the request has finished with success or failure. Client can delete
     173                 :            :  *   the reply object before the request is processed - download manager will finish its
     174                 :            :  *   download (and it will get cached for a future use).
     175                 :            :  * - All requests are done by QgsNetworkAccessManager
     176                 :            :  * - A worker thread responsible for all network tile requests is started first time a tile
     177                 :            :  *   is requested. Having a dedicated thread rather than reusing main thread makes things
     178                 :            :  *   more predictable as we won't get stuck in case the main thread is doing some work or
     179                 :            :  *   it is waiting for map rendering to finish.
     180                 :            :  * - There is a shared download queue (protected by a mutex) with a list of active requests
     181                 :            :  *   and requests waiting to be processed.
     182                 :            :  *
     183                 :            :  * \since QGIS 3.18
     184                 :            :  */
     185                 :            : class CORE_EXPORT QgsTileDownloadManager
     186                 :            : {
     187                 :            : 
     188                 :            :     //! An entry in the queue of requests to be handled by this class
     189                 :          0 :     class QueueEntry
     190                 :            :     {
     191                 :            :       public:
     192                 :          0 :         bool isValid() const { return !request.url().isEmpty(); }
     193                 :            : 
     194                 :            :         //! The actual original Qt network request
     195                 :            :         QNetworkRequest request;
     196                 :            :         //! Helper QObject that lives in worker thread that emits signals
     197                 :          0 :         QgsTileDownloadManagerReplyWorkerObject *objWorker = nullptr;
     198                 :            :         //! Internal network reply - only to be touched by the worker thread
     199                 :          0 :         QNetworkReply *networkReply = nullptr;
     200                 :            :     };
     201                 :            : 
     202                 :            :   public:
     203                 :            : 
     204                 :            :     /**
     205                 :            :      * \ingroup core
     206                 :            :      * \brief Encapsulates any statistics we would like to keep about requests
     207                 :            :      * \since QGIS 3.18
     208                 :            :      */
     209                 :          5 :     class Stats
     210                 :            :     {
     211                 :            :       public:
     212                 :            :         //! How many requests were done through the download manager
     213                 :          5 :         int requestsTotal = 0;
     214                 :            :         //! How many requests were same as some other pending request and got "merged"
     215                 :          5 :         int requestsMerged = 0;
     216                 :            :         //! How many requests were deleted early by the client (i.e. lost interest)
     217                 :          5 :         int requestsEarlyDeleted = 0;
     218                 :            : 
     219                 :            :         //! How many actual network requests were started
     220                 :          5 :         int networkRequestsStarted = 0;
     221                 :            :         //! How many network requests have been successful
     222                 :          5 :         int networkRequestsOk = 0;
     223                 :            :         //! How many network requests have failed
     224                 :          5 :         int networkRequestsFailed = 0;
     225                 :            :     };
     226                 :            : 
     227                 :            :     QgsTileDownloadManager();
     228                 :            :     ~QgsTileDownloadManager();
     229                 :            : 
     230                 :            :     /**
     231                 :            :      * Starts a request. Returns a new object that should be deleted by the caller
     232                 :            :      * when not needed anymore.
     233                 :            :      */
     234                 :            :     QgsTileDownloadManagerReply *get( const QNetworkRequest &request );
     235                 :            : 
     236                 :            :     //! Returns whether there are any pending requests in the queue
     237                 :            :     bool hasPendingRequests() const;
     238                 :            : 
     239                 :            :     /**
     240                 :            :      * Blocks the current thread until the queue is empty. This should not be used
     241                 :            :      * in production code, it is however useful for auto tests
     242                 :            :      */
     243                 :            :     bool waitForPendingRequests( int msec = -1 );
     244                 :            : 
     245                 :            :     //! Asks the worker thread to stop and blocks until it is not stopped.
     246                 :            :     void shutdown();
     247                 :            : 
     248                 :            :     /**
     249                 :            :      * Returns whether the worker thread is running currently (it may be stopped
     250                 :            :      * if there were no requests recently
     251                 :            :      */
     252                 :            :     bool hasWorkerThreadRunning() const;
     253                 :            : 
     254                 :            :     /**
     255                 :            :      * Sets after how many milliseconds the idle worker therad should terminate.
     256                 :            :      * This function is meant mainly for unit testing.
     257                 :            :      */
     258                 :            :     void setIdleThreadTimeout( int timeoutMs ) { mIdleThreadTimeoutMs = timeoutMs; }
     259                 :            : 
     260                 :            :     //! Returns basic statistics of the queries handled by this class
     261                 :            :     Stats statistics() { return mStats; }
     262                 :            : 
     263                 :            :     //! Resets statistics of numbers of queries handled by this class
     264                 :            :     void resetStatistics();
     265                 :            : 
     266                 :            :     friend class QgsTileDownloadManagerWorker;
     267                 :            :     friend class QgsTileDownloadManagerReply;
     268                 :            :     friend class QgsTileDownloadManagerReplyWorkerObject;
     269                 :            : 
     270                 :            :   private:
     271                 :            : 
     272                 :            :     // these can be only used with mutex locked!
     273                 :            :     QueueEntry findEntryForRequest( const QNetworkRequest &request );
     274                 :            :     void addEntry( const QueueEntry &entry );
     275                 :            :     void updateEntry( const QueueEntry &entry );
     276                 :            :     void removeEntry( const QNetworkRequest &request );
     277                 :            : 
     278                 :            :     void signalQueueModified();
     279                 :            : 
     280                 :            :   private:
     281                 :            : 
     282                 :            :     QList<QueueEntry> mQueue;
     283                 :            :     bool mShuttingDown = false;
     284                 :            :     mutable QMutex mMutex;
     285                 :            :     QThread *mWorkerThread = nullptr;
     286                 :            :     QgsTileDownloadManagerWorker *mWorker = nullptr;
     287                 :            :     Stats mStats;
     288                 :            : 
     289                 :            :     int mIdleThreadTimeoutMs = 10000;
     290                 :            : };
     291                 :            : 
     292                 :            : #endif // QGSTILEDOWNLOADMANAGER_H

Generated by: LCOV version 1.14