Skip to content

Commit 3fef9cd

Browse files
authoredMay 7, 2018
Merge pull request #6872 from 3nids/fetchregistry
QgsNetworkContentFetcherRegistry: a registry for temporary downloaded files
2 parents ba04dea + b4d00d9 commit 3fef9cd

10 files changed

+698
-0
lines changed
 

‎python/core/core_auto.sip

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,7 @@
336336
%Include qgsmessageoutput.sip
337337
%Include qgsnetworkaccessmanager.sip
338338
%Include qgsnetworkcontentfetcher.sip
339+
%Include qgsnetworkcontentfetcherregistry.sip
339340
%Include qgsnetworkcontentfetchertask.sip
340341
%Include qgsofflineediting.sip
341342
%Include qgspluginlayer.sip

‎python/core/qgsapplication.sip.in

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -641,6 +641,13 @@ Returns the application's SVG cache, used for caching SVG images and handling pa
641641
within SVG files.
642642

643643
.. versionadded:: 3.0
644+
%End
645+
646+
static QgsNetworkContentFetcherRegistry *networkContentFetcherRegistry();
647+
%Docstring
648+
Returns the application's network content registry used for fetching temporary files during QGIS session
649+
650+
.. versionadded:: 3.2
644651
%End
645652

646653
static QgsSymbolLayerRegistry *symbolLayerRegistry();
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/************************************************************************
2+
* This file has been generated automatically from *
3+
* *
4+
* src/core/qgsnetworkcontentfetcherregistry.h *
5+
* *
6+
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
7+
************************************************************************/
8+
9+
10+
11+
12+
13+
14+
15+
16+
class QgsFetchedContent : QObject
17+
{
18+
%Docstring
19+
FetchedContent holds useful information about a network content being fetched
20+
21+
.. seealso:: :py:class:`QgsNetworkContentFetcherRegistry`
22+
23+
.. versionadded:: 3.2
24+
%End
25+
26+
%TypeHeaderCode
27+
#include "qgsnetworkcontentfetcherregistry.h"
28+
%End
29+
public:
30+
enum ContentStatus
31+
{
32+
NotStarted,
33+
Downloading,
34+
Finished,
35+
Failed
36+
};
37+
38+
explicit QgsFetchedContent( QTemporaryFile *file = 0, ContentStatus status = NotStarted );
39+
%Docstring
40+
Constructs a FetchedContent with pointer to the downloaded file and status of the download
41+
%End
42+
~QgsFetchedContent();
43+
44+
45+
46+
const QString filePath() const;
47+
%Docstring
48+
Return the path to the local file, an empty string if the file is not accessible yet.
49+
%End
50+
51+
ContentStatus status() const;
52+
%Docstring
53+
Return the status of the download
54+
%End
55+
56+
QNetworkReply::NetworkError error() const;
57+
%Docstring
58+
Return the potential error of the download
59+
%End
60+
61+
public slots:
62+
63+
void download( bool redownload = false );
64+
%Docstring
65+
Start the download
66+
67+
:param redownload: if set to true, it will restart any achieved or pending download.
68+
%End
69+
70+
void cancel();
71+
%Docstring
72+
Cancel the download operation
73+
%End
74+
75+
signals:
76+
void fetched();
77+
%Docstring
78+
Emitted when the file is fetched and accessible
79+
%End
80+
81+
void downloadStarted( const bool redownload );
82+
%Docstring
83+
Emitted when the download actually starts
84+
%End
85+
86+
void cancelTriggered();
87+
%Docstring
88+
Emitted when download is canceled.
89+
%End
90+
91+
void taskCompleted();
92+
%Docstring
93+
Emitted when the download is finished (although file not accessible yet)
94+
%End
95+
96+
};
97+
98+
class QgsNetworkContentFetcherRegistry : QObject
99+
{
100+
%Docstring
101+
Registry for temporary fetched files
102+
103+
This provides a simple way of downloading and accessing
104+
remote files during QGIS application running.
105+
106+
.. seealso:: :py:class:`QgsFetchedContent`
107+
108+
.. versionadded:: 3.2
109+
%End
110+
111+
%TypeHeaderCode
112+
#include "qgsnetworkcontentfetcherregistry.h"
113+
%End
114+
public:
115+
enum FetchingMode
116+
{
117+
DownloadLater,
118+
DownloadImmediately,
119+
};
120+
121+
explicit QgsNetworkContentFetcherRegistry();
122+
%Docstring
123+
Create the registry for temporary downloaded files
124+
%End
125+
126+
~QgsNetworkContentFetcherRegistry();
127+
128+
const QgsFetchedContent *fetch( const QUrl &url, const FetchingMode fetchingMode = DownloadLater );
129+
%Docstring
130+
Initialize a download for the given URL
131+
132+
:param url: the URL to be fetched
133+
:param fetchingMode: defines if the download will start immediately or shall be manually triggered
134+
135+
.. note::
136+
137+
If the download starts immediately, it will not redownload any already fetched or currently fetching file.
138+
%End
139+
140+
141+
QString localPath( const QString &filePathOrUrl );
142+
%Docstring
143+
Returns the path to a local file or to a temporary file previously fetched by the registry
144+
145+
:param filePathOrUrl: can either be a local file path or a remote content which has previously been fetched
146+
%End
147+
148+
};
149+
150+
/************************************************************************
151+
* This file has been generated automatically from *
152+
* *
153+
* src/core/qgsnetworkcontentfetcherregistry.h *
154+
* *
155+
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
156+
************************************************************************/

