diff --git a/src/base/bittorrent/session.cpp b/src/base/bittorrent/session.cpp index 02b7a452c..98c75ffe9 100644 --- a/src/base/bittorrent/session.cpp +++ b/src/base/bittorrent/session.cpp @@ -1844,11 +1844,10 @@ void Session::handleRedirectedToMagnet(const QString &url, const QString &magnet } // Add to BitTorrent session the downloaded torrent file -void Session::handleDownloadFinished(const QString &url, const QString &filePath) +void Session::handleDownloadFinished(const QString &url, const QByteArray &data) { emit downloadFromUrlFinished(url); - addTorrent_impl(m_downloadedTorrents.take(url), MagnetUri(), TorrentInfo::loadFromFile(filePath)); - Utils::Fs::forceRemove(filePath); // remove temporary file + addTorrent_impl(m_downloadedTorrents.take(url), MagnetUri(), TorrentInfo::load(data)); } // Return the torrent handle, given its hash @@ -2077,10 +2076,11 @@ bool Session::addTorrent(QString source, const AddTorrentParams ¶ms) return addTorrent_impl(params, magnetUri); } else if (Utils::Misc::isUrl(source)) { - Logger::instance()->addMessage(tr("Downloading '%1', please wait...", "e.g: Downloading 'xxx.torrent', please wait...").arg(source)); + LogMsg(tr("Downloading '%1', please wait...", "e.g: Downloading 'xxx.torrent', please wait...").arg(source)); // Launch downloader - Net::DownloadHandler *handler = Net::DownloadManager::instance()->downloadUrl(source, true, 10485760 /* 10MB */, true); - connect(handler, static_cast(&Net::DownloadHandler::downloadFinished) + Net::DownloadHandler *handler = + Net::DownloadManager::instance()->download(Net::DownloadRequest(source).limit(10485760 /* 10MB */).handleRedirectToMagnet(true)); + connect(handler, static_cast(&Net::DownloadHandler::downloadFinished) , this, &Session::handleDownloadFinished); connect(handler, &Net::DownloadHandler::downloadFailed, this, &Session::handleDownloadFailed); connect(handler, &Net::DownloadHandler::redirectedToMagnet, this, &Session::handleRedirectedToMagnet); diff --git a/src/base/bittorrent/session.h b/src/base/bittorrent/session.h index dec225a73..60c34e46a 100644 --- a/src/base/bittorrent/session.h +++ b/src/base/bittorrent/session.h @@ -547,7 +547,7 @@ namespace BitTorrent void generateResumeData(bool final = false); void handleIPFilterParsed(int ruleCount); void handleIPFilterError(); - void handleDownloadFinished(const QString &url, const QString &filePath); + void handleDownloadFinished(const QString &url, const QByteArray &data); void handleDownloadFailed(const QString &url, const QString &reason); void handleRedirectedToMagnet(const QString &url, const QString &magnetUri); diff --git a/src/base/bittorrent/torrentinfo.cpp b/src/base/bittorrent/torrentinfo.cpp index 5ca481c6f..84454683d 100644 --- a/src/base/bittorrent/torrentinfo.cpp +++ b/src/base/bittorrent/torrentinfo.cpp @@ -62,13 +62,32 @@ TorrentInfo &TorrentInfo::operator=(const TorrentInfo &other) TorrentInfo TorrentInfo::load(const QByteArray &data, QString *error) noexcept { + // 2-step construction to overcome default limits of `depth_limit` & `token_limit` which are + // used in `torrent_info()` constructor + const int depthLimit = 100; + const int tokenLimit = 10000000; libt::error_code ec; - TorrentInfo info(NativePtr(new libt::torrent_info(data.constData(), data.size(), ec))); - if (error) { - if (ec) + +#if LIBTORRENT_VERSION_NUM < 10100 + libt::lazy_entry node; + libt::lazy_bdecode(data.constData(), (data.constData() + data.size()), node, ec + , nullptr, depthLimit, tokenLimit); +#else + libt::bdecode_node node; + bdecode(data.constData(), (data.constData() + data.size()), node, ec + , nullptr, depthLimit, tokenLimit); +#endif + if (ec) { + if (error) *error = QString::fromStdString(ec.message()); - else - error->clear(); + return TorrentInfo(); + } + + TorrentInfo info {NativePtr(new libt::torrent_info(node, ec))}; + if (ec) { + if (error) + *error = QString::fromStdString(ec.message()); + return TorrentInfo(); } return info; @@ -102,35 +121,7 @@ TorrentInfo TorrentInfo::loadFromFile(const QString &path, QString *error) noexc file.close(); - // 2-step construction to overcome default limits of `depth_limit` & `token_limit` which are - // used in `torrent_info()` constructor - const int depthLimit = 100; - const int tokenLimit = 10000000; - libt::error_code ec; - -#if LIBTORRENT_VERSION_NUM < 10100 - libt::lazy_entry node; - libt::lazy_bdecode(data.constData(), (data.constData() + data.size()), node, ec - , nullptr, depthLimit, tokenLimit); -#else - libt::bdecode_node node; - bdecode(data.constData(), (data.constData() + data.size()), node, ec - , nullptr, depthLimit, tokenLimit); -#endif - if (ec) { - if (error) - *error = QString::fromStdString(ec.message()); - return TorrentInfo(); - } - - TorrentInfo info {NativePtr(new libt::torrent_info(node, ec))}; - if (ec) { - if (error) - *error = QString::fromStdString(ec.message()); - return TorrentInfo(); - } - - return info; + return load(data, error); } bool TorrentInfo::isValid() const diff --git a/src/base/net/dnsupdater.cpp b/src/base/net/dnsupdater.cpp index bcdd5f509..91fd1a377 100644 --- a/src/base/net/dnsupdater.cpp +++ b/src/base/net/dnsupdater.cpp @@ -75,9 +75,8 @@ void DNSUpdater::checkPublicIP() { Q_ASSERT(m_state == OK); - DownloadHandler *handler = DownloadManager::instance()->downloadUrl( - "http://checkip.dyndns.org", false, 0, false, - "qBittorrent/" QBT_VERSION_2); + DownloadHandler *handler = DownloadManager::instance()->download( + DownloadRequest("http://checkip.dyndns.org").userAgent("qBittorrent/" QBT_VERSION_2)); connect(handler, static_cast(&Net::DownloadHandler::downloadFinished) , this, &DNSUpdater::ipRequestFinished); connect(handler, &Net::DownloadHandler::downloadFailed, this, &DNSUpdater::ipRequestFailed); @@ -123,9 +122,8 @@ void DNSUpdater::updateDNSService() qDebug() << Q_FUNC_INFO; m_lastIPCheckTime = QDateTime::currentDateTime(); - DownloadHandler *handler = DownloadManager::instance()->downloadUrl( - getUpdateUrl(), false, 0, false, - "qBittorrent/" QBT_VERSION_2); + DownloadHandler *handler = DownloadManager::instance()->download( + DownloadRequest(getUpdateUrl()).userAgent("qBittorrent/" QBT_VERSION_2)); connect(handler, static_cast(&Net::DownloadHandler::downloadFinished) , this, &DNSUpdater::ipUpdateFinished); connect(handler, &Net::DownloadHandler::downloadFailed, this, &DNSUpdater::ipUpdateFailed); diff --git a/src/base/net/downloadhandler.cpp b/src/base/net/downloadhandler.cpp index d03e1b696..4f04102e6 100644 --- a/src/base/net/downloadhandler.cpp +++ b/src/base/net/downloadhandler.cpp @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2015 Vladimir Golovnev + * Copyright (C) 2015, 2018 Vladimir Golovnev * Copyright (C) 2006 Christophe Dumez * * This program is free software; you can redistribute it and/or @@ -29,6 +29,7 @@ #include "downloadhandler.h" +#include #include #include #include @@ -43,35 +44,46 @@ #include "base/utils/misc.h" #include "downloadmanager.h" -static QString errorCodeToString(QNetworkReply::NetworkError status); +namespace +{ + QString tr(const char *message); + QString errorCodeToString(QNetworkReply::NetworkError status); +} -using namespace Net; - -DownloadHandler::DownloadHandler(QNetworkReply *reply, DownloadManager *manager, bool saveToFile, qint64 limit, bool handleRedirectToMagnet) +Net::DownloadHandler::DownloadHandler(QNetworkReply *reply, DownloadManager *manager, const DownloadRequest &downloadRequest) : QObject(manager) , m_reply(reply) , m_manager(manager) - , m_saveToFile(saveToFile) - , m_sizeLimit(limit) - , m_handleRedirectToMagnet(handleRedirectToMagnet) - , m_url(reply->url().toString()) + , m_downloadRequest(downloadRequest) { - init(); + if (reply) + assignNetworkReply(reply); } -DownloadHandler::~DownloadHandler() +Net::DownloadHandler::~DownloadHandler() { if (m_reply) delete m_reply; } -// Returns original url -QString DownloadHandler::url() const +void Net::DownloadHandler::assignNetworkReply(QNetworkReply *reply) { - return m_url; + Q_ASSERT(reply); + + m_reply = reply; + m_reply->setParent(this); + if (m_downloadRequest.limit() > 0) + connect(m_reply, &QNetworkReply::downloadProgress, this, &Net::DownloadHandler::checkDownloadSize); + connect(m_reply, &QNetworkReply::finished, this, &Net::DownloadHandler::processFinishedDownload); } -void DownloadHandler::processFinishedDownload() +// Returns original url +QString Net::DownloadHandler::url() const +{ + return m_downloadRequest.url(); +} + +void Net::DownloadHandler::processFinishedDownload() { QString url = m_reply->url().toString(); qDebug("Download finished: %s", qUtf8Printable(url)); @@ -79,7 +91,7 @@ void DownloadHandler::processFinishedDownload() if (m_reply->error() != QNetworkReply::NoError) { // Failure qDebug("Download failure (%s), reason: %s", qUtf8Printable(url), qUtf8Printable(errorCodeToString(m_reply->error()))); - emit downloadFailed(m_url, errorCodeToString(m_reply->error())); + emit downloadFailed(m_downloadRequest.url(), errorCodeToString(m_reply->error())); this->deleteLater(); } else { @@ -97,15 +109,15 @@ void DownloadHandler::processFinishedDownload() replyData = Utils::Gzip::decompress(replyData); } - if (m_saveToFile) { + if (m_downloadRequest.saveToFile()) { QString filePath; if (saveToFile(replyData, filePath)) - emit downloadFinished(m_url, filePath); + emit downloadFinished(m_downloadRequest.url(), filePath); else - emit downloadFailed(m_url, tr("I/O Error")); - } + emit downloadFailed(m_downloadRequest.url(), tr("I/O Error")); + } else { - emit downloadFinished(m_url, replyData); + emit downloadFinished(m_downloadRequest.url(), replyData); } this->deleteLater(); @@ -113,35 +125,27 @@ void DownloadHandler::processFinishedDownload() } } -void DownloadHandler::checkDownloadSize(qint64 bytesReceived, qint64 bytesTotal) +void Net::DownloadHandler::checkDownloadSize(qint64 bytesReceived, qint64 bytesTotal) { QString msg = tr("The file size is %1. It exceeds the download limit of %2."); if (bytesTotal > 0) { // Total number of bytes is available - if (bytesTotal > m_sizeLimit) { + if (bytesTotal > m_downloadRequest.limit()) { m_reply->abort(); - emit downloadFailed(m_url, msg.arg(Utils::Misc::friendlyUnit(bytesTotal), Utils::Misc::friendlyUnit(m_sizeLimit))); + emit downloadFailed(m_downloadRequest.url(), msg.arg(Utils::Misc::friendlyUnit(bytesTotal), Utils::Misc::friendlyUnit(m_downloadRequest.limit()))); } else { disconnect(m_reply, &QNetworkReply::downloadProgress, this, &Net::DownloadHandler::checkDownloadSize); } } - else if (bytesReceived > m_sizeLimit) { + else if (bytesReceived > m_downloadRequest.limit()) { m_reply->abort(); - emit downloadFailed(m_url, msg.arg(Utils::Misc::friendlyUnit(bytesReceived), Utils::Misc::friendlyUnit(m_sizeLimit))); + emit downloadFailed(m_downloadRequest.url(), msg.arg(Utils::Misc::friendlyUnit(bytesReceived), Utils::Misc::friendlyUnit(m_downloadRequest.limit()))); } } -void DownloadHandler::init() -{ - m_reply->setParent(this); - if (m_sizeLimit > 0) - connect(m_reply, &QNetworkReply::downloadProgress, this, &Net::DownloadHandler::checkDownloadSize); - connect(m_reply, &QNetworkReply::finished, this, &Net::DownloadHandler::processFinishedDownload); -} - -bool DownloadHandler::saveToFile(const QByteArray &replyData, QString &filePath) +bool Net::DownloadHandler::saveToFile(const QByteArray &replyData, QString &filePath) { QTemporaryFile *tmpfile = new QTemporaryFile(Utils::Fs::tempPath() + "XXXXXX"); if (!tmpfile->open()) { @@ -168,82 +172,104 @@ bool DownloadHandler::saveToFile(const QByteArray &replyData, QString &filePath) return false; } -void DownloadHandler::handleRedirection(QUrl newUrl) +void Net::DownloadHandler::handleRedirection(QUrl newUrl) { // Resolve relative urls if (newUrl.isRelative()) newUrl = m_reply->url().resolved(newUrl); const QString newUrlString = newUrl.toString(); - qDebug("Redirecting from %s to %s", qUtf8Printable(m_reply->url().toString()), qUtf8Printable(newUrlString)); + qDebug("Redirecting from %s to %s...", qUtf8Printable(m_reply->url().toString()), qUtf8Printable(newUrlString)); // Redirect to magnet workaround if (newUrlString.startsWith("magnet:", Qt::CaseInsensitive)) { qDebug("Magnet redirect detected."); m_reply->abort(); - if (m_handleRedirectToMagnet) - emit redirectedToMagnet(m_url, newUrlString); + if (m_downloadRequest.handleRedirectToMagnet()) + emit redirectedToMagnet(m_downloadRequest.url(), newUrlString); else - emit downloadFailed(m_url, tr("Unexpected redirect to magnet URI.")); + emit downloadFailed(m_downloadRequest.url(), tr("Unexpected redirect to magnet URI.")); this->deleteLater(); } else { - DownloadHandler *tmp = m_manager->downloadUrl(newUrlString, m_saveToFile, m_sizeLimit, m_handleRedirectToMagnet); - m_reply->deleteLater(); - m_reply = tmp->m_reply; - init(); - tmp->m_reply = nullptr; - delete tmp; + DownloadHandler *redirected = m_manager->download(DownloadRequest(m_downloadRequest).url(newUrlString)); + connect(redirected, &DownloadHandler::destroyed, this, &DownloadHandler::deleteLater); + connect(redirected, &DownloadHandler::downloadFailed, this, [this](const QString &, const QString &reason) + { + emit downloadFailed(url(), reason); + }); + connect(redirected, &DownloadHandler::redirectedToMagnet, this, [this](const QString &, const QString &magnetUri) + { + emit redirectedToMagnet(url(), magnetUri); + }); + connect(redirected, static_cast(&DownloadHandler::downloadFinished) + , this, [this](const QString &, const QString &fileName) + { + emit downloadFinished(url(), fileName); + }); + connect(redirected, static_cast(&DownloadHandler::downloadFinished) + , this, [this](const QString &, const QByteArray &data) + { + emit downloadFinished(url(), data); + }); } } -QString errorCodeToString(QNetworkReply::NetworkError status) +namespace { - switch (status) { - case QNetworkReply::HostNotFoundError: - return QObject::tr("The remote host name was not found (invalid hostname)"); - case QNetworkReply::OperationCanceledError: - return QObject::tr("The operation was canceled"); - case QNetworkReply::RemoteHostClosedError: - return QObject::tr("The remote server closed the connection prematurely, before the entire reply was received and processed"); - case QNetworkReply::TimeoutError: - return QObject::tr("The connection to the remote server timed out"); - case QNetworkReply::SslHandshakeFailedError: - return QObject::tr("SSL/TLS handshake failed"); - case QNetworkReply::ConnectionRefusedError: - return QObject::tr("The remote server refused the connection"); - case QNetworkReply::ProxyConnectionRefusedError: - return QObject::tr("The connection to the proxy server was refused"); - case QNetworkReply::ProxyConnectionClosedError: - return QObject::tr("The proxy server closed the connection prematurely"); - case QNetworkReply::ProxyNotFoundError: - return QObject::tr("The proxy host name was not found"); - case QNetworkReply::ProxyTimeoutError: - return QObject::tr("The connection to the proxy timed out or the proxy did not reply in time to the request sent"); - case QNetworkReply::ProxyAuthenticationRequiredError: - return QObject::tr("The proxy requires authentication in order to honor the request but did not accept any credentials offered"); - case QNetworkReply::ContentAccessDenied: - return QObject::tr("The access to the remote content was denied (401)"); - case QNetworkReply::ContentOperationNotPermittedError: - return QObject::tr("The operation requested on the remote content is not permitted"); - case QNetworkReply::ContentNotFoundError: - return QObject::tr("The remote content was not found at the server (404)"); - case QNetworkReply::AuthenticationRequiredError: - return QObject::tr("The remote server requires authentication to serve the content but the credentials provided were not accepted"); - case QNetworkReply::ProtocolUnknownError: - return QObject::tr("The Network Access API cannot honor the request because the protocol is not known"); - case QNetworkReply::ProtocolInvalidOperationError: - return QObject::tr("The requested operation is invalid for this protocol"); - case QNetworkReply::UnknownNetworkError: - return QObject::tr("An unknown network-related error was detected"); - case QNetworkReply::UnknownProxyError: - return QObject::tr("An unknown proxy-related error was detected"); - case QNetworkReply::UnknownContentError: - return QObject::tr("An unknown error related to the remote content was detected"); - case QNetworkReply::ProtocolFailure: - return QObject::tr("A breakdown in protocol was detected"); - default: - return QObject::tr("Unknown error"); + QString tr(const char *message) + { + return QCoreApplication::translate("DownloadHandler", message); + } + + QString errorCodeToString(QNetworkReply::NetworkError status) + { + switch (status) { + case QNetworkReply::HostNotFoundError: + return tr("The remote host name was not found (invalid hostname)"); + case QNetworkReply::OperationCanceledError: + return tr("The operation was canceled"); + case QNetworkReply::RemoteHostClosedError: + return tr("The remote server closed the connection prematurely, before the entire reply was received and processed"); + case QNetworkReply::TimeoutError: + return tr("The connection to the remote server timed out"); + case QNetworkReply::SslHandshakeFailedError: + return tr("SSL/TLS handshake failed"); + case QNetworkReply::ConnectionRefusedError: + return tr("The remote server refused the connection"); + case QNetworkReply::ProxyConnectionRefusedError: + return tr("The connection to the proxy server was refused"); + case QNetworkReply::ProxyConnectionClosedError: + return tr("The proxy server closed the connection prematurely"); + case QNetworkReply::ProxyNotFoundError: + return tr("The proxy host name was not found"); + case QNetworkReply::ProxyTimeoutError: + return tr("The connection to the proxy timed out or the proxy did not reply in time to the request sent"); + case QNetworkReply::ProxyAuthenticationRequiredError: + return tr("The proxy requires authentication in order to honor the request but did not accept any credentials offered"); + case QNetworkReply::ContentAccessDenied: + return tr("The access to the remote content was denied (401)"); + case QNetworkReply::ContentOperationNotPermittedError: + return tr("The operation requested on the remote content is not permitted"); + case QNetworkReply::ContentNotFoundError: + return tr("The remote content was not found at the server (404)"); + case QNetworkReply::AuthenticationRequiredError: + return tr("The remote server requires authentication to serve the content but the credentials provided were not accepted"); + case QNetworkReply::ProtocolUnknownError: + return tr("The Network Access API cannot honor the request because the protocol is not known"); + case QNetworkReply::ProtocolInvalidOperationError: + return tr("The requested operation is invalid for this protocol"); + case QNetworkReply::UnknownNetworkError: + return tr("An unknown network-related error was detected"); + case QNetworkReply::UnknownProxyError: + return tr("An unknown proxy-related error was detected"); + case QNetworkReply::UnknownContentError: + return tr("An unknown error related to the remote content was detected"); + case QNetworkReply::ProtocolFailure: + return tr("A breakdown in protocol was detected"); + default: + return tr("Unknown error"); + } } } diff --git a/src/base/net/downloadhandler.h b/src/base/net/downloadhandler.h index 5d69f81f2..ef6dc9553 100644 --- a/src/base/net/downloadhandler.h +++ b/src/base/net/downloadhandler.h @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2015 Vladimir Golovnev + * Copyright (C) 2015, 2018 Vladimir Golovnev * Copyright (C) 2006 Christophe Dumez * * This program is free software; you can redistribute it and/or @@ -31,8 +31,8 @@ #define NET_DOWNLOADHANDLER_H #include +#include "downloadmanager.h" -class QNetworkAccessManager; class QNetworkReply; class QUrl; @@ -43,10 +43,14 @@ namespace Net class DownloadHandler : public QObject { Q_OBJECT + Q_DISABLE_COPY(DownloadHandler) + + friend class DownloadManager; + + DownloadHandler(QNetworkReply *reply, DownloadManager *manager, const DownloadRequest &downloadRequest); public: - DownloadHandler(QNetworkReply *reply, DownloadManager *manager, bool saveToFile = false, qint64 limit = 0, bool handleRedirectToMagnet = false); - ~DownloadHandler(); + ~DownloadHandler() override; QString url() const; @@ -61,16 +65,13 @@ namespace Net void checkDownloadSize(qint64 bytesReceived, qint64 bytesTotal); private: - void init(); + void assignNetworkReply(QNetworkReply *reply); bool saveToFile(const QByteArray &replyData, QString &filePath); void handleRedirection(QUrl newUrl); QNetworkReply *m_reply; DownloadManager *m_manager; - bool m_saveToFile; - qint64 m_sizeLimit; - bool m_handleRedirectToMagnet; - QString m_url; + const DownloadRequest m_downloadRequest; }; } diff --git a/src/base/net/downloadmanager.cpp b/src/base/net/downloadmanager.cpp index 5194deaae..6e62388fb 100644 --- a/src/base/net/downloadmanager.cpp +++ b/src/base/net/downloadmanager.cpp @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2015 Vladimir Golovnev + * Copyright (C) 2015, 2018 Vladimir Golovnev * Copyright (C) 2006 Christophe Dumez * * This program is free software; you can redistribute it and/or @@ -103,28 +103,46 @@ namespace return QNetworkCookieJar::setCookiesFromUrl(cookies, url); } }; + + QNetworkRequest createNetworkRequest(const Net::DownloadRequest &downloadRequest) + { + QNetworkRequest request {downloadRequest.url()}; + + if (downloadRequest.userAgent().isEmpty()) + request.setRawHeader("User-Agent", DEFAULT_USER_AGENT); + else + request.setRawHeader("User-Agent", downloadRequest.userAgent().toUtf8()); + + // Spoof HTTP Referer to allow adding torrent link from Torcache/KickAssTorrents + request.setRawHeader("Referer", request.url().toEncoded().data()); + // Accept gzip + request.setRawHeader("Accept-Encoding", "gzip"); + + return request; + } } -using namespace Net; +Net::DownloadManager *Net::DownloadManager::m_instance = nullptr; -DownloadManager *DownloadManager::m_instance = nullptr; - -DownloadManager::DownloadManager(QObject *parent) +Net::DownloadManager::DownloadManager(QObject *parent) : QObject(parent) { #ifndef QT_NO_OPENSSL connect(&m_networkManager, &QNetworkAccessManager::sslErrors, this, &Net::DownloadManager::ignoreSslErrors); #endif + connect(&m_networkManager, &QNetworkAccessManager::finished, this, &DownloadManager::handleReplyFinished); + connect(ProxyConfigurationManager::instance(), &ProxyConfigurationManager::proxyConfigurationChanged + , this, &DownloadManager::applyProxySettings); m_networkManager.setCookieJar(new NetworkCookieJar(this)); } -void DownloadManager::initInstance() +void Net::DownloadManager::initInstance() { if (!m_instance) m_instance = new DownloadManager; } -void DownloadManager::freeInstance() +void Net::DownloadManager::freeInstance() { if (m_instance) { delete m_instance; @@ -132,62 +150,65 @@ void DownloadManager::freeInstance() } } -DownloadManager *DownloadManager::instance() +Net::DownloadManager *Net::DownloadManager::instance() { return m_instance; } -DownloadHandler *DownloadManager::downloadUrl(const QString &url, bool saveToFile, qint64 limit, bool handleRedirectToMagnet, const QString &userAgent) +Net::DownloadHandler *Net::DownloadManager::download(const DownloadRequest &downloadRequest) { - // Update proxy settings - applyProxySettings(); - // Process download request - qDebug("url is %s", qUtf8Printable(url)); - const QUrl qurl = QUrl(url); - QNetworkRequest request(qurl); + const QNetworkRequest request = createNetworkRequest(downloadRequest); + const ServiceID id = ServiceID::fromURL(request.url()); + const bool isSequentialService = m_sequentialServices.contains(id); + if (!isSequentialService || !m_busyServices.contains(id)) { + qDebug("Downloading %s...", qUtf8Printable(downloadRequest.url())); + if (isSequentialService) + m_busyServices.insert(id); + return new DownloadHandler { + m_networkManager.get(request), this, downloadRequest}; + } - if (userAgent.isEmpty()) - request.setRawHeader("User-Agent", DEFAULT_USER_AGENT); - else - request.setRawHeader("User-Agent", userAgent.toUtf8()); - - // Spoof HTTP Referer to allow adding torrent link from Torcache/KickAssTorrents - request.setRawHeader("Referer", request.url().toEncoded().data()); - - qDebug("Downloading %s...", request.url().toEncoded().data()); - qDebug() << "Cookies:" << m_networkManager.cookieJar()->cookiesForUrl(request.url()); - // accept gzip - request.setRawHeader("Accept-Encoding", "gzip"); - return new DownloadHandler(m_networkManager.get(request), this, saveToFile, limit, handleRedirectToMagnet); + auto *downloadHandler = new DownloadHandler {nullptr, this, downloadRequest}; + connect(downloadHandler, &DownloadHandler::destroyed, this, [this, id, downloadHandler]() + { + m_waitingJobs[id].removeOne(downloadHandler); + }); + m_waitingJobs[id].enqueue(downloadHandler); + return downloadHandler; } -QList DownloadManager::cookiesForUrl(const QUrl &url) const +void Net::DownloadManager::registerSequentialService(const Net::ServiceID &serviceID) +{ + m_sequentialServices.insert(serviceID); +} + +QList Net::DownloadManager::cookiesForUrl(const QUrl &url) const { return m_networkManager.cookieJar()->cookiesForUrl(url); } -bool DownloadManager::setCookiesFromUrl(const QList &cookieList, const QUrl &url) +bool Net::DownloadManager::setCookiesFromUrl(const QList &cookieList, const QUrl &url) { return m_networkManager.cookieJar()->setCookiesFromUrl(cookieList, url); } -QList DownloadManager::allCookies() const +QList Net::DownloadManager::allCookies() const { return static_cast(m_networkManager.cookieJar())->allCookies(); } -void DownloadManager::setAllCookies(const QList &cookieList) +void Net::DownloadManager::setAllCookies(const QList &cookieList) { static_cast(m_networkManager.cookieJar())->setAllCookies(cookieList); } -bool DownloadManager::deleteCookie(const QNetworkCookie &cookie) +bool Net::DownloadManager::deleteCookie(const QNetworkCookie &cookie) { return static_cast(m_networkManager.cookieJar())->deleteCookie(cookie); } -void DownloadManager::applyProxySettings() +void Net::DownloadManager::applyProxySettings() { auto proxyManager = ProxyConfigurationManager::instance(); ProxyConfiguration proxyConfig = proxyManager->proxyConfiguration(); @@ -208,7 +229,7 @@ void DownloadManager::applyProxySettings() } // Authentication? if (proxyManager->isAuthenticationRequired()) { - qDebug("Proxy requires authentication, authenticating"); + qDebug("Proxy requires authentication, authenticating..."); proxy.setUser(proxyConfig.username); proxy.setPassword(proxyConfig.password); } @@ -220,11 +241,101 @@ void DownloadManager::applyProxySettings() m_networkManager.setProxy(proxy); } +void Net::DownloadManager::handleReplyFinished(QNetworkReply *reply) +{ + const ServiceID id = ServiceID::fromURL(reply->url()); + auto waitingJobsIter = m_waitingJobs.find(id); + if ((waitingJobsIter == m_waitingJobs.end()) || waitingJobsIter.value().isEmpty()) { + m_busyServices.remove(id); + return; + } + + DownloadHandler *handler = waitingJobsIter.value().dequeue(); + qDebug("Downloading %s...", qUtf8Printable(handler->m_downloadRequest.url())); + handler->assignNetworkReply(m_networkManager.get(createNetworkRequest(handler->m_downloadRequest))); + handler->disconnect(this); +} + #ifndef QT_NO_OPENSSL -void DownloadManager::ignoreSslErrors(QNetworkReply *reply, const QList &errors) +void Net::DownloadManager::ignoreSslErrors(QNetworkReply *reply, const QList &errors) { Q_UNUSED(errors) // Ignore all SSL errors reply->ignoreSslErrors(); } #endif + +Net::DownloadRequest::DownloadRequest(const QString &url) + : m_url {url} +{ +} + +QString Net::DownloadRequest::url() const +{ + return m_url; +} + +Net::DownloadRequest &Net::DownloadRequest::url(const QString &value) +{ + m_url = value; + return *this; +} + +QString Net::DownloadRequest::userAgent() const +{ + return m_userAgent; +} + +Net::DownloadRequest &Net::DownloadRequest::userAgent(const QString &value) +{ + m_userAgent = value; + return *this; +} + +qint64 Net::DownloadRequest::limit() const +{ + return m_limit; +} + +Net::DownloadRequest &Net::DownloadRequest::limit(qint64 value) +{ + m_limit = value; + return *this; +} + +bool Net::DownloadRequest::saveToFile() const +{ + return m_saveToFile; +} + +Net::DownloadRequest &Net::DownloadRequest::saveToFile(bool value) +{ + m_saveToFile = value; + return *this; +} + +bool Net::DownloadRequest::handleRedirectToMagnet() const +{ + return m_handleRedirectToMagnet; +} + +Net::DownloadRequest &Net::DownloadRequest::handleRedirectToMagnet(bool value) +{ + m_handleRedirectToMagnet = value; + return *this; +} + +Net::ServiceID Net::ServiceID::fromURL(const QUrl &url) +{ + return {url.host(), url.port(80)}; +} + +uint Net::qHash(const ServiceID &serviceID, uint seed) +{ + return ::qHash(serviceID.hostName, seed) ^ serviceID.port; +} + +bool Net::operator==(const ServiceID &lhs, const ServiceID &rhs) +{ + return ((lhs.hostName == rhs.hostName) && (lhs.port == rhs.port)); +} diff --git a/src/base/net/downloadmanager.h b/src/base/net/downloadmanager.h index e847dbd01..1035ddf27 100644 --- a/src/base/net/downloadmanager.h +++ b/src/base/net/downloadmanager.h @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2015 Vladimir Golovnev + * Copyright (C) 2015, 2018 Vladimir Golovnev * Copyright (C) 2006 Christophe Dumez * * This program is free software; you can redistribute it and/or @@ -30,8 +30,12 @@ #ifndef NET_DOWNLOADMANAGER_H #define NET_DOWNLOADMANAGER_H -#include +#include #include +#include +#include +#include +#include class QNetworkReply; class QNetworkCookie; @@ -42,16 +46,57 @@ namespace Net { class DownloadHandler; + class DownloadRequest + { + public: + DownloadRequest(const QString &url); + DownloadRequest(const DownloadRequest &other) = default; + + QString url() const; + DownloadRequest &url(const QString &value); + + QString userAgent() const; + DownloadRequest &userAgent(const QString &value); + + qint64 limit() const; + DownloadRequest &limit(qint64 value); + + bool saveToFile() const; + DownloadRequest &saveToFile(bool value); + + bool handleRedirectToMagnet() const; + DownloadRequest &handleRedirectToMagnet(bool value); + + private: + QString m_url; + QString m_userAgent; + qint64 m_limit = 0; + bool m_saveToFile = false; + bool m_handleRedirectToMagnet = false; + }; + + struct ServiceID + { + QString hostName; + int port; + + static ServiceID fromURL(const QUrl &url); + }; + class DownloadManager : public QObject { Q_OBJECT + Q_DISABLE_COPY(DownloadManager) public: static void initInstance(); static void freeInstance(); static DownloadManager *instance(); - DownloadHandler *downloadUrl(const QString &url, bool saveToFile = false, qint64 limit = 0, bool handleRedirectToMagnet = false, const QString &userAgent = ""); + DownloadHandler *download(const DownloadRequest &downloadRequest); + + void registerSequentialService(const ServiceID &serviceID); + QList cookiesForUrl(const QUrl &url) const; bool setCookiesFromUrl(const QList &cookieList, const QUrl &url); QList allCookies() const; @@ -60,17 +105,25 @@ namespace Net private slots: #ifndef QT_NO_OPENSSL - void ignoreSslErrors(QNetworkReply *,const QList &); + void ignoreSslErrors(QNetworkReply *, const QList &); #endif private: explicit DownloadManager(QObject *parent = nullptr); void applyProxySettings(); + void handleReplyFinished(QNetworkReply *reply); static DownloadManager *m_instance; QNetworkAccessManager m_networkManager; + + QSet m_sequentialServices; + QSet m_busyServices; + QHash> m_waitingJobs; }; + + uint qHash(const ServiceID &serviceID, uint seed); + bool operator==(const ServiceID &lhs, const ServiceID &rhs); } #endif // NET_DOWNLOADMANAGER_H diff --git a/src/base/net/geoipmanager.cpp b/src/base/net/geoipmanager.cpp index 20db4eea7..ace840e98 100644 --- a/src/base/net/geoipmanager.cpp +++ b/src/base/net/geoipmanager.cpp @@ -118,7 +118,7 @@ void GeoIPManager::manageDatabaseUpdate() void GeoIPManager::downloadDatabaseFile() { - DownloadHandler *handler = DownloadManager::instance()->downloadUrl(DATABASE_URL); + DownloadHandler *handler = DownloadManager::instance()->download({DATABASE_URL}); connect(handler, static_cast(&Net::DownloadHandler::downloadFinished) , this, &GeoIPManager::downloadFinished); connect(handler, &Net::DownloadHandler::downloadFailed, this, &GeoIPManager::downloadFailed); diff --git a/src/base/rss/rss_feed.cpp b/src/base/rss/rss_feed.cpp index 87c9a16ba..f094a8e0a 100644 --- a/src/base/rss/rss_feed.cpp +++ b/src/base/rss/rss_feed.cpp @@ -88,6 +88,8 @@ Feed::Feed(const QUuid &uid, const QString &url, const QString &path, Session *s else connect(m_session, &Session::processingStateChanged, this, &Feed::handleSessionProcessingEnabledChanged); + Net::DownloadManager::instance()->registerSequentialService(Net::ServiceID::fromURL(m_url)); + load(); } @@ -127,7 +129,7 @@ void Feed::refresh() // NOTE: Should we allow manually refreshing for disabled session? - Net::DownloadHandler *handler = Net::DownloadManager::instance()->downloadUrl(m_url); + Net::DownloadHandler *handler = Net::DownloadManager::instance()->download({m_url}); connect(handler , static_cast(&Net::DownloadHandler::downloadFinished) , this, &Feed::handleDownloadFinished); @@ -416,7 +418,8 @@ void Feed::downloadIcon() // XXX: This works for most sites but it is not perfect const QUrl url(m_url); auto iconUrl = QString("%1://%2/favicon.ico").arg(url.scheme(), url.host()); - Net::DownloadHandler *handler = Net::DownloadManager::instance()->downloadUrl(iconUrl, true); + Net::DownloadHandler *handler = Net::DownloadManager::instance()->download( + Net::DownloadRequest(iconUrl).saveToFile(true)); connect(handler , static_cast(&Net::DownloadHandler::downloadFinished) , this, &Feed::handleIconDownloadFinished); diff --git a/src/base/search/searchpluginmanager.cpp b/src/base/search/searchpluginmanager.cpp index 892b3186a..190814129 100644 --- a/src/base/search/searchpluginmanager.cpp +++ b/src/base/search/searchpluginmanager.cpp @@ -171,7 +171,7 @@ void SearchPluginManager::installPlugin(const QString &source) if (Utils::Misc::isUrl(source)) { using namespace Net; - DownloadHandler *handler = DownloadManager::instance()->downloadUrl(source, true); + DownloadHandler *handler = DownloadManager::instance()->download(DownloadRequest(source).saveToFile(true)); connect(handler, static_cast(&DownloadHandler::downloadFinished) , this, &SearchPluginManager::pluginDownloaded); connect(handler, &DownloadHandler::downloadFailed, this, &SearchPluginManager::pluginDownloadFailed); @@ -275,7 +275,7 @@ void SearchPluginManager::checkForUpdates() { // Download version file from update server using namespace Net; - DownloadHandler *handler = DownloadManager::instance()->downloadUrl(m_updateUrl + "versions.txt"); + DownloadHandler *handler = DownloadManager::instance()->download({m_updateUrl + "versions.txt"}); connect(handler, static_cast(&DownloadHandler::downloadFinished) , this, &SearchPluginManager::versionInfoDownloaded); connect(handler, &DownloadHandler::downloadFailed, this, &SearchPluginManager::versionInfoDownloadFailed); diff --git a/src/gui/addnewtorrentdialog.cpp b/src/gui/addnewtorrentdialog.cpp index 031a9ca44..0a9e900a4 100644 --- a/src/gui/addnewtorrentdialog.cpp +++ b/src/gui/addnewtorrentdialog.cpp @@ -230,7 +230,9 @@ void AddNewTorrentDialog::show(QString source, const BitTorrent::AddTorrentParam if (Utils::Misc::isUrl(source)) { // Launch downloader - Net::DownloadHandler *handler = Net::DownloadManager::instance()->downloadUrl(source, true, 10485760 /* 10MB */, true); + // TODO: Don't save loaded torrent to file, just use downloaded data! + Net::DownloadHandler *handler = Net::DownloadManager::instance()->download( + Net::DownloadRequest(source).limit(10485760 /* 10MB */).handleRedirectToMagnet(true).saveToFile(true)); connect(handler, static_cast(&Net::DownloadHandler::downloadFinished) , dlg, &AddNewTorrentDialog::handleDownloadFinished); connect(handler, &Net::DownloadHandler::downloadFailed, dlg, &AddNewTorrentDialog::handleDownloadFailed); diff --git a/src/gui/mainwindow.cpp b/src/gui/mainwindow.cpp index dbe5a85ef..803b4afc2 100644 --- a/src/gui/mainwindow.cpp +++ b/src/gui/mainwindow.cpp @@ -2041,11 +2041,11 @@ void MainWindow::installPython() { setCursor(QCursor(Qt::WaitCursor)); // Download python - Net::DownloadHandler *handler = nullptr; - if (QSysInfo::windowsVersion() >= QSysInfo::WV_VISTA) - handler = Net::DownloadManager::instance()->downloadUrl("https://www.python.org/ftp/python/3.5.2/python-3.5.2.exe", true); - else - handler = Net::DownloadManager::instance()->downloadUrl("https://www.python.org/ftp/python/3.4.4/python-3.4.4.msi", true); + const QString installerURL = ((QSysInfo::windowsVersion() >= QSysInfo::WV_VISTA) + ? "https://www.python.org/ftp/python/3.5.2/python-3.5.2.exe" + : "https://www.python.org/ftp/python/3.4.4/python-3.4.4.msi"); + Net::DownloadHandler *handler = Net::DownloadManager::instance()->download( + Net::DownloadRequest(installerURL).saveToFile(true)); using Func = void (Net::DownloadHandler::*)(const QString &, const QString &); connect(handler, static_cast(&Net::DownloadHandler::downloadFinished), this, &MainWindow::pythonDownloadSuccess); diff --git a/src/gui/programupdater.cpp b/src/gui/programupdater.cpp index 0e0f69560..7e5cc124a 100644 --- a/src/gui/programupdater.cpp +++ b/src/gui/programupdater.cpp @@ -61,11 +61,10 @@ ProgramUpdater::ProgramUpdater(QObject *parent, bool invokedByUser) void ProgramUpdater::checkForUpdates() { - Net::DownloadHandler *handler = Net::DownloadManager::instance()->downloadUrl( - RSS_URL, false, 0, false, - // Don't change this User-Agent. In case our updater goes haywire, - // the filehost can identify it and contact us. - "qBittorrent/" QBT_VERSION_2 " ProgramUpdater (www.qbittorrent.org)"); + // Don't change this User-Agent. In case our updater goes haywire, + // the filehost can identify it and contact us. + Net::DownloadHandler *handler = Net::DownloadManager::instance()->download( + Net::DownloadRequest(RSS_URL).userAgent("qBittorrent/" QBT_VERSION_2 " ProgramUpdater (www.qbittorrent.org)")); connect(handler, static_cast(&Net::DownloadHandler::downloadFinished) , this, &ProgramUpdater::rssDownloadFinished); connect(handler, &Net::DownloadHandler::downloadFailed, this, &ProgramUpdater::rssDownloadFailed); diff --git a/src/gui/properties/trackersadditiondialog.cpp b/src/gui/properties/trackersadditiondialog.cpp index be1fef2a7..ee9d11219 100644 --- a/src/gui/properties/trackersadditiondialog.cpp +++ b/src/gui/properties/trackersadditiondialog.cpp @@ -25,9 +25,10 @@ * but you are not obligated to do so. If you do not wish to do so, delete this * exception statement from your version. */ + #include "trackersadditiondialog.h" -#include +#include #include #include #include @@ -70,25 +71,16 @@ QStringList TrackersAdditionDialog::newTrackers() const void TrackersAdditionDialog::on_uTorrentListButton_clicked() { m_ui->uTorrentListButton->setEnabled(false); - Net::DownloadHandler *handler = Net::DownloadManager::instance()->downloadUrl(m_ui->list_url->text(), true); - connect(handler, static_cast(&Net::DownloadHandler::downloadFinished) + Net::DownloadHandler *handler = Net::DownloadManager::instance()->download({m_ui->list_url->text()}); + connect(handler, static_cast(&Net::DownloadHandler::downloadFinished) , this, &TrackersAdditionDialog::parseUTorrentList); connect(handler, &Net::DownloadHandler::downloadFailed, this, &TrackersAdditionDialog::getTrackerError); // Just to show that it takes times setCursor(Qt::WaitCursor); } -void TrackersAdditionDialog::parseUTorrentList(const QString &, const QString &path) +void TrackersAdditionDialog::parseUTorrentList(const QString &, const QByteArray &data) { - QFile listFile(path); - if (!listFile.open(QFile::ReadOnly)) { - QMessageBox::warning(this, tr("I/O Error"), tr("Error while trying to open the downloaded file."), QMessageBox::Ok); - setCursor(Qt::ArrowCursor); - m_ui->uTorrentListButton->setEnabled(true); - Utils::Fs::forceRemove(path); - return; - } - // Load from torrent handle QList existingTrackers = m_torrent->trackers(); // Load from current user list @@ -103,19 +95,21 @@ void TrackersAdditionDialog::parseUTorrentList(const QString &, const QString &p if (!m_ui->trackers_list->toPlainText().isEmpty() && !m_ui->trackers_list->toPlainText().endsWith('\n')) m_ui->trackers_list->insertPlainText("\n"); int nb = 0; - while (!listFile.atEnd()) { - const QString line = listFile.readLine().trimmed(); + QBuffer buffer; + buffer.setData(data); + buffer.open(QBuffer::ReadOnly); + while (!buffer.atEnd()) { + const QString line = buffer.readLine().trimmed(); if (line.isEmpty()) continue; + BitTorrent::TrackerEntry newTracker(line); if (!existingTrackers.contains(newTracker)) { m_ui->trackers_list->insertPlainText(line + '\n'); ++nb; } } - // Clean up - listFile.close(); - Utils::Fs::forceRemove(path); - //To restore the cursor ... + + // To restore the cursor ... setCursor(Qt::ArrowCursor); m_ui->uTorrentListButton->setEnabled(true); // Display information message if necessary diff --git a/src/gui/properties/trackersadditiondialog.h b/src/gui/properties/trackersadditiondialog.h index 276b4c47d..c6843d340 100644 --- a/src/gui/properties/trackersadditiondialog.h +++ b/src/gui/properties/trackersadditiondialog.h @@ -57,7 +57,7 @@ public: public slots: void on_uTorrentListButton_clicked(); - void parseUTorrentList(const QString &, const QString &path); + void parseUTorrentList(const QString &, const QByteArray &data); void getTrackerError(const QString &, const QString &error); private: diff --git a/src/gui/search/pluginselectdialog.cpp b/src/gui/search/pluginselectdialog.cpp index 11135fe40..e19c17ea9 100644 --- a/src/gui/search/pluginselectdialog.cpp +++ b/src/gui/search/pluginselectdialog.cpp @@ -292,7 +292,8 @@ void PluginSelectDialog::addNewPlugin(QString pluginName) else { // Icon is missing, we must download it using namespace Net; - DownloadHandler *handler = DownloadManager::instance()->downloadUrl(plugin->url + "/favicon.ico", true); + DownloadHandler *handler = DownloadManager::instance()->download( + DownloadRequest(plugin->url + "/favicon.ico").saveToFile(true)); connect(handler, static_cast(&DownloadHandler::downloadFinished) , this, &PluginSelectDialog::iconDownloaded); connect(handler, &DownloadHandler::downloadFailed, this, &PluginSelectDialog::iconDownloadFailed); diff --git a/src/gui/transferlistfilterswidget.cpp b/src/gui/transferlistfilterswidget.cpp index c8c71983f..843fad8c0 100644 --- a/src/gui/transferlistfilterswidget.cpp +++ b/src/gui/transferlistfilterswidget.cpp @@ -404,7 +404,8 @@ void TrackerFiltersList::trackerWarning(const QString &hash, const QString &trac void TrackerFiltersList::downloadFavicon(const QString& url) { if (!m_downloadTrackerFavicon) return; - Net::DownloadHandler *h = Net::DownloadManager::instance()->downloadUrl(url, true); + Net::DownloadHandler *h = Net::DownloadManager::instance()->download( + Net::DownloadRequest(url).saveToFile(true)); using Func = void (Net::DownloadHandler::*)(const QString &, const QString &); connect(h, static_cast(&Net::DownloadHandler::downloadFinished), this , &TrackerFiltersList::handleFavicoDownload);