1 /***************************************************************************
2   qgsfiledownloader.cpp
3   --------------------------------------
4   Date                 : November 2016
5   Copyright            : (C) 2016 by Alessandro Pasotti
6   Email                : apasotti at boundlessgeo dot com
7  ***************************************************************************
8  *                                                                         *
9  *   This program is free software; you can redistribute it and/or modify  *
10  *   it under the terms of the GNU General Public License as published by  *
11  *   the Free Software Foundation; either version 2 of the License, or     *
12  *   (at your option) any later version.                                   *
13  *                                                                         *
14  ***************************************************************************/
15 
16 #include "qgsfiledownloader.h"
17 #include "qgsnetworkaccessmanager.h"
18 #include "qgsapplication.h"
19 #include "qgsauthmanager.h"
20 
21 #include <QNetworkAccessManager>
22 #include <QNetworkRequest>
23 #include <QNetworkReply>
24 #ifndef QT_NO_SSL
25 #include <QSslError>
26 #endif
27 
QgsFileDownloader(const QUrl & url,const QString & outputFileName,const QString & authcfg,bool delayStart,Qgis::HttpMethod httpMethod,const QByteArray & data)28 QgsFileDownloader::QgsFileDownloader( const QUrl &url, const QString &outputFileName, const QString &authcfg, bool delayStart, Qgis::HttpMethod httpMethod, const QByteArray &data )
29   : mUrl( url )
30   , mDownloadCanceled( false )
31   , mHttpMethod( httpMethod )
32   , mData( data )
33 {
34   mFile.setFileName( outputFileName );
35   mAuthCfg = authcfg;
36   if ( !delayStart )
37     startDownload();
38 }
39 
40 
~QgsFileDownloader()41 QgsFileDownloader::~QgsFileDownloader()
42 {
43   if ( mReply )
44   {
45     mReply->abort();
46     mReply->deleteLater();
47   }
48 }
49 
startDownload()50 void QgsFileDownloader::startDownload()
51 {
52   QgsNetworkAccessManager *nam = QgsNetworkAccessManager::instance();
53 
54   QNetworkRequest request( mUrl );
55   QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsFileDownloader" ) );
56   if ( !mAuthCfg.isEmpty() )
57   {
58     QgsApplication::authManager()->updateNetworkRequest( request, mAuthCfg );
59   }
60 
61   if ( mReply )
62   {
63     disconnect( mReply, &QNetworkReply::readyRead, this, &QgsFileDownloader::onReadyRead );
64     disconnect( mReply, &QNetworkReply::finished, this, &QgsFileDownloader::onFinished );
65     disconnect( mReply, &QNetworkReply::downloadProgress, this, &QgsFileDownloader::onDownloadProgress );
66     mReply->abort();
67     mReply->deleteLater();
68   }
69 
70   switch ( mHttpMethod )
71   {
72     case Qgis::HttpMethod::Get:
73     {
74       mReply = nam->get( request );
75       break;
76     }
77     case Qgis::HttpMethod::Post:
78     {
79       mReply = nam->post( request, mData );
80       break;
81     }
82   }
83 
84   if ( !mAuthCfg.isEmpty() )
85   {
86     QgsApplication::authManager()->updateNetworkReply( mReply, mAuthCfg );
87   }
88 
89   connect( mReply, &QNetworkReply::readyRead, this, &QgsFileDownloader::onReadyRead );
90   connect( mReply, &QNetworkReply::finished, this, &QgsFileDownloader::onFinished );
91   connect( mReply, &QNetworkReply::downloadProgress, this, &QgsFileDownloader::onDownloadProgress );
92   connect( nam, qOverload< QNetworkReply *>( &QgsNetworkAccessManager::requestTimedOut ), this, &QgsFileDownloader::onRequestTimedOut, Qt::UniqueConnection );
93 #ifndef QT_NO_SSL
94   connect( nam, &QgsNetworkAccessManager::sslErrors, this, &QgsFileDownloader::onSslErrors, Qt::UniqueConnection );
95 #endif
96 }
97 
cancelDownload()98 void QgsFileDownloader::cancelDownload()
99 {
100   mDownloadCanceled = true;
101   emit downloadCanceled();
102   onFinished();
103 }
104 
onRequestTimedOut(QNetworkReply * reply)105 void QgsFileDownloader::onRequestTimedOut( QNetworkReply *reply )
106 {
107   if ( reply == mReply )
108     error( tr( "Network request %1 timed out" ).arg( mUrl.toString() ) );
109 }
110 
111 #ifndef QT_NO_SSL
onSslErrors(QNetworkReply * reply,const QList<QSslError> & errors)112 void QgsFileDownloader::onSslErrors( QNetworkReply *reply, const QList<QSslError> &errors )
113 {
114   if ( reply == mReply )
115   {
116     QStringList errorMessages;
117     errorMessages.reserve( errors.size() + 1 );
118     errorMessages <<  QStringLiteral( "SSL Errors: " );
119 
120     for ( const QSslError &error : errors )
121       errorMessages << error.errorString();
122 
123     error( errorMessages );
124   }
125 }
126 #endif
127 
128 
error(const QStringList & errorMessages)129 void QgsFileDownloader::error( const QStringList &errorMessages )
130 {
131   for ( const QString &error : errorMessages )
132     mErrors << error;
133 
134   if ( mReply )
135     mReply->abort();
136   emit downloadError( mErrors );
137 }
138 
error(const QString & errorMessage)139 void QgsFileDownloader::error( const QString &errorMessage )
140 {
141   error( QStringList() << errorMessage );
142 }
143 
onReadyRead()144 void QgsFileDownloader::onReadyRead()
145 {
146   Q_ASSERT( mReply );
147   if ( mFile.fileName().isEmpty() )
148   {
149     error( tr( "No output filename specified" ) );
150     onFinished();
151   }
152   else if ( ! mFile.isOpen() && ! mFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
153   {
154     error( tr( "Cannot open output file: %1" ).arg( mFile.fileName() ) );
155     onFinished();
156   }
157   else
158   {
159     const QByteArray data = mReply->readAll();
160     mFile.write( data );
161   }
162 }
163 
onFinished()164 void QgsFileDownloader::onFinished()
165 {
166   // when canceled
167   if ( ! mErrors.isEmpty() || mDownloadCanceled )
168   {
169     if ( mFile.isOpen() )
170       mFile.close();
171     if ( mFile.exists() )
172       mFile.remove();
173   }
174   else
175   {
176     // download finished normally
177     if ( mFile.isOpen() )
178     {
179       mFile.flush();
180       mFile.close();
181     }
182 
183     // get redirection url
184     const QVariant redirectionTarget = mReply->attribute( QNetworkRequest::RedirectionTargetAttribute );
185     if ( mReply->error() )
186     {
187       mFile.remove();
188       error( tr( "Download failed: %1" ).arg( mReply->errorString() ) );
189     }
190     else if ( !redirectionTarget.isNull() )
191     {
192       const QUrl newUrl = mUrl.resolved( redirectionTarget.toUrl() );
193       mUrl = newUrl;
194       mReply->deleteLater();
195       if ( !mFile.open( QIODevice::WriteOnly ) )
196       {
197         mFile.remove();
198         error( tr( "Cannot open output file: %1" ).arg( mFile.fileName() ) );
199       }
200       else
201       {
202         mFile.resize( 0 );
203         mFile.close();
204         startDownload();
205       }
206       return;
207     }
208     else
209     {
210       emit downloadCompleted( mReply->url() );
211     }
212   }
213   emit downloadExited();
214   this->deleteLater();
215 }
216 
217 
onDownloadProgress(qint64 bytesReceived,qint64 bytesTotal)218 void QgsFileDownloader::onDownloadProgress( qint64 bytesReceived, qint64 bytesTotal )
219 {
220   if ( mDownloadCanceled )
221   {
222     return;
223   }
224   emit downloadProgress( bytesReceived, bytesTotal );
225 }
226 
227