‎src/core/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@ SET(QGIS_CORE_SRCS
238238
qgsnetworkaccessmanager.cpp
239239
qgsnetworkdiskcache.cpp
240240
qgsnetworkcontentfetcher.cpp
241+
qgsnetworkcontentfetcherregistry.cpp
241242
qgsnetworkcontentfetchertask.cpp
242243
qgsnetworkreplyparser.cpp
243244
qgsobjectcustomproperties.cpp
@@ -625,6 +626,7 @@ SET(QGIS_CORE_MOC_HDRS
625626
qgsnetworkaccessmanager.h
626627
qgsnetworkdiskcache.h
627628
qgsnetworkcontentfetcher.h
629+
qgsnetworkcontentfetcherregistry.h
628630
qgsnetworkcontentfetchertask.h
629631
qgsnetworkreplyparser.h
630632
qgsofflineediting.h

‎src/core/qgsapplication.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "qgslogger.h"
2323
#include "qgsproject.h"
2424
#include "qgsnetworkaccessmanager.h"
25+
#include "qgsnetworkcontentfetcherregistry.h"
2526
#include "qgsproviderregistry.h"
2627
#include "qgsexpression.h"
2728
#include "qgsactionscoperegistry.h"
@@ -1664,6 +1665,11 @@ QgsSvgCache *QgsApplication::svgCache()
16641665
return members()->mSvgCache;
16651666
}
16661667

1668+
QgsNetworkContentFetcherRegistry *QgsApplication::networkContentFetcherRegistry()
1669+
{
1670+
return members()->mNetworkContentFetcherRegistry;
1671+
}
1672+
16671673
QgsSymbolLayerRegistry *QgsApplication::symbolLayerRegistry()
16681674
{
16691675
return members()->mSymbolLayerRegistry;
@@ -1743,6 +1749,7 @@ QgsApplication::ApplicationMembers::ApplicationMembers()
17431749
mAnnotationRegistry = new QgsAnnotationRegistry();
17441750
m3DRendererRegistry = new Qgs3DRendererRegistry();
17451751
mProjectStorageRegistry = new QgsProjectStorageRegistry();
1752+
mNetworkContentFetcherRegistry = new QgsNetworkContentFetcherRegistry();
17461753
}
17471754

17481755
QgsApplication::ApplicationMembers::~ApplicationMembers()
@@ -1766,6 +1773,7 @@ QgsApplication::ApplicationMembers::~ApplicationMembers()
17661773
delete mSvgCache;
17671774
delete mSymbolLayerRegistry;
17681775
delete mTaskManager;
1776+
delete mNetworkContentFetcherRegistry;
17691777
}
17701778

17711779
QgsApplication::ApplicationMembers *QgsApplication::members()

‎src/core/qgsapplication.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ class QgsUserProfileManager;
4646
class QgsPageSizeRegistry;
4747
class QgsLayoutItemRegistry;
4848
class QgsAuthManager;
49+
class QgsNetworkContentFetcherRegistry;
4950

