Merge pull request #10346 from glassez/download-manager

Improve "Download manager"
This commit is contained in:
Vladimir Golovnev
2019-03-10 09:13:07 +03:00
committed by GitHub
28 changed files with 555 additions and 681 deletions

View File

@@ -43,7 +43,6 @@
#include "base/bittorrent/torrenthandle.h"
#include "base/bittorrent/torrentinfo.h"
#include "base/global.h"
#include "base/net/downloadhandler.h"
#include "base/net/downloadmanager.h"
#include "base/preferences.h"
#include "base/settingsstorage.h"
@@ -238,12 +237,9 @@ void AddNewTorrentDialog::show(const QString &source, const BitTorrent::AddTorre
if (Net::DownloadManager::hasSupportedScheme(source)) {
// Launch downloader
Net::DownloadHandler *handler = Net::DownloadManager::instance()->download(
Net::DownloadRequest(source).limit(10485760 /* 10MB */).handleRedirectToMagnet(true));
connect(handler, static_cast<void (Net::DownloadHandler::*)(const QString &, const QByteArray &)>(&Net::DownloadHandler::downloadFinished)
, dlg, &AddNewTorrentDialog::handleDownloadFinished);
connect(handler, &Net::DownloadHandler::downloadFailed, dlg, &AddNewTorrentDialog::handleDownloadFailed);
connect(handler, &Net::DownloadHandler::redirectedToMagnet, dlg, &AddNewTorrentDialog::handleRedirectedToMagnet);
Net::DownloadManager::instance()->download(
Net::DownloadRequest(source).limit(10485760 /* 10MB */)
, dlg, &AddNewTorrentDialog::handleDownloadFinished);
return;
}
@@ -762,39 +758,36 @@ void AddNewTorrentDialog::setupTreeview()
showAdvancedSettings(settings()->loadValue(KEY_EXPANDED, false).toBool());
}
void AddNewTorrentDialog::handleDownloadFailed(const QString &url, const QString &reason)
{
RaisedMessageBox::critical(this, tr("Download Error"),
QString("Cannot download '%1': %2").arg(url, reason));
this->deleteLater();
}
void AddNewTorrentDialog::handleRedirectedToMagnet(const QString &url, const QString &magnetUri)
{
Q_UNUSED(url)
if (loadMagnet(BitTorrent::MagnetUri(magnetUri)))
open();
else
this->deleteLater();
}
void AddNewTorrentDialog::handleDownloadFinished(const QString &url, const QByteArray &data)
void AddNewTorrentDialog::handleDownloadFinished(const Net::DownloadResult &result)
{
QString error;
m_torrentInfo = BitTorrent::TorrentInfo::load(data, &error);
if (!m_torrentInfo.isValid()) {
RaisedMessageBox::critical(this, tr("Invalid torrent"), tr("Failed to load from URL: %1.\nError: %2")
.arg(url, error));
return;
switch (result.status) {
case Net::DownloadStatus::Success:
m_torrentInfo = BitTorrent::TorrentInfo::load(result.data, &error);
if (!m_torrentInfo.isValid()) {
RaisedMessageBox::critical(this, tr("Invalid torrent"), tr("Failed to load from URL: %1.\nError: %2")
.arg(result.url, error));
return;
}
m_torrentGuard.reset(new TorrentFileGuard);
if (loadTorrentImpl())
open();
else
deleteLater();
break;
case Net::DownloadStatus::RedirectedToMagnet:
if (loadMagnet(BitTorrent::MagnetUri(result.magnet)))
open();
else
deleteLater();
break;
default:
RaisedMessageBox::critical(this, tr("Download Error"),
tr("Cannot download '%1': %2").arg(result.url, result.errorString));
deleteLater();
}
m_torrentGuard.reset(new TorrentFileGuard);
if (loadTorrentImpl())
open();
else
this->deleteLater();
}
void AddNewTorrentDialog::TMMChanged(int index)

View File

@@ -43,6 +43,11 @@ namespace BitTorrent
class MagnetUri;
}
namespace Net
{
struct DownloadResult;
}
namespace Ui
{
class AddNewTorrentDialog;
@@ -55,12 +60,13 @@ class TorrentFileGuard;
class AddNewTorrentDialog : public QDialog
{
Q_OBJECT
Q_DISABLE_COPY(AddNewTorrentDialog)
public:
static const int minPathHistoryLength = 0;
static const int maxPathHistoryLength = 99;
~AddNewTorrentDialog();
~AddNewTorrentDialog() override;
static bool isEnabled();
static void setEnabled(bool value);
@@ -79,9 +85,7 @@ private slots:
void onSavePathChanged(const QString &newPath);
void renameSelectedFile();
void updateMetadata(const BitTorrent::TorrentInfo &info);
void handleDownloadFailed(const QString &url, const QString &reason);
void handleRedirectedToMagnet(const QString &url, const QString &magnetUri);
void handleDownloadFinished(const QString &url, const QByteArray &data);
void handleDownloadFinished(const Net::DownloadResult &result);
void TMMChanged(int index);
void categoryChanged(int index);
void doNotDeleteTorrentClicked(bool checked);

View File

@@ -97,7 +97,6 @@
#include "utils.h"
#ifdef Q_OS_WIN
#include "base/net/downloadhandler.h"
#include "base/net/downloadmanager.h"
#endif
#ifdef Q_OS_MAC
@@ -2013,28 +2012,33 @@ void MainWindow::installPython()
const QString installerURL = ((QSysInfo::windowsVersion() >= QSysInfo::WV_VISTA)
? "https://www.python.org/ftp/python/3.6.6/python-3.6.6.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<Func>(&Net::DownloadHandler::downloadFinished), this, &MainWindow::pythonDownloadSuccess);
connect(handler, static_cast<Func>(&Net::DownloadHandler::downloadFailed), this, &MainWindow::pythonDownloadFailure);
Net::DownloadManager::instance()->download(
Net::DownloadRequest(installerURL).saveToFile(true)
, this, &MainWindow::pythonDownloadFinished);
}
void MainWindow::pythonDownloadSuccess(const QString &url, const QString &filePath)
void MainWindow::pythonDownloadFinished(const Net::DownloadResult &result)
{
Q_UNUSED(url)
if (result.status != Net::DownloadStatus::Success) {
setCursor(QCursor(Qt::ArrowCursor));
QMessageBox::warning(
this, tr("Download error")
, tr("Python setup could not be downloaded, reason: %1.\nPlease install it manually.")
.arg(result.errorString));
return;
}
setCursor(QCursor(Qt::ArrowCursor));
QProcess installer;
qDebug("Launching Python installer in passive mode...");
if (QSysInfo::windowsVersion() >= QSysInfo::WV_VISTA) {
QFile::rename(filePath, filePath + ".exe");
installer.start('"' + Utils::Fs::toNativePath(filePath) + ".exe\" /passive");
QFile::rename(result.filePath, result.filePath + ".exe");
installer.start('"' + Utils::Fs::toNativePath(result.filePath) + ".exe\" /passive");
}
else {
QFile::rename(filePath, filePath + ".msi");
installer.start(Utils::Misc::windowsSystemPath() + "\\msiexec.exe /passive /i \"" + Utils::Fs::toNativePath(filePath) + ".msi\"");
QFile::rename(result.filePath, result.filePath + ".msi");
installer.start(Utils::Misc::windowsSystemPath() + "\\msiexec.exe /passive /i \"" + Utils::Fs::toNativePath(result.filePath) + ".msi\"");
}
// Wait for setup to complete
@@ -2045,21 +2049,13 @@ void MainWindow::pythonDownloadSuccess(const QString &url, const QString &filePa
qDebug("Setup should be complete!");
// Delete temp file
if (QSysInfo::windowsVersion() >= QSysInfo::WV_VISTA)
Utils::Fs::forceRemove(filePath + ".exe");
Utils::Fs::forceRemove(result.filePath + ".exe");
else
Utils::Fs::forceRemove(filePath + ".msi");
Utils::Fs::forceRemove(result.filePath + ".msi");
// Reload search engine
if (Utils::ForeignApps::pythonInfo().isSupportedVersion()) {
m_ui->actionSearchWidget->setChecked(true);
displaySearchTab(true);
}
}
void MainWindow::pythonDownloadFailure(const QString &url, const QString &error)
{
Q_UNUSED(url)
setCursor(QCursor(Qt::ArrowCursor));
QMessageBox::warning(this, tr("Download error"), tr("Python setup could not be downloaded, reason: %1.\nPlease install it manually.").arg(error));
}
#endif // Q_OS_WIN

View File

@@ -62,6 +62,11 @@ namespace BitTorrent
class TorrentHandle;
}
namespace Net
{
struct DownloadResult;
}
namespace Ui
{
class MainWindow;
@@ -135,8 +140,7 @@ private slots:
void toggleAlternativeSpeeds();
#ifdef Q_OS_WIN
void pythonDownloadSuccess(const QString &url, const QString &filePath);
void pythonDownloadFailure(const QString &url, const QString &error);
void pythonDownloadFinished(const Net::DownloadResult &result);
#endif
void addToolbarContextMenu();
void manageCookies();

View File

@@ -35,7 +35,6 @@
#include <QSysInfo>
#include <QXmlStreamReader>
#include "base/net/downloadhandler.h"
#include "base/net/downloadmanager.h"
#include "base/utils/fs.h"
@@ -56,16 +55,20 @@ void ProgramUpdater::checkForUpdates()
{
// 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<void (Net::DownloadHandler::*)(const QString &, const QByteArray &)>(&Net::DownloadHandler::downloadFinished)
, this, &ProgramUpdater::rssDownloadFinished);
connect(handler, &Net::DownloadHandler::downloadFailed, this, &ProgramUpdater::rssDownloadFailed);
Net::DownloadManager::instance()->download(
Net::DownloadRequest(RSS_URL).userAgent("qBittorrent/" QBT_VERSION_2 " ProgramUpdater (www.qbittorrent.org)")
, this, &ProgramUpdater::rssDownloadFinished);
}
void ProgramUpdater::rssDownloadFinished(const QString &url, const QByteArray &data)
void ProgramUpdater::rssDownloadFinished(const Net::DownloadResult &result)
{
Q_UNUSED(url);
if (result.status != Net::DownloadStatus::Success) {
qDebug() << "Downloading the new qBittorrent updates RSS failed:" << result.errorString;
emit updateCheckFinished(false, QString(), m_invokedByUser);
return;
}
qDebug("Finished downloading the new qBittorrent updates RSS");
#ifdef Q_OS_MAC
@@ -77,7 +80,7 @@ void ProgramUpdater::rssDownloadFinished(const QString &url, const QByteArray &d
#endif
QString version;
QXmlStreamReader xml(data);
QXmlStreamReader xml(result.data);
bool inItem = false;
QString updateLink;
QString type;
@@ -118,14 +121,6 @@ void ProgramUpdater::rssDownloadFinished(const QString &url, const QByteArray &d
emit updateCheckFinished(!m_updateUrl.isEmpty(), version, m_invokedByUser);
}
void ProgramUpdater::rssDownloadFailed(const QString &url, const QString &error)
{
Q_UNUSED(url);
qDebug() << "Downloading the new qBittorrent updates RSS failed:" << error;
emit updateCheckFinished(false, QString(), m_invokedByUser);
}
void ProgramUpdater::updateProgram()
{
Q_ASSERT(!m_updateUrl.isEmpty());

View File

@@ -32,9 +32,15 @@
#include <QObject>
#include <QUrl>
namespace Net
{
struct DownloadResult;
}
class ProgramUpdater : public QObject
{
Q_OBJECT
Q_DISABLE_COPY(ProgramUpdater)
public:
explicit ProgramUpdater(QObject *parent = nullptr, bool invokedByUser = false);
@@ -46,8 +52,7 @@ signals:
void updateCheckFinished(bool updateAvailable, QString version, bool invokedByUser);
private slots:
void rssDownloadFinished(const QString &url, const QByteArray &data);
void rssDownloadFailed(const QString &url, const QString &error);
void rssDownloadFinished(const Net::DownloadResult &result);
private:
bool isVersionMoreRecent(const QString &remoteVersion) const;

View File

@@ -35,7 +35,6 @@
#include "base/bittorrent/torrenthandle.h"
#include "base/bittorrent/trackerentry.h"
#include "base/global.h"
#include "base/net/downloadhandler.h"
#include "base/net/downloadmanager.h"
#include "base/utils/fs.h"
#include "base/utils/misc.h"
@@ -71,16 +70,25 @@ QStringList TrackersAdditionDialog::newTrackers() const
void TrackersAdditionDialog::on_uTorrentListButton_clicked()
{
m_ui->uTorrentListButton->setEnabled(false);
Net::DownloadHandler *handler = Net::DownloadManager::instance()->download(m_ui->lineEditListURL->text());
connect(handler, static_cast<void (Net::DownloadHandler::*)(const QString &, const QByteArray &)>(&Net::DownloadHandler::downloadFinished)
, this, &TrackersAdditionDialog::parseUTorrentList);
connect(handler, &Net::DownloadHandler::downloadFailed, this, &TrackersAdditionDialog::getTrackerError);
Net::DownloadManager::instance()->download(m_ui->lineEditListURL->text()
, this, &TrackersAdditionDialog::torrentListDownloadFinished);
// Just to show that it takes times
setCursor(Qt::WaitCursor);
}
void TrackersAdditionDialog::parseUTorrentList(const QString &, const QByteArray &data)
void TrackersAdditionDialog::torrentListDownloadFinished(const Net::DownloadResult &result)
{
if (result.status != Net::DownloadStatus::Success) {
// To restore the cursor ...
setCursor(Qt::ArrowCursor);
m_ui->uTorrentListButton->setEnabled(true);
QMessageBox::warning(
this, tr("Download error")
, tr("The trackers list could not be downloaded, reason: %1")
.arg(result.errorString), QMessageBox::Ok);
return;
}
// Load from torrent handle
QList<BitTorrent::TrackerEntry> existingTrackers = m_torrent->trackers();
// Load from current user list
@@ -96,7 +104,7 @@ void TrackersAdditionDialog::parseUTorrentList(const QString &, const QByteArray
m_ui->textEditTrackersList->insertPlainText("\n");
int nb = 0;
QBuffer buffer;
buffer.setData(data);
buffer.setData(result.data);
buffer.open(QBuffer::ReadOnly);
while (!buffer.atEnd()) {
const QString line = buffer.readLine().trimmed();
@@ -117,14 +125,6 @@ void TrackersAdditionDialog::parseUTorrentList(const QString &, const QByteArray
QMessageBox::information(this, tr("No change"), tr("No additional trackers were found."), QMessageBox::Ok);
}
void TrackersAdditionDialog::getTrackerError(const QString &, const QString &error)
{
// To restore the cursor ...
setCursor(Qt::ArrowCursor);
m_ui->uTorrentListButton->setEnabled(true);
QMessageBox::warning(this, tr("Download error"), tr("The trackers list could not be downloaded, reason: %1").arg(error), QMessageBox::Ok);
}
QStringList TrackersAdditionDialog::askForTrackers(QWidget *parent, BitTorrent::TorrentHandle *const torrent)
{
QStringList trackers;

View File

@@ -39,6 +39,11 @@ namespace BitTorrent
class TorrentHandle;
}
namespace Net
{
struct DownloadResult;
}
namespace Ui
{
class TrackersAdditionDialog;
@@ -57,8 +62,7 @@ public:
public slots:
void on_uTorrentListButton_clicked();
void parseUTorrentList(const QString &, const QByteArray &data);
void getTrackerError(const QString &, const QString &error);
void torrentListDownloadFinished(const Net::DownloadResult &result);
private:
Ui::TrackersAdditionDialog *m_ui;

View File

@@ -40,7 +40,6 @@
#include <QTableView>
#include "base/global.h"
#include "base/net/downloadhandler.h"
#include "base/net/downloadmanager.h"
#include "base/utils/fs.h"
#include "autoexpandabledialog.h"
@@ -291,11 +290,9 @@ void PluginSelectDialog::addNewPlugin(const QString &pluginName)
else {
// Icon is missing, we must download it
using namespace Net;
DownloadHandler *handler = DownloadManager::instance()->download(
DownloadRequest(plugin->url + "/favicon.ico").saveToFile(true));
connect(handler, static_cast<void (DownloadHandler::*)(const QString &, const QString &)>(&DownloadHandler::downloadFinished)
, this, &PluginSelectDialog::iconDownloaded);
connect(handler, &DownloadHandler::downloadFailed, this, &PluginSelectDialog::iconDownloadFailed);
DownloadManager::instance()->download(
DownloadRequest(plugin->url + "/favicon.ico").saveToFile(true)
, this, &PluginSelectDialog::iconDownloadFinished);
}
item->setText(PLUGIN_VERSION, plugin->version);
}
@@ -369,9 +366,14 @@ void PluginSelectDialog::askForLocalPlugin()
}
}
void PluginSelectDialog::iconDownloaded(const QString &url, QString filePath)
void PluginSelectDialog::iconDownloadFinished(const Net::DownloadResult &result)
{
filePath = Utils::Fs::fromNativePath(filePath);
if (result.status != Net::DownloadStatus::Success) {
qDebug("Could not download favicon: %s, reason: %s", qUtf8Printable(result.url), qUtf8Printable(result.errorString));
return;
}
const QString filePath = Utils::Fs::fromNativePath(result.filePath);
// Icon downloaded
QIcon icon(filePath);
@@ -379,7 +381,7 @@ void PluginSelectDialog::iconDownloaded(const QString &url, QString filePath)
QList<QSize> sizes = icon.availableSizes();
bool invalid = (sizes.isEmpty() || icon.pixmap(sizes.first()).isNull());
if (!invalid) {
for (QTreeWidgetItem *item : asConst(findItemsWithUrl(url))) {
for (QTreeWidgetItem *item : asConst(findItemsWithUrl(result.url))) {
QString id = item->text(PLUGIN_ID);
PluginInfo *plugin = m_pluginManager->pluginInfo(id);
if (!plugin) continue;
@@ -387,7 +389,7 @@ void PluginSelectDialog::iconDownloaded(const QString &url, QString filePath)
QString iconPath = QString("%1/%2.%3")
.arg(SearchPluginManager::pluginsLocation()
, id
, url.endsWith(".ico", Qt::CaseInsensitive) ? "ico" : "png");
, result.url.endsWith(".ico", Qt::CaseInsensitive) ? "ico" : "png");
if (QFile::copy(filePath, iconPath)) {
// This 2nd check is necessary. Some favicons (eg from piratebay)
// decode fine without an ext, but fail to do so when appending the ext
@@ -409,11 +411,6 @@ void PluginSelectDialog::iconDownloaded(const QString &url, QString filePath)
Utils::Fs::forceRemove(filePath);
}
void PluginSelectDialog::iconDownloadFailed(const QString &url, const QString &reason)
{
qDebug("Could not download favicon: %s, reason: %s", qUtf8Printable(url), qUtf8Printable(reason));
}
void PluginSelectDialog::checkForUpdatesFinished(const QHash<QString, PluginVersion> &updateInfo)
{
finishAsyncOp();

View File

@@ -38,6 +38,11 @@ class QDropEvent;
class QStringList;
class QTreeWidgetItem;
namespace Net
{
struct DownloadResult;
}
namespace Ui
{
class PluginSelectDialog;
@@ -46,10 +51,11 @@ namespace Ui
class PluginSelectDialog : public QDialog
{
Q_OBJECT
Q_DISABLE_COPY(PluginSelectDialog)
public:
explicit PluginSelectDialog(SearchPluginManager *pluginManager, QWidget *parent = nullptr);
~PluginSelectDialog();
~PluginSelectDialog() override;
QList<QTreeWidgetItem*> findItemsWithUrl(const QString &url);
QTreeWidgetItem *findItemWithID(const QString &id);
@@ -69,8 +75,7 @@ private slots:
void enableSelection(bool enable);
void askForLocalPlugin();
void askForPluginUrl();
void iconDownloaded(const QString &url, QString filePath);
void iconDownloadFailed(const QString &url, const QString &reason);
void iconDownloadFinished(const Net::DownloadResult &result);
void checkForUpdatesFinished(const QHash<QString, PluginVersion> &updateInfo);
void checkForUpdatesFailed(const QString &reason);

View File

@@ -42,7 +42,6 @@
#include "base/bittorrent/trackerentry.h"
#include "base/global.h"
#include "base/logger.h"
#include "base/net/downloadhandler.h"
#include "base/net/downloadmanager.h"
#include "base/preferences.h"
#include "base/torrentfilter.h"
@@ -406,49 +405,44 @@ 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()->download(
Net::DownloadRequest(url).saveToFile(true));
using Func = void (Net::DownloadHandler::*)(const QString &, const QString &);
connect(h, static_cast<Func>(&Net::DownloadHandler::downloadFinished), this
, &TrackerFiltersList::handleFavicoDownload);
connect(h, static_cast<Func>(&Net::DownloadHandler::downloadFailed), this
, &TrackerFiltersList::handleFavicoFailure);
Net::DownloadManager::instance()->download(
Net::DownloadRequest(url).saveToFile(true)
, this, &TrackerFiltersList::handleFavicoDownloadFinished);
}
void TrackerFiltersList::handleFavicoDownload(const QString &url, const QString &filePath)
void TrackerFiltersList::handleFavicoDownloadFinished(const Net::DownloadResult &result)
{
const QString host = getHost(url);
if (result.status != Net::DownloadStatus::Success) {
if (result.url.endsWith(".ico", Qt::CaseInsensitive))
downloadFavicon(result.url.left(result.url.size() - 4) + ".png");
return;
}
const QString host = getHost(result.url);
if (!m_trackers.contains(host)) {
Utils::Fs::forceRemove(filePath);
Utils::Fs::forceRemove(result.filePath);
return;
}
QListWidgetItem *trackerItem = item(rowFromTracker(host));
if (!trackerItem) return;
QIcon icon(filePath);
QIcon icon(result.filePath);
//Detect a non-decodable icon
QList<QSize> sizes = icon.availableSizes();
bool invalid = (sizes.isEmpty() || icon.pixmap(sizes.first()).isNull());
if (invalid) {
if (url.endsWith(".ico", Qt::CaseInsensitive))
downloadFavicon(url.left(url.size() - 4) + ".png");
Utils::Fs::forceRemove(filePath);
if (result.url.endsWith(".ico", Qt::CaseInsensitive))
downloadFavicon(result.url.left(result.url.size() - 4) + ".png");
Utils::Fs::forceRemove(result.filePath);
}
else {
trackerItem->setData(Qt::DecorationRole, QVariant(QIcon(filePath)));
m_iconPaths.append(filePath);
trackerItem->setData(Qt::DecorationRole, QVariant(QIcon(result.filePath)));
m_iconPaths.append(result.filePath);
}
}
void TrackerFiltersList::handleFavicoFailure(const QString &url, const QString &error)
{
Q_UNUSED(error)
if (url.endsWith(".ico", Qt::CaseInsensitive))
downloadFavicon(url.left(url.size() - 4) + ".png");
}
void TrackerFiltersList::showMenu(QPoint)
{
QMenu menu(this);

View File

@@ -42,9 +42,15 @@ namespace BitTorrent
class TrackerEntry;
}
namespace Net
{
struct DownloadResult;
}
class BaseFilterWidget : public QListWidget
{
Q_OBJECT
Q_DISABLE_COPY(BaseFilterWidget)
public:
BaseFilterWidget(QWidget *parent, TransferListWidget *transferList);
@@ -68,6 +74,7 @@ private slots:
class StatusFilterWidget : public BaseFilterWidget
{
Q_OBJECT
Q_DISABLE_COPY(StatusFilterWidget)
public:
StatusFilterWidget(QWidget *parent, TransferListWidget *transferList);
@@ -88,6 +95,7 @@ private:
class TrackerFiltersList : public BaseFilterWidget
{
Q_OBJECT
Q_DISABLE_COPY(TrackerFiltersList)
public:
TrackerFiltersList(QWidget *parent, TransferListWidget *transferList);
@@ -105,8 +113,7 @@ public slots:
void trackerWarning(const QString &hash, const QString &tracker);
private slots:
void handleFavicoDownload(const QString &url, const QString &filePath);
void handleFavicoFailure(const QString &url, const QString &error);
void handleFavicoDownloadFinished(const Net::DownloadResult &result);
private:
// These 4 methods are virtual slots in the base class.