2015-11-23 02:33:01 +01:00
|
|
|
#include <QFile>
|
|
|
|
#include <QFileInfo>
|
2017-02-07 23:36:06 +01:00
|
|
|
#include <QNetworkRequest>
|
2018-03-14 07:23:07 +01:00
|
|
|
#include <QBasicTimer>
|
2015-11-23 23:19:57 +01:00
|
|
|
#include "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 ATTR_REDIRECT QNetworkRequest::RedirectionTargetAttribute
|
|
|
|
#define ATTR_FILE QNetworkRequest::User
|
|
|
|
#define ATTR_ORIGIN (QNetworkRequest::Attribute)(QNetworkRequest::User + 1)
|
|
|
|
#define ATTR_LEVEL (QNetworkRequest::Attribute)(QNetworkRequest::User + 2)
|
|
|
|
|
|
|
|
#define MAX_REDIRECT_LEVEL 5
|
2018-04-28 19:07:52 +02:00
|
|
|
#define RETRIES 3
|
2017-01-16 21:45:27 +01:00
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2018-03-15 18:46:34 +01:00
|
|
|
class Downloader::ReplyTimeout : public QObject
|
2018-03-14 07:23:07 +01:00
|
|
|
{
|
|
|
|
public:
|
2018-03-15 18:46:34 +01:00
|
|
|
static void setTimeout(QNetworkReply *reply, int timeout)
|
2018-03-14 07:23:07 +01:00
|
|
|
{
|
|
|
|
Q_ASSERT(reply);
|
|
|
|
new ReplyTimeout(reply, timeout);
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
ReplyTimeout(QNetworkReply *reply, int timeout) : QObject(reply)
|
|
|
|
{
|
|
|
|
_timer.start(timeout * 1000, this);
|
|
|
|
}
|
|
|
|
|
|
|
|
void timerEvent(QTimerEvent *ev)
|
|
|
|
{
|
|
|
|
if (!_timer.isActive() || ev->timerId() != _timer.timerId())
|
|
|
|
return;
|
|
|
|
QNetworkReply *reply = static_cast<QNetworkReply*>(parent());
|
|
|
|
if (reply->isRunning())
|
|
|
|
reply->close();
|
|
|
|
_timer.stop();
|
|
|
|
}
|
|
|
|
|
|
|
|
QBasicTimer _timer;
|
|
|
|
};
|
|
|
|
|
2018-03-15 18:46:34 +01:00
|
|
|
class Downloader::Redirect
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
Redirect() : _level(0) {}
|
|
|
|
Redirect(const QUrl &origin, int level) :
|
|
|
|
_origin(origin), _level(level) {}
|
|
|
|
|
|
|
|
const QUrl &origin() const {return _origin;}
|
|
|
|
int level() const {return _level;}
|
|
|
|
|
|
|
|
private:
|
|
|
|
QUrl _origin;
|
|
|
|
int _level;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2018-04-27 19:31:27 +02:00
|
|
|
QNetworkAccessManager *Downloader::_manager = 0;
|
|
|
|
int Downloader::_timeout = 30;
|
2015-11-23 02:33:01 +01:00
|
|
|
|
2018-04-01 20:01:25 +02:00
|
|
|
bool Downloader::doDownload(const Download &dl,
|
|
|
|
const QByteArray &authorization, const Redirect *redirect)
|
2015-11-23 02:33:01 +01:00
|
|
|
{
|
|
|
|
QUrl url(dl.url());
|
2015-12-04 22:56:34 +01:00
|
|
|
|
2018-04-19 19:46:56 +02:00
|
|
|
if (!url.isValid() || !(url.scheme() == "http" || url.scheme() == "https")) {
|
|
|
|
qWarning("%s: Invalid URL\n", qPrintable(url.toString()));
|
2018-04-28 19:07:52 +02:00
|
|
|
if (redirect)
|
|
|
|
_errorDownloads.insert(redirect->origin(), RETRIES);
|
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;
|
2018-04-19 19:46:56 +02:00
|
|
|
if (_currentDownloads.contains(url) && !redirect)
|
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);
|
2017-01-16 09:53:01 +01:00
|
|
|
request.setAttribute(ATTR_FILE, QVariant(dl.file()));
|
2018-03-15 18:46:34 +01:00
|
|
|
if (redirect) {
|
|
|
|
request.setAttribute(ATTR_ORIGIN, QVariant(redirect->origin()));
|
|
|
|
request.setAttribute(ATTR_LEVEL, QVariant(redirect->level()));
|
2017-01-16 21:45:27 +01:00
|
|
|
}
|
2015-11-23 23:19:57 +01:00
|
|
|
request.setRawHeader("User-Agent", USER_AGENT);
|
2018-04-01 20:01:25 +02:00
|
|
|
if (!authorization.isNull())
|
|
|
|
request.setRawHeader("Authorization", authorization);
|
2015-11-23 02:33:01 +01:00
|
|
|
|
2018-04-27 19:31:27 +02:00
|
|
|
QNetworkReply *reply = _manager->get(request);
|
2018-04-19 19:46:56 +02:00
|
|
|
if (reply && reply->isRunning()) {
|
2018-03-15 18:46:34 +01:00
|
|
|
_currentDownloads.insert(url);
|
2018-04-27 19:31:27 +02:00
|
|
|
ReplyTimeout::setTimeout(reply, _timeout);
|
|
|
|
connect(reply, SIGNAL(finished()), this, SLOT(emitFinished()));
|
2018-04-19 19:46:56 +02:00
|
|
|
} else if (reply)
|
|
|
|
downloadFinished(reply);
|
|
|
|
else
|
2018-03-14 07:23:07 +01:00
|
|
|
return false;
|
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()
|
|
|
|
{
|
|
|
|
downloadFinished(static_cast<QNetworkReply*>(sender()));
|
|
|
|
}
|
|
|
|
|
2015-11-23 02:33:01 +01:00
|
|
|
bool Downloader::saveToDisk(const QString &filename, QIODevice *data)
|
|
|
|
{
|
|
|
|
QFile file(filename);
|
|
|
|
|
|
|
|
if (!file.open(QIODevice::WriteOnly)) {
|
2018-02-20 23:37:19 +01:00
|
|
|
qWarning("Error writing file: %s: %s\n",
|
2015-11-23 02:33:01 +01:00
|
|
|
qPrintable(filename), qPrintable(file.errorString()));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
file.write(data->readAll());
|
|
|
|
file.close();
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2015-11-23 02:33:01 +01:00
|
|
|
void Downloader::downloadFinished(QNetworkReply *reply)
|
|
|
|
{
|
2017-01-16 09:53:01 +01: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
|
|
|
|
2018-04-28 19:07:52 +02:00
|
|
|
if (error) {
|
2017-01-16 09:53:01 +01:00
|
|
|
QUrl origin = reply->request().attribute(ATTR_ORIGIN).toUrl();
|
|
|
|
if (origin.isEmpty()) {
|
2018-04-28 19:07:52 +02:00
|
|
|
insertError(url, error);
|
2018-02-20 23:37:19 +01:00
|
|
|
qWarning("Error downloading file: %s: %s\n",
|
2017-01-16 09:53:01 +01:00
|
|
|
url.toEncoded().constData(), qPrintable(reply->errorString()));
|
|
|
|
} else {
|
2018-04-28 19:07:52 +02:00
|
|
|
insertError(origin, error);
|
2018-02-20 23:37:19 +01:00
|
|
|
qWarning("Error downloading file: %s -> %s: %s\n",
|
2017-01-16 09:53:01 +01:00
|
|
|
origin.toEncoded().constData(), url.toEncoded().constData(),
|
|
|
|
qPrintable(reply->errorString()));
|
|
|
|
}
|
2015-11-23 02:33:01 +01:00
|
|
|
} else {
|
2017-01-16 21:45:27 +01:00
|
|
|
QUrl location = reply->attribute(ATTR_REDIRECT).toUrl();
|
2017-01-16 09:53:01 +01:00
|
|
|
QString filename = reply->request().attribute(ATTR_FILE)
|
2015-11-23 02:33:01 +01:00
|
|
|
.toString();
|
2016-06-16 20:32:11 +02:00
|
|
|
|
2017-01-16 21:45:27 +01:00
|
|
|
if (!location.isEmpty()) {
|
2017-01-16 09:53:01 +01:00
|
|
|
QUrl origin = reply->request().attribute(ATTR_ORIGIN).toUrl();
|
2017-01-16 21:45:27 +01:00
|
|
|
int level = reply->request().attribute(ATTR_LEVEL).toInt();
|
|
|
|
|
2018-04-19 19:46:56 +02:00
|
|
|
if (level >= MAX_REDIRECT_LEVEL) {
|
2018-04-28 19:07:52 +02:00
|
|
|
_errorDownloads.insert(origin, RETRIES);
|
2018-02-20 23:37:19 +01:00
|
|
|
qWarning("Error downloading file: %s: "
|
2018-04-19 19:46:56 +02:00
|
|
|
"redirect level limit reached (redirect loop?)\n",
|
2017-01-16 21:45:27 +01:00
|
|
|
origin.toEncoded().constData());
|
2017-01-17 10:37:02 +01:00
|
|
|
} else {
|
2018-04-19 19:46:56 +02:00
|
|
|
QUrl redirectUrl;
|
|
|
|
if (location.isRelative()) {
|
|
|
|
QString path = QDir::isAbsolutePath(location.path())
|
|
|
|
? location.path() : "/" + location.path();
|
|
|
|
redirectUrl = QUrl(url.scheme() + "://" + url.host() + path);
|
|
|
|
} else
|
|
|
|
redirectUrl = location;
|
|
|
|
|
2017-01-17 10:37:02 +01:00
|
|
|
Redirect redirect(origin.isEmpty() ? url : origin, level + 1);
|
2018-04-19 19:46:56 +02:00
|
|
|
Download dl(redirectUrl, filename);
|
2018-04-28 19:07:52 +02:00
|
|
|
doDownload(dl, reply->request().rawHeader("Authorization"),
|
|
|
|
&redirect);
|
2017-01-16 21:45:27 +01:00
|
|
|
}
|
2018-04-27 19:31:27 +02:00
|
|
|
} else {
|
2016-08-02 23:05:52 +02:00
|
|
|
if (!saveToDisk(filename, reply))
|
2018-04-28 19:07:52 +02:00
|
|
|
_errorDownloads.insert(url, RETRIES);
|
2018-04-27 19:31:27 +02:00
|
|
|
}
|
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++)
|
2018-04-01 20:01:25 +02:00
|
|
|
finishEmitted |= doDownload(list.at(i), authorization.header());
|
2016-05-19 01:10:40 +02:00
|
|
|
|
|
|
|
return finishEmitted;
|
2015-11-23 02:33:01 +01:00
|
|
|
}
|