5051
/**
5152
* \ingroup core
@@ -586,6 +587,12 @@ class CORE_EXPORT QgsApplication : public QApplication
586587
*/
587588
static QgsSvgCache *svgCache();
588589

590+
/**
591+
* Returns the application's network content registry used for fetching temporary files during QGIS session
592+
* \since QGIS 3.2
593+
*/
594+
static QgsNetworkContentFetcherRegistry *networkContentFetcherRegistry();
595+
589596
/**
590597
* Returns the application's symbol layer registry, used for managing symbol layers.
591598
* \since QGIS 3.0
@@ -802,6 +809,7 @@ class CORE_EXPORT QgsApplication : public QApplication
802809
QgsColorSchemeRegistry *mColorSchemeRegistry = nullptr;
803810
QgsFieldFormatterRegistry *mFieldFormatterRegistry = nullptr;
804811
QgsGpsConnectionRegistry *mGpsConnectionRegistry = nullptr;
812+
QgsNetworkContentFetcherRegistry *mNetworkContentFetcherRegistry = nullptr;
805813
QgsMessageLog *mMessageLog = nullptr;
806814
QgsPaintEffectRegistry *mPaintEffectRegistry = nullptr;
807815
QgsPluginLayerRegistry *mPluginLayerRegistry = nullptr;
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
/***************************************************************************
2+
qgsnetworkcontentfetcherregistry.cpp
3+
-------------------
4+
begin : April, 2018
5+
copyright : (C) 2018 by Denis Rouzaud
6+
email : denis@opengis.ch
7+
8+
***************************************************************************/
9+
10+
/***************************************************************************
11+
* *
12+
* This program is free software; you can redistribute it and/or modify *
13+
* it under the terms of the GNU General Public License as published by *
14+
* the Free Software Foundation; either version 2 of the License, or *
15+
* (at your option) any later version. *
16+
* *
17+
***************************************************************************/
18+
19+
#include "qgsnetworkcontentfetcherregistry.h"
20+
21+
#include "qgsapplication.h"
22+
23+
QgsNetworkContentFetcherRegistry::QgsNetworkContentFetcherRegistry()
24+
: QObject()
25+
{
26+
}
27+
28+
QgsNetworkContentFetcherRegistry::~QgsNetworkContentFetcherRegistry()
29+
{
30+
QMap<QUrl, QgsFetchedContent *>::const_iterator it = mFileRegistry.constBegin();
31+
for ( ; it != mFileRegistry.constEnd(); ++it )
32+
{
33+
delete it.value();
34+
}
35+
mFileRegistry.clear();
36+
}
37+
38+
const QgsFetchedContent *QgsNetworkContentFetcherRegistry::fetch( const QUrl &url, const FetchingMode fetchingMode )
39+
{
40+
QMutexLocker locker( &mMutex );
41+
if ( mFileRegistry.contains( url ) )
42+
{
43+
return mFileRegistry.value( url );
44+
}
45+
46+
QgsFetchedContent *content = new QgsFetchedContent( nullptr, QgsFetchedContent::NotStarted );
47+
48+
// start
49+
QObject::connect( content, &QgsFetchedContent::downloadStarted, this, [ = ]( const bool redownload )
50+
{
51+
if ( redownload && content->status() == QgsFetchedContent::Downloading )
52+
{
53+
{
54+
QMutexLocker locker( &mMutex );
55+
if ( content->mFetchingTask )
56+
disconnect( content->mFetchingTask, &QgsNetworkContentFetcherTask::fetched, content, &QgsFetchedContent::taskCompleted );
57+
}
58+
// no locker when calling cancel!
59+
content->cancel();
60+
}
61+
QMutexLocker locker( &mMutex );
62+
if ( redownload ||
63+
content->status() == QgsFetchedContent::NotStarted ||
64+
content->status() == QgsFetchedContent::Failed )
65+
{
66+
content->mFetchingTask = new QgsNetworkContentFetcherTask( url );
67+
connect( content->mFetchingTask, &QgsNetworkContentFetcherTask::fetched, content, &QgsFetchedContent::taskCompleted );
68+
QgsApplication::instance()->taskManager()->addTask( content->mFetchingTask );
69+
content->mStatus = QgsFetchedContent::Downloading;
70+
}
71+
} );
72+
73+
// cancel
74+
QObject::connect( content, &QgsFetchedContent::cancelTriggered, this, [ = ]()
75+
{
76+
QMutexLocker locker( &mMutex );
77+
if ( content->mFetchingTask && content->mFetchingTask->canCancel() )
78+
{
79+
content->mFetchingTask->cancel();
80+
}
81+
if ( content->mFile )
82+
{
83+
content->mFile->deleteLater();
84+
content->mFilePath = QString();
85+
}
86+
} );
87+
88+
// finished
89+
connect( content, &QgsFetchedContent::taskCompleted, this, [ = ]()
90+
{
91+
QMutexLocker locker( &mMutex );
92+
if ( !content->mFetchingTask || !content->mFetchingTask->reply() )
93+
{
94+
// if no reply, it has been canceled
95+
content->mStatus = QgsFetchedContent::Failed;
96+
content->mError = QNetworkReply::OperationCanceledError;
97+
content->mFilePath = QString();
98+
}
99+
else
100+
{
101+
QNetworkReply *reply = content->mFetchingTask->reply();
102+
if ( reply->error() == QNetworkReply::NoError )
103+
{
104+
QTemporaryFile *tf = new QTemporaryFile( QStringLiteral( "XXXXXX" ) );
105+
content->mFile = tf;
106+
tf->open();
107+
content->mFile->write( reply->readAll() );
108+
// Qt docs notes that on some system if fileName is not called before close, file might get deleted
109+
content->mFilePath = tf->fileName();
110+
tf->close();
111+
content->mStatus = QgsFetchedContent::Finished;
112+
}
113+
else
114+
{
115+
content->mStatus = QgsFetchedContent::Failed;
116+
content->mError = reply->error();
117+
content->mFilePath = QString();
118+
}
119+
}
120+
content->emitFetched();
121+
} );
122+
123+
mFileRegistry.insert( url, content );
124+
125+
if ( fetchingMode == DownloadImmediately )
126+
content->download();
127+
128+
return content;
129+
}
130+
131+
const QFile *QgsNetworkContentFetcherRegistry::localFile( const QString &filePathOrUrl )
132+
{
133+
QFile *file = nullptr;
134+
QString path = filePathOrUrl;
135+
136+
if ( !QUrl::fromUserInput( filePathOrUrl ).isLocalFile() )
137+
{
138+
QMutexLocker locker( &mMutex );
139+
if ( mFileRegistry.contains( QUrl( path ) ) )
140+
{
141+
const QgsFetchedContent *content = mFileRegistry.value( QUrl( path ) );
142+
if ( content->status() == QgsFetchedContent::Finished && !content->file() )
143+
{
144+
file = content->file();
145+
}
146+
else
147+
{
148+
// if the file is not downloaded yet or has failed, return nullptr
149+
}
150+
}
151+
else
152+
{
153+
// if registry doesn't contain the URL, return nullptr
154+
}
155+
}
156+
else
157+
{
158+
file = new QFile( filePathOrUrl );
159+
}
160+
return file;
161+
}
162+
163+
QString QgsNetworkContentFetcherRegistry::localPath( const QString &filePathOrUrl )
164+
{
165+
QString path = filePathOrUrl;
166+
167+
if ( !QUrl::fromUserInput( filePathOrUrl ).isLocalFile() )
168+
{
169+
QMutexLocker locker( &mMutex );
170+
if ( mFileRegistry.contains( QUrl( path ) ) )
171+
{
172+
const QgsFetchedContent *content = mFileRegistry.value( QUrl( path ) );
173+
if ( content->status() == QgsFetchedContent::Finished && !content->filePath().isEmpty() )
174+
{
175+
path = content->filePath();
176+
}
177+
else
178+
{
179+
// if the file is not downloaded yet or has failed, return empty string
180+
path = QString();
181+
}
182+
}
183+
else
184+
{
185+
// if registry doesn't contain the URL, keep path unchanged
186+
}
187+
}
188+
return path;
189+
}
190+
191+
192+
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
/***************************************************************************
2+
qgsnetworkcontentfetcherregistry.h
3+
-------------------
4+
begin : April, 2018
5+
copyright : (C) 2018 by Denis Rouzaud
6+
email : denis@opengis.ch
7+
8+
***************************************************************************/
9+
10+
/***************************************************************************
11+
* *
12+
* This program is free software; you can redistribute it and/or modify *
13+
* it under the terms of the GNU General Public License as published by *
14+
* the Free Software Foundation; either version 2 of the License, or *
15+
* (at your option) any later version. *
16+
* *
17+
***************************************************************************/
18+
19+
#ifndef QGSNETWORKCONTENTFETCHERREGISTRY_H
20+
#define QGSNETWORKCONTENTFETCHERREGISTRY_H
21+
22+
#include <QObject>
23+
#include <QMap>
24+
#include <QMutex>
25+
#include <QNetworkReply>
26+
#include <QTemporaryFile>
27+
28+
#include "qgis_core.h"
29+
30+
class QTemporaryFile;
31+
32+
#include "qgstaskmanager.h"
33+
#include "qgsnetworkcontentfetchertask.h"
34+
35+
36+
/**
37+
* \class QgsFetchedContent
38+
* \ingroup core
39+
* FetchedContent holds useful information about a network content being fetched
40+
* \see QgsNetworkContentFetcherRegistry
41+
* \since QGIS 3.2
42+
*/
43+
class CORE_EXPORT QgsFetchedContent : public QObject
44+
{
45+
Q_OBJECT
46+
public:
47+
//! Status of fetched content
48+
enum ContentStatus
49+
{
50+
NotStarted, //!< No download started for such URL
51+
Downloading, //!< Currently downloading
52+
Finished, //!< Download finished and successful
53+
Failed //!< Download failed
54+
};
55+
56+
//! Constructs a FetchedContent with pointer to the downloaded file and status of the download
57+
explicit QgsFetchedContent( QTemporaryFile *file = nullptr, ContentStatus status = NotStarted )
58+
: QObject(), mFile( file ), mStatus( status ) {}
59+
60+
~QgsFetchedContent()
61+
{
62+
mFile->close();
63+
delete mFile;
64+
}
65+
66+
67+
#ifndef SIP_RUN
68+
//! Return a pointer to the local file, a null pointer if the file is not accessible yet.
69+
QFile *file() const {return mFile;}
70+
#endif
71+
72+
//! Return the path to the local file, an empty string if the file is not accessible yet.
73+
const QString filePath() const {return mFilePath;}
74+
75+
//! Return the status of the download
76+
ContentStatus status() const {return mStatus;}
77+
78+
//! Return the potential error of the download
79+
QNetworkReply::NetworkError error() const {return mError;}
80+
81+
public slots:
82+
83+
/**
84+
* \brief Start the download
85+
* \param redownload if set to true, it will restart any achieved or pending download.
86+
*/
87+
void download( bool redownload = false ) {emit downloadStarted( redownload );}
88+
89+
/**
90+
* @brief Cancel the download operation
91+
*/
92+
void cancel() {emit cancelTriggered();}
93+
94+
signals:
95+
//! Emitted when the file is fetched and accessible
96+
void fetched();
97+
98+
//! Emitted when the download actually starts
99+
void downloadStarted( const bool redownload );
100+
101+
//! Emitted when download is canceled.
102+
void cancelTriggered();
103+
104+
//! Emitted when the download is finished (although file not accessible yet)
105+
void taskCompleted();
106+
107+
private:
108+
void emitFetched() {emit fetched();}
109+
QTemporaryFile *mFile;
110+
QString mFilePath = QStringLiteral();
111+
QgsNetworkContentFetcherTask *mFetchingTask = nullptr;
112+
ContentStatus mStatus;
113+
QNetworkReply::NetworkError mError = QNetworkReply::NoError;
114+
115+
// allow modification of task and file from main class
116+
friend class QgsNetworkContentFetcherRegistry;
117+
};
118+
119+
/**
120+
* \class QgsNetworkContentFetcherRegistry
121+
* \ingroup core
122+
* \brief Registry for temporary fetched files
123+
*
124+
* This provides a simple way of downloading and accessing
125+
* remote files during QGIS application running.
126+
*
127+
* \see QgsFetchedContent
128+
*
129+
* \since QGIS 3.2
130+
*/
131+
class CORE_EXPORT QgsNetworkContentFetcherRegistry : public QObject
132+
{
133+
Q_OBJECT
134+
public:
135+
//! Enum to determine when the download should start
136+
enum FetchingMode
137+
{
138+
DownloadLater, //!< Do not start immediately the download to properly connect the fetched signal
139+
DownloadImmediately, //!< The download will start immediately, not need to run QgsFecthedContent::download()
140+
};
141+
Q_ENUM( FetchingMode )
142+
143+
//! Create the registry for temporary downloaded files
144+
explicit QgsNetworkContentFetcherRegistry();
145+
146+
~QgsNetworkContentFetcherRegistry();
147+
148+
/**
149+
* \brief Initialize a download for the given URL
150+
* \param url the URL to be fetched
151+
* \param fetchingMode defines if the download will start immediately or shall be manually triggered
152+
* \note If the download starts immediately, it will not redownload any already fetched or currently fetching file.
153+
*/
154+
const QgsFetchedContent *fetch( const QUrl &url, const FetchingMode fetchingMode = DownloadLater );
155+
156+
#ifndef SIP_RUN
157+
158+
/**
159+
* \brief Returns a QFile from a local file or to a temporary file previously fetched by the registry
160+
* \param filePathOrUrl can either be a local file path or a remote content which has previously been fetched
161+
*/
162+
const QFile *localFile( const QString &filePathOrUrl );
163+
#endif
164+
165+
/**
166+
* \brief Returns the path to a local file or to a temporary file previously fetched by the registry
167+
* \param filePathOrUrl can either be a local file path or a remote content which has previously been fetched
168+
*/
169+
QString localPath( const QString &filePathOrUrl );
170+
171+
private:
172+
QMap<QUrl, QgsFetchedContent *> mFileRegistry;
173+
174+
//! Mutex to prevent concurrent access to the class from multiple threads at once (may corrupt the entries otherwise).
175+
mutable QMutex mMutex;
176+
177+
};
178+
179+
#endif // QGSNETWORKCONTENTFETCHERREGISTRY_H

