QGIS API Documentation  3.8.0-Zanzibar (11aff65)
qgsnetworkcontentfetcher.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsnetworkcontentfetcher.cpp
3  -------------------
4  begin : July, 2014
5  copyright : (C) 2014 by Nyall Dawson
6  email : nyall dot dawson at gmail dot com
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 
21 #include "qgsmessagelog.h"
22 #include "qgsapplication.h"
23 #include <QNetworkReply>
24 #include <QTextCodec>
25 
27 {
28  if ( mReply && mReply->isRunning() )
29  {
30  //cancel running request
31  mReply->abort();
32  }
33  if ( mReply )
34  {
35  mReply->deleteLater();
36  }
37 }
38 
40 {
41  fetchContent( QNetworkRequest( url ) );
42 }
43 
44 void QgsNetworkContentFetcher::fetchContent( const QNetworkRequest &request )
45 {
46  mContentLoaded = false;
47  mIsCanceled = false;
48 
49  if ( mReply )
50  {
51  //cancel any in progress requests
52  mReply->abort();
53  mReply->deleteLater();
54  mReply = nullptr;
55  }
56 
57  mReply = QgsNetworkAccessManager::instance()->get( request );
58  mReply->setParent( nullptr ); // we don't want thread locale QgsNetworkAccessManagers to delete the reply - we want ownership of it to belong to this object
59  connect( mReply, &QNetworkReply::finished, this, [ = ] { contentLoaded(); } );
60  connect( mReply, &QNetworkReply::downloadProgress, this, &QgsNetworkContentFetcher::downloadProgress );
61 }
62 
64 {
65  if ( !mContentLoaded )
66  {
67  return nullptr;
68  }
69 
70  return mReply;
71 }
72 
74 {
75  if ( !mContentLoaded || !mReply )
76  {
77  return QString();
78  }
79 
80  QByteArray array = mReply->readAll();
81 
82  //correctly encode reply as unicode
83  QTextCodec *codec = codecForHtml( array );
84  return codec->toUnicode( array );
85 }
86 
88 {
89  mIsCanceled = true;
90 
91  if ( mReply )
92  {
93  //cancel any in progress requests
94  mReply->abort();
95  mReply->deleteLater();
96  mReply = nullptr;
97  }
98 }
99 
100 QTextCodec *QgsNetworkContentFetcher::codecForHtml( QByteArray &array ) const
101 {
102  //QTextCodec::codecForHtml fails to detect "<meta charset="utf-8"/>" type tags
103  //see https://bugreports.qt.io/browse/QTBUG-41011
104  //so test for that ourselves
105 
106  //basic check
107  QTextCodec *codec = QTextCodec::codecForUtfText( array, nullptr );
108  if ( codec )
109  {
110  return codec;
111  }
112 
113  //check for meta charset tag
114  QByteArray header = array.left( 1024 ).toLower();
115  int pos = header.indexOf( "meta charset=" );
116  if ( pos != -1 )
117  {
118  pos += int( strlen( "meta charset=" ) ) + 1;
119  int pos2 = header.indexOf( '\"', pos );
120  QByteArray cs = header.mid( pos, pos2 - pos );
121  codec = QTextCodec::codecForName( cs );
122  if ( codec )
123  {
124  return codec;
125  }
126  }
127 
128  //fallback to QTextCodec::codecForHtml
129  codec = QTextCodec::codecForHtml( array, codec );
130  if ( codec )
131  {
132  return codec;
133  }
134 
135  //no luck, default to utf-8
136  return QTextCodec::codecForName( "UTF-8" );
137 }
138 
139 void QgsNetworkContentFetcher::contentLoaded( bool ok )
140 {
141  Q_UNUSED( ok )
142 
143  if ( mIsCanceled )
144  {
145  emit finished();
146  return;
147  }
148 
149  if ( mReply->error() != QNetworkReply::NoError )
150  {
151  QgsMessageLog::logMessage( tr( "HTTP fetch %1 failed with error %2" ).arg( mReply->url().toString(), mReply->errorString() ) );
152  mContentLoaded = true;
153  emit finished();
154  return;
155  }
156 
157  QVariant redirect = mReply->attribute( QNetworkRequest::RedirectionTargetAttribute );
158  if ( redirect.isNull() )
159  {
160  //no error or redirect, got target
161  QVariant status = mReply->attribute( QNetworkRequest::HttpStatusCodeAttribute );
162  if ( !status.isNull() && status.toInt() >= 400 )
163  {
164  QgsMessageLog::logMessage( tr( "HTTP fetch %1 failed with error %2" ).arg( mReply->url().toString(), status.toString() ) );
165  }
166  mContentLoaded = true;
167  emit finished();
168  return;
169  }
170 
171  //redirect, so fetch redirect target
172  mReply->deleteLater();
173  fetchContent( redirect.toUrl() );
174 }
175 
176 
177 
178 
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
Emitted when data is received.
QNetworkReply * reply()
Returns a reference to the network reply.
QString contentAsString() const
Returns the fetched content as a string.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
void finished()
Emitted when content has loaded.
void cancel()
Cancels any ongoing request.
static QgsNetworkAccessManager * instance(Qt::ConnectionType connectionType=Qt::BlockingQueuedConnection)
Returns a pointer to the active QgsNetworkAccessManager for the current thread.
void fetchContent(const QUrl &url)
Fetches content from a remote URL and handles redirects.