Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
New class QgsNetworkReplyContent, which encapsulates the useful
information from a QNetworkReply in a container which is safe
and cheap to pass between threads

(QNetworkReplys are QObject based, so not safe to access or
pass between threads)

Use this new class in a thread safe QgsNetworkAccessManager::finished
signal, which is fired on the main thread QgsNetworkAccessManager instance
when responses are finished from any thread
  • Loading branch information
nyalldawson committed Jan 23, 2019
1 parent 397194a commit b5379ce
Show file tree
Hide file tree
Showing 9 changed files with 375 additions and 0 deletions.
18 changes: 18 additions & 0 deletions python/core/auto_generated/qgsnetworkaccessmanager.sip.in
Expand Up @@ -165,6 +165,24 @@ This signal is propagated to the main thread QgsNetworkAccessManager instance, s
only to connect to the main thread's signal in order to receive notifications about requests
created in any thread.

.. seealso:: :py:func:`finished`

.. versionadded:: 3.6
%End

void finished( QgsNetworkReplyContent reply );
%Docstring
This signal is emitted whenever a pending network reply is finished.

The ``reply`` parameter will contain a QgsNetworkReplyContent object, containing all the useful
information relating to the reply, including headers and reply content.

This signal is propagated to the main thread QgsNetworkAccessManager instance, so it is necessary
only to connect to the main thread's signal in order to receive notifications about requests
created in any thread.

.. seealso:: :py:func:`requestAboutToBeCreated`

.. versionadded:: 3.6
%End

Expand Down
110 changes: 110 additions & 0 deletions python/core/auto_generated/qgsnetworkreply.sip.in
@@ -0,0 +1,110 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/qgsnetworkreply.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/



class QgsNetworkReplyContent
{
%Docstring
Encapsulates a network reply within a container which is inexpensive to copy and safe to pass between threads.

.. versionadded:: 3.6
%End

%TypeHeaderCode
#include "qgsnetworkreply.h"
%End
public:

QgsNetworkReplyContent();
%Docstring
Default constructor for an empty reply.
%End

explicit QgsNetworkReplyContent( QNetworkReply *reply );
%Docstring
Constructor for QgsNetworkReplyContent, populated from the specified ``reply``.
%End

void clear();
%Docstring
Clears the reply, resetting it back to a default, empty reply.
%End

QVariant attribute( QNetworkRequest::Attribute code ) const;
%Docstring
Returns the attribute associated with the ``code``. If the attribute has not been set, it returns an
invalid QVariant.

You can expect the default values listed in QNetworkRequest.Attribute to be
applied to the values returned by this function.

.. seealso:: :py:func:`attributes`
%End


QNetworkReply::NetworkError error() const;
%Docstring
Returns the reply's error message, or QNetworkReply.NoError if no
error was encountered.

.. seealso:: :py:func:`errorString`
%End

QString errorString() const;
%Docstring
Returns the error text for the reply, or an empty string if no
error was encountered.

.. seealso:: :py:func:`error`
%End


bool hasRawHeader( const QByteArray &headerName ) const;
%Docstring
Returns true if the reply contains a header with the specified ``headerName``.

.. seealso:: :py:func:`rawHeaderPairs`

.. seealso:: :py:func:`rawHeaderList`

.. seealso:: :py:func:`rawHeader`
%End

QList<QByteArray> rawHeaderList() const;
%Docstring
Returns a list of raw header names contained within the reply.

.. seealso:: :py:func:`rawHeaderPairs`

.. seealso:: :py:func:`hasRawHeader`

.. seealso:: :py:func:`rawHeader`
%End

QByteArray rawHeader( const QByteArray &headerName ) const;
%Docstring
Returns the content of the header with the specified ``headerName``, or an
empty QByteArray if the specified header was not found in the reply.

.. seealso:: :py:func:`rawHeaderPairs`

.. seealso:: :py:func:`hasRawHeader`

.. seealso:: :py:func:`rawHeaderList`
%End

};