‎tests/src/python/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ ADD_PYTHON_TEST(PyQgsMetadataWidget test_qgsmetadatawidget.py)
116116
ADD_PYTHON_TEST(PyQgsMemoryProvider test_provider_memory.py)
117117
ADD_PYTHON_TEST(PyQgsMultiEditToolButton test_qgsmultiedittoolbutton.py)
118118
ADD_PYTHON_TEST(PyQgsNetworkContentFetcher test_qgsnetworkcontentfetcher.py)
119+
ADD_PYTHON_TEST(PyQgsNetworkContentFetcherRegistry test_qgsnetworkcontentfetcherregistry.py)
119120
ADD_PYTHON_TEST(PyQgsNetworkContentFetcherTask test_qgsnetworkcontentfetchertask.py)
120121
ADD_PYTHON_TEST(PyQgsNullSymbolRenderer test_qgsnullsymbolrenderer.py)
121122
ADD_PYTHON_TEST(PyQgsNewGeoPackageLayerDialog test_qgsnewgeopackagelayerdialog.py)
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
# -*- coding: utf-8 -*-
2+
"""QGIS Unit tests for QgsNetworkContentFetcherRegistry
3+
4+
.. note:: This program is free software; you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation; either version 2 of the License, or
7+
(at your option) any later version.
8+
"""
9+
10+
from builtins import chr
11+
from builtins import str
12+
13+
__author__ = 'Denis Rouzaud'
14+
__date__ = '27/04/2018'
15+
__copyright__ = 'Copyright 2018, The QGIS Project'
16+
# This will get replaced with a git SHA1 when you do a git archive
17+
__revision__ = '$Format:%H$'
18+
19+
import qgis # NOQA
20+
21+
import os
22+
from qgis.testing import unittest, start_app
23+
from qgis.core import QgsNetworkContentFetcherRegistry, QgsFetchedContent, QgsApplication
24+
from utilities import unitTestDataPath
25+
from qgis.PyQt.QtCore import QUrl
26+
from qgis.PyQt.QtNetwork import QNetworkReply, QNetworkRequest
27+
import socketserver
28+
import threading
29+
import http.server
30+
31+
app = start_app()
32+
33+
34+
class TestQgsNetworkContentFetcherTask(unittest.TestCase):
35+
36+
@classmethod
37+
def setUpClass(cls):
38+
# Bring up a simple HTTP server
39+
os.chdir(unitTestDataPath() + '')
40+
handler = http.server.SimpleHTTPRequestHandler
41+
42+
cls.httpd = socketserver.TCPServer(('localhost', 0), handler)
43+
cls.port = cls.httpd.server_address[1]
44+
45+
cls.httpd_thread = threading.Thread(target=cls.httpd.serve_forever)
46+
cls.httpd_thread.setDaemon(True)
47+
cls.httpd_thread.start()
48+
49+
def __init__(self, methodName):
50+
"""Run once on class initialization."""
51+
unittest.TestCase.__init__(self, methodName)
52+
53+
self.loaded = False
54+
self.file_content = ''
55+
56+
def testFetchBadUrl(self):
57+
registry = QgsApplication.networkContentFetcherRegistry()
58+
content = registry.fetch(QUrl('http://x'))
59+
self.loaded = False
60+
61+
def check_reply():
62+
self.assertEqual(content.status(), QgsFetchedContent.Failed)
63+
self.assertNotEqual(content.error(), QNetworkReply.NoError)
64+
self.assertEqual(content.filePath(), '')
65+
self.loaded = True
66+
67+
content.fetched.connect(check_reply)
68+
content.download()
69+
while not self.loaded:
70+
app.processEvents()
71+
72+
def testFetchGoodUrl(self):
73+
url = 'http://localhost:' + str(self.port) + '/qgis_local_server/index.html'
74+
registry = QgsApplication.networkContentFetcherRegistry()
75+
content = registry.fetch(QUrl(url))
76+
self.loaded = False
77+
78+
def check_reply():
79+
self.loaded = True
80+
self.assertEqual(content.status(), QgsFetchedContent.Finished)
81+
self.assertEqual(content.error(), QNetworkReply.NoError)
82+
self.assertNotEqual(content.filePath(), '')
83+
84+
content.fetched.connect(check_reply)
85+
content.download()
86+
while not self.loaded:
87+
app.processEvents()
88+
89+
self.assertEqual(registry.localPath(url), content.filePath())
90+
91+
# create new content with same URL
92+
contentV2 = registry.fetch(QUrl(url))
93+
self.assertEqual(contentV2.status(), QgsFetchedContent.Finished)
94+
95+
def testFetchReloadUrl(self):
96+
def writeSimpleFile(content):
97+
with open('qgis_local_server/simple_content.txt', 'w') as f:
98+
f.write(content)
99+
self.file_content = content
100+
101+
registry = QgsApplication.networkContentFetcherRegistry()
102+
content = registry.fetch(QUrl('http://localhost:' + str(self.port) + '/qgis_local_server/simple_content.txt'))
103+
self.loaded = False
104+
writeSimpleFile('my initial content')
105+
106+
def check_reply():
107+
self.loaded = True
108+
self.assertEqual(content.status(), QgsFetchedContent.Finished)
109+
self.assertEqual(content.error(), QNetworkReply.NoError)
110+
self.assertNotEqual(content.filePath(), '')
111+
with open(content.filePath(), encoding="utf-8") as file:
112+
self.assertEqual(file.readline().rstrip(), self.file_content)
113+
114+
content.fetched.connect(check_reply)
115+
content.download()
116+
while not self.loaded:
117+
app.processEvents()
118+
119+
writeSimpleFile('my second content')
120+
content.download()
121+
with open(content.filePath(), encoding="utf-8") as file:
122+
self.assertNotEqual(file.readline().rstrip(), self.file_content)
123+
124+
content.download(True)
125+
while not self.loaded:
126+
app.processEvents()
127+
128+
os.remove('qgis_local_server/simple_content.txt')
129+
130+
def testLocalPath(self):
131+
registry = QgsApplication.networkContentFetcherRegistry()
132+
filePath = 'qgis_local_server/index.html'
133+
self.assertEqual(registry.localPath(filePath), filePath)
134+
135+
# a non existent download shall return untouched the path
136+
self.assertEqual(registry.localPath('xxxx'), 'xxxx')
137+
138+
# an existent but unfinished download should return an empty path
139+
content = registry.fetch(QUrl('xxxx'))
140+
self.assertEqual(registry.localPath('xxxx'), '')
141+
142+
143+
if __name__ == "__main__":
144+
unittest.main()

0 commit comments

Comments
 (0)
Please sign in to comment.