Merge pull request #3751 from elpaso/downloader_master

[bugfix][forwardport] File downloader for identify dialog hyperlinks
This commit is contained in:
Alessandro Pasotti 2016-11-11 17:42:56 +01:00 committed by GitHub
commit fb124baaf6
12 changed files with 856 additions and 1 deletions

View File

@ -24,5 +24,5 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
# Set OTB application path (installed in before_install.sh script)
export OTB_APPLICATION_PATH=${HOME}/OTB-5.6.0-Linux64/lib/otb/applications
xvfb-run ctest -V -E "qgis_openstreetmaptest|qgis_wcsprovidertest|PyQgsWFSProviderGUI|qgis_ziplayertest|$(cat ${DIR}/blacklist.txt | paste -sd '|' -)" -S ./qgis-test-travis.ctest --output-on-failure
xvfb-run ctest -V -E "qgis_filedownloader|qgis_openstreetmaptest|qgis_wcsprovidertest|PyQgsWFSProviderGUI|qgis_ziplayertest|$(cat ${DIR}/blacklist.txt | paste -sd '|' -)" -S ./qgis-test-travis.ctest --output-on-failure
# xvfb-run ctest -V -E "qgis_openstreetmaptest|qgis_wcsprovidertest" -S ./qgis-test-travis.ctest --output-on-failure

View File

@ -77,6 +77,7 @@
%Include qgsfieldvalidator.sip
%Include qgsfiledropedit.sip
%Include qgsfilewidget.sip
%Include qgsfiledownloader.sip
%Include qgsfilterlineedit.sip
%Include qgsfocuswatcher.sip
%Include qgsformannotationitem.sip

View File

@ -0,0 +1,68 @@
/***************************************************************************
qgsfiledownloader.sip
--------------------------------------
Date : November 2016
Copyright : (C) 2016 by Alessandro Pasotti
Email : elpaso at itopen dot it
***************************************************************************
* *
* 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. *
* *
***************************************************************************/
/** \ingroup gui
* QgsFileDownloader is a utility class for downloading files.
*
* To use this class, it is necessary to pass the URL and an output file name as
* arguments to the constructor, the download will start immediately.
* The download is asynchronous and depending on the guiNotificationsEnabled
* parameter accepted by the constructor (default = true) the class will
* show a progress dialog and report all errors in a QMessageBox::warning dialog.
* If the guiNotificationsEnabled parameter is set to false, the class can still
* be used through the signals and slots mechanism.
* The object will destroy itself when the request completes, errors or is canceled.
*
* @note added in QGIS 2.18.1
*/
class QgsFileDownloader : public QObject
{
%TypeHeaderCode
#include <qgsfiledownloader.h>
%End
public:
/**
* QgsFileDownloader
* @param url the download url
* @param outputFileName file name where the downloaded content will be stored
* @param guiNotificationsEnabled if false, the downloader will not display any progress bar or error message
*/
QgsFileDownloader(QUrl url, QString outputFileName, bool guiNotificationsEnabled = true);
signals:
/** Emitted when the download has completed successfully */
void downloadCompleted();
/** Emitted always when the downloader exits */
void downloadExited();
/** Emitted when the download was canceled by the user */
void downloadCanceled();
/** Emitted when an error makes the download fail */
void downloadError( QStringList errorMessages );
/** Emitted when data ready to be processed */
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal);
public slots:
/**
* Called when a download is canceled by the user
* this slot aborts the download and deletes the object
* Never call this slot directly: this is meant to
* be managed by the signal-slot system.
*/
void onDownloadCanceled();
private:
~QgsFileDownloader();
};

View File