/************************************************************************
* This file has been generated automatically from *
* *
* src/core/qgsnetworkreply.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
1 change: 1 addition & 0 deletions python/core/core_auto.sip
Expand Up @@ -80,6 +80,7 @@
%Include auto_generated/qgsmargins.sip
%Include auto_generated/qgsmimedatautils.sip
%Include auto_generated/qgsmultirenderchecker.sip
%Include auto_generated/qgsnetworkreply.sip
%Include auto_generated/qgsobjectcustomproperties.sip
%Include auto_generated/qgsogcutils.sip
%Include auto_generated/qgsoptional.sip
Expand Down
2 changes: 2 additions & 0 deletions src/core/CMakeLists.txt
Expand Up @@ -257,6 +257,7 @@ SET(QGIS_CORE_SRCS
qgsnetworkcontentfetcher.cpp
qgsnetworkcontentfetcherregistry.cpp
qgsnetworkcontentfetchertask.cpp
qgsnetworkreply.cpp
qgsnetworkreplyparser.cpp
qgsobjectcustomproperties.cpp
qgsofflineediting.cpp
Expand Down Expand Up @@ -913,6 +914,7 @@ SET(QGIS_CORE_HDRS
qgsmargins.h
qgsmimedatautils.h
qgsmultirenderchecker.h
qgsnetworkreply.h
qgsobjectcustomproperties.h
qgsogcutils.h
qgsoptional.h
Expand Down
2 changes: 2 additions & 0 deletions src/core/qgsapplication.cpp
Expand Up @@ -23,6 +23,7 @@
#include "qgsproject.h"
#include "qgsnetworkaccessmanager.h"
#include "qgsnetworkcontentfetcherregistry.h"
#include "qgsnetworkreply.h"
#include "qgsproviderregistry.h"
#include "qgsexpression.h"
#include "qgsactionscoperegistry.h"
Expand Down Expand Up @@ -207,6 +208,7 @@ void QgsApplication::init( QString profileFolder )
qRegisterMetaType<QgsCoordinateReferenceSystem>( "QgsCoordinateReferenceSystem" );
qRegisterMetaType<QgsAuthManager::MessageLevel>( "QgsAuthManager::MessageLevel" );
qRegisterMetaType<QgsNetworkRequestParameters>( "QgsNetworkRequestParameters" );
qRegisterMetaType<QgsNetworkReplyContent>( "QgsNetworkReplyContent" );

( void ) resolvePkgPath();

Expand Down
10 changes: 10 additions & 0 deletions src/core/qgsnetworkaccessmanager.cpp
Expand Up @@ -28,6 +28,7 @@
#include "qgssettings.h"
#include "qgsnetworkdiskcache.h"
#include "qgsauthmanager.h"
#include "qgsnetworkreply.h"

#include <QUrl>
#include <QTimer>
Expand Down Expand Up @@ -242,6 +243,10 @@ void QgsNetworkAccessManager::abortRequest()

}

void QgsNetworkAccessManager::onReplyFinished( QNetworkReply *reply )
{
emit finished( QgsNetworkReplyContent( reply ) );
}

QString QgsNetworkAccessManager::cacheLoadControlName( QNetworkRequest::CacheLoadControl control )
{
Expand Down Expand Up @@ -305,13 +310,18 @@ void QgsNetworkAccessManager::setupDefaultProxyAndCache( Qt::ConnectionType conn
connect( this, qgis::overload< QgsNetworkRequestParameters >::of( &QgsNetworkAccessManager::requestAboutToBeCreated ),
sMainNAM, qgis::overload< QgsNetworkRequestParameters >::of( &QgsNetworkAccessManager::requestAboutToBeCreated ) );

connect( this, qgis::overload< QgsNetworkReplyContent >::of( &QgsNetworkAccessManager::finished ),
sMainNAM, qgis::overload< QgsNetworkReplyContent >::of( &QgsNetworkAccessManager::finished ) );

#ifndef QT_NO_SSL
connect( this, &QNetworkAccessManager::sslErrors,
sMainNAM, &QNetworkAccessManager::sslErrors,
connectionType );
#endif
}

connect( this, &QNetworkAccessManager::finished, this, &QgsNetworkAccessManager::onReplyFinished );

// check if proxy is enabled
QgsSettings settings;
QNetworkProxy proxy;
Expand Down
19 changes: 19 additions & 0 deletions src/core/qgsnetworkaccessmanager.h
Expand Up @@ -20,6 +20,7 @@

#include <QList>
#include "qgis.h"
#include "qgsnetworkreply.h"
#include <QStringList>
#include <QNetworkAccessManager>
#include <QNetworkProxy>
Expand Down Expand Up @@ -166,16 +167,34 @@ class CORE_EXPORT QgsNetworkAccessManager : public QNetworkAccessManager
* only to connect to the main thread's signal in order to receive notifications about requests
* created in any thread.
*
* \see finished( QgsNetworkReplyContent )
* \since QGIS 3.6
*/
void requestAboutToBeCreated( QgsNetworkRequestParameters request );

