2015-11-23 02:33:01 +01:00
|
|
|
#include <QFile>
|
|
|
|
#include <QFileInfo>
|
2017-02-07 23:36:06 +01:00
|
|
|
#include <QNetworkRequest>
|
2018-11-02 20:01:19 +01:00
|
|
|
#include <QDir>
|
|
|
|
#include <QTimerEvent>
|
2020-12-22 22:09:09 +01:00
|
|
|
#include "common/config.h"
|
2015-11-23 02:33:01 +01:00
|
|
|
#include "downloader.h"
|
|
|
|
|
|
|
|
|
2015-11-24 10:05:28 +01:00
|
|
|
#if defined(Q_OS_LINUX)
|
2015-11-23 23:19:57 +01:00
|
|
|
#define PLATFORM_STR "Linux"
|
2015-11-24 10:05:28 +01:00
|
|
|
#elif defined(Q_OS_WIN32)
|
2015-11-23 23:19:57 +01:00
|
|
|
#define PLATFORM_STR "Windows"
|
2015-11-24 10:05:28 +01:00
|
|
|
#elif defined(Q_OS_MAC)
|
2015-11-23 23:19:57 +01:00
|
|
|
#define PLATFORM_STR "OS X"
|
|
|
|
#else
|
|
|
|
#define PLATFORM_STR "Unknown"
|
|
|
|
#endif
|
|
|
|
|
2016-04-01 23:14:57 +02:00
|
|
|
#define USER_AGENT \
|
|
|
|
APP_NAME "/" APP_VERSION " (" PLATFORM_STR "; Qt " QT_VERSION_STR ")"
|
2015-11-23 23:19:57 +01:00
|
|
|
|
2017-01-16 21:45:27 +01:00
|
|
|
#define MAX_REDIRECT_LEVEL 5
|
2018-04-28 19:07:52 +02:00
|
|
|
#define RETRIES 3
|
2017-01-16 21:45:27 +01:00
|
|
|
|
2021-08-26 22:22:18 +02:00
|
|
|
#define ATTR_REDIRECT_POLICY QNetworkRequest::RedirectPolicyAttribute
|
|
|
|
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
|
|
|
|
#define ATTR_HTTP2_ALLOWED QNetworkRequest::HTTP2AllowedAttribute
|
|
|
|
#else // QT 5.15
|
|
|
|
#define ATTR_HTTP2_ALLOWED QNetworkRequest::Http2AllowedAttribute
|
|
|
|
#endif // QT 5.15
|
|
|
|
|
|
|
|
#define TMP_SUFFIX ".download"
|
|
|
|
|
|
|
|
|
|
|
|
static QString tmpName(const QString &origName)
|
|
|
|
{
|
|
|
|
return origName + TMP_SUFFIX;
|
|
|
|
}
|
|
|
|
|
|
|
|
static QString origName(const QString &tmpName)
|
|
|
|
{
|
|
|
|
return tmpName.left(tmpName.size() - (sizeof(TMP_SUFFIX) - 1));
|
|
|
|
}
|
2017-01-16 09:53:01 +01:00
|
|
|
|
2018-04-01 20:01:25 +02:00
|
|
|
Authorization::Authorization(const QString &username, const QString &password)
|
|
|
|
{
|
|
|
|
QString concatenated = username + ":" + password;
|
|
|
|
QByteArray data = concatenated.toLocal8Bit().toBase64();
|
|
|
|
_header = "Basic " + data;
|
|
|
|
}
|
|
|
|
|
2021-08-29 20:28:08 +02:00
|
|
|
NetworkTimeout::NetworkTimeout(int timeout, QNetworkReply *reply)
|
|
|
|
: QObject(reply), _timeout(timeout)
|
2018-03-14 07:23:07 +01:00
|
|
|
{
|
2021-08-29 20:28:08 +02:00
|
|
|
connect(reply, &QIODevice::readyRead, this, &NetworkTimeout::reset);
|
|
|
|
_timer.start(timeout * 1000, this);
|
|
|
|
}
|
2018-03-14 07:23:07 +01:00
|
|
|
|
2021-08-29 20:28:08 +02:00
|
|
|
void NetworkTimeout::reset()
|
|
|
|
{
|
|
|
|
_timer.start(_timeout * 1000, this);
|
|
|
|
}
|
2018-03-14 07:23:07 +01:00
|
|
|
|
2021-08-29 20:28:08 +02:00
|
|
|
void NetworkTimeout::timerEvent(QTimerEvent *ev)
|
|
|
|
{
|
|
|
|
if (!_timer.isActive() || ev->timerId() != _timer.timerId())
|
|
|
|
return;
|
|
|
|
QNetworkReply *reply = static_cast<QNetworkReply*>(parent());
|
|
|
|
if (reply->isRunning())
|
|
|
|
reply->close();
|
|
|
|
_timer.stop();
|
|
|
|
}
|
2018-03-14 07:23:07 +01:00
|
|
|
|
|
|
|
|
2018-10-08 22:07:36 +02:00
|
|
|
QNetworkAccessManager *Downloader::_manager = 0;
|
2018-04-27 19:31:27 +02:00
|
|
|
int Downloader::_timeout = 30;
|
2018-07-23 23:53:58 +02:00
|
|
|
bool Downloader::_http2 = true;
|
2015-11-23 02:33:01 +01:00
|
|
|
|
2021-08-30 20:31:33 +02:00
|
|
|
bool Downloader::doDownload(const Download &dl, const Authorization &auth)
|
2015-11-23 02:33:01 +01:00
|
|
|
{
|
2018-10-04 23:02:43 +02:00
|
|
|
const QUrl &url = dl.url();
|
2015-12-04 22:56:34 +01:00
|
|
|
|
2018-10-04 23:02:43 +02:00
|
|
|
if (!url.isValid() || !(url.scheme() == QLatin1String("http")
|
|
|
|
|| url.scheme() == QLatin1String("https"))) {
|
2018-09-24 23:07:11 +02:00
|
|
|
qWarning("%s: Invalid URL", qPrintable(url.toString()));
|
2018-04-19 19:46:56 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-04-28 19:07:52 +02:00
|
|
|
if (_errorDownloads.value(url) >= RETRIES)
|
2016-05-19 01:10:40 +02:00
|
|
|
return false;
|
2021-08-26 22:22:18 +02:00
|
|
|
if (_currentDownloads.contains(url))
|
2017-01-16 09:53:01 +01:00
|
|
|
return false;
|
2015-12-04 22:56:34 +01:00
|
|
|
|
2015-11-23 02:33:01 +01:00
|
|
|
QNetworkRequest request(url);
|
2021-08-26 22:22:18 +02:00
|
|
|
request.setMaximumRedirectsAllowed(MAX_REDIRECT_LEVEL);
|
|
|
|
request.setAttribute(ATTR_REDIRECT_POLICY,
|
|
|
|
QNetworkRequest::NoLessSafeRedirectPolicy);
|
|
|
|
request.setAttribute(ATTR_HTTP2_ALLOWED, QVariant(_http2));
|
2015-11-23 23:19:57 +01:00
|
|
|
request.setRawHeader("User-Agent", USER_AGENT);
|
2021-08-30 20:31:33 +02:00
|
|
|
if (!auth.isNull())
|
|
|
|
request.setRawHeader("Authorization", auth.header());
|
2021-08-26 22:22:18 +02:00
|
|
|
|
|
|
|
QFile *file = new QFile(tmpName(dl.file()));
|
2021-08-29 20:28:08 +02:00
|
|
|
if (!file->open(QIODevice::WriteOnly)) {
|
2021-08-26 22:22:18 +02:00
|
|
|
qWarning("%s: %s", qPrintable(file->fileName()),
|
|
|
|
qPrintable(file->errorString()));
|
|
|
|
_errorDownloads.insert(url, RETRIES);
|
|
|
|
return false;
|
|
|
|
}
|
2015-11-23 02:33:01 +01:00
|
|
|
|
2018-10-08 22:07:36 +02:00
|
|
|
Q_ASSERT(_manager);
|
|
|
|
QNetworkReply *reply = _manager->get(request);
|
2021-08-26 22:22:18 +02:00
|
|
|
file->setParent(reply);
|
|
|
|
_currentDownloads.insert(url, file);
|
|
|
|
|
|
|
|
if (reply->isRunning()) {
|
2021-08-29 20:28:08 +02:00
|
|
|
/* Starting with Qt 5.15 this can be replaced by
|
|
|
|
QNetworkRequest::setTransferTimeout() */
|
|
|
|
new NetworkTimeout(_timeout, reply);
|
2021-08-26 22:22:18 +02:00
|
|
|
connect(reply, &QIODevice::readyRead, this, &Downloader::emitReadReady);
|
2021-04-28 00:01:07 +02:00
|
|
|
connect(reply, &QNetworkReply::finished, this, &Downloader::emitFinished);
|
2021-08-26 22:22:18 +02:00
|
|
|
} else {
|
|
|
|
readData(reply);
|
2018-04-19 19:46:56 +02:00
|
|
|
downloadFinished(reply);
|
2021-08-26 22:22:18 +02:00
|
|
|
}
|
2016-05-19 01:10:40 +02:00
|
|
|
|
|
|
|
return true;
|
2015-11-23 02:33:01 +01:00
|
|
|
}
|
|
|
|
|
2018-04-27 19:31:27 +02:00
|
|
|
void Downloader::emitFinished()
|
|
|
|
{
|
2021-08-29 20:28:08 +02:00
|
|
|
downloadFinished(static_cast<QNetworkReply*>(sender()));
|
2018-04-27 19:31:27 +02:00
|
|
|
}
|
|
|
|
|
2021-08-26 22:22:18 +02:00
|
|
|
void Downloader::emitReadReady()
|
2015-11-23 02:33:01 +01:00
|
|
|
{
|
2021-08-29 20:28:08 +02:00
|
|
|
readData(static_cast<QNetworkReply*>(sender()));
|
2015-11-23 02:33:01 +01:00
|
|
|
}
|
|
|
|
|
2018-04-28 19:07:52 +02:00
|
|
|
void Downloader::insertError(const QUrl &url, QNetworkReply::NetworkError error)
|
|
|
|
{
|
|
|
|
if (error == QNetworkReply::OperationCanceledError)
|
|
|
|
_errorDownloads.insert(url, _errorDownloads.value(url) + 1);
|
|
|
|
else
|
|
|
|
_errorDownloads.insert(url, RETRIES);
|
|
|
|
}
|
|
|
|
|
2021-08-26 22:22:18 +02:00
|
|
|
void Downloader::readData(QNetworkReply *reply)
|
|
|
|
{
|
|
|
|
QFile *file = _currentDownloads.value(reply->request().url());
|
|
|
|
Q_ASSERT(file);
|
|
|
|
file->write(reply->readAll());
|
|
|
|
}
|
|
|
|
|
2015-11-23 02:33:01 +01:00
|
|
|
void Downloader::downloadFinished(QNetworkReply *reply)
|
|
|
|
{
|
2018-10-04 23:02:43 +02:00
|
|
|
QUrl url(reply->request().url());
|
2018-04-28 19:07:52 +02:00
|
|
|
QNetworkReply::NetworkError error = reply->error();
|
2017-01-16 09:53:01 +01:00
|
|
|
|
2021-08-26 22:22:18 +02:00
|
|
|
QFile *file = _currentDownloads.value(reply->request().url());
|
2018-04-28 19:07:52 +02:00
|
|
|
if (error) {
|
2021-08-26 22:22:18 +02:00
|
|
|
insertError(url, error);
|
|
|
|
qWarning("%s: %s", url.toEncoded().constData(),
|
|
|
|
qPrintable(reply->errorString()));
|
|
|
|
file->remove();
|
2015-11-23 02:33:01 +01:00
|
|
|
} else {
|
2021-08-26 22:22:18 +02:00
|
|
|
file->close();
|
|
|
|
file->rename(origName(file->fileName()));
|
2015-11-23 02:33:01 +01:00
|
|
|
}
|
|
|
|
|
2018-03-19 22:40:49 +01:00
|
|
|
_currentDownloads.remove(url);
|
2015-11-23 02:33:01 +01:00
|
|
|
reply->deleteLater();
|
|
|
|
|
2015-12-04 22:56:34 +01:00
|
|
|
if (_currentDownloads.isEmpty())
|
2015-11-23 02:33:01 +01:00
|
|
|
emit finished();
|
|
|
|
}
|
|
|
|
|
2018-04-01 20:01:25 +02:00
|
|
|
bool Downloader::get(const QList<Download> &list,
|
|
|
|
const Authorization &authorization)
|
2015-11-23 02:33:01 +01:00
|
|
|
{
|
2016-05-19 01:10:40 +02:00
|
|
|
bool finishEmitted = false;
|
|
|
|
|
2015-11-23 02:33:01 +01:00
|
|
|
for (int i = 0; i < list.count(); i++)
|
2021-08-30 20:31:33 +02:00
|
|
|
finishEmitted |= doDownload(list.at(i), authorization);
|
2016-05-19 01:10:40 +02:00
|
|
|
|
|
|
|
return finishEmitted;
|
2015-11-23 02:33:01 +01:00
|
|
|
}
|
2018-10-07 14:22:13 +02:00
|
|
|
|
|
|
|
void Downloader::enableHTTP2(bool enable)
|
|
|
|
{
|
2018-10-08 22:07:36 +02:00
|
|
|
Q_ASSERT(_manager);
|
2018-10-07 14:22:13 +02:00
|
|
|
_http2 = enable;
|
2018-10-08 22:07:36 +02:00
|
|
|
_manager->clearConnectionCache();
|
2018-10-07 14:22:13 +02:00
|
|
|
}
|
2021-08-26 22:22:18 +02:00
|
|
|
|
|
|
|
#ifndef QT_NO_DEBUG
|
|
|
|
QDebug operator<<(QDebug dbg, const Download &download)
|
|
|
|
{
|
|
|
|
dbg.nospace() << "Download(" << download.url() << "," << download.file()
|
|
|
|
<< ")";
|
|
|
|
return dbg.space();
|
|
|
|
}
|
|
|
|
#endif // QT_NO_DEBUG
|