@ -42,6 +42,7 @@
#include "qgswebframe.h"
#include "qgsstringutils.h"
#include "qgstreewidgetitem.h"
#include "qgsfiledownloader.h"
#include <QCloseEvent>
#include <QLabel>
@ -59,6 +60,11 @@
#include <QMessageBox>
#include <QComboBox>
#include <QTextDocument>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QFileDialog>
#include <QFileInfo>
#include <QRegExp>
//graph
#include <qwt_plot.h>
@ -72,6 +78,7 @@ QgsIdentifyResultsWebView::QgsIdentifyResultsWebView( QWidget *parent ) : QgsWeb
setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Minimum );
page()->setNetworkAccessManager( QgsNetworkAccessManager::instance() );
// page()->setLinkDelegationPolicy( QWebPage::DelegateAllLinks );
page()->setForwardUnsupportedContent( true );
page()->setLinkDelegationPolicy( QWebPage::DontDelegateLinks );
settings()->setAttribute( QWebSettings::LocalContentCanAccessRemoteUrls, true );
settings()->setAttribute( QWebSettings::JavascriptCanOpenWindows, true );
@ -79,6 +86,51 @@ QgsIdentifyResultsWebView::QgsIdentifyResultsWebView( QWidget *parent ) : QgsWeb
#ifdef QGISDEBUG
settings()->setAttribute( QWebSettings::DeveloperExtrasEnabled, true );
#endif
connect( page(), SIGNAL( downloadRequested( QNetworkRequest ) ), this, SLOT( downloadRequested( QNetworkRequest ) ) );
connect( page(), SIGNAL( unsupportedContent( QNetworkReply* ) ), this, SLOT( unsupportedContent( QNetworkReply* ) ) );
}
void QgsIdentifyResultsWebView::downloadRequested( const QNetworkRequest &request )
{
handleDownload( request.url() );
}
void QgsIdentifyResultsWebView::unsupportedContent( QNetworkReply * reply )
{
handleDownload( reply->url() );
}
void QgsIdentifyResultsWebView::handleDownload( QUrl url )
{
if ( ! url.isValid() )
{
QMessageBox::warning( this, tr( "Invalid URL" ), tr( "The download URL is not valid: %1" ).arg( url.toString( ) ) );
}
else
{
const QString DOWNLOADER_LAST_DIR_KEY( "Qgis/fileDownloaderLastDir" );
QSettings settings;
// Try to get some information from the URL
QFileInfo info( url.toString( ) );
QString savePath = settings.value( DOWNLOADER_LAST_DIR_KEY ).toString( );
QString fileName = info.fileName().replace( QRegExp( "[^A-z0-9\\-_\\.]" ), "_" );
if ( ! savePath.isEmpty() && ! fileName.isEmpty( ) )
{
savePath = QDir::cleanPath( savePath + QDir::separator() + fileName );
}
QString targetFile = QFileDialog::getSaveFileName( this,
tr( "Save as" ),
savePath,
info.suffix( ).isEmpty() ? QString( ) : "*." + info.suffix( )
);
if ( ! targetFile.isEmpty() )
{
settings.setValue( DOWNLOADER_LAST_DIR_KEY, QFileInfo( targetFile ).dir().absolutePath( ) );
// Start the download
new QgsFileDownloader( url, targetFile );
}
}
}
void QgsIdentifyResultsWebView::print()

View File

@ -28,6 +28,9 @@
#include <QWidget>
#include <QList>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QUrl>
class QCloseEvent;
class QTreeWidgetItem;
@ -57,9 +60,13 @@ class APP_EXPORT QgsIdentifyResultsWebView : public QgsWebView
QSize sizeHint() const override;
public slots:
void print();
void downloadRequested( const QNetworkRequest &request );
void unsupportedContent( QNetworkReply *reply );
protected:
void contextMenuEvent( QContextMenuEvent* ) override;
QgsWebView *createWindow( QWebPage::WebWindowType type ) override;
private:
void handleDownload( QUrl url );
};
class APP_EXPORT QgsIdentifyResultsFeatureItem: public QTreeWidgetItem

View File