/**
* This signal is emitted whenever a pending network reply is finished.
*
* The \a reply parameter will contain a QgsNetworkReplyContent object, containing all the useful
* information relating to the reply, including headers and reply content.
*
* This signal is propagated to the main thread QgsNetworkAccessManager instance, so it is necessary
* only to connect to the main thread's signal in order to receive notifications about requests
* created in any thread.
*
* \see requestAboutToBeCreated( QgsNetworkRequestParameters )
* \since QGIS 3.6
*/
void finished( QgsNetworkReplyContent reply );

void requestCreated( QNetworkReply * );
void requestTimedOut( QNetworkReply * );

private slots:
void abortRequest();

void onReplyFinished( QNetworkReply *reply );

protected:
QNetworkReply *createRequest( QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *outgoingData = nullptr ) override;

Expand Down
74 changes: 74 additions & 0 deletions src/core/qgsnetworkreply.cpp
@@ -0,0 +1,74 @@
/***************************************************************************
qgsnetworkreply.cpp
-------------------
begin : November 2018
copyright : (C) 2018 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#include "qgsnetworkreply.h"
#include <QNetworkReply>

QgsNetworkReplyContent::QgsNetworkReplyContent( QNetworkReply *reply )
: mError( reply->error() )
, mErrorString( reply->errorString() )
, mRawHeaderPairs( reply->rawHeaderPairs() )
{
int maxAttribute = static_cast< int >( QNetworkRequest::RedirectPolicyAttribute );
#if QT_VERSION >= QT_VERSION_CHECK( 5, 11, 0 )
maxAttribute = static_cast< int >( QNetworkRequest::Http2DirectAttribute );
#endif
for ( int i = 0; i <= maxAttribute; ++i )
{
if ( reply->attribute( static_cast< QNetworkRequest::Attribute>( i ) ).isValid() )
mAttributes[ static_cast< QNetworkRequest::Attribute>( i ) ] = reply->attribute( static_cast< QNetworkRequest::Attribute>( i ) );
}
}

void QgsNetworkReplyContent::clear()
{
*this = QgsNetworkReplyContent();
}

QVariant QgsNetworkReplyContent::attribute( QNetworkRequest::Attribute code ) const
{
return mAttributes.value( code );
}

bool QgsNetworkReplyContent::hasRawHeader( const QByteArray &headerName ) const
{
for ( auto &header : mRawHeaderPairs )
{
if ( header.first == headerName )
return true;
}
return false;
}

QList<QByteArray> QgsNetworkReplyContent::rawHeaderList() const
{
QList< QByteArray > res;
res.reserve( mRawHeaderPairs.length() );
for ( auto &header : mRawHeaderPairs )
{
res << header.first;
}
return res;
}

QByteArray QgsNetworkReplyContent::rawHeader( const QByteArray &headerName ) const
{
for ( auto &header : mRawHeaderPairs )
{
if ( header.first == headerName )
return header.second;
}
return QByteArray();
}

0 comments on commit b5379ce

Please sign in to comment.