@ -317,6 +317,7 @@ SET(QGIS_GUI_SRCS
qgsuserinputdockwidget.cpp
qgsvariableeditorwidget.cpp
qgsvertexmarker.cpp
qgsfiledownloader.cpp
)
IF (WITH_QTWEBKIT)
@ -471,6 +472,7 @@ SET(QGIS_GUI_MOC_HDRS
qgsunitselectionwidget.h
qgsuserinputdockwidget.h
qgsvariableeditorwidget.h
qgsfiledownloader.h
raster/qgsmultibandcolorrendererwidget.h
raster/qgspalettedrendererwidget.h
@ -664,6 +666,7 @@ SET(QGIS_GUI_HDRS
qgsuserinputdockwidget.h
qgsvectorlayertools.h
qgsvertexmarker.h
qgsfiledownloader.h
attributetable/qgsfeaturemodel.h

View File

@ -0,0 +1,203 @@
/***************************************************************************
qgsfiledownloader.cpp
--------------------------------------
Date : November 2016
Copyright : (C) 2016 by Alessandro Pasotti
Email : apasotti at boundlessgeo 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 "qgsfiledownloader.h"
#include "qgsnetworkaccessmanager.h"
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QMessageBox>
#ifndef QT_NO_OPENSSL
#include <QSslError>
#endif
QgsFileDownloader::QgsFileDownloader( QUrl url, QString outputFileName, bool enableGuiNotifications )
: mUrl( url )
, mReply( nullptr )
, mProgressDialog( nullptr )
, mDownloadCanceled( false )
, mErrors()
, mGuiNotificationsEnabled( enableGuiNotifications )
{
mFile.setFileName( outputFileName );
startDownload();
}
QgsFileDownloader::~QgsFileDownloader()
{
if ( mReply )
{
mReply->abort();
mReply->deleteLater();
}
if ( mProgressDialog )
{
mProgressDialog->deleteLater();
}
}
void QgsFileDownloader::startDownload()
{
QgsNetworkAccessManager* nam = QgsNetworkAccessManager::instance();
QNetworkRequest request( mUrl );
mReply = nam->get( request );
connect( mReply, &QNetworkReply::readyRead, this, &QgsFileDownloader::onReadyRead );
connect( mReply, static_cast < void ( QNetworkReply::* )( QNetworkReply::NetworkError ) > ( &QNetworkReply::error ), this, &QgsFileDownloader::onNetworkError );
connect( mReply, &QNetworkReply::finished, this, &QgsFileDownloader::onFinished );
connect( mReply, &QNetworkReply::downloadProgress, this, &QgsFileDownloader::onDownloadProgress );
connect( nam, &QgsNetworkAccessManager::requestTimedOut, this, &QgsFileDownloader::onRequestTimedOut );
#ifndef QT_NO_OPENSSL
connect( nam, &QgsNetworkAccessManager::sslErrors, this, &QgsFileDownloader::onSslErrors );
#endif
if ( mGuiNotificationsEnabled )
{
mProgressDialog = new QProgressDialog();
mProgressDialog->setWindowTitle( tr( "Download" ) );
mProgressDialog->setLabelText( tr( "Downloading %1." ).arg( mFile.fileName() ) );
mProgressDialog->show();
connect( mProgressDialog, &QProgressDialog::canceled, this, &QgsFileDownloader::onDownloadCanceled );
}
}
void QgsFileDownloader::onDownloadCanceled()
{
mDownloadCanceled = true;
emit downloadCanceled();
onFinished();
}
void QgsFileDownloader::onRequestTimedOut()
{
error( tr( "Network request %1 timed out" ).arg( mUrl.toString() ) );
}
#ifndef QT_NO_OPENSSL
void QgsFileDownloader::onSslErrors( QNetworkReply *reply, const QList<QSslError> &errors )
{
Q_UNUSED( reply );
QStringList errorMessages;
errorMessages << "SSL Errors: ";
for ( auto end = errors.size(), i = 0; i != end; ++i )
{
errorMessages << errors[i].errorString();
}
error( errorMessages );
}
#endif
void QgsFileDownloader::error( QStringList errorMessages )
{
for ( auto end = errorMessages.size(), i = 0; i != end; ++i )
{
mErrors.append( errorMessages[i] );
}
// Show error
if ( mGuiNotificationsEnabled )
{
QMessageBox::warning( nullptr, tr( "Download failed" ), mErrors.join( "<br>" ) );
}
emit downloadError( mErrors );
}
void QgsFileDownloader::error( QString errorMessage )
{
error( QStringList() << errorMessage );
}
void QgsFileDownloader::onReadyRead()
{
Q_ASSERT( mReply );
if ( ! mFile.isOpen() && ! mFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
{
error( tr( "Cannot open output file: %1" ).arg( mFile.fileName() ) );
onFinished();
}
else
{
QByteArray data = mReply->readAll();
mFile.write( data );
}
}
void QgsFileDownloader::onFinished()
{
// when canceled
if ( ! mErrors.isEmpty() || mDownloadCanceled )
{
mFile.close();
mFile.remove();
if ( mGuiNotificationsEnabled )
mProgressDialog->hide();
}
else
{
// download finished normally
if ( mGuiNotificationsEnabled )
mProgressDialog->hide();
mFile.flush();
mFile.close();
// get redirection url
QVariant redirectionTarget = mReply->attribute( QNetworkRequest::RedirectionTargetAttribute );
if ( mReply->error() )
{
mFile.remove();
error( tr( "Download failed: %1." ).arg( mReply->errorString() ) );
}
else if ( !redirectionTarget.isNull() )
{
QUrl newUrl = mUrl.resolved( redirectionTarget.toUrl() );
mUrl = newUrl;
mReply->deleteLater();
mFile.open( QIODevice::WriteOnly );
mFile.resize( 0 );
mFile.close();
startDownload();
return;
}
// All done
emit downloadCompleted();
}
emit downloadExited();
this->deleteLater();
}
void QgsFileDownloader::onNetworkError( QNetworkReply::NetworkError err )
{
Q_ASSERT( mReply );
error( QString( "Network error %1: %2" ).arg( err ).arg( mReply->errorString() ) );
}
void QgsFileDownloader::onDownloadProgress( qint64 bytesReceived, qint64 bytesTotal )
{
if ( mDownloadCanceled )
{
return;
}
if ( mGuiNotificationsEnabled )
{
mProgressDialog->setMaximum( bytesTotal );
mProgressDialog->setValue( bytesReceived );
}
emit downloadProgress( bytesReceived, bytesTotal );
}

117
src/gui/qgsfiledownloader.h Normal file
View File

@ -0,0 +1,117 @@
/***************************************************************************
qgsfiledownloader.h
--------------------------------------
Date : November 2016
Copyright : (C) 2016 by Alessandro Pasotti
Email : apasotti at boundlessgeo 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. *
* *
***************************************************************************/
#ifndef QGSFILEDOWNLOADER_H
#define QGSFILEDOWNLOADER_H
#include <QObject>
#include <QFile>
#include <QNetworkReply>
#include <QProgressDialog>
#ifndef QT_NO_OPENSSL
#include <QSslError>
#endif
/** \ingroup gui
* QgsFileDownloader is a utility class for downloading files.
*
* To use this class, it is necessary to pass the URL and an output file name as
* arguments to the constructor, the download will start immediately.
* The download is asynchronous and depending on the guiNotificationsEnabled
* parameter accepted by the constructor (default = true) the class will
* show a progress dialog and report all errors in a QMessageBox::warning dialog.
* If the guiNotificationsEnabled parameter is set to false, the class can still
* be used through the signals and slots mechanism.
* The object will destroy itself when the request completes, errors or is canceled.
*
* @note added in QGIS 2.18.1
*/
class GUI_EXPORT QgsFileDownloader : public QObject
{
Q_OBJECT
public:
/**
* QgsFileDownloader
* @param url the download url
* @param outputFileName file name where the downloaded content will be stored
* @param guiNotificationsEnabled if false, the downloader will not display any progress bar or error message
*/
QgsFileDownloader( QUrl url, QString outputFileName, bool guiNotificationsEnabled = true );
signals:
//! Emitted when the download has completed successfully
void downloadCompleted();
//! Emitted always when the downloader exits
void downloadExited();
//! Emitted when the download was canceled by the user
void downloadCanceled();
//! Emitted when an error makes the download fail
void downloadError( QStringList errorMessages );
//! Emitted when data ready to be processed
void downloadProgress( qint64 bytesReceived, qint64 bytesTotal );
public slots:
/**
* Called when a download is canceled by the user
* this slot aborts the download and deletes
* the object.
* Never call this slot directly: this is meant to
* be managed by the signal-slot system.
*/
void onDownloadCanceled();
private slots:
//! Called when the network reply data are ready
void onReadyRead();
//! Called when the network reply has finished
void onFinished();
//! Called on Network Error
void onNetworkError( QNetworkReply::NetworkError err );
//! Called on data ready to be processed
void onDownloadProgress( qint64 bytesReceived, qint64 bytesTotal );
//! Called when a network request times out
void onRequestTimedOut();
//! Called to start the download
void startDownload();
#ifndef QT_NO_OPENSSL
/**
* Called on SSL network Errors
* @param reply
* @param errors
*/
void onSslErrors( QNetworkReply *reply, const QList<QSslError> &errors );
#endif
private:
~QgsFileDownloader();
/**
* Abort current request and show an error if the instance has GUI
* notifications enabled.
*/
void error( QStringList errorMessages );
void error( QString errorMessage );
QUrl mUrl;
QNetworkReply* mReply;
QFile mFile;
QProgressDialog* mProgressDialog;
bool mDownloadCanceled;
QStringList mErrors;
bool mGuiNotificationsEnabled;
};
#endif // QGSFILEDOWNLOADER_H

View File

@ -143,3 +143,5 @@ ADD_QGIS_TEST(sqlcomposerdialog testqgssqlcomposerdialog.cpp)
ADD_QGIS_TEST(editorwidgetregistrytest testqgseditorwidgetregistry.cpp)
ADD_QGIS_TEST(keyvaluewidgettest testqgskeyvaluewidget.cpp)
ADD_QGIS_TEST(listwidgettest testqgslistwidget.cpp)
ADD_QGIS_TEST(filedownloader testqgsfiledownloader.cpp)

View File

@ -0,0 +1,254 @@
/***************************************************************************
testqgsfilefiledownloader.cpp
--------------------------------------
Date : 11.8.2016
Copyright : (C) 2016 Alessandro Pasotti
Email : apasotti at boundlessgeo 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 <QtTest/QtTest>
#include <QObject>
#include <QTemporaryFile>
#include <QUrl>
#include <QEventLoop>
#include <QTimer>
#include <qgsapplication.h>
#include <qgsfiledownloader.h>
class TestQgsFileDownloader: public QObject
{
Q_OBJECT
public:
TestQgsFileDownloader()
: mTempFile( nullptr )
, mErrorMessage()
, mCanceled( false )
, mProgress( false )
, mError( false )
, mCompleted( false )
, mExited( false )
, mFileDownloader( nullptr )
{}
public slots:
//! Called when the download has completed successfully
void downloadCompleted()
{
mCompleted = true;
}
//! Called when the download exits
void downloadExited()
{
mExited = true;
}
//! Called when the download was canceled by the user
void downloadCanceled()
{
mCanceled = true;
}
//! Called when an error makes the download fail
void downloadError( QStringList errorMessages )
{
mError = true;
errorMessages.sort();
mErrorMessage = errorMessages.join( ";" );
}
//! Called when data ready to be processed
void downloadProgress( qint64 bytesReceived, qint64 bytesTotal )
{
Q_UNUSED( bytesReceived );
Q_UNUSED( bytesTotal );
mProgress = true;
}
private slots:
void initTestCase(); // will be called before the first testfunction is executed.
void cleanupTestCase(); // will be called after the last testfunction was executed.
void init(); // will be called before each testfunction is executed.
void cleanup(); // will be called after every testfunction.
void testValidDownload();
void testInValidDownload();
void testCanceledDownload();
void testInvalidFile();
void testInvalidUrl();
void testBlankUrl();
#ifndef QT_NO_OPENSSL
void testSslError_data();
void testSslError();
#endif
private:
void makeCall( QUrl url , QString fileName, bool cancel = false );
QTemporaryFile *mTempFile;
QString mErrorMessage;
bool mCanceled;
bool mProgress;
bool mError;
bool mCompleted;
bool mExited;
QgsFileDownloader *mFileDownloader;
};
void TestQgsFileDownloader::makeCall( QUrl url, QString fileName, bool cancel )
{
QEventLoop loop;
mFileDownloader = new QgsFileDownloader( url, fileName, false );
connect( mFileDownloader, &QgsFileDownloader::downloadCompleted, this, &TestQgsFileDownloader::downloadCompleted );
connect( mFileDownloader, &QgsFileDownloader::downloadCanceled, this, &TestQgsFileDownloader::downloadCanceled );
connect( mFileDownloader, &QgsFileDownloader::downloadExited, this, &TestQgsFileDownloader::downloadExited );
connect( mFileDownloader, &QgsFileDownloader::downloadError, this, &TestQgsFileDownloader::downloadError );
connect( mFileDownloader, &QgsFileDownloader::downloadProgress, this, &TestQgsFileDownloader::downloadProgress );
connect( mFileDownloader, &QgsFileDownloader::downloadExited, &loop, &QEventLoop::quit );
if ( cancel )
QTimer::singleShot( 1000, mFileDownloader, &QgsFileDownloader::onDownloadCanceled );
loop.exec();
}
void TestQgsFileDownloader::initTestCase()
{
QgsApplication::init();
QgsApplication::initQgis();
}
void TestQgsFileDownloader::cleanupTestCase()
{
QgsApplication::exitQgis();
}
void TestQgsFileDownloader::init()
{
mErrorMessage.clear();
mCanceled = false;
mProgress = false;
mError = false;
mCompleted = false;
mExited = false;
mTempFile = new QTemporaryFile( );
Q_ASSERT( mTempFile->open() );
mTempFile->close();
}
void TestQgsFileDownloader::cleanup()
{
delete mTempFile;
}
void TestQgsFileDownloader::testValidDownload()
{
QVERIFY( ! mTempFile->fileName().isEmpty() );
makeCall( QUrl( "http://www.qgis.org" ), mTempFile->fileName() );
QVERIFY( mExited );
QVERIFY( mCompleted );
QVERIFY( mProgress );
QVERIFY( !mError );
QVERIFY( !mCanceled );
QVERIFY( mTempFile->size() > 0 );
}
void TestQgsFileDownloader::testInValidDownload()
{
QVERIFY( ! mTempFile->fileName().isEmpty() );
makeCall( QUrl( "http://www.doesnotexistofthatimsure.qgis" ), mTempFile->fileName() );
QVERIFY( mExited );
QVERIFY( !mCompleted );
QVERIFY( mError );
QVERIFY( !mCanceled );
QVERIFY( mTempFile->size() == 0 );
QCOMPARE( mErrorMessage, QString( "Network error 3: Host www.doesnotexistofthatimsure.qgis not found" ) );
}
void TestQgsFileDownloader::testCanceledDownload()
{
QVERIFY( ! mTempFile->fileName().isEmpty() );
makeCall( QUrl( "https://github.com/qgis/QGIS/archive/master.zip" ), mTempFile->fileName(), true );
QVERIFY( mExited );
QVERIFY( !mCompleted );
QVERIFY( !mError );
QVERIFY( mProgress );
QVERIFY( mCanceled );
QVERIFY( mTempFile->size() == 0 );
}
void TestQgsFileDownloader::testInvalidFile()
{
makeCall( QUrl( "https://github.com/qgis/QGIS/archive/master.zip" ), QString() );
QVERIFY( mExited );
QVERIFY( !mCompleted );
QVERIFY( mError );
QVERIFY( !mCanceled );
QCOMPARE( mErrorMessage, QString( "Cannot open output file: " ) );
}
void TestQgsFileDownloader::testInvalidUrl()
{
QVERIFY( ! mTempFile->fileName().isEmpty() );
makeCall( QUrl( "xyz://www" ), mTempFile->fileName() );
QVERIFY( mExited );
QVERIFY( !mCompleted );
QVERIFY( mError );
QVERIFY( !mCanceled );
QCOMPARE( mErrorMessage, QString( "Network error 301: Protocol \"xyz\" is unknown" ) );
}
void TestQgsFileDownloader::testBlankUrl()
{
QVERIFY( ! mTempFile->fileName().isEmpty() );
makeCall( QUrl( "" ), mTempFile->fileName() );
QVERIFY( mExited );
QVERIFY( !mCompleted );
QVERIFY( mError );
QVERIFY( !mCanceled );
QCOMPARE( mErrorMessage, QString( "Network error 301: Protocol \"\" is unknown" ) );
}
#ifndef QT_NO_OPENSSL
void TestQgsFileDownloader::testSslError_data()
{
QTest::addColumn<QString>( "url" );
QTest::addColumn<QString>( "result" );
QTest::newRow( "expired" ) << "https://expired.badssl.com/"
<< "Network error 6: SSL handshake failed;SSL Errors: ;The certificate has expired";
QTest::newRow( "self-signed" ) << "https://self-signed.badssl.com/"
<< "Network error 6: SSL handshake failed;SSL Errors: ;The certificate is self-signed, and untrusted";
QTest::newRow( "untrusted-root" ) << "https://untrusted-root.badssl.com/"
<< "Network error 6: SSL handshake failed;No certificates could be verified;SSL Errors: ;The issuer certificate of a locally looked up certificate could not be found";
}
void TestQgsFileDownloader::testSslError()
{
QFETCH( QString, url );
QFETCH( QString, result );
QVERIFY( ! mTempFile->fileName().isEmpty() );
makeCall( QUrl( url ), mTempFile->fileName() );
QCOMPARE( mErrorMessage, result );
QVERIFY( !mCompleted );
QVERIFY( mError );
QVERIFY( !mCanceled );
}
#endif
QTEST_MAIN( TestQgsFileDownloader )
#include "testqgsfiledownloader.moc"

View File

@ -123,6 +123,7 @@ ADD_PYTHON_TEST(PyQgsConsole test_console.py)
ADD_PYTHON_TEST(PyQgsLayerDependencies test_layer_dependencies.py)
ADD_PYTHON_TEST(PyQgsVersionCompare test_versioncompare.py)
ADD_PYTHON_TEST(PyQgsDBManagerGpkg test_db_manager_gpkg.py)
ADD_PYTHON_TEST(PyQgsFileDownloader test_qgsfiledownloader.py)
IF (NOT WIN32)
ADD_PYTHON_TEST(PyQgsLogger test_qgslogger.py)

View File

@ -0,0 +1,147 @@
# -*- coding: utf-8 -*-
"""
Test the QgsFileDownloader class
.. note:: 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.
"""
from __future__ import print_function
from future import standard_library
import os
import tempfile
from functools import partial
from qgis.PyQt.QtCore import QEventLoop, QUrl, QTimer
from qgis.gui import (QgsFileDownloader,)
from qgis.testing import start_app, unittest
standard_library.install_aliases()
__author__ = 'Alessandro Pasotti'
__date__ = '08/11/2016'
__copyright__ = 'Copyright 2016, The QGIS Project'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'
start_app()
class TestQgsFileDownloader(unittest.TestCase):
"""
This class tests the QgsFileDownloader class
"""
def _make_download(self, url, destination, cancel=False):
self.completed_was_called = False
self.error_was_called = False
self.canceled_was_called = False
self.progress_was_called = False
self.exited_was_called = False
loop = QEventLoop()
downloader = QgsFileDownloader(QUrl(url), destination, False)
downloader.downloadCompleted.connect(partial(self._set_slot, 'completed'))
downloader.downloadExited.connect(partial(self._set_slot, 'exited'))
downloader.downloadCanceled.connect(partial(self._set_slot, 'canceled'))
downloader.downloadError.connect(partial(self._set_slot, 'error'))
downloader.downloadProgress.connect(partial(self._set_slot, 'progress'))
downloader.downloadExited.connect(loop.quit)
if cancel:
downloader.downloadProgress.connect(downloader.onDownloadCanceled)
loop.exec_()
def test_validDownload(self):
"""Tests a valid download"""
destination = tempfile.mktemp()
self._make_download('http://www.qgis.org', destination)
self.assertTrue(self.exited_was_called)
self.assertTrue(self.completed_was_called)
self.assertTrue(self.progress_was_called)
self.assertFalse(self.canceled_was_called)
self.assertFalse(self.error_was_called)
self.assertTrue(os.path.isfile(destination))
self.assertGreater(os.path.getsize(destination), 0)
def test_inValidDownload(self):
"""Tests an invalid download"""
destination = tempfile.mktemp()
self._make_download('http://www.doesnotexistofthatimsure.qgis', destination)
self.assertTrue(self.exited_was_called)
self.assertFalse(self.completed_was_called)
self.assertTrue(self.progress_was_called)
self.assertFalse(self.canceled_was_called)
self.assertTrue(self.error_was_called)
self.assertEqual(self.error_args[1], [u'Network error 3: Host www.doesnotexistofthatimsure.qgis not found'])
self.assertFalse(os.path.isfile(destination))
def test_dowloadCanceled(self):
"""Tests user canceled download"""
destination = tempfile.mktemp()
self._make_download('https://github.com/qgis/QGIS/archive/master.zip', destination, True)
self.assertTrue(self.exited_was_called)
self.assertFalse(self.completed_was_called)
self.assertTrue(self.canceled_was_called)
self.assertFalse(self.error_was_called)
self.assertFalse(os.path.isfile(destination))
def test_InvalidUrl(self):
destination = tempfile.mktemp()
self._make_download('xyz://www', destination)
self.assertTrue(self.exited_was_called)
self.assertFalse(self.completed_was_called)
self.assertFalse(self.canceled_was_called)
self.assertTrue(self.error_was_called)
self.assertFalse(os.path.isfile(destination))
self.assertEqual(self.error_args[1], [u"Network error 301: Protocol \"xyz\" is unknown"])
def test_InvalidFile(self):
self._make_download('https://github.com/qgis/QGIS/archive/master.zip', "")
self.assertTrue(self.exited_was_called)
self.assertFalse(self.completed_was_called)
self.assertFalse(self.canceled_was_called)
self.assertTrue(self.error_was_called)
self.assertEqual(self.error_args[1], [u"Cannot open output file: "])
def test_BlankUrl(self):
destination = tempfile.mktemp()
self._make_download('', destination)
self.assertTrue(self.exited_was_called)
self.assertFalse(self.completed_was_called)
self.assertFalse(self.canceled_was_called)
self.assertTrue(self.error_was_called)
self.assertFalse(os.path.isfile(destination))
self.assertEqual(self.error_args[1], [u"Network error 301: Protocol \"\" is unknown"])
def ssl_compare(self, name, url, error):
destination = tempfile.mktemp()
self._make_download(url, destination)
msg = "Failed in %s: %s" % (name, url)
self.assertTrue(self.exited_was_called)
self.assertFalse(self.completed_was_called, msg)
self.assertFalse(self.canceled_was_called, msg)
self.assertTrue(self.error_was_called, msg)
self.assertFalse(os.path.isfile(destination), msg)
result = sorted(self.error_args[1])
result = ';'.join(result)
self.assertTrue(result.startswith(error), msg + "expected:\n%s\nactual:\n%s\n" % (result, error))
def test_sslExpired(self):
self.ssl_compare("expired", "https://expired.badssl.com/", "Network error 6: SSL handshake failed;SSL Errors: ;The certificate has expired")
self.ssl_compare("self-signed", "https://self-signed.badssl.com/", "Network error 6: SSL handshake failed;SSL Errors: ;The certificate is self-signed, and untrusted")
self.ssl_compare("untrusted-root", "https://untrusted-root.badssl.com/", "Network error 6: SSL handshake failed;No certificates could be verified;")
def _set_slot(self, *args, **kwargs):
#print('_set_slot(%s) called' % args[0])
setattr(self, args[0] + '_was_called', True)
setattr(self, args[0] + '_args', args)
if __name__ == '__main__':
unittest.main()