mirror of
https://github.com/qbittorrent/qBittorrent
synced 2025-08-19 21:03:30 -07:00
parent
e4313d6651
commit
dcf3e97291
42 changed files with 933 additions and 639 deletions
|
@ -50,6 +50,7 @@
|
||||||
#include <QProcess>
|
#include <QProcess>
|
||||||
|
|
||||||
#ifndef DISABLE_GUI
|
#ifndef DISABLE_GUI
|
||||||
|
#include <QAbstractButton>
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QPixmapCache>
|
#include <QPixmapCache>
|
||||||
|
@ -63,6 +64,7 @@
|
||||||
#endif // Q_OS_MACOS
|
#endif // Q_OS_MACOS
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "base/addtorrentmanager.h"
|
||||||
#include "base/bittorrent/infohash.h"
|
#include "base/bittorrent/infohash.h"
|
||||||
#include "base/bittorrent/session.h"
|
#include "base/bittorrent/session.h"
|
||||||
#include "base/bittorrent/torrent.h"
|
#include "base/bittorrent/torrent.h"
|
||||||
|
@ -81,7 +83,6 @@
|
||||||
#include "base/search/searchpluginmanager.h"
|
#include "base/search/searchpluginmanager.h"
|
||||||
#include "base/settingsstorage.h"
|
#include "base/settingsstorage.h"
|
||||||
#include "base/torrentfileswatcher.h"
|
#include "base/torrentfileswatcher.h"
|
||||||
#include "base/utils/compare.h"
|
|
||||||
#include "base/utils/fs.h"
|
#include "base/utils/fs.h"
|
||||||
#include "base/utils/misc.h"
|
#include "base/utils/misc.h"
|
||||||
#include "base/utils/string.h"
|
#include "base/utils/string.h"
|
||||||
|
@ -91,7 +92,7 @@
|
||||||
#include "upgrade.h"
|
#include "upgrade.h"
|
||||||
|
|
||||||
#ifndef DISABLE_GUI
|
#ifndef DISABLE_GUI
|
||||||
#include "gui/addnewtorrentdialog.h"
|
#include "gui/guiaddtorrentmanager.h"
|
||||||
#include "gui/desktopintegration.h"
|
#include "gui/desktopintegration.h"
|
||||||
#include "gui/mainwindow.h"
|
#include "gui/mainwindow.h"
|
||||||
#include "gui/shutdownconfirmdialog.h"
|
#include "gui/shutdownconfirmdialog.h"
|
||||||
|
@ -682,6 +683,21 @@ void Application::torrentFinished(const BitTorrent::Torrent *torrent)
|
||||||
LogMsg(tr("Torrent: %1, sending mail notification").arg(torrent->name()));
|
LogMsg(tr("Torrent: %1, sending mail notification").arg(torrent->name()));
|
||||||
sendNotificationEmail(torrent);
|
sendNotificationEmail(torrent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef DISABLE_GUI
|
||||||
|
if (Preferences::instance()->isRecursiveDownloadEnabled())
|
||||||
|
{
|
||||||
|
// Check whether it contains .torrent files
|
||||||
|
for (const Path &torrentRelpath : asConst(torrent->filePaths()))
|
||||||
|
{
|
||||||
|
if (torrentRelpath.hasExtension(u".torrent"_s))
|
||||||
|
{
|
||||||
|
askRecursiveTorrentDownloadConfirmation(torrent);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::allTorrentsFinished()
|
void Application::allTorrentsFinished()
|
||||||
|
@ -744,18 +760,15 @@ void Application::processParams(const QBtCommandLineParameters ¶ms)
|
||||||
// be shown and skipTorrentDialog is undefined. The other is when
|
// be shown and skipTorrentDialog is undefined. The other is when
|
||||||
// skipTorrentDialog is false, meaning that the application setting
|
// skipTorrentDialog is false, meaning that the application setting
|
||||||
// should be overridden.
|
// should be overridden.
|
||||||
const bool showDialog = !params.skipDialog.value_or(!AddNewTorrentDialog::isEnabled());
|
AddTorrentOption addTorrentOption = AddTorrentOption::Default;
|
||||||
if (showDialog)
|
if (params.skipDialog.has_value())
|
||||||
{
|
addTorrentOption = params.skipDialog.value() ? AddTorrentOption::SkipDialog : AddTorrentOption::ShowDialog;
|
||||||
for (const QString &torrentSource : params.torrentSources)
|
for (const QString &torrentSource : params.torrentSources)
|
||||||
AddNewTorrentDialog::show(torrentSource, params.addTorrentParams, m_window);
|
m_addTorrentManager->addTorrent(torrentSource, params.addTorrentParams, addTorrentOption);
|
||||||
}
|
#else
|
||||||
else
|
for (const QString &torrentSource : params.torrentSources)
|
||||||
|
m_addTorrentManager->addTorrent(torrentSource, params.addTorrentParams);
|
||||||
#endif
|
#endif
|
||||||
{
|
|
||||||
for (const QString &torrentSource : params.torrentSources)
|
|
||||||
BitTorrent::Session::instance()->addTorrent(torrentSource, params.addTorrentParams);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int Application::exec()
|
int Application::exec()
|
||||||
|
@ -822,11 +835,13 @@ int Application::exec()
|
||||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentFinished, this, &Application::torrentFinished);
|
connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentFinished, this, &Application::torrentFinished);
|
||||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::allTorrentsFinished, this, &Application::allTorrentsFinished, Qt::QueuedConnection);
|
connect(BitTorrent::Session::instance(), &BitTorrent::Session::allTorrentsFinished, this, &Application::allTorrentsFinished, Qt::QueuedConnection);
|
||||||
|
|
||||||
|
m_addTorrentManager = new AddTorrentManagerImpl(this, BitTorrent::Session::instance(), this);
|
||||||
|
|
||||||
Net::GeoIPManager::initInstance();
|
Net::GeoIPManager::initInstance();
|
||||||
TorrentFilesWatcher::initInstance();
|
TorrentFilesWatcher::initInstance();
|
||||||
|
|
||||||
new RSS::Session; // create RSS::Session singleton
|
new RSS::Session; // create RSS::Session singleton
|
||||||
new RSS::AutoDownloader; // create RSS::AutoDownloader singleton
|
new RSS::AutoDownloader(this); // create RSS::AutoDownloader singleton
|
||||||
|
|
||||||
#ifndef DISABLE_GUI
|
#ifndef DISABLE_GUI
|
||||||
const auto *btSession = BitTorrent::Session::instance();
|
const auto *btSession = BitTorrent::Session::instance();
|
||||||
|
@ -837,27 +852,22 @@ int Application::exec()
|
||||||
, tr("An I/O error occurred for torrent '%1'.\n Reason: %2"
|
, tr("An I/O error occurred for torrent '%1'.\n Reason: %2"
|
||||||
, "e.g: An error occurred for torrent 'xxx.avi'.\n Reason: disk is full.").arg(torrent->name(), msg));
|
, "e.g: An error occurred for torrent 'xxx.avi'.\n Reason: disk is full.").arg(torrent->name(), msg));
|
||||||
});
|
});
|
||||||
connect(btSession, &BitTorrent::Session::loadTorrentFailed, this
|
|
||||||
, [this](const QString &error)
|
|
||||||
{
|
|
||||||
m_desktopIntegration->showNotification(tr("Error"), tr("Failed to add torrent: %1").arg(error));
|
|
||||||
});
|
|
||||||
connect(btSession, &BitTorrent::Session::torrentAdded, this
|
|
||||||
, [this](const BitTorrent::Torrent *torrent)
|
|
||||||
{
|
|
||||||
if (isTorrentAddedNotificationsEnabled())
|
|
||||||
m_desktopIntegration->showNotification(tr("Torrent added"), tr("'%1' was added.", "e.g: xxx.avi was added.").arg(torrent->name()));
|
|
||||||
});
|
|
||||||
connect(btSession, &BitTorrent::Session::torrentFinished, this
|
connect(btSession, &BitTorrent::Session::torrentFinished, this
|
||||||
, [this](const BitTorrent::Torrent *torrent)
|
, [this](const BitTorrent::Torrent *torrent)
|
||||||
{
|
{
|
||||||
m_desktopIntegration->showNotification(tr("Download completed"), tr("'%1' has finished downloading.", "e.g: xxx.avi has finished downloading.").arg(torrent->name()));
|
m_desktopIntegration->showNotification(tr("Download completed"), tr("'%1' has finished downloading.", "e.g: xxx.avi has finished downloading.").arg(torrent->name()));
|
||||||
});
|
});
|
||||||
connect(btSession, &BitTorrent::Session::downloadFromUrlFailed, this
|
connect(m_addTorrentManager, &AddTorrentManager::torrentAdded, this
|
||||||
, [this](const QString &url, const QString &reason)
|
, [this]([[maybe_unused]] const QString &source, const BitTorrent::Torrent *torrent)
|
||||||
{
|
{
|
||||||
m_desktopIntegration->showNotification(tr("URL download error")
|
if (isTorrentAddedNotificationsEnabled())
|
||||||
, tr("Couldn't download file at URL '%1', reason: %2.").arg(url, reason));
|
m_desktopIntegration->showNotification(tr("Torrent added"), tr("'%1' was added.", "e.g: xxx.avi was added.").arg(torrent->name()));
|
||||||
|
});
|
||||||
|
connect(m_addTorrentManager, &AddTorrentManager::addTorrentFailed, this
|
||||||
|
, [this](const QString &source, const QString &reason)
|
||||||
|
{
|
||||||
|
m_desktopIntegration->showNotification(tr("Add torrent failed")
|
||||||
|
, tr("Couldn't add torrent '%1', reason: %2.").arg(source, reason));
|
||||||
});
|
});
|
||||||
|
|
||||||
disconnect(m_desktopIntegration, &DesktopIntegration::activationRequested, this, &Application::createStartupProgressDialog);
|
disconnect(m_desktopIntegration, &DesktopIntegration::activationRequested, this, &Application::createStartupProgressDialog);
|
||||||
|
@ -983,6 +993,57 @@ void Application::createStartupProgressDialog()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Application::askRecursiveTorrentDownloadConfirmation(const BitTorrent::Torrent *torrent)
|
||||||
|
{
|
||||||
|
const auto torrentID = torrent->id();
|
||||||
|
|
||||||
|
QMessageBox *confirmBox = new QMessageBox(QMessageBox::Question, tr("Recursive download confirmation")
|
||||||
|
, tr("The torrent '%1' contains .torrent files, do you want to proceed with their downloads?").arg(torrent->name())
|
||||||
|
, (QMessageBox::Yes | QMessageBox::No | QMessageBox::NoToAll), mainWindow());
|
||||||
|
confirmBox->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
|
||||||
|
const QAbstractButton *yesButton = confirmBox->button(QMessageBox::Yes);
|
||||||
|
QAbstractButton *neverButton = confirmBox->button(QMessageBox::NoToAll);
|
||||||
|
neverButton->setText(tr("Never"));
|
||||||
|
|
||||||
|
connect(confirmBox, &QMessageBox::buttonClicked, this
|
||||||
|
, [this, torrentID, yesButton, neverButton](const QAbstractButton *button)
|
||||||
|
{
|
||||||
|
if (button == yesButton)
|
||||||
|
{
|
||||||
|
recursiveTorrentDownload(torrentID);
|
||||||
|
}
|
||||||
|
else if (button == neverButton)
|
||||||
|
{
|
||||||
|
Preferences::instance()->setRecursiveDownloadEnabled(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
confirmBox->open();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::recursiveTorrentDownload(const BitTorrent::TorrentID &torrentID)
|
||||||
|
{
|
||||||
|
const BitTorrent::Torrent *torrent = BitTorrent::Session::instance()->getTorrent(torrentID);
|
||||||
|
if (!torrent)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (const Path &torrentRelpath : asConst(torrent->filePaths()))
|
||||||
|
{
|
||||||
|
if (torrentRelpath.hasExtension(u".torrent"_s))
|
||||||
|
{
|
||||||
|
const Path torrentFullpath = torrent->savePath() / torrentRelpath;
|
||||||
|
|
||||||
|
LogMsg(tr("Recursive download .torrent file within torrent. Source torrent: \"%1\". File: \"%2\"")
|
||||||
|
.arg(torrent->name(), torrentFullpath.toString()));
|
||||||
|
|
||||||
|
BitTorrent::AddTorrentParams params;
|
||||||
|
// Passing the save path along to the sub torrent file
|
||||||
|
params.savePath = torrent->savePath();
|
||||||
|
addTorrentManager()->addTorrent(torrentFullpath.data(), params, AddTorrentOption::SkipDialog);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef Q_OS_MACOS
|
#ifdef Q_OS_MACOS
|
||||||
bool Application::event(QEvent *ev)
|
bool Application::event(QEvent *ev)
|
||||||
{
|
{
|
||||||
|
@ -1262,6 +1323,7 @@ void Application::cleanup()
|
||||||
delete RSS::Session::instance();
|
delete RSS::Session::instance();
|
||||||
|
|
||||||
TorrentFilesWatcher::freeInstance();
|
TorrentFilesWatcher::freeInstance();
|
||||||
|
delete m_addTorrentManager;
|
||||||
BitTorrent::Session::freeInstance();
|
BitTorrent::Session::freeInstance();
|
||||||
Net::GeoIPManager::freeInstance();
|
Net::GeoIPManager::freeInstance();
|
||||||
Net::DownloadManager::freeInstance();
|
Net::DownloadManager::freeInstance();
|
||||||
|
@ -1296,3 +1358,8 @@ void Application::cleanup()
|
||||||
Utils::Misc::shutdownComputer(m_shutdownAct);
|
Utils::Misc::shutdownComputer(m_shutdownAct);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AddTorrentManagerImpl *Application::addTorrentManager() const
|
||||||
|
{
|
||||||
|
return m_addTorrentManager;
|
||||||
|
}
|
||||||
|
|
|
@ -72,6 +72,7 @@ class QProgressDialog;
|
||||||
class DesktopIntegration;
|
class DesktopIntegration;
|
||||||
class MainWindow;
|
class MainWindow;
|
||||||
|
|
||||||
|
using AddTorrentManagerImpl = GUIAddTorrentManager;
|
||||||
using BaseApplication = QApplication;
|
using BaseApplication = QApplication;
|
||||||
using BaseIApplication = IGUIApplication;
|
using BaseIApplication = IGUIApplication;
|
||||||
|
|
||||||
|
@ -79,6 +80,7 @@ using BaseIApplication = IGUIApplication;
|
||||||
class QSessionManager;
|
class QSessionManager;
|
||||||
#endif
|
#endif
|
||||||
#else // DISABLE_GUI
|
#else // DISABLE_GUI
|
||||||
|
using AddTorrentManagerImpl = AddTorrentManager;
|
||||||
using BaseApplication = QCoreApplication;
|
using BaseApplication = QCoreApplication;
|
||||||
using BaseIApplication = IApplication;
|
using BaseIApplication = IApplication;
|
||||||
#endif // DISABLE_GUI
|
#endif // DISABLE_GUI
|
||||||
|
@ -149,6 +151,8 @@ private slots:
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
AddTorrentManagerImpl *addTorrentManager() const override;
|
||||||
|
|
||||||
void initializeTranslation();
|
void initializeTranslation();
|
||||||
void processParams(const QBtCommandLineParameters ¶ms);
|
void processParams(const QBtCommandLineParameters ¶ms);
|
||||||
void runExternalProgram(const QString &programTemplate, const BitTorrent::Torrent *torrent) const;
|
void runExternalProgram(const QString &programTemplate, const BitTorrent::Torrent *torrent) const;
|
||||||
|
@ -168,6 +172,8 @@ private:
|
||||||
#ifdef Q_OS_MACOS
|
#ifdef Q_OS_MACOS
|
||||||
bool event(QEvent *) override;
|
bool event(QEvent *) override;
|
||||||
#endif
|
#endif
|
||||||
|
void askRecursiveTorrentDownloadConfirmation(const BitTorrent::Torrent *torrent);
|
||||||
|
void recursiveTorrentDownload(const BitTorrent::TorrentID &torrentID);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
ApplicationInstanceManager *m_instanceManager = nullptr;
|
ApplicationInstanceManager *m_instanceManager = nullptr;
|
||||||
|
@ -197,6 +203,8 @@ private:
|
||||||
SettingValue<MemoryPriority> m_processMemoryPriority;
|
SettingValue<MemoryPriority> m_processMemoryPriority;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
AddTorrentManagerImpl *m_addTorrentManager = nullptr;
|
||||||
|
|
||||||
#ifndef DISABLE_GUI
|
#ifndef DISABLE_GUI
|
||||||
SettingValue<WindowState> m_startUpWindowState;
|
SettingValue<WindowState> m_startUpWindowState;
|
||||||
SettingValue<bool> m_storeNotificationTorrentAdded;
|
SettingValue<bool> m_storeNotificationTorrentAdded;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
add_library(qbt_base STATIC
|
add_library(qbt_base STATIC
|
||||||
# headers
|
# headers
|
||||||
3rdparty/expected.hpp
|
3rdparty/expected.hpp
|
||||||
|
addtorrentmanager.h
|
||||||
algorithm.h
|
algorithm.h
|
||||||
applicationcomponent.h
|
applicationcomponent.h
|
||||||
asyncfilestorage.h
|
asyncfilestorage.h
|
||||||
|
@ -106,6 +107,7 @@ add_library(qbt_base STATIC
|
||||||
version.h
|
version.h
|
||||||
|
|
||||||
# sources
|
# sources
|
||||||
|
addtorrentmanager.cpp
|
||||||
applicationcomponent.cpp
|
applicationcomponent.cpp
|
||||||
asyncfilestorage.cpp
|
asyncfilestorage.cpp
|
||||||
bittorrent/abstractfilestorage.cpp
|
bittorrent/abstractfilestorage.cpp
|
||||||
|
|
217
src/base/addtorrentmanager.cpp
Normal file
217
src/base/addtorrentmanager.cpp
Normal file
|
@ -0,0 +1,217 @@
|
||||||
|
/*
|
||||||
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
|
* Copyright (C) 2015-2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||||
|
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*
|
||||||
|
* In addition, as a special exception, the copyright holders give permission to
|
||||||
|
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||||
|
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||||
|
* and distribute the linked executables. You must obey the GNU General Public
|
||||||
|
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||||
|
* modify file(s), you may extend this exception to your version of the file(s),
|
||||||
|
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||||
|
* exception statement from your version.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "addtorrentmanager.h"
|
||||||
|
|
||||||
|
#include "base/bittorrent/infohash.h"
|
||||||
|
#include "base/bittorrent/session.h"
|
||||||
|
#include "base/bittorrent/torrentdescriptor.h"
|
||||||
|
#include "base/logger.h"
|
||||||
|
#include "base/net/downloadmanager.h"
|
||||||
|
#include "base/preferences.h"
|
||||||
|
|
||||||
|
AddTorrentManager::AddTorrentManager(IApplication *app, BitTorrent::Session *btSession, QObject *parent)
|
||||||
|
: ApplicationComponent(app, parent)
|
||||||
|
, m_btSession {btSession}
|
||||||
|
{
|
||||||
|
Q_ASSERT(btSession);
|
||||||
|
connect(btSession, &BitTorrent::Session::torrentAdded, this, &AddTorrentManager::onSessionTorrentAdded);
|
||||||
|
connect(btSession, &BitTorrent::Session::addTorrentFailed, this, &AddTorrentManager::onSessionAddTorrentFailed);
|
||||||
|
}
|
||||||
|
|
||||||
|
BitTorrent::Session *AddTorrentManager::btSession() const
|
||||||
|
{
|
||||||
|
return m_btSession;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AddTorrentManager::addTorrent(const QString &source, const BitTorrent::AddTorrentParams ¶ms)
|
||||||
|
{
|
||||||
|
// `source`: .torrent file path, magnet URI or URL
|
||||||
|
|
||||||
|
if (source.isEmpty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (Net::DownloadManager::hasSupportedScheme(source))
|
||||||
|
{
|
||||||
|
LogMsg(tr("Downloading torrent... Source: \"%1\"").arg(source));
|
||||||
|
const auto *pref = Preferences::instance();
|
||||||
|
// Launch downloader
|
||||||
|
Net::DownloadManager::instance()->download(Net::DownloadRequest(source).limit(pref->getTorrentFileSizeLimit())
|
||||||
|
, pref->useProxyForGeneralPurposes(), this, &AddTorrentManager::onDownloadFinished);
|
||||||
|
m_downloadedTorrents[source] = params;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (const auto parseResult = BitTorrent::TorrentDescriptor::parse(source))
|
||||||
|
{
|
||||||
|
return processTorrent(source, parseResult.value(), params);
|
||||||
|
}
|
||||||
|
else if (source.startsWith(u"magnet:", Qt::CaseInsensitive))
|
||||||
|
{
|
||||||
|
handleAddTorrentFailed(source, parseResult.error());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Path decodedPath {source.startsWith(u"file://", Qt::CaseInsensitive)
|
||||||
|
? QUrl::fromEncoded(source.toLocal8Bit()).toLocalFile() : source};
|
||||||
|
auto torrentFileGuard = std::make_shared<TorrentFileGuard>(decodedPath);
|
||||||
|
if (const auto loadResult = BitTorrent::TorrentDescriptor::loadFromFile(decodedPath))
|
||||||
|
{
|
||||||
|
setTorrentFileGuard(source, torrentFileGuard);
|
||||||
|
return processTorrent(source, loadResult.value(), params);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
handleAddTorrentFailed(source, loadResult.error());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AddTorrentManager::addTorrentToSession(const QString &source, const BitTorrent::TorrentDescriptor &torrentDescr
|
||||||
|
, const BitTorrent::AddTorrentParams &addTorrentParams)
|
||||||
|
{
|
||||||
|
const bool result = btSession()->addTorrent(torrentDescr, addTorrentParams);
|
||||||
|
if (result)
|
||||||
|
m_sourcesByInfoHash[torrentDescr.infoHash()] = source;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddTorrentManager::onDownloadFinished(const Net::DownloadResult &result)
|
||||||
|
{
|
||||||
|
const QString &source = result.url;
|
||||||
|
const BitTorrent::AddTorrentParams addTorrentParams = m_downloadedTorrents.take(source);
|
||||||
|
|
||||||
|
switch (result.status)
|
||||||
|
{
|
||||||
|
case Net::DownloadStatus::Success:
|
||||||
|
if (const auto loadResult = BitTorrent::TorrentDescriptor::load(result.data))
|
||||||
|
processTorrent(source, loadResult.value(), addTorrentParams);
|
||||||
|
else
|
||||||
|
handleAddTorrentFailed(source, loadResult.error());
|
||||||
|
break;
|
||||||
|
case Net::DownloadStatus::RedirectedToMagnet:
|
||||||
|
if (const auto parseResult = BitTorrent::TorrentDescriptor::parse(result.magnetURI))
|
||||||
|
processTorrent(source, parseResult.value(), addTorrentParams);
|
||||||
|
else
|
||||||
|
handleAddTorrentFailed(source, parseResult.error());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
handleAddTorrentFailed(source, result.errorString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddTorrentManager::onSessionTorrentAdded(BitTorrent::Torrent *torrent)
|
||||||
|
{
|
||||||
|
if (const QString source = m_sourcesByInfoHash.take(torrent->infoHash()); !source.isEmpty())
|
||||||
|
{
|
||||||
|
auto torrentFileGuard = m_guardedTorrentFiles.take(source);
|
||||||
|
if (torrentFileGuard)
|
||||||
|
torrentFileGuard->markAsAddedToSession();
|
||||||
|
emit torrentAdded(source, torrent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddTorrentManager::onSessionAddTorrentFailed(const BitTorrent::InfoHash &infoHash, const QString &reason)
|
||||||
|
{
|
||||||
|
if (const QString source = m_sourcesByInfoHash.take(infoHash); !source.isEmpty())
|
||||||
|
{
|
||||||
|
auto torrentFileGuard = m_guardedTorrentFiles.take(source);
|
||||||
|
if (torrentFileGuard)
|
||||||
|
torrentFileGuard->setAutoRemove(false);
|
||||||
|
emit addTorrentFailed(source, reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddTorrentManager::handleAddTorrentFailed(const QString &source, const QString &reason)
|
||||||
|
{
|
||||||
|
LogMsg(tr("Failed to add torrent. Source: \"%1\". Reason: \"%2\"").arg(source, reason), Log::WARNING);
|
||||||
|
emit addTorrentFailed(source, reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddTorrentManager::handleDuplicateTorrent(const QString &source, BitTorrent::Torrent *torrent, const QString &message)
|
||||||
|
{
|
||||||
|
LogMsg(tr("Detected an attempt to add a duplicate torrent. Source: %1. Existing torrent: %2. Result: %3")
|
||||||
|
.arg(source, torrent->name(), message));
|
||||||
|
emit addTorrentFailed(source, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddTorrentManager::setTorrentFileGuard(const QString &source, std::shared_ptr<TorrentFileGuard> torrentFileGuard)
|
||||||
|
{
|
||||||
|
m_guardedTorrentFiles.emplace(source, std::move(torrentFileGuard));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddTorrentManager::releaseTorrentFileGuard(const QString &source)
|
||||||
|
{
|
||||||
|
auto torrentFileGuard = m_guardedTorrentFiles.take(source);
|
||||||
|
if (torrentFileGuard)
|
||||||
|
torrentFileGuard->setAutoRemove(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AddTorrentManager::processTorrent(const QString &source, const BitTorrent::TorrentDescriptor &torrentDescr
|
||||||
|
, const BitTorrent::AddTorrentParams &addTorrentParams)
|
||||||
|
{
|
||||||
|
const BitTorrent::InfoHash infoHash = torrentDescr.infoHash();
|
||||||
|
|
||||||
|
if (BitTorrent::Torrent *torrent = btSession()->findTorrent(infoHash))
|
||||||
|
{
|
||||||
|
// a duplicate torrent is being added
|
||||||
|
|
||||||
|
const bool hasMetadata = torrentDescr.info().has_value();
|
||||||
|
if (hasMetadata)
|
||||||
|
{
|
||||||
|
// Trying to set metadata to existing torrent in case if it has none
|
||||||
|
torrent->setMetadata(*torrentDescr.info());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!btSession()->isMergeTrackersEnabled())
|
||||||
|
{
|
||||||
|
handleDuplicateTorrent(source, torrent, tr("Merging of trackers is disabled"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool isPrivate = torrent->isPrivate() || (hasMetadata && torrentDescr.info()->isPrivate());
|
||||||
|
if (isPrivate)
|
||||||
|
{
|
||||||
|
handleDuplicateTorrent(source, torrent, tr("Trackers cannot be merged because it is a private torrent"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// merge trackers and web seeds
|
||||||
|
torrent->addTrackers(torrentDescr.trackers());
|
||||||
|
torrent->addUrlSeeds(torrentDescr.urlSeeds());
|
||||||
|
|
||||||
|
handleDuplicateTorrent(source, torrent, tr("Trackers are merged from new source"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return addTorrentToSession(source, torrentDescr, addTorrentParams);
|
||||||
|
}
|
86
src/base/addtorrentmanager.h
Normal file
86
src/base/addtorrentmanager.h
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
/*
|
||||||
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
|
* Copyright (C) 2015-2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||||
|
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*
|
||||||
|
* In addition, as a special exception, the copyright holders give permission to
|
||||||
|
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||||
|
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||||
|
* and distribute the linked executables. You must obey the GNU General Public
|
||||||
|
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||||
|
* modify file(s), you may extend this exception to your version of the file(s),
|
||||||
|
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||||
|
* exception statement from your version.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include <QHash>
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
#include "base/applicationcomponent.h"
|
||||||
|
#include "base/bittorrent/addtorrentparams.h"
|
||||||
|
#include "base/torrentfileguard.h"
|
||||||
|
|
||||||
|
namespace Net
|
||||||
|
{
|
||||||
|
struct DownloadResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace BitTorrent
|
||||||
|
{
|
||||||
|
class Session;
|
||||||
|
class TorrentDescriptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
class AddTorrentManager : public ApplicationComponent<QObject>
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_DISABLE_COPY_MOVE(AddTorrentManager)
|
||||||
|
|
||||||
|
public:
|
||||||
|
AddTorrentManager(IApplication *app, BitTorrent::Session *btSession, QObject *parent = nullptr);
|
||||||
|
|
||||||
|
BitTorrent::Session *btSession() const;
|
||||||
|
bool addTorrent(const QString &source, const BitTorrent::AddTorrentParams ¶ms = {});
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void torrentAdded(const QString &source, BitTorrent::Torrent *torrent);
|
||||||
|
void addTorrentFailed(const QString &source, const QString &reason);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool addTorrentToSession(const QString &source, const BitTorrent::TorrentDescriptor &torrentDescr
|
||||||
|
, const BitTorrent::AddTorrentParams &addTorrentParams);
|
||||||
|
void handleAddTorrentFailed(const QString &source, const QString &reason);
|
||||||
|
void handleDuplicateTorrent(const QString &source, BitTorrent::Torrent *torrent, const QString &message);
|
||||||
|
void setTorrentFileGuard(const QString &source, std::shared_ptr<TorrentFileGuard> torrentFileGuard);
|
||||||
|
void releaseTorrentFileGuard(const QString &source);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void onDownloadFinished(const Net::DownloadResult &result);
|
||||||
|
void onSessionTorrentAdded(BitTorrent::Torrent *torrent);
|
||||||
|
void onSessionAddTorrentFailed(const BitTorrent::InfoHash &infoHash, const QString &reason);
|
||||||
|
bool processTorrent(const QString &source, const BitTorrent::TorrentDescriptor &torrentDescr
|
||||||
|
, const BitTorrent::AddTorrentParams &addTorrentParams);
|
||||||
|
|
||||||
|
BitTorrent::Session *m_btSession = nullptr;
|
||||||
|
QHash<QString, BitTorrent::AddTorrentParams> m_downloadedTorrents;
|
||||||
|
QHash<BitTorrent::InfoHash, QString> m_sourcesByInfoHash;
|
||||||
|
QHash<QString, std::shared_ptr<TorrentFileGuard>> m_guardedTorrentFiles;
|
||||||
|
};
|
|
@ -28,12 +28,12 @@
|
||||||
|
|
||||||
#include "applicationcomponent.h"
|
#include "applicationcomponent.h"
|
||||||
|
|
||||||
ApplicationComponent::ApplicationComponent(IApplication *app)
|
ApplicationComponentBase::ApplicationComponentBase(IApplication *app)
|
||||||
: m_app {app}
|
: m_app {app}
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
IApplication *ApplicationComponent::app() const
|
IApplication *ApplicationComponentBase::app() const
|
||||||
{
|
{
|
||||||
return m_app;
|
return m_app;
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,20 +28,41 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <concepts>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include <QtClassHelperMacros>
|
#include <QtClassHelperMacros>
|
||||||
|
|
||||||
#include "interfaces/iapplication.h"
|
class IApplication;
|
||||||
|
|
||||||
class ApplicationComponent
|
class ApplicationComponentBase
|
||||||
{
|
{
|
||||||
Q_DISABLE_COPY_MOVE(ApplicationComponent)
|
Q_DISABLE_COPY_MOVE(ApplicationComponentBase)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ApplicationComponent(IApplication *app);
|
virtual ~ApplicationComponentBase() = default;
|
||||||
virtual ~ApplicationComponent() = default;
|
|
||||||
|
|
||||||
virtual IApplication *app() const;
|
IApplication *app() const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
explicit ApplicationComponentBase(IApplication *app);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
IApplication *m_app = nullptr;
|
IApplication *m_app = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
concept IsApplicationComponent = std::derived_from<T, ApplicationComponentBase>;
|
||||||
|
|
||||||
|
template <typename Base>
|
||||||
|
requires (!IsApplicationComponent<Base>)
|
||||||
|
class ApplicationComponent : public Base, public ApplicationComponentBase
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
template <typename... Args>
|
||||||
|
explicit ApplicationComponent(IApplication *app, Args&&... args)
|
||||||
|
: Base(std::forward<Args>(args)...)
|
||||||
|
, ApplicationComponentBase(app)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -116,6 +116,11 @@ std::size_t BitTorrent::qHash(const BitTorrent::TorrentID &key, const std::size_
|
||||||
return ::qHash(static_cast<TorrentID::BaseType>(key), seed);
|
return ::qHash(static_cast<TorrentID::BaseType>(key), seed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::size_t BitTorrent::qHash(const InfoHash &key, const std::size_t seed)
|
||||||
|
{
|
||||||
|
return qHashMulti(seed, key.v1(), key.v2());
|
||||||
|
}
|
||||||
|
|
||||||
bool BitTorrent::operator==(const BitTorrent::InfoHash &left, const BitTorrent::InfoHash &right)
|
bool BitTorrent::operator==(const BitTorrent::InfoHash &left, const BitTorrent::InfoHash &right)
|
||||||
{
|
{
|
||||||
return (static_cast<InfoHash::WrappedType>(left) == static_cast<InfoHash::WrappedType>(right));
|
return (static_cast<InfoHash::WrappedType>(left) == static_cast<InfoHash::WrappedType>(right));
|
||||||
|
|
|
@ -87,6 +87,7 @@ namespace BitTorrent
|
||||||
};
|
};
|
||||||
|
|
||||||
std::size_t qHash(const TorrentID &key, std::size_t seed = 0);
|
std::size_t qHash(const TorrentID &key, std::size_t seed = 0);
|
||||||
|
std::size_t qHash(const InfoHash &key, std::size_t seed = 0);
|
||||||
|
|
||||||
bool operator==(const InfoHash &left, const InfoHash &right);
|
bool operator==(const InfoHash &left, const InfoHash &right);
|
||||||
}
|
}
|
||||||
|
|
|
@ -440,13 +440,11 @@ namespace BitTorrent
|
||||||
virtual void banIP(const QString &ip) = 0;
|
virtual void banIP(const QString &ip) = 0;
|
||||||
|
|
||||||
virtual bool isKnownTorrent(const InfoHash &infoHash) const = 0;
|
virtual bool isKnownTorrent(const InfoHash &infoHash) const = 0;
|
||||||
virtual bool addTorrent(const QString &source, const AddTorrentParams ¶ms = {}) = 0;
|
|
||||||
virtual bool addTorrent(const TorrentDescriptor &torrentDescr, const AddTorrentParams ¶ms = {}) = 0;
|
virtual bool addTorrent(const TorrentDescriptor &torrentDescr, const AddTorrentParams ¶ms = {}) = 0;
|
||||||
virtual bool deleteTorrent(const TorrentID &id, DeleteOption deleteOption = DeleteOption::DeleteTorrent) = 0;
|
virtual bool deleteTorrent(const TorrentID &id, DeleteOption deleteOption = DeleteOption::DeleteTorrent) = 0;
|
||||||
virtual bool downloadMetadata(const TorrentDescriptor &torrentDescr) = 0;
|
virtual bool downloadMetadata(const TorrentDescriptor &torrentDescr) = 0;
|
||||||
virtual bool cancelDownloadMetadata(const TorrentID &id) = 0;
|
virtual bool cancelDownloadMetadata(const TorrentID &id) = 0;
|
||||||
|
|
||||||
virtual void recursiveTorrentDownload(const TorrentID &id) = 0;
|
|
||||||
virtual void increaseTorrentsQueuePos(const QVector<TorrentID> &ids) = 0;
|
virtual void increaseTorrentsQueuePos(const QVector<TorrentID> &ids) = 0;
|
||||||
virtual void decreaseTorrentsQueuePos(const QVector<TorrentID> &ids) = 0;
|
virtual void decreaseTorrentsQueuePos(const QVector<TorrentID> &ids) = 0;
|
||||||
virtual void topTorrentsQueuePos(const QVector<TorrentID> &ids) = 0;
|
virtual void topTorrentsQueuePos(const QVector<TorrentID> &ids) = 0;
|
||||||
|
@ -454,17 +452,15 @@ namespace BitTorrent
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void startupProgressUpdated(int progress);
|
void startupProgressUpdated(int progress);
|
||||||
|
void addTorrentFailed(const InfoHash &infoHash, const QString &reason);
|
||||||
void allTorrentsFinished();
|
void allTorrentsFinished();
|
||||||
void categoryAdded(const QString &categoryName);
|
void categoryAdded(const QString &categoryName);
|
||||||
void categoryRemoved(const QString &categoryName);
|
void categoryRemoved(const QString &categoryName);
|
||||||
void categoryOptionsChanged(const QString &categoryName);
|
void categoryOptionsChanged(const QString &categoryName);
|
||||||
void downloadFromUrlFailed(const QString &url, const QString &reason);
|
|
||||||
void downloadFromUrlFinished(const QString &url);
|
|
||||||
void fullDiskError(Torrent *torrent, const QString &msg);
|
void fullDiskError(Torrent *torrent, const QString &msg);
|
||||||
void IPFilterParsed(bool error, int ruleCount);
|
void IPFilterParsed(bool error, int ruleCount);
|
||||||
void loadTorrentFailed(const QString &error);
|
void loadTorrentFailed(const QString &error);
|
||||||
void metadataDownloaded(const TorrentInfo &info);
|
void metadataDownloaded(const TorrentInfo &info);
|
||||||
void recursiveTorrentDownloadPossible(Torrent *torrent);
|
|
||||||
void restored();
|
void restored();
|
||||||
void speedLimitModeChanged(bool alternative);
|
void speedLimitModeChanged(bool alternative);
|
||||||
void statsUpdated();
|
void statsUpdated();
|
||||||
|
|
|
@ -77,11 +77,9 @@
|
||||||
#include "base/algorithm.h"
|
#include "base/algorithm.h"
|
||||||
#include "base/global.h"
|
#include "base/global.h"
|
||||||
#include "base/logger.h"
|
#include "base/logger.h"
|
||||||
#include "base/net/downloadmanager.h"
|
|
||||||
#include "base/net/proxyconfigurationmanager.h"
|
#include "base/net/proxyconfigurationmanager.h"
|
||||||
#include "base/preferences.h"
|
#include "base/preferences.h"
|
||||||
#include "base/profile.h"
|
#include "base/profile.h"
|
||||||
#include "base/torrentfileguard.h"
|
|
||||||
#include "base/torrentfilter.h"
|
#include "base/torrentfilter.h"
|
||||||
#include "base/unicodestrings.h"
|
#include "base/unicodestrings.h"
|
||||||
#include "base/utils/bytearray.h"
|
#include "base/utils/bytearray.h"
|
||||||
|
@ -2254,35 +2252,6 @@ void SessionImpl::processShareLimits()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add to BitTorrent session the downloaded torrent file
|
|
||||||
void SessionImpl::handleDownloadFinished(const Net::DownloadResult &result)
|
|
||||||
{
|
|
||||||
switch (result.status)
|
|
||||||
{
|
|
||||||
case Net::DownloadStatus::Success:
|
|
||||||
emit downloadFromUrlFinished(result.url);
|
|
||||||
if (const auto loadResult = TorrentDescriptor::load(result.data))
|
|
||||||
addTorrent(loadResult.value(), m_downloadedTorrents.take(result.url));
|
|
||||||
else
|
|
||||||
LogMsg(tr("Failed to load torrent. Reason: \"%1\"").arg(loadResult.error()), Log::WARNING);
|
|
||||||
break;
|
|
||||||
case Net::DownloadStatus::RedirectedToMagnet:
|
|
||||||
emit downloadFromUrlFinished(result.url);
|
|
||||||
if (const auto parseResult = TorrentDescriptor::parse(result.magnetURI))
|
|
||||||
{
|
|
||||||
addTorrent(parseResult.value(), m_downloadedTorrents.take(result.url));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
LogMsg(tr("Failed to load torrent. The request was redirected to invalid Magnet URI. Reason: \"%1\"")
|
|
||||||
.arg(parseResult.error()), Log::WARNING);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
emit downloadFromUrlFailed(result.url, result.errorString);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SessionImpl::fileSearchFinished(const TorrentID &id, const Path &savePath, const PathList &fileNames)
|
void SessionImpl::fileSearchFinished(const TorrentID &id, const Path &savePath, const PathList &fileNames)
|
||||||
{
|
{
|
||||||
TorrentImpl *torrent = m_torrents.value(id);
|
TorrentImpl *torrent = m_torrents.value(id);
|
||||||
|
@ -2593,40 +2562,6 @@ qsizetype SessionImpl::torrentsCount() const
|
||||||
return m_torrents.size();
|
return m_torrents.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SessionImpl::addTorrent(const QString &source, const AddTorrentParams ¶ms)
|
|
||||||
{
|
|
||||||
// `source`: .torrent file path/url or magnet uri
|
|
||||||
|
|
||||||
if (!isRestored())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (Net::DownloadManager::hasSupportedScheme(source))
|
|
||||||
{
|
|
||||||
LogMsg(tr("Downloading torrent, please wait... Source: \"%1\"").arg(source));
|
|
||||||
const auto *pref = Preferences::instance();
|
|
||||||
// Launch downloader
|
|
||||||
Net::DownloadManager::instance()->download(Net::DownloadRequest(source).limit(pref->getTorrentFileSizeLimit())
|
|
||||||
, pref->useProxyForGeneralPurposes(), this, &SessionImpl::handleDownloadFinished);
|
|
||||||
m_downloadedTorrents[source] = params;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (const auto parseResult = TorrentDescriptor::parse(source))
|
|
||||||
return addTorrent(parseResult.value(), params);
|
|
||||||
|
|
||||||
const Path path {source};
|
|
||||||
TorrentFileGuard guard {path};
|
|
||||||
const auto loadResult = TorrentDescriptor::loadFromFile(path);
|
|
||||||
if (!loadResult)
|
|
||||||
{
|
|
||||||
LogMsg(tr("Failed to load torrent. Source: \"%1\". Reason: \"%2\"").arg(source, loadResult.error()), Log::WARNING);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
guard.markAsAddedToSession();
|
|
||||||
return addTorrent(loadResult.value(), params);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SessionImpl::addTorrent(const TorrentDescriptor &torrentDescr, const AddTorrentParams ¶ms)
|
bool SessionImpl::addTorrent(const TorrentDescriptor &torrentDescr, const AddTorrentParams ¶ms)
|
||||||
{
|
{
|
||||||
if (!isRestored())
|
if (!isRestored())
|
||||||
|
@ -2713,35 +2648,8 @@ bool SessionImpl::addTorrent_impl(const TorrentDescriptor &source, const AddTorr
|
||||||
if (m_loadingTorrents.contains(id) || (infoHash.isHybrid() && m_loadingTorrents.contains(altID)))
|
if (m_loadingTorrents.contains(id) || (infoHash.isHybrid() && m_loadingTorrents.contains(altID)))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (Torrent *torrent = findTorrent(infoHash))
|
if (findTorrent(infoHash))
|
||||||
{
|
|
||||||
// a duplicate torrent is being added
|
|
||||||
if (hasMetadata)
|
|
||||||
{
|
|
||||||
// Trying to set metadata to existing torrent in case if it has none
|
|
||||||
torrent->setMetadata(*source.info());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isMergeTrackersEnabled())
|
|
||||||
{
|
|
||||||
LogMsg(tr("Detected an attempt to add a duplicate torrent. Merging of trackers is disabled. Torrent: %1").arg(torrent->name()));
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
const bool isPrivate = torrent->isPrivate() || (hasMetadata && source.info()->isPrivate());
|
|
||||||
if (isPrivate)
|
|
||||||
{
|
|
||||||
LogMsg(tr("Detected an attempt to add a duplicate torrent. Trackers cannot be merged because it is a private torrent. Torrent: %1").arg(torrent->name()));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// merge trackers and web seeds
|
|
||||||
torrent->addTrackers(source.trackers());
|
|
||||||
torrent->addUrlSeeds(source.urlSeeds());
|
|
||||||
|
|
||||||
LogMsg(tr("Detected an attempt to add a duplicate torrent. Trackers are merged from new source. Torrent: %1").arg(torrent->name()));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// It looks illogical that we don't just use an existing handle,
|
// It looks illogical that we don't just use an existing handle,
|
||||||
// but as previous experience has shown, it actually creates unnecessary
|
// but as previous experience has shown, it actually creates unnecessary
|
||||||
|
@ -4961,16 +4869,6 @@ void SessionImpl::handleTorrentFinished(TorrentImpl *const torrent)
|
||||||
if (const Path exportPath = finishedTorrentExportDirectory(); !exportPath.isEmpty())
|
if (const Path exportPath = finishedTorrentExportDirectory(); !exportPath.isEmpty())
|
||||||
exportTorrentFile(torrent, exportPath);
|
exportTorrentFile(torrent, exportPath);
|
||||||
|
|
||||||
// Check whether it contains .torrent files
|
|
||||||
for (const Path &torrentRelpath : asConst(torrent->filePaths()))
|
|
||||||
{
|
|
||||||
if (torrentRelpath.hasExtension(u".torrent"_s))
|
|
||||||
{
|
|
||||||
emit recursiveTorrentDownloadPossible(torrent);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const bool hasUnfinishedTorrents = std::any_of(m_torrents.cbegin(), m_torrents.cend(), [](const TorrentImpl *torrent)
|
const bool hasUnfinishedTorrents = std::any_of(m_torrents.cbegin(), m_torrents.cend(), [](const TorrentImpl *torrent)
|
||||||
{
|
{
|
||||||
return !(torrent->isFinished() || torrent->isPaused() || torrent->isErrored());
|
return !(torrent->isFinished() || torrent->isPaused() || torrent->isErrored());
|
||||||
|
@ -5263,37 +5161,6 @@ void SessionImpl::disableIPFilter()
|
||||||
m_nativeSession->set_ip_filter(filter);
|
m_nativeSession->set_ip_filter(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SessionImpl::recursiveTorrentDownload(const TorrentID &id)
|
|
||||||
{
|
|
||||||
const TorrentImpl *torrent = m_torrents.value(id);
|
|
||||||
if (!torrent)
|
|
||||||
return;
|
|
||||||
|
|
||||||
for (const Path &torrentRelpath : asConst(torrent->filePaths()))
|
|
||||||
{
|
|
||||||
if (torrentRelpath.hasExtension(u".torrent"_s))
|
|
||||||
{
|
|
||||||
const Path torrentFullpath = torrent->savePath() / torrentRelpath;
|
|
||||||
|
|
||||||
LogMsg(tr("Recursive download .torrent file within torrent. Source torrent: \"%1\". File: \"%2\"")
|
|
||||||
.arg(torrent->name(), torrentFullpath.toString()));
|
|
||||||
|
|
||||||
AddTorrentParams params;
|
|
||||||
// Passing the save path along to the sub torrent file
|
|
||||||
params.savePath = torrent->savePath();
|
|
||||||
if (const auto loadResult = TorrentDescriptor::loadFromFile(torrentFullpath))
|
|
||||||
{
|
|
||||||
addTorrent(loadResult.value(), params);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
LogMsg(tr("Failed to load .torrent file within torrent. Source torrent: \"%1\". File: \"%2\". Error: \"%3\"")
|
|
||||||
.arg(torrent->name(), torrentFullpath.toString(), loadResult.error()), Log::WARNING);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const SessionStatus &SessionImpl::status() const
|
const SessionStatus &SessionImpl::status() const
|
||||||
{
|
{
|
||||||
return m_status;
|
return m_status;
|
||||||
|
@ -5406,6 +5273,7 @@ void SessionImpl::handleAddTorrentAlerts(const std::vector<lt::alert *> &alerts)
|
||||||
if (const auto loadingTorrentsIter = m_loadingTorrents.find(TorrentID::fromInfoHash(infoHash))
|
if (const auto loadingTorrentsIter = m_loadingTorrents.find(TorrentID::fromInfoHash(infoHash))
|
||||||
; loadingTorrentsIter != m_loadingTorrents.end())
|
; loadingTorrentsIter != m_loadingTorrents.end())
|
||||||
{
|
{
|
||||||
|
emit addTorrentFailed(infoHash, msg);
|
||||||
m_loadingTorrents.erase(loadingTorrentsIter);
|
m_loadingTorrents.erase(loadingTorrentsIter);
|
||||||
}
|
}
|
||||||
else if (const auto downloadedMetadataIter = m_downloadedMetadata.find(TorrentID::fromInfoHash(infoHash))
|
else if (const auto downloadedMetadataIter = m_downloadedMetadata.find(TorrentID::fromInfoHash(infoHash))
|
||||||
|
|
|
@ -67,11 +67,6 @@ class FileSearcher;
|
||||||
class FilterParserThread;
|
class FilterParserThread;
|
||||||
class NativeSessionExtension;
|
class NativeSessionExtension;
|
||||||
|
|
||||||
namespace Net
|
|
||||||
{
|
|
||||||
struct DownloadResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace BitTorrent
|
namespace BitTorrent
|
||||||
{
|
{
|
||||||
class InfoHash;
|
class InfoHash;
|
||||||
|
@ -414,13 +409,11 @@ namespace BitTorrent
|
||||||
void banIP(const QString &ip) override;
|
void banIP(const QString &ip) override;
|
||||||
|
|
||||||
bool isKnownTorrent(const InfoHash &infoHash) const override;
|
bool isKnownTorrent(const InfoHash &infoHash) const override;
|
||||||
bool addTorrent(const QString &source, const AddTorrentParams ¶ms = {}) override;
|
|
||||||
bool addTorrent(const TorrentDescriptor &torrentDescr, const AddTorrentParams ¶ms = {}) override;
|
bool addTorrent(const TorrentDescriptor &torrentDescr, const AddTorrentParams ¶ms = {}) override;
|
||||||
bool deleteTorrent(const TorrentID &id, DeleteOption deleteOption = DeleteTorrent) override;
|
bool deleteTorrent(const TorrentID &id, DeleteOption deleteOption = DeleteTorrent) override;
|
||||||
bool downloadMetadata(const TorrentDescriptor &torrentDescr) override;
|
bool downloadMetadata(const TorrentDescriptor &torrentDescr) override;
|
||||||
bool cancelDownloadMetadata(const TorrentID &id) override;
|
bool cancelDownloadMetadata(const TorrentID &id) override;
|
||||||
|
|
||||||
void recursiveTorrentDownload(const TorrentID &id) override;
|
|
||||||
void increaseTorrentsQueuePos(const QVector<TorrentID> &ids) override;
|
void increaseTorrentsQueuePos(const QVector<TorrentID> &ids) override;
|
||||||
void decreaseTorrentsQueuePos(const QVector<TorrentID> &ids) override;
|
void decreaseTorrentsQueuePos(const QVector<TorrentID> &ids) override;
|
||||||
void topTorrentsQueuePos(const QVector<TorrentID> &ids) override;
|
void topTorrentsQueuePos(const QVector<TorrentID> &ids) override;
|
||||||
|
@ -477,7 +470,6 @@ namespace BitTorrent
|
||||||
void generateResumeData();
|
void generateResumeData();
|
||||||
void handleIPFilterParsed(int ruleCount);
|
void handleIPFilterParsed(int ruleCount);
|
||||||
void handleIPFilterError();
|
void handleIPFilterError();
|
||||||
void handleDownloadFinished(const Net::DownloadResult &result);
|
|
||||||
void fileSearchFinished(const TorrentID &id, const Path &savePath, const PathList &fileNames);
|
void fileSearchFinished(const TorrentID &id, const Path &savePath, const PathList &fileNames);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -746,7 +738,6 @@ namespace BitTorrent
|
||||||
QHash<TorrentID, TorrentImpl *> m_torrents;
|
QHash<TorrentID, TorrentImpl *> m_torrents;
|
||||||
QHash<TorrentID, TorrentImpl *> m_hybridTorrentsByAltID;
|
QHash<TorrentID, TorrentImpl *> m_hybridTorrentsByAltID;
|
||||||
QHash<TorrentID, LoadTorrentParams> m_loadingTorrents;
|
QHash<TorrentID, LoadTorrentParams> m_loadingTorrents;
|
||||||
QHash<QString, AddTorrentParams> m_downloadedTorrents;
|
|
||||||
QHash<TorrentID, RemovingTorrentData> m_removingTorrents;
|
QHash<TorrentID, RemovingTorrentData> m_removingTorrents;
|
||||||
QSet<TorrentID> m_needSaveResumeDataTorrents;
|
QSet<TorrentID> m_needSaveResumeDataTorrents;
|
||||||
QHash<TorrentID, TorrentID> m_changedTorrentIDs;
|
QHash<TorrentID, TorrentID> m_changedTorrentIDs;
|
||||||
|
|
|
@ -33,6 +33,8 @@
|
||||||
#include <QtSystemDetection>
|
#include <QtSystemDetection>
|
||||||
#include <QMetaObject>
|
#include <QMetaObject>
|
||||||
|
|
||||||
|
#include "base/addtorrentmanager.h"
|
||||||
|
|
||||||
class QString;
|
class QString;
|
||||||
|
|
||||||
class Path;
|
class Path;
|
||||||
|
@ -83,4 +85,6 @@ public:
|
||||||
virtual MemoryPriority processMemoryPriority() const = 0;
|
virtual MemoryPriority processMemoryPriority() const = 0;
|
||||||
virtual void setProcessMemoryPriority(MemoryPriority priority) = 0;
|
virtual void setProcessMemoryPriority(MemoryPriority priority) = 0;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
virtual AddTorrentManager *addTorrentManager() const = 0;
|
||||||
};
|
};
|
||||||
|
|
|
@ -2086,6 +2086,50 @@ void Preferences::setSpeedWidgetGraphEnable(const int id, const bool enable)
|
||||||
setValue(u"SpeedWidget/graph_enable_%1"_s.arg(id), enable);
|
setValue(u"SpeedWidget/graph_enable_%1"_s.arg(id), enable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Preferences::isAddNewTorrentDialogEnabled() const
|
||||||
|
{
|
||||||
|
return value(u"AddNewTorrentDialog/Enabled"_s, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Preferences::setAddNewTorrentDialogEnabled(const bool value)
|
||||||
|
{
|
||||||
|
if (value == isAddNewTorrentDialogEnabled())
|
||||||
|
return;
|
||||||
|
|
||||||
|
setValue(u"AddNewTorrentDialog/Enabled"_s, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Preferences::isAddNewTorrentDialogTopLevel() const
|
||||||
|
{
|
||||||
|
return value(u"AddNewTorrentDialog/TopLevel"_s, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Preferences::setAddNewTorrentDialogTopLevel(const bool value)
|
||||||
|
{
|
||||||
|
if (value == isAddNewTorrentDialogTopLevel())
|
||||||
|
return;
|
||||||
|
|
||||||
|
setValue(u"AddNewTorrentDialog/TopLevel"_s, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
int Preferences::addNewTorrentDialogSavePathHistoryLength() const
|
||||||
|
{
|
||||||
|
const int defaultHistoryLength = 8;
|
||||||
|
|
||||||
|
const int val = value(u"AddNewTorrentDialog/SavePathHistoryLength"_s, defaultHistoryLength);
|
||||||
|
return std::clamp(val, 0, 99);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Preferences::setAddNewTorrentDialogSavePathHistoryLength(const int value)
|
||||||
|
{
|
||||||
|
const int clampedValue = qBound(0, value, 99);
|
||||||
|
const int oldValue = addNewTorrentDialogSavePathHistoryLength();
|
||||||
|
if (clampedValue == oldValue)
|
||||||
|
return;
|
||||||
|
|
||||||
|
setValue(u"AddNewTorrentDialog/SavePathHistoryLength"_s, clampedValue);
|
||||||
|
}
|
||||||
|
|
||||||
void Preferences::apply()
|
void Preferences::apply()
|
||||||
{
|
{
|
||||||
if (SettingsStorage::instance()->save())
|
if (SettingsStorage::instance()->save())
|
||||||
|
|
|
@ -424,6 +424,14 @@ public:
|
||||||
bool getSpeedWidgetGraphEnable(int id) const;
|
bool getSpeedWidgetGraphEnable(int id) const;
|
||||||
void setSpeedWidgetGraphEnable(int id, bool enable);
|
void setSpeedWidgetGraphEnable(int id, bool enable);
|
||||||
|
|
||||||
|
// AddNewTorrentDialog
|
||||||
|
bool isAddNewTorrentDialogEnabled() const;
|
||||||
|
void setAddNewTorrentDialogEnabled(bool value);
|
||||||
|
bool isAddNewTorrentDialogTopLevel() const;
|
||||||
|
void setAddNewTorrentDialogTopLevel(bool value);
|
||||||
|
int addNewTorrentDialogSavePathHistoryLength() const;
|
||||||
|
void setAddNewTorrentDialogSavePathHistoryLength(int value);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void setStatusFilterState(bool checked);
|
void setStatusFilterState(bool checked);
|
||||||
void setCategoryFilterState(bool checked);
|
void setCategoryFilterState(bool checked);
|
||||||
|
|
|
@ -41,14 +41,16 @@
|
||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
|
|
||||||
#include "../bittorrent/session.h"
|
#include "base/addtorrentmanager.h"
|
||||||
#include "../bittorrent/torrentdescriptor.h"
|
#include "base/asyncfilestorage.h"
|
||||||
#include "../asyncfilestorage.h"
|
#include "base/bittorrent/session.h"
|
||||||
#include "../global.h"
|
#include "base/bittorrent/torrentdescriptor.h"
|
||||||
#include "../logger.h"
|
#include "base/global.h"
|
||||||
#include "../profile.h"
|
#include "base/interfaces/iapplication.h"
|
||||||
#include "../utils/fs.h"
|
#include "base/logger.h"
|
||||||
#include "../utils/io.h"
|
#include "base/profile.h"
|
||||||
|
#include "base/utils/fs.h"
|
||||||
|
#include "base/utils/io.h"
|
||||||
#include "rss_article.h"
|
#include "rss_article.h"
|
||||||
#include "rss_autodownloadrule.h"
|
#include "rss_autodownloadrule.h"
|
||||||
#include "rss_feed.h"
|
#include "rss_feed.h"
|
||||||
|
@ -100,12 +102,13 @@ QString computeSmartFilterRegex(const QStringList &filters)
|
||||||
return u"(?:_|\\b)(?:%1)(?:_|\\b)"_s.arg(filters.join(u")|(?:"));
|
return u"(?:_|\\b)(?:%1)(?:_|\\b)"_s.arg(filters.join(u")|(?:"));
|
||||||
}
|
}
|
||||||
|
|
||||||
AutoDownloader::AutoDownloader()
|
AutoDownloader::AutoDownloader(IApplication *app)
|
||||||
: m_storeProcessingEnabled(u"RSS/AutoDownloader/EnableProcessing"_s, false)
|
: ApplicationComponent(app)
|
||||||
, m_storeSmartEpisodeFilter(u"RSS/AutoDownloader/SmartEpisodeFilter"_s)
|
, m_storeProcessingEnabled {u"RSS/AutoDownloader/EnableProcessing"_s, false}
|
||||||
, m_storeDownloadRepacks(u"RSS/AutoDownloader/DownloadRepacks"_s)
|
, m_storeSmartEpisodeFilter {u"RSS/AutoDownloader/SmartEpisodeFilter"_s}
|
||||||
, m_processingTimer(new QTimer(this))
|
, m_storeDownloadRepacks {u"RSS/AutoDownloader/DownloadRepacks"_s}
|
||||||
, m_ioThread(new QThread)
|
, m_processingTimer {new QTimer(this)}
|
||||||
|
, m_ioThread {new QThread}
|
||||||
{
|
{
|
||||||
Q_ASSERT(!m_instance); // only one instance is allowed
|
Q_ASSERT(!m_instance); // only one instance is allowed
|
||||||
m_instance = this;
|
m_instance = this;
|
||||||
|
@ -122,15 +125,15 @@ AutoDownloader::AutoDownloader()
|
||||||
|
|
||||||
m_ioThread->start();
|
m_ioThread->start();
|
||||||
|
|
||||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::downloadFromUrlFinished
|
connect(app->addTorrentManager(), &AddTorrentManager::torrentAdded
|
||||||
, this, &AutoDownloader::handleTorrentDownloadFinished);
|
, this, &AutoDownloader::handleTorrentAdded);
|
||||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::downloadFromUrlFailed
|
connect(app->addTorrentManager(), &AddTorrentManager::addTorrentFailed
|
||||||
, this, &AutoDownloader::handleTorrentDownloadFailed);
|
, this, &AutoDownloader::handleAddTorrentFailed);
|
||||||
|
|
||||||
// initialise the smart episode regex
|
// initialise the smart episode regex
|
||||||
const QString regex = computeSmartFilterRegex(smartEpisodeFilters());
|
const QString regex = computeSmartFilterRegex(smartEpisodeFilters());
|
||||||
m_smartEpisodeRegex = QRegularExpression(regex,
|
m_smartEpisodeRegex = QRegularExpression(regex
|
||||||
QRegularExpression::CaseInsensitiveOption
|
, QRegularExpression::CaseInsensitiveOption
|
||||||
| QRegularExpression::ExtendedPatternSyntaxOption
|
| QRegularExpression::ExtendedPatternSyntaxOption
|
||||||
| QRegularExpression::UseUnicodePropertiesOption);
|
| QRegularExpression::UseUnicodePropertiesOption);
|
||||||
|
|
||||||
|
@ -358,9 +361,9 @@ void AutoDownloader::process()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AutoDownloader::handleTorrentDownloadFinished(const QString &url)
|
void AutoDownloader::handleTorrentAdded(const QString &source)
|
||||||
{
|
{
|
||||||
const auto job = m_waitingJobs.take(url);
|
const auto job = m_waitingJobs.take(source);
|
||||||
if (!job)
|
if (!job)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -371,9 +374,9 @@ void AutoDownloader::handleTorrentDownloadFinished(const QString &url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AutoDownloader::handleTorrentDownloadFailed(const QString &url)
|
void AutoDownloader::handleAddTorrentFailed(const QString &source)
|
||||||
{
|
{
|
||||||
m_waitingJobs.remove(url);
|
m_waitingJobs.remove(source);
|
||||||
// TODO: Re-schedule job here.
|
// TODO: Re-schedule job here.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -472,7 +475,7 @@ void AutoDownloader::processJob(const QSharedPointer<ProcessingJob> &job)
|
||||||
.arg(job->articleData.value(Article::KeyTitle).toString(), rule.name()));
|
.arg(job->articleData.value(Article::KeyTitle).toString(), rule.name()));
|
||||||
|
|
||||||
const auto torrentURL = job->articleData.value(Article::KeyTorrentURL).toString();
|
const auto torrentURL = job->articleData.value(Article::KeyTorrentURL).toString();
|
||||||
BitTorrent::Session::instance()->addTorrent(torrentURL, rule.addTorrentParams());
|
app()->addTorrentManager()->addTorrent(torrentURL, rule.addTorrentParams());
|
||||||
|
|
||||||
if (BitTorrent::TorrentDescriptor::parse(torrentURL))
|
if (BitTorrent::TorrentDescriptor::parse(torrentURL))
|
||||||
{
|
{
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QSharedPointer>
|
#include <QSharedPointer>
|
||||||
|
|
||||||
|
#include "base/applicationcomponent.h"
|
||||||
#include "base/exceptions.h"
|
#include "base/exceptions.h"
|
||||||
#include "base/settingvalue.h"
|
#include "base/settingvalue.h"
|
||||||
#include "base/utils/thread.h"
|
#include "base/utils/thread.h"
|
||||||
|
@ -61,14 +62,14 @@ namespace RSS
|
||||||
using RuntimeError::RuntimeError;
|
using RuntimeError::RuntimeError;
|
||||||
};
|
};
|
||||||
|
|
||||||
class AutoDownloader final : public QObject
|
class AutoDownloader final : public ApplicationComponent<QObject>
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_DISABLE_COPY_MOVE(AutoDownloader)
|
Q_DISABLE_COPY_MOVE(AutoDownloader)
|
||||||
|
|
||||||
friend class ::Application;
|
friend class ::Application;
|
||||||
|
|
||||||
AutoDownloader();
|
explicit AutoDownloader(IApplication *app);
|
||||||
~AutoDownloader() override;
|
~AutoDownloader() override;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -110,8 +111,8 @@ namespace RSS
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void process();
|
void process();
|
||||||
void handleTorrentDownloadFinished(const QString &url);
|
void handleTorrentAdded(const QString &source);
|
||||||
void handleTorrentDownloadFailed(const QString &url);
|
void handleAddTorrentFailed(const QString &url);
|
||||||
void handleNewArticle(const Article *article);
|
void handleNewArticle(const Article *article);
|
||||||
void handleFeedURLChanged(Feed *feed, const QString &oldURL);
|
void handleFeedURLChanged(Feed *feed, const QString &oldURL);
|
||||||
|
|
||||||
|
|
|
@ -54,6 +54,7 @@ add_library(qbt_gui STATIC
|
||||||
flowlayout.h
|
flowlayout.h
|
||||||
fspathedit.h
|
fspathedit.h
|
||||||
fspathedit_p.h
|
fspathedit_p.h
|
||||||
|
guiaddtorrentmanager.h
|
||||||
guiapplicationcomponent.h
|
guiapplicationcomponent.h
|
||||||
hidabletabwidget.h
|
hidabletabwidget.h
|
||||||
interfaces/iguiapplication.h
|
interfaces/iguiapplication.h
|
||||||
|
@ -148,7 +149,7 @@ add_library(qbt_gui STATIC
|
||||||
flowlayout.cpp
|
flowlayout.cpp
|
||||||
fspathedit.cpp
|
fspathedit.cpp
|
||||||
fspathedit_p.cpp
|
fspathedit_p.cpp
|
||||||
guiapplicationcomponent.cpp
|
guiaddtorrentmanager.cpp
|
||||||
hidabletabwidget.cpp
|
hidabletabwidget.cpp
|
||||||
ipsubnetwhitelistoptionsdialog.cpp
|
ipsubnetwhitelistoptionsdialog.cpp
|
||||||
lineedit.cpp
|
lineedit.cpp
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
|
#include <QMessageBox>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QShortcut>
|
#include <QShortcut>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
@ -50,7 +51,6 @@
|
||||||
#include "base/bittorrent/torrentcontenthandler.h"
|
#include "base/bittorrent/torrentcontenthandler.h"
|
||||||
#include "base/bittorrent/torrentcontentlayout.h"
|
#include "base/bittorrent/torrentcontentlayout.h"
|
||||||
#include "base/global.h"
|
#include "base/global.h"
|
||||||
#include "base/net/downloadmanager.h"
|
|
||||||
#include "base/preferences.h"
|
#include "base/preferences.h"
|
||||||
#include "base/settingsstorage.h"
|
#include "base/settingsstorage.h"
|
||||||
#include "base/torrentfileguard.h"
|
#include "base/torrentfileguard.h"
|
||||||
|
@ -58,19 +58,16 @@
|
||||||
#include "base/utils/fs.h"
|
#include "base/utils/fs.h"
|
||||||
#include "base/utils/misc.h"
|
#include "base/utils/misc.h"
|
||||||
#include "lineedit.h"
|
#include "lineedit.h"
|
||||||
#include "raisedmessagebox.h"
|
|
||||||
#include "torrenttagsdialog.h"
|
#include "torrenttagsdialog.h"
|
||||||
#include "ui_addnewtorrentdialog.h"
|
|
||||||
#include "uithememanager.h"
|
#include "uithememanager.h"
|
||||||
|
|
||||||
|
#include "ui_addnewtorrentdialog.h"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
#define SETTINGS_KEY(name) u"AddNewTorrentDialog/" name
|
#define SETTINGS_KEY(name) u"AddNewTorrentDialog/" name
|
||||||
const QString KEY_ENABLED = SETTINGS_KEY(u"Enabled"_s);
|
|
||||||
const QString KEY_TOPLEVEL = SETTINGS_KEY(u"TopLevel"_s);
|
|
||||||
const QString KEY_SAVEPATHHISTORY = SETTINGS_KEY(u"SavePathHistory"_s);
|
const QString KEY_SAVEPATHHISTORY = SETTINGS_KEY(u"SavePathHistory"_s);
|
||||||
const QString KEY_DOWNLOADPATHHISTORY = SETTINGS_KEY(u"DownloadPathHistory"_s);
|
const QString KEY_DOWNLOADPATHHISTORY = SETTINGS_KEY(u"DownloadPathHistory"_s);
|
||||||
const QString KEY_SAVEPATHHISTORYLENGTH = SETTINGS_KEY(u"SavePathHistoryLength"_s);
|
|
||||||
|
|
||||||
// just a shortcut
|
// just a shortcut
|
||||||
inline SettingsStorage *settings()
|
inline SettingsStorage *settings()
|
||||||
|
@ -267,19 +264,18 @@ private:
|
||||||
BitTorrent::TorrentContentLayout m_currentContentLayout;
|
BitTorrent::TorrentContentLayout m_currentContentLayout;
|
||||||
};
|
};
|
||||||
|
|
||||||
const int AddNewTorrentDialog::minPathHistoryLength;
|
AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::TorrentDescriptor &torrentDescr
|
||||||
const int AddNewTorrentDialog::maxPathHistoryLength;
|
, const BitTorrent::AddTorrentParams &inParams, QWidget *parent)
|
||||||
|
|
||||||
AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::AddTorrentParams &inParams, QWidget *parent)
|
|
||||||
: QDialog(parent)
|
: QDialog(parent)
|
||||||
, m_ui(new Ui::AddNewTorrentDialog)
|
, m_ui {new Ui::AddNewTorrentDialog}
|
||||||
, m_filterLine(new LineEdit(this))
|
, m_torrentDescr {torrentDescr}
|
||||||
, m_torrentParams(inParams)
|
, m_torrentParams {inParams}
|
||||||
, m_storeDialogSize(SETTINGS_KEY(u"DialogSize"_s))
|
, m_filterLine {new LineEdit(this)}
|
||||||
, m_storeDefaultCategory(SETTINGS_KEY(u"DefaultCategory"_s))
|
, m_storeDialogSize {SETTINGS_KEY(u"DialogSize"_s)}
|
||||||
, m_storeRememberLastSavePath(SETTINGS_KEY(u"RememberLastSavePath"_s))
|
, m_storeDefaultCategory {SETTINGS_KEY(u"DefaultCategory"_s)}
|
||||||
, m_storeTreeHeaderState(u"GUI/Qt6/" SETTINGS_KEY(u"TreeHeaderState"_s))
|
, m_storeRememberLastSavePath {SETTINGS_KEY(u"RememberLastSavePath"_s)}
|
||||||
, m_storeSplitterState(u"GUI/Qt6/" SETTINGS_KEY(u"SplitterState"_s))
|
, m_storeTreeHeaderState {u"GUI/Qt6/" SETTINGS_KEY(u"TreeHeaderState"_s)}
|
||||||
|
, m_storeSplitterState {u"GUI/Qt6/" SETTINGS_KEY(u"SplitterState"_s)}
|
||||||
{
|
{
|
||||||
// TODO: set dialog file properties using m_torrentParams.filePriorities
|
// TODO: set dialog file properties using m_torrentParams.filePriorities
|
||||||
m_ui->setupUi(this);
|
m_ui->setupUi(this);
|
||||||
|
@ -384,8 +380,6 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::AddTorrentParams &inP
|
||||||
|
|
||||||
loadState();
|
loadState();
|
||||||
|
|
||||||
connect(m_ui->doNotDeleteTorrentCheckBox, &QCheckBox::clicked, this, &AddNewTorrentDialog::doNotDeleteTorrentClicked);
|
|
||||||
|
|
||||||
connect(m_ui->buttonSelectAll, &QPushButton::clicked, m_ui->contentTreeView, &TorrentContentWidget::checkAll);
|
connect(m_ui->buttonSelectAll, &QPushButton::clicked, m_ui->contentTreeView, &TorrentContentWidget::checkAll);
|
||||||
connect(m_ui->buttonSelectNone, &QPushButton::clicked, m_ui->contentTreeView, &TorrentContentWidget::checkNone);
|
connect(m_ui->buttonSelectNone, &QPushButton::clicked, m_ui->contentTreeView, &TorrentContentWidget::checkNone);
|
||||||
|
|
||||||
|
@ -408,6 +402,33 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::AddTorrentParams &inP
|
||||||
m_ui->savePath->setFocus();
|
m_ui->savePath->setFocus();
|
||||||
else
|
else
|
||||||
m_ui->categoryComboBox->setFocus();
|
m_ui->categoryComboBox->setFocus();
|
||||||
|
|
||||||
|
connect(Preferences::instance(), &Preferences::changed, []
|
||||||
|
{
|
||||||
|
const int length = Preferences::instance()->addNewTorrentDialogSavePathHistoryLength();
|
||||||
|
settings()->storeValue(KEY_SAVEPATHHISTORY
|
||||||
|
, QStringList(settings()->loadValue<QStringList>(KEY_SAVEPATHHISTORY).mid(0, length)));
|
||||||
|
});
|
||||||
|
|
||||||
|
const BitTorrent::InfoHash infoHash = m_torrentDescr.infoHash();
|
||||||
|
|
||||||
|
m_ui->labelInfohash1Data->setText(infoHash.v1().isValid() ? infoHash.v1().toString() : tr("N/A"));
|
||||||
|
m_ui->labelInfohash2Data->setText(infoHash.v2().isValid() ? infoHash.v2().toString() : tr("N/A"));
|
||||||
|
|
||||||
|
if (hasMetadata())
|
||||||
|
{
|
||||||
|
setupTreeview();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Set dialog title
|
||||||
|
const QString torrentName = m_torrentDescr.name();
|
||||||
|
setWindowTitle(torrentName.isEmpty() ? tr("Magnet link") : torrentName);
|
||||||
|
updateDiskSpaceLabel();
|
||||||
|
setMetadataProgressIndicator(true, tr("Retrieving metadata..."));
|
||||||
|
}
|
||||||
|
|
||||||
|
TMMChanged(m_ui->comboTTM->currentIndex());
|
||||||
}
|
}
|
||||||
|
|
||||||
AddNewTorrentDialog::~AddNewTorrentDialog()
|
AddNewTorrentDialog::~AddNewTorrentDialog()
|
||||||
|
@ -416,43 +437,19 @@ AddNewTorrentDialog::~AddNewTorrentDialog()
|
||||||
delete m_ui;
|
delete m_ui;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AddNewTorrentDialog::isEnabled()
|
BitTorrent::TorrentDescriptor AddNewTorrentDialog::torrentDescriptor() const
|
||||||
{
|
{
|
||||||
return settings()->loadValue(KEY_ENABLED, true);
|
return m_torrentDescr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddNewTorrentDialog::setEnabled(const bool value)
|
BitTorrent::AddTorrentParams AddNewTorrentDialog::addTorrentParams() const
|
||||||
{
|
{
|
||||||
settings()->storeValue(KEY_ENABLED, value);
|
return m_torrentParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AddNewTorrentDialog::isTopLevel()
|
bool AddNewTorrentDialog::isDoNotDeleteTorrentChecked() const
|
||||||
{
|
{
|
||||||
return settings()->loadValue(KEY_TOPLEVEL, true);
|
return m_ui->doNotDeleteTorrentCheckBox->isChecked();
|
||||||
}
|
|
||||||
|
|
||||||
void AddNewTorrentDialog::setTopLevel(const bool value)
|
|
||||||
{
|
|
||||||
settings()->storeValue(KEY_TOPLEVEL, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
int AddNewTorrentDialog::savePathHistoryLength()
|
|
||||||
{
|
|
||||||
const int defaultHistoryLength = 8;
|
|
||||||
const int value = settings()->loadValue(KEY_SAVEPATHHISTORYLENGTH, defaultHistoryLength);
|
|
||||||
return std::clamp(value, minPathHistoryLength, maxPathHistoryLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddNewTorrentDialog::setSavePathHistoryLength(const int value)
|
|
||||||
{
|
|
||||||
const int clampedValue = qBound(minPathHistoryLength, value, maxPathHistoryLength);
|
|
||||||
const int oldValue = savePathHistoryLength();
|
|
||||||
if (clampedValue == oldValue)
|
|
||||||
return;
|
|
||||||
|
|
||||||
settings()->storeValue(KEY_SAVEPATHHISTORYLENGTH, clampedValue);
|
|
||||||
settings()->storeValue(KEY_SAVEPATHHISTORY
|
|
||||||
, QStringList(settings()->loadValue<QStringList>(KEY_SAVEPATHHISTORY).mid(0, clampedValue)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddNewTorrentDialog::loadState()
|
void AddNewTorrentDialog::loadState()
|
||||||
|
@ -471,140 +468,10 @@ void AddNewTorrentDialog::saveState()
|
||||||
m_storeTreeHeaderState = m_ui->contentTreeView->header()->saveState();
|
m_storeTreeHeaderState = m_ui->contentTreeView->header()->saveState();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddNewTorrentDialog::show(const QString &source, const BitTorrent::AddTorrentParams &inParams, QWidget *parent)
|
|
||||||
{
|
|
||||||
auto *dlg = new AddNewTorrentDialog(inParams, parent);
|
|
||||||
dlg->setAttribute(Qt::WA_DeleteOnClose);
|
|
||||||
|
|
||||||
if (Net::DownloadManager::hasSupportedScheme(source))
|
|
||||||
{
|
|
||||||
const auto *pref = Preferences::instance();
|
|
||||||
// Launch downloader
|
|
||||||
Net::DownloadManager::instance()->download(
|
|
||||||
Net::DownloadRequest(source).limit(pref->getTorrentFileSizeLimit())
|
|
||||||
, pref->useProxyForGeneralPurposes()
|
|
||||||
, dlg, &AddNewTorrentDialog::handleDownloadFinished);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dlg->loadTorrent(source))
|
|
||||||
dlg->QDialog::show();
|
|
||||||
else
|
|
||||||
delete dlg;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddNewTorrentDialog::show(const QString &source, QWidget *parent)
|
|
||||||
{
|
|
||||||
show(source, BitTorrent::AddTorrentParams(), parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AddNewTorrentDialog::loadTorrent(const QString &source)
|
|
||||||
{
|
|
||||||
if (const auto parseResult = BitTorrent::TorrentDescriptor::parse(source))
|
|
||||||
{
|
|
||||||
m_torrentDescr = parseResult.value();
|
|
||||||
return loadTorrentImpl();
|
|
||||||
}
|
|
||||||
else if (source.startsWith(u"magnet:", Qt::CaseInsensitive))
|
|
||||||
{
|
|
||||||
RaisedMessageBox::critical(this, tr("Invalid torrent")
|
|
||||||
, tr("Failed to load the torrent: %1.\nError: %2").arg(source, parseResult.error()));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Path decodedPath {source.startsWith(u"file://", Qt::CaseInsensitive)
|
|
||||||
? QUrl::fromEncoded(source.toLocal8Bit()).toLocalFile() : source};
|
|
||||||
|
|
||||||
if (const auto loadResult = BitTorrent::TorrentDescriptor::loadFromFile(decodedPath))
|
|
||||||
{
|
|
||||||
m_torrentDescr = loadResult.value();
|
|
||||||
m_torrentGuard = std::make_unique<TorrentFileGuard>(decodedPath);
|
|
||||||
|
|
||||||
return loadTorrentImpl();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
RaisedMessageBox::critical(this, tr("Invalid torrent")
|
|
||||||
, tr("Failed to load the torrent: %1.\nError: %2", "Don't remove the '\n' characters. They insert a newline.")
|
|
||||||
.arg(decodedPath.toString(), loadResult.error()));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AddNewTorrentDialog::loadTorrentImpl()
|
|
||||||
{
|
|
||||||
const BitTorrent::InfoHash infoHash = m_torrentDescr.infoHash();
|
|
||||||
|
|
||||||
// Prevent showing the dialog if download is already present
|
|
||||||
const auto *btSession = BitTorrent::Session::instance();
|
|
||||||
if (btSession->isKnownTorrent(infoHash))
|
|
||||||
{
|
|
||||||
if (BitTorrent::Torrent *torrent = btSession->findTorrent(infoHash))
|
|
||||||
{
|
|
||||||
if (hasMetadata())
|
|
||||||
{
|
|
||||||
// Trying to set metadata to existing torrent in case if it has none
|
|
||||||
torrent->setMetadata(*m_torrentDescr.info());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (torrent->isPrivate() || (hasMetadata() && m_torrentDescr.info()->isPrivate()))
|
|
||||||
{
|
|
||||||
RaisedMessageBox::warning(this, tr("Torrent is already present"), tr("Torrent '%1' is already in the transfer list. Trackers cannot be merged because it is a private torrent.").arg(torrent->name()), QMessageBox::Ok);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
bool mergeTrackers = btSession->isMergeTrackersEnabled();
|
|
||||||
if (Preferences::instance()->confirmMergeTrackers())
|
|
||||||
{
|
|
||||||
const QMessageBox::StandardButton btn = RaisedMessageBox::question(this, tr("Torrent is already present")
|
|
||||||
, tr("Torrent '%1' is already in the transfer list. Do you want to merge trackers from new source?").arg(torrent->name())
|
|
||||||
, (QMessageBox::Yes | QMessageBox::No), QMessageBox::Yes);
|
|
||||||
mergeTrackers = (btn == QMessageBox::Yes);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mergeTrackers)
|
|
||||||
{
|
|
||||||
torrent->addTrackers(m_torrentDescr.trackers());
|
|
||||||
torrent->addUrlSeeds(m_torrentDescr.urlSeeds());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
RaisedMessageBox::information(this, tr("Torrent is already present"), tr("Torrent is already queued for processing."), QMessageBox::Ok);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_ui->labelInfohash1Data->setText(infoHash.v1().isValid() ? infoHash.v1().toString() : tr("N/A"));
|
|
||||||
m_ui->labelInfohash2Data->setText(infoHash.v2().isValid() ? infoHash.v2().toString() : tr("N/A"));
|
|
||||||
|
|
||||||
if (hasMetadata())
|
|
||||||
{
|
|
||||||
setupTreeview();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::metadataDownloaded, this, &AddNewTorrentDialog::updateMetadata);
|
|
||||||
|
|
||||||
// Set dialog title
|
|
||||||
const QString torrentName = m_torrentDescr.name();
|
|
||||||
setWindowTitle(torrentName.isEmpty() ? tr("Magnet link") : torrentName);
|
|
||||||
updateDiskSpaceLabel();
|
|
||||||
BitTorrent::Session::instance()->downloadMetadata(m_torrentDescr);
|
|
||||||
setMetadataProgressIndicator(true, tr("Retrieving metadata..."));
|
|
||||||
}
|
|
||||||
|
|
||||||
TMMChanged(m_ui->comboTTM->currentIndex());
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddNewTorrentDialog::showEvent(QShowEvent *event)
|
void AddNewTorrentDialog::showEvent(QShowEvent *event)
|
||||||
{
|
{
|
||||||
QDialog::showEvent(event);
|
QDialog::showEvent(event);
|
||||||
if (!isTopLevel())
|
if (!Preferences::instance()->isAddNewTorrentDialogTopLevel())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
activateWindow();
|
activateWindow();
|
||||||
|
@ -818,16 +685,17 @@ void AddNewTorrentDialog::accept()
|
||||||
m_torrentParams.useAutoTMM = useAutoTMM;
|
m_torrentParams.useAutoTMM = useAutoTMM;
|
||||||
if (!useAutoTMM)
|
if (!useAutoTMM)
|
||||||
{
|
{
|
||||||
|
const int savePathHistoryLength = Preferences::instance()->addNewTorrentDialogSavePathHistoryLength();
|
||||||
const Path savePath = m_ui->savePath->selectedPath();
|
const Path savePath = m_ui->savePath->selectedPath();
|
||||||
m_torrentParams.savePath = savePath;
|
m_torrentParams.savePath = savePath;
|
||||||
updatePathHistory(KEY_SAVEPATHHISTORY, savePath, savePathHistoryLength());
|
updatePathHistory(KEY_SAVEPATHHISTORY, savePath, savePathHistoryLength);
|
||||||
|
|
||||||
m_torrentParams.useDownloadPath = m_ui->groupBoxDownloadPath->isChecked();
|
m_torrentParams.useDownloadPath = m_ui->groupBoxDownloadPath->isChecked();
|
||||||
if (m_torrentParams.useDownloadPath)
|
if (m_torrentParams.useDownloadPath)
|
||||||
{
|
{
|
||||||
const Path downloadPath = m_ui->downloadPath->selectedPath();
|
const Path downloadPath = m_ui->downloadPath->selectedPath();
|
||||||
m_torrentParams.downloadPath = downloadPath;
|
m_torrentParams.downloadPath = downloadPath;
|
||||||
updatePathHistory(KEY_DOWNLOADPATHHISTORY, downloadPath, savePathHistoryLength());
|
updatePathHistory(KEY_DOWNLOADPATHHISTORY, downloadPath, savePathHistoryLength);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -843,11 +711,6 @@ void AddNewTorrentDialog::accept()
|
||||||
|
|
||||||
setEnabled(!m_ui->checkBoxNeverShow->isChecked());
|
setEnabled(!m_ui->checkBoxNeverShow->isChecked());
|
||||||
|
|
||||||
// Add torrent
|
|
||||||
BitTorrent::Session::instance()->addTorrent(m_torrentDescr, m_torrentParams);
|
|
||||||
|
|
||||||
if (m_torrentGuard)
|
|
||||||
m_torrentGuard->markAsAddedToSession();
|
|
||||||
QDialog::accept();
|
QDialog::accept();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -865,13 +728,13 @@ void AddNewTorrentDialog::reject()
|
||||||
void AddNewTorrentDialog::updateMetadata(const BitTorrent::TorrentInfo &metadata)
|
void AddNewTorrentDialog::updateMetadata(const BitTorrent::TorrentInfo &metadata)
|
||||||
{
|
{
|
||||||
Q_ASSERT(metadata.isValid());
|
Q_ASSERT(metadata.isValid());
|
||||||
|
if (!metadata.isValid()) [[unlikely]]
|
||||||
if (!metadata.matchesInfoHash(m_torrentDescr.infoHash()))
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
disconnect(BitTorrent::Session::instance(), &BitTorrent::Session::metadataDownloaded, this, &AddNewTorrentDialog::updateMetadata);
|
Q_ASSERT(metadata.matchesInfoHash(m_torrentDescr.infoHash()));
|
||||||
|
if (!metadata.matchesInfoHash(m_torrentDescr.infoHash())) [[unlikely]]
|
||||||
|
return;
|
||||||
|
|
||||||
// Good to go
|
|
||||||
m_torrentDescr.setTorrentInfo(metadata);
|
m_torrentDescr.setTorrentInfo(metadata);
|
||||||
setMetadataProgressIndicator(true, tr("Parsing metadata..."));
|
setMetadataProgressIndicator(true, tr("Parsing metadata..."));
|
||||||
|
|
||||||
|
@ -941,50 +804,6 @@ void AddNewTorrentDialog::setupTreeview()
|
||||||
updateDiskSpaceLabel();
|
updateDiskSpaceLabel();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddNewTorrentDialog::handleDownloadFinished(const Net::DownloadResult &downloadResult)
|
|
||||||
{
|
|
||||||
switch (downloadResult.status)
|
|
||||||
{
|
|
||||||
case Net::DownloadStatus::Success:
|
|
||||||
if (const auto loadResult = BitTorrent::TorrentDescriptor::load(downloadResult.data))
|
|
||||||
{
|
|
||||||
m_torrentDescr = loadResult.value();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
RaisedMessageBox::critical(this, tr("Invalid torrent")
|
|
||||||
, tr("Failed to load from URL: %1.\nError: %2").arg(downloadResult.url, loadResult.error()));
|
|
||||||
deleteLater();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case Net::DownloadStatus::RedirectedToMagnet:
|
|
||||||
if (const auto parseResult = BitTorrent::TorrentDescriptor::parse(downloadResult.magnetURI))
|
|
||||||
{
|
|
||||||
m_torrentDescr = parseResult.value();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
RaisedMessageBox::critical(this, tr("Invalid torrent")
|
|
||||||
, tr("Failed to load torrent. The request was redirected to invalid Magnet URI.\nError: %1")
|
|
||||||
.arg(parseResult.error()));
|
|
||||||
deleteLater();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
RaisedMessageBox::critical(this, tr("Download Error")
|
|
||||||
, tr("Cannot download '%1': %2").arg(downloadResult.url, downloadResult.errorString));
|
|
||||||
deleteLater();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loadTorrentImpl())
|
|
||||||
open();
|
|
||||||
else
|
|
||||||
deleteLater();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddNewTorrentDialog::TMMChanged(int index)
|
void AddNewTorrentDialog::TMMChanged(int index)
|
||||||
{
|
{
|
||||||
if (index != 1)
|
if (index != 1)
|
||||||
|
@ -1014,9 +833,3 @@ void AddNewTorrentDialog::TMMChanged(int index)
|
||||||
|
|
||||||
updateDiskSpaceLabel();
|
updateDiskSpaceLabel();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddNewTorrentDialog::doNotDeleteTorrentClicked(bool checked)
|
|
||||||
{
|
|
||||||
if (m_torrentGuard)
|
|
||||||
m_torrentGuard->setAutoRemove(!checked);
|
|
||||||
}
|
|
||||||
|
|
|
@ -29,8 +29,6 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
|
|
||||||
#include "base/bittorrent/addtorrentparams.h"
|
#include "base/bittorrent/addtorrentparams.h"
|
||||||
|
@ -38,23 +36,12 @@
|
||||||
#include "base/path.h"
|
#include "base/path.h"
|
||||||
#include "base/settingvalue.h"
|
#include "base/settingvalue.h"
|
||||||
|
|
||||||
namespace BitTorrent
|
|
||||||
{
|
|
||||||
class InfoHash;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Net
|
|
||||||
{
|
|
||||||
struct DownloadResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Ui
|
namespace Ui
|
||||||
{
|
{
|
||||||
class AddNewTorrentDialog;
|
class AddNewTorrentDialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
class LineEdit;
|
class LineEdit;
|
||||||
class TorrentFileGuard;
|
|
||||||
|
|
||||||
class AddNewTorrentDialog final : public QDialog
|
class AddNewTorrentDialog final : public QDialog
|
||||||
{
|
{
|
||||||
|
@ -62,32 +49,24 @@ class AddNewTorrentDialog final : public QDialog
|
||||||
Q_DISABLE_COPY_MOVE(AddNewTorrentDialog)
|
Q_DISABLE_COPY_MOVE(AddNewTorrentDialog)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static const int minPathHistoryLength = 0;
|
explicit AddNewTorrentDialog(const BitTorrent::TorrentDescriptor &torrentDescr
|
||||||
static const int maxPathHistoryLength = 99;
|
, const BitTorrent::AddTorrentParams &inParams, QWidget *parent);
|
||||||
|
|
||||||
~AddNewTorrentDialog() override;
|
~AddNewTorrentDialog() override;
|
||||||
|
|
||||||
static bool isEnabled();
|
BitTorrent::TorrentDescriptor torrentDescriptor() const;
|
||||||
static void setEnabled(bool value);
|
BitTorrent::AddTorrentParams addTorrentParams() const;
|
||||||
static bool isTopLevel();
|
bool isDoNotDeleteTorrentChecked() const;
|
||||||
static void setTopLevel(bool value);
|
|
||||||
static int savePathHistoryLength();
|
|
||||||
static void setSavePathHistoryLength(int value);
|
|
||||||
|
|
||||||
static void show(const QString &source, const BitTorrent::AddTorrentParams &inParams, QWidget *parent);
|
void updateMetadata(const BitTorrent::TorrentInfo &metadata);
|
||||||
static void show(const QString &source, QWidget *parent);
|
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void updateDiskSpaceLabel();
|
void updateDiskSpaceLabel();
|
||||||
void onSavePathChanged(const Path &newPath);
|
void onSavePathChanged(const Path &newPath);
|
||||||
void onDownloadPathChanged(const Path &newPath);
|
void onDownloadPathChanged(const Path &newPath);
|
||||||
void onUseDownloadPathChanged(bool checked);
|
void onUseDownloadPathChanged(bool checked);
|
||||||
void updateMetadata(const BitTorrent::TorrentInfo &metadata);
|
|
||||||
void handleDownloadFinished(const Net::DownloadResult &downloadResult);
|
|
||||||
void TMMChanged(int index);
|
void TMMChanged(int index);
|
||||||
void categoryChanged(int index);
|
void categoryChanged(int index);
|
||||||
void contentLayoutChanged();
|
void contentLayoutChanged();
|
||||||
void doNotDeleteTorrentClicked(bool checked);
|
|
||||||
|
|
||||||
void accept() override;
|
void accept() override;
|
||||||
void reject() override;
|
void reject() override;
|
||||||
|
@ -95,10 +74,6 @@ private slots:
|
||||||
private:
|
private:
|
||||||
class TorrentContentAdaptor;
|
class TorrentContentAdaptor;
|
||||||
|
|
||||||
explicit AddNewTorrentDialog(const BitTorrent::AddTorrentParams &inParams, QWidget *parent);
|
|
||||||
|
|
||||||
bool loadTorrent(const QString &source);
|
|
||||||
bool loadTorrentImpl();
|
|
||||||
void populateSavePaths();
|
void populateSavePaths();
|
||||||
void loadState();
|
void loadState();
|
||||||
void saveState();
|
void saveState();
|
||||||
|
@ -112,12 +87,11 @@ private:
|
||||||
Ui::AddNewTorrentDialog *m_ui = nullptr;
|
Ui::AddNewTorrentDialog *m_ui = nullptr;
|
||||||
TorrentContentAdaptor *m_contentAdaptor = nullptr;
|
TorrentContentAdaptor *m_contentAdaptor = nullptr;
|
||||||
BitTorrent::TorrentDescriptor m_torrentDescr;
|
BitTorrent::TorrentDescriptor m_torrentDescr;
|
||||||
|
BitTorrent::AddTorrentParams m_torrentParams;
|
||||||
int m_savePathIndex = -1;
|
int m_savePathIndex = -1;
|
||||||
int m_downloadPathIndex = -1;
|
int m_downloadPathIndex = -1;
|
||||||
bool m_useDownloadPath = false;
|
bool m_useDownloadPath = false;
|
||||||
LineEdit *m_filterLine = nullptr;
|
LineEdit *m_filterLine = nullptr;
|
||||||
std::unique_ptr<TorrentFileGuard> m_torrentGuard;
|
|
||||||
BitTorrent::AddTorrentParams m_torrentParams;
|
|
||||||
|
|
||||||
SettingValue<QSize> m_storeDialogSize;
|
SettingValue<QSize> m_storeDialogSize;
|
||||||
SettingValue<QString> m_storeDefaultCategory;
|
SettingValue<QString> m_storeDefaultCategory;
|
||||||
|
|
|
@ -168,8 +168,7 @@ namespace
|
||||||
}
|
}
|
||||||
|
|
||||||
AdvancedSettings::AdvancedSettings(IGUIApplication *app, QWidget *parent)
|
AdvancedSettings::AdvancedSettings(IGUIApplication *app, QWidget *parent)
|
||||||
: QTableWidget(parent)
|
: GUIApplicationComponent(app, parent)
|
||||||
, GUIApplicationComponent(app)
|
|
||||||
{
|
{
|
||||||
// column
|
// column
|
||||||
setColumnCount(COL_COUNT);
|
setColumnCount(COL_COUNT);
|
||||||
|
@ -306,7 +305,7 @@ void AdvancedSettings::saveAdvancedSettings() const
|
||||||
session->setReannounceWhenAddressChangedEnabled(m_checkBoxReannounceWhenAddressChanged.isChecked());
|
session->setReannounceWhenAddressChangedEnabled(m_checkBoxReannounceWhenAddressChanged.isChecked());
|
||||||
// Misc GUI properties
|
// Misc GUI properties
|
||||||
app()->mainWindow()->setDownloadTrackerFavicon(m_checkBoxTrackerFavicon.isChecked());
|
app()->mainWindow()->setDownloadTrackerFavicon(m_checkBoxTrackerFavicon.isChecked());
|
||||||
AddNewTorrentDialog::setSavePathHistoryLength(m_spinBoxSavePathHistoryLength.value());
|
pref->setAddNewTorrentDialogSavePathHistoryLength(m_spinBoxSavePathHistoryLength.value());
|
||||||
pref->setSpeedWidgetEnabled(m_checkBoxSpeedWidgetEnabled.isChecked());
|
pref->setSpeedWidgetEnabled(m_checkBoxSpeedWidgetEnabled.isChecked());
|
||||||
#ifndef Q_OS_MACOS
|
#ifndef Q_OS_MACOS
|
||||||
pref->setIconsInMenusEnabled(m_checkBoxIconsInMenusEnabled.isChecked());
|
pref->setIconsInMenusEnabled(m_checkBoxIconsInMenusEnabled.isChecked());
|
||||||
|
@ -786,8 +785,8 @@ void AdvancedSettings::loadAdvancedSettings()
|
||||||
m_checkBoxTrackerFavicon.setChecked(app()->mainWindow()->isDownloadTrackerFavicon());
|
m_checkBoxTrackerFavicon.setChecked(app()->mainWindow()->isDownloadTrackerFavicon());
|
||||||
addRow(DOWNLOAD_TRACKER_FAVICON, tr("Download tracker's favicon"), &m_checkBoxTrackerFavicon);
|
addRow(DOWNLOAD_TRACKER_FAVICON, tr("Download tracker's favicon"), &m_checkBoxTrackerFavicon);
|
||||||
// Save path history length
|
// Save path history length
|
||||||
m_spinBoxSavePathHistoryLength.setRange(AddNewTorrentDialog::minPathHistoryLength, AddNewTorrentDialog::maxPathHistoryLength);
|
m_spinBoxSavePathHistoryLength.setRange(0, 99);
|
||||||
m_spinBoxSavePathHistoryLength.setValue(AddNewTorrentDialog::savePathHistoryLength());
|
m_spinBoxSavePathHistoryLength.setValue(pref->addNewTorrentDialogSavePathHistoryLength());
|
||||||
addRow(SAVE_PATH_HISTORY_LENGTH, tr("Save path history length"), &m_spinBoxSavePathHistoryLength);
|
addRow(SAVE_PATH_HISTORY_LENGTH, tr("Save path history length"), &m_spinBoxSavePathHistoryLength);
|
||||||
// Enable speed graphs
|
// Enable speed graphs
|
||||||
m_checkBoxSpeedWidgetEnabled.setChecked(pref->isSpeedWidgetEnabled());
|
m_checkBoxSpeedWidgetEnabled.setChecked(pref->isSpeedWidgetEnabled());
|
||||||
|
|
|
@ -38,7 +38,7 @@
|
||||||
|
|
||||||
#include "guiapplicationcomponent.h"
|
#include "guiapplicationcomponent.h"
|
||||||
|
|
||||||
class AdvancedSettings final : public QTableWidget, public GUIApplicationComponent
|
class AdvancedSettings final : public GUIApplicationComponent<QTableWidget>
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_DISABLE_COPY_MOVE(AdvancedSettings)
|
Q_DISABLE_COPY_MOVE(AdvancedSettings)
|
||||||
|
|
200
src/gui/guiaddtorrentmanager.cpp
Normal file
200
src/gui/guiaddtorrentmanager.cpp
Normal file
|
@ -0,0 +1,200 @@
|
||||||
|
/*
|
||||||
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
|
* Copyright (C) 2015-2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||||
|
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*
|
||||||
|
* In addition, as a special exception, the copyright holders give permission to
|
||||||
|
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||||
|
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||||
|
* and distribute the linked executables. You must obey the GNU General Public
|
||||||
|
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||||
|
* modify file(s), you may extend this exception to your version of the file(s),
|
||||||
|
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||||
|
* exception statement from your version.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "guiaddtorrentmanager.h"
|
||||||
|
|
||||||
|
#include "base/bittorrent/session.h"
|
||||||
|
#include "base/bittorrent/torrentdescriptor.h"
|
||||||
|
#include "base/logger.h"
|
||||||
|
#include "base/net/downloadmanager.h"
|
||||||
|
#include "base/preferences.h"
|
||||||
|
#include "base/torrentfileguard.h"
|
||||||
|
#include "addnewtorrentdialog.h"
|
||||||
|
#include "interfaces/iguiapplication.h"
|
||||||
|
#include "mainwindow.h"
|
||||||
|
#include "raisedmessagebox.h"
|
||||||
|
|
||||||
|
GUIAddTorrentManager::GUIAddTorrentManager(IGUIApplication *app, BitTorrent::Session *session, QObject *parent)
|
||||||
|
: GUIApplicationComponent(app, session, parent)
|
||||||
|
{
|
||||||
|
connect(btSession(), &BitTorrent::Session::metadataDownloaded, this, &GUIAddTorrentManager::onMetadataDownloaded);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GUIAddTorrentManager::addTorrent(const QString &source, const BitTorrent::AddTorrentParams ¶ms, const AddTorrentOption option)
|
||||||
|
{
|
||||||
|
// `source`: .torrent file path, magnet URI or URL
|
||||||
|
|
||||||
|
if (source.isEmpty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const auto *pref = Preferences::instance();
|
||||||
|
|
||||||
|
if ((option == AddTorrentOption::SkipDialog)
|
||||||
|
|| ((option == AddTorrentOption::Default) && !pref->isAddNewTorrentDialogEnabled()))
|
||||||
|
{
|
||||||
|
return AddTorrentManager::addTorrent(source, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Net::DownloadManager::hasSupportedScheme(source))
|
||||||
|
{
|
||||||
|
LogMsg(tr("Downloading torrent... Source: \"%1\"").arg(source));
|
||||||
|
// Launch downloader
|
||||||
|
Net::DownloadManager::instance()->download(Net::DownloadRequest(source).limit(pref->getTorrentFileSizeLimit())
|
||||||
|
, pref->useProxyForGeneralPurposes(), this, &GUIAddTorrentManager::onDownloadFinished);
|
||||||
|
m_downloadedTorrents[source] = params;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (const auto parseResult = BitTorrent::TorrentDescriptor::parse(source))
|
||||||
|
{
|
||||||
|
return processTorrent(source, parseResult.value(), params);
|
||||||
|
}
|
||||||
|
else if (source.startsWith(u"magnet:", Qt::CaseInsensitive))
|
||||||
|
{
|
||||||
|
handleAddTorrentFailed(source, parseResult.error());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Path decodedPath {source.startsWith(u"file://", Qt::CaseInsensitive)
|
||||||
|
? QUrl::fromEncoded(source.toLocal8Bit()).toLocalFile() : source};
|
||||||
|
auto torrentFileGuard = std::make_shared<TorrentFileGuard>(decodedPath);
|
||||||
|
if (const auto loadResult = BitTorrent::TorrentDescriptor::loadFromFile(decodedPath))
|
||||||
|
{
|
||||||
|
const BitTorrent::TorrentDescriptor &torrentDescriptor = loadResult.value();
|
||||||
|
const bool isProcessing = processTorrent(source, torrentDescriptor, params);
|
||||||
|
if (isProcessing)
|
||||||
|
setTorrentFileGuard(source, torrentFileGuard);
|
||||||
|
return isProcessing;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
handleAddTorrentFailed(decodedPath.toString(), loadResult.error());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GUIAddTorrentManager::onDownloadFinished(const Net::DownloadResult &result)
|
||||||
|
{
|
||||||
|
const QString &source = result.url;
|
||||||
|
const BitTorrent::AddTorrentParams addTorrentParams = m_downloadedTorrents.take(source);
|
||||||
|
|
||||||
|
switch (result.status)
|
||||||
|
{
|
||||||
|
case Net::DownloadStatus::Success:
|
||||||
|
if (const auto loadResult = BitTorrent::TorrentDescriptor::load(result.data))
|
||||||
|
processTorrent(source, loadResult.value(), addTorrentParams);
|
||||||
|
else
|
||||||
|
handleAddTorrentFailed(source, loadResult.error());
|
||||||
|
break;
|
||||||
|
case Net::DownloadStatus::RedirectedToMagnet:
|
||||||
|
if (const auto parseResult = BitTorrent::TorrentDescriptor::parse(result.magnetURI))
|
||||||
|
processTorrent(source, parseResult.value(), addTorrentParams);
|
||||||
|
else
|
||||||
|
handleAddTorrentFailed(source, parseResult.error());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
handleAddTorrentFailed(source, result.errorString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GUIAddTorrentManager::onMetadataDownloaded(const BitTorrent::TorrentInfo &metadata)
|
||||||
|
{
|
||||||
|
Q_ASSERT(metadata.isValid());
|
||||||
|
if (!metadata.isValid()) [[unlikely]]
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (const auto &[infoHash, dialog] : m_dialogs.asKeyValueRange())
|
||||||
|
{
|
||||||
|
if (metadata.matchesInfoHash(infoHash))
|
||||||
|
dialog->updateMetadata(metadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GUIAddTorrentManager::processTorrent(const QString &source, const BitTorrent::TorrentDescriptor &torrentDescr, const BitTorrent::AddTorrentParams ¶ms)
|
||||||
|
{
|
||||||
|
const bool hasMetadata = torrentDescr.info().has_value();
|
||||||
|
const BitTorrent::InfoHash infoHash = torrentDescr.infoHash();
|
||||||
|
|
||||||
|
// Prevent showing the dialog if download is already present
|
||||||
|
if (BitTorrent::Torrent *torrent = btSession()->findTorrent(infoHash))
|
||||||
|
{
|
||||||
|
if (hasMetadata)
|
||||||
|
{
|
||||||
|
// Trying to set metadata to existing torrent in case if it has none
|
||||||
|
torrent->setMetadata(*torrentDescr.info());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (torrent->isPrivate() || (hasMetadata && torrentDescr.info()->isPrivate()))
|
||||||
|
{
|
||||||
|
handleDuplicateTorrent(source, torrent, tr("Trackers cannot be merged because it is a private torrent"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bool mergeTrackers = btSession()->isMergeTrackersEnabled();
|
||||||
|
if (Preferences::instance()->confirmMergeTrackers())
|
||||||
|
{
|
||||||
|
const QMessageBox::StandardButton btn = RaisedMessageBox::question(app()->mainWindow(), tr("Torrent is already present")
|
||||||
|
, tr("Torrent '%1' is already in the transfer list. Do you want to merge trackers from new source?").arg(torrent->name())
|
||||||
|
, (QMessageBox::Yes | QMessageBox::No), QMessageBox::Yes);
|
||||||
|
mergeTrackers = (btn == QMessageBox::Yes);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mergeTrackers)
|
||||||
|
{
|
||||||
|
torrent->addTrackers(torrentDescr.trackers());
|
||||||
|
torrent->addUrlSeeds(torrentDescr.urlSeeds());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasMetadata)
|
||||||
|
btSession()->downloadMetadata(torrentDescr);
|
||||||
|
|
||||||
|
auto *dlg = new AddNewTorrentDialog(torrentDescr, params, app()->mainWindow());
|
||||||
|
dlg->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
m_dialogs[infoHash] = dlg;
|
||||||
|
connect(dlg, &QDialog::finished, this, [this, source, infoHash, dlg](int result)
|
||||||
|
{
|
||||||
|
if (dlg->isDoNotDeleteTorrentChecked())
|
||||||
|
releaseTorrentFileGuard(source);
|
||||||
|
|
||||||
|
if (result == QDialog::Accepted)
|
||||||
|
addTorrentToSession(source, dlg->torrentDescriptor(), dlg->addTorrentParams());
|
||||||
|
|
||||||
|
m_dialogs.remove(infoHash);
|
||||||
|
});
|
||||||
|
dlg->show();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
|
* Copyright (C) 2015-2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||||
|
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License
|
* modify it under the terms of the GNU General Public License
|
||||||
|
@ -26,14 +27,48 @@
|
||||||
* exception statement from your version.
|
* exception statement from your version.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "base/addtorrentmanager.h"
|
||||||
|
#include "base/bittorrent/infohash.h"
|
||||||
#include "guiapplicationcomponent.h"
|
#include "guiapplicationcomponent.h"
|
||||||
|
|
||||||
GUIApplicationComponent::GUIApplicationComponent(IGUIApplication *app)
|
#include <QHash>
|
||||||
: ApplicationComponent(app)
|
|
||||||
|
namespace BitTorrent
|
||||||
{
|
{
|
||||||
|
class TorrentDescriptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
IGUIApplication *GUIApplicationComponent::app() const
|
namespace Net
|
||||||
{
|
{
|
||||||
return static_cast<IGUIApplication *>(ApplicationComponent::app());
|
struct DownloadResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class AddNewTorrentDialog;
|
||||||
|
|
||||||
|
enum class AddTorrentOption
|
||||||
|
{
|
||||||
|
Default,
|
||||||
|
ShowDialog,
|
||||||
|
SkipDialog,
|
||||||
|
};
|
||||||
|
|
||||||
|
class GUIAddTorrentManager : public GUIApplicationComponent<AddTorrentManager>
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_DISABLE_COPY_MOVE(GUIAddTorrentManager)
|
||||||
|
|
||||||
|
public:
|
||||||
|
GUIAddTorrentManager(IGUIApplication *app, BitTorrent::Session *session, QObject *parent = nullptr);
|
||||||
|
|
||||||
|
bool addTorrent(const QString &source, const BitTorrent::AddTorrentParams ¶ms = {}, AddTorrentOption option = AddTorrentOption::Default);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void onDownloadFinished(const Net::DownloadResult &result);
|
||||||
|
void onMetadataDownloaded(const BitTorrent::TorrentInfo &metadata);
|
||||||
|
bool processTorrent(const QString &source, const BitTorrent::TorrentDescriptor &torrentDescr, const BitTorrent::AddTorrentParams ¶ms);
|
||||||
|
|
||||||
|
QHash<QString, BitTorrent::AddTorrentParams> m_downloadedTorrents;
|
||||||
|
QHash<BitTorrent::InfoHash, AddNewTorrentDialog *> m_dialogs;
|
||||||
|
};
|
|
@ -29,14 +29,38 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "base/applicationcomponent.h"
|
#include "base/applicationcomponent.h"
|
||||||
#include "interfaces/iguiapplication.h"
|
|
||||||
|
|
||||||
class GUIApplicationComponent : public ApplicationComponent
|
class IGUIApplication;
|
||||||
|
|
||||||
|
template <typename Base>
|
||||||
|
class GUIApplicationComponent : public Base, public ApplicationComponentBase
|
||||||
{
|
{
|
||||||
Q_DISABLE_COPY_MOVE(GUIApplicationComponent)
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit GUIApplicationComponent(IGUIApplication *app);
|
template <typename... Args>
|
||||||
|
explicit GUIApplicationComponent(IGUIApplication *app, Args&&... args)
|
||||||
|
: Base(std::forward<Args>(args)...)
|
||||||
|
, ApplicationComponentBase(reinterpret_cast<IApplication *>(app))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
IGUIApplication *app() const override;
|
IGUIApplication *app() const
|
||||||
|
{
|
||||||
|
return reinterpret_cast<IGUIApplication *>(ApplicationComponentBase::app());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <IsApplicationComponent Base>
|
||||||
|
class GUIApplicationComponent<Base> : public Base
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
template <typename... Args>
|
||||||
|
explicit GUIApplicationComponent(IGUIApplication *app, Args&&... args)
|
||||||
|
: Base(reinterpret_cast<IApplication *>(app), std::forward<Args>(args)...)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
IGUIApplication *app() const
|
||||||
|
{
|
||||||
|
return reinterpret_cast<IGUIApplication *>(ApplicationComponentBase::app());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -31,6 +31,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "base/interfaces/iapplication.h"
|
#include "base/interfaces/iapplication.h"
|
||||||
|
#include "gui/guiaddtorrentmanager.h"
|
||||||
#include "gui/windowstate.h"
|
#include "gui/windowstate.h"
|
||||||
|
|
||||||
class DesktopIntegration;
|
class DesktopIntegration;
|
||||||
|
@ -41,6 +42,8 @@ class IGUIApplication : public IApplication
|
||||||
public:
|
public:
|
||||||
~IGUIApplication() override = default;
|
~IGUIApplication() override = default;
|
||||||
|
|
||||||
|
GUIAddTorrentManager *addTorrentManager() const override = 0;
|
||||||
|
|
||||||
virtual DesktopIntegration *desktopIntegration() = 0;
|
virtual DesktopIntegration *desktopIntegration() = 0;
|
||||||
virtual MainWindow *mainWindow() = 0;
|
virtual MainWindow *mainWindow() = 0;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
|
* Copyright (C) 2022-2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
|
@ -75,13 +75,13 @@
|
||||||
#include "base/utils/password.h"
|
#include "base/utils/password.h"
|
||||||
#include "base/version.h"
|
#include "base/version.h"
|
||||||
#include "aboutdialog.h"
|
#include "aboutdialog.h"
|
||||||
#include "addnewtorrentdialog.h"
|
|
||||||
#include "autoexpandabledialog.h"
|
#include "autoexpandabledialog.h"
|
||||||
#include "cookiesdialog.h"
|
#include "cookiesdialog.h"
|
||||||
#include "desktopintegration.h"
|
#include "desktopintegration.h"
|
||||||
#include "downloadfromurldialog.h"
|
#include "downloadfromurldialog.h"
|
||||||
#include "executionlogwidget.h"
|
#include "executionlogwidget.h"
|
||||||
#include "hidabletabwidget.h"
|
#include "hidabletabwidget.h"
|
||||||
|
#include "interfaces/iguiapplication.h"
|
||||||
#include "lineedit.h"
|
#include "lineedit.h"
|
||||||
#include "optionsdialog.h"
|
#include "optionsdialog.h"
|
||||||
#include "powermanagement/powermanagement.h"
|
#include "powermanagement/powermanagement.h"
|
||||||
|
@ -184,7 +184,6 @@ MainWindow::MainWindow(IGUIApplication *app, WindowState initialState)
|
||||||
updateAltSpeedsBtn(BitTorrent::Session::instance()->isAltGlobalSpeedLimitEnabled());
|
updateAltSpeedsBtn(BitTorrent::Session::instance()->isAltGlobalSpeedLimitEnabled());
|
||||||
|
|
||||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::speedLimitModeChanged, this, &MainWindow::updateAltSpeedsBtn);
|
connect(BitTorrent::Session::instance(), &BitTorrent::Session::speedLimitModeChanged, this, &MainWindow::updateAltSpeedsBtn);
|
||||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::recursiveTorrentDownloadPossible, this, &MainWindow::askRecursiveTorrentDownloadConfirmation);
|
|
||||||
|
|
||||||
qDebug("create tabWidget");
|
qDebug("create tabWidget");
|
||||||
m_tabs = new HidableTabWidget(this);
|
m_tabs = new HidableTabWidget(this);
|
||||||
|
@ -673,7 +672,7 @@ void MainWindow::displayRSSTab(bool enable)
|
||||||
// RSS tab
|
// RSS tab
|
||||||
if (!m_rssWidget)
|
if (!m_rssWidget)
|
||||||
{
|
{
|
||||||
m_rssWidget = new RSSWidget(m_tabs);
|
m_rssWidget = new RSSWidget(app(), m_tabs);
|
||||||
connect(m_rssWidget.data(), &RSSWidget::unreadCountUpdated, this, &MainWindow::handleRSSUnreadCountUpdated);
|
connect(m_rssWidget.data(), &RSSWidget::unreadCountUpdated, this, &MainWindow::handleRSSUnreadCountUpdated);
|
||||||
#ifdef Q_OS_MACOS
|
#ifdef Q_OS_MACOS
|
||||||
m_tabs->addTab(m_rssWidget, tr("RSS (%1)").arg(RSS::Session::instance()->rootFolder()->unreadCount()));
|
m_tabs->addTab(m_rssWidget, tr("RSS (%1)").arg(RSS::Session::instance()->rootFolder()->unreadCount()));
|
||||||
|
@ -919,37 +918,6 @@ void MainWindow::displayExecutionLogTab()
|
||||||
|
|
||||||
// End of keyboard shortcuts slots
|
// End of keyboard shortcuts slots
|
||||||
|
|
||||||
void MainWindow::askRecursiveTorrentDownloadConfirmation(const BitTorrent::Torrent *torrent)
|
|
||||||
{
|
|
||||||
if (!Preferences::instance()->isRecursiveDownloadEnabled())
|
|
||||||
return;
|
|
||||||
|
|
||||||
const auto torrentID = torrent->id();
|
|
||||||
|
|
||||||
QMessageBox *confirmBox = new QMessageBox(QMessageBox::Question, tr("Recursive download confirmation")
|
|
||||||
, tr("The torrent '%1' contains .torrent files, do you want to proceed with their downloads?").arg(torrent->name())
|
|
||||||
, (QMessageBox::Yes | QMessageBox::No | QMessageBox::NoToAll), this);
|
|
||||||
confirmBox->setAttribute(Qt::WA_DeleteOnClose);
|
|
||||||
|
|
||||||
const QAbstractButton *yesButton = confirmBox->button(QMessageBox::Yes);
|
|
||||||
QAbstractButton *neverButton = confirmBox->button(QMessageBox::NoToAll);
|
|
||||||
neverButton->setText(tr("Never"));
|
|
||||||
|
|
||||||
connect(confirmBox, &QMessageBox::buttonClicked, this
|
|
||||||
, [torrentID, yesButton, neverButton](const QAbstractButton *button)
|
|
||||||
{
|
|
||||||
if (button == yesButton)
|
|
||||||
{
|
|
||||||
BitTorrent::Session::instance()->recursiveTorrentDownload(torrentID);
|
|
||||||
}
|
|
||||||
else if (button == neverButton)
|
|
||||||
{
|
|
||||||
Preferences::instance()->setRecursiveDownloadEnabled(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
confirmBox->open();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainWindow::on_actionSetGlobalSpeedLimits_triggered()
|
void MainWindow::on_actionSetGlobalSpeedLimits_triggered()
|
||||||
{
|
{
|
||||||
auto *dialog = new SpeedLimitDialog {this};
|
auto *dialog = new SpeedLimitDialog {this};
|
||||||
|
@ -1124,7 +1092,6 @@ void MainWindow::keyPressEvent(QKeyEvent *event)
|
||||||
|
|
||||||
if (mimeData->hasText())
|
if (mimeData->hasText())
|
||||||
{
|
{
|
||||||
const bool useTorrentAdditionDialog = AddNewTorrentDialog::isEnabled();
|
|
||||||
const QStringList lines = mimeData->text().split(u'\n', Qt::SkipEmptyParts);
|
const QStringList lines = mimeData->text().split(u'\n', Qt::SkipEmptyParts);
|
||||||
|
|
||||||
for (QString line : lines)
|
for (QString line : lines)
|
||||||
|
@ -1134,10 +1101,7 @@ void MainWindow::keyPressEvent(QKeyEvent *event)
|
||||||
if (!isTorrentLink(line))
|
if (!isTorrentLink(line))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (useTorrentAdditionDialog)
|
app()->addTorrentManager()->addTorrent(line);
|
||||||
AddNewTorrentDialog::show(line, this);
|
|
||||||
else
|
|
||||||
BitTorrent::Session::instance()->addTorrent(line);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -1320,14 +1284,8 @@ void MainWindow::dropEvent(QDropEvent *event)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Download torrents
|
// Download torrents
|
||||||
const bool useTorrentAdditionDialog = AddNewTorrentDialog::isEnabled();
|
|
||||||
for (const QString &file : asConst(torrentFiles))
|
for (const QString &file : asConst(torrentFiles))
|
||||||
{
|
app()->addTorrentManager()->addTorrent(file);
|
||||||
if (useTorrentAdditionDialog)
|
|
||||||
AddNewTorrentDialog::show(file, this);
|
|
||||||
else
|
|
||||||
BitTorrent::Session::instance()->addTorrent(file);
|
|
||||||
}
|
|
||||||
if (!torrentFiles.isEmpty()) return;
|
if (!torrentFiles.isEmpty()) return;
|
||||||
|
|
||||||
// Create torrent
|
// Create torrent
|
||||||
|
@ -1364,22 +1322,14 @@ void MainWindow::on_actionOpen_triggered()
|
||||||
Preferences *const pref = Preferences::instance();
|
Preferences *const pref = Preferences::instance();
|
||||||
// Open File Open Dialog
|
// Open File Open Dialog
|
||||||
// Note: it is possible to select more than one file
|
// Note: it is possible to select more than one file
|
||||||
const QStringList pathsList =
|
const QStringList pathsList = QFileDialog::getOpenFileNames(this, tr("Open Torrent Files")
|
||||||
QFileDialog::getOpenFileNames(this, tr("Open Torrent Files"), pref->getMainLastDir().data(),
|
, pref->getMainLastDir().data(), tr("Torrent Files") + u" (*" + TORRENT_FILE_EXTENSION + u')');
|
||||||
tr("Torrent Files") + u" (*" + TORRENT_FILE_EXTENSION + u')');
|
|
||||||
|
|
||||||
if (pathsList.isEmpty())
|
if (pathsList.isEmpty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const bool useTorrentAdditionDialog = AddNewTorrentDialog::isEnabled();
|
|
||||||
|
|
||||||
for (const QString &file : pathsList)
|
for (const QString &file : pathsList)
|
||||||
{
|
app()->addTorrentManager()->addTorrent(file);
|
||||||
if (useTorrentAdditionDialog)
|
|
||||||
AddNewTorrentDialog::show(file, this);
|
|
||||||
else
|
|
||||||
BitTorrent::Session::instance()->addTorrent(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save last dir to remember it
|
// Save last dir to remember it
|
||||||
const Path topDir {pathsList.at(0)};
|
const Path topDir {pathsList.at(0)};
|
||||||
|
@ -1576,14 +1526,8 @@ void MainWindow::reloadTorrentStats(const QVector<BitTorrent::Torrent *> &torren
|
||||||
|
|
||||||
void MainWindow::downloadFromURLList(const QStringList &urlList)
|
void MainWindow::downloadFromURLList(const QStringList &urlList)
|
||||||
{
|
{
|
||||||
const bool useTorrentAdditionDialog = AddNewTorrentDialog::isEnabled();
|
|
||||||
for (const QString &url : urlList)
|
for (const QString &url : urlList)
|
||||||
{
|
app()->addTorrentManager()->addTorrent(url);
|
||||||
if (useTorrentAdditionDialog)
|
|
||||||
AddNewTorrentDialog::show(url, this);
|
|
||||||
else
|
|
||||||
BitTorrent::Session::instance()->addTorrent(url);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*****************************************************
|
/*****************************************************
|
||||||
|
|
|
@ -71,7 +71,7 @@ namespace Ui
|
||||||
class MainWindow;
|
class MainWindow;
|
||||||
}
|
}
|
||||||
|
|
||||||
class MainWindow final : public QMainWindow, public GUIApplicationComponent
|
class MainWindow final : public GUIApplicationComponent<QMainWindow>
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_DISABLE_COPY_MOVE(MainWindow)
|
Q_DISABLE_COPY_MOVE(MainWindow)
|
||||||
|
@ -122,7 +122,6 @@ private slots:
|
||||||
void reloadSessionStats();
|
void reloadSessionStats();
|
||||||
void reloadTorrentStats(const QVector<BitTorrent::Torrent *> &torrents);
|
void reloadTorrentStats(const QVector<BitTorrent::Torrent *> &torrents);
|
||||||
void loadPreferences();
|
void loadPreferences();
|
||||||
void askRecursiveTorrentDownloadConfirmation(const BitTorrent::Torrent *torrent);
|
|
||||||
void optionsSaved();
|
void optionsSaved();
|
||||||
void toggleAlternativeSpeeds();
|
void toggleAlternativeSpeeds();
|
||||||
|
|
||||||
|
|
|
@ -110,8 +110,7 @@ namespace
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
OptionsDialog::OptionsDialog(IGUIApplication *app, QWidget *parent)
|
OptionsDialog::OptionsDialog(IGUIApplication *app, QWidget *parent)
|
||||||
: QDialog(parent)
|
: GUIApplicationComponent(app, parent)
|
||||||
, GUIApplicationComponent(app)
|
|
||||||
, m_ui {new Ui::OptionsDialog}
|
, m_ui {new Ui::OptionsDialog}
|
||||||
, m_storeDialogSize {SETTINGS_KEY(u"Size"_s)}
|
, m_storeDialogSize {SETTINGS_KEY(u"Size"_s)}
|
||||||
, m_storeHSplitterSize {SETTINGS_KEY(u"HorizontalSplitterSizes"_s)}
|
, m_storeHSplitterSize {SETTINGS_KEY(u"HorizontalSplitterSizes"_s)}
|
||||||
|
@ -484,8 +483,8 @@ void OptionsDialog::loadDownloadsTabOptions()
|
||||||
const auto *pref = Preferences::instance();
|
const auto *pref = Preferences::instance();
|
||||||
const auto *session = BitTorrent::Session::instance();
|
const auto *session = BitTorrent::Session::instance();
|
||||||
|
|
||||||
m_ui->checkAdditionDialog->setChecked(AddNewTorrentDialog::isEnabled());
|
m_ui->checkAdditionDialog->setChecked(pref->isAddNewTorrentDialogEnabled());
|
||||||
m_ui->checkAdditionDialogFront->setChecked(AddNewTorrentDialog::isTopLevel());
|
m_ui->checkAdditionDialogFront->setChecked(pref->isAddNewTorrentDialogTopLevel());
|
||||||
|
|
||||||
m_ui->contentLayoutComboBox->setCurrentIndex(static_cast<int>(session->torrentContentLayout()));
|
m_ui->contentLayoutComboBox->setCurrentIndex(static_cast<int>(session->torrentContentLayout()));
|
||||||
m_ui->checkAddToQueueTop->setChecked(session->isAddTorrentToQueueTop());
|
m_ui->checkAddToQueueTop->setChecked(session->isAddTorrentToQueueTop());
|
||||||
|
@ -686,8 +685,8 @@ void OptionsDialog::saveDownloadsTabOptions() const
|
||||||
auto *pref = Preferences::instance();
|
auto *pref = Preferences::instance();
|
||||||
auto *session = BitTorrent::Session::instance();
|
auto *session = BitTorrent::Session::instance();
|
||||||
|
|
||||||
AddNewTorrentDialog::setEnabled(useAdditionDialog());
|
pref->setAddNewTorrentDialogEnabled(useAdditionDialog());
|
||||||
AddNewTorrentDialog::setTopLevel(m_ui->checkAdditionDialogFront->isChecked());
|
pref->setAddNewTorrentDialogTopLevel(m_ui->checkAdditionDialogFront->isChecked());
|
||||||
|
|
||||||
session->setTorrentContentLayout(static_cast<BitTorrent::TorrentContentLayout>(m_ui->contentLayoutComboBox->currentIndex()));
|
session->setTorrentContentLayout(static_cast<BitTorrent::TorrentContentLayout>(m_ui->contentLayoutComboBox->currentIndex()));
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,7 @@ namespace Ui
|
||||||
class OptionsDialog;
|
class OptionsDialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
class OptionsDialog final : public QDialog, public GUIApplicationComponent
|
class OptionsDialog final : public GUIApplicationComponent<QDialog>
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_DISABLE_COPY_MOVE(OptionsDialog)
|
Q_DISABLE_COPY_MOVE(OptionsDialog)
|
||||||
|
|
|
@ -39,7 +39,6 @@
|
||||||
#include <QShortcut>
|
#include <QShortcut>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
#include "base/bittorrent/session.h"
|
|
||||||
#include "base/global.h"
|
#include "base/global.h"
|
||||||
#include "base/net/downloadmanager.h"
|
#include "base/net/downloadmanager.h"
|
||||||
#include "base/preferences.h"
|
#include "base/preferences.h"
|
||||||
|
@ -47,17 +46,17 @@
|
||||||
#include "base/rss/rss_feed.h"
|
#include "base/rss/rss_feed.h"
|
||||||
#include "base/rss/rss_folder.h"
|
#include "base/rss/rss_folder.h"
|
||||||
#include "base/rss/rss_session.h"
|
#include "base/rss/rss_session.h"
|
||||||
#include "gui/addnewtorrentdialog.h"
|
|
||||||
#include "gui/autoexpandabledialog.h"
|
#include "gui/autoexpandabledialog.h"
|
||||||
|
#include "gui/interfaces/iguiapplication.h"
|
||||||
#include "gui/uithememanager.h"
|
#include "gui/uithememanager.h"
|
||||||
#include "articlelistwidget.h"
|
#include "articlelistwidget.h"
|
||||||
#include "automatedrssdownloader.h"
|
#include "automatedrssdownloader.h"
|
||||||
#include "feedlistwidget.h"
|
#include "feedlistwidget.h"
|
||||||
#include "ui_rsswidget.h"
|
#include "ui_rsswidget.h"
|
||||||
|
|
||||||
RSSWidget::RSSWidget(QWidget *parent)
|
RSSWidget::RSSWidget(IGUIApplication *app, QWidget *parent)
|
||||||
: QWidget(parent)
|
: GUIApplicationComponent(app, parent)
|
||||||
, m_ui(new Ui::RSSWidget)
|
, m_ui {new Ui::RSSWidget}
|
||||||
{
|
{
|
||||||
m_ui->setupUi(this);
|
m_ui->setupUi(this);
|
||||||
|
|
||||||
|
@ -370,13 +369,7 @@ void RSSWidget::downloadSelectedTorrents()
|
||||||
// Mark as read
|
// Mark as read
|
||||||
article->markAsRead();
|
article->markAsRead();
|
||||||
|
|
||||||
if (!article->torrentUrl().isEmpty())
|
app()->addTorrentManager()->addTorrent(article->torrentUrl());
|
||||||
{
|
|
||||||
if (AddNewTorrentDialog::isEnabled())
|
|
||||||
AddNewTorrentDialog::show(article->torrentUrl(), window());
|
|
||||||
else
|
|
||||||
BitTorrent::Session::instance()->addTorrent(article->torrentUrl());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
* Copyright (C) 2017 Vladimir Golovnev <glassez@yandex.ru>
|
* Copyright (C) 2017-2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||||
* Copyright (C) 2006 Arnaud Demaiziere <arnaud@qbittorrent.org>
|
* Copyright (C) 2006 Arnaud Demaiziere <arnaud@qbittorrent.org>
|
||||||
*
|
*
|
||||||
|
@ -32,6 +32,8 @@
|
||||||
|
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
|
#include "gui/guiapplicationcomponent.h"
|
||||||
|
|
||||||
class QListWidgetItem;
|
class QListWidgetItem;
|
||||||
class QTreeWidgetItem;
|
class QTreeWidgetItem;
|
||||||
|
|
||||||
|
@ -43,14 +45,14 @@ namespace Ui
|
||||||
class RSSWidget;
|
class RSSWidget;
|
||||||
}
|
}
|
||||||
|
|
||||||
class RSSWidget : public QWidget
|
class RSSWidget final : public GUIApplicationComponent<QWidget>
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_DISABLE_COPY_MOVE(RSSWidget)
|
Q_DISABLE_COPY_MOVE(RSSWidget)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
RSSWidget(QWidget *parent);
|
explicit RSSWidget(IGUIApplication *app, QWidget *parent = nullptr);
|
||||||
~RSSWidget();
|
~RSSWidget() override;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void deleteSelectedItems();
|
void deleteSelectedItems();
|
||||||
|
|
|
@ -39,24 +39,22 @@
|
||||||
#include <QStandardItemModel>
|
#include <QStandardItemModel>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
#include "base/bittorrent/session.h"
|
|
||||||
#include "base/preferences.h"
|
#include "base/preferences.h"
|
||||||
#include "base/search/searchdownloadhandler.h"
|
#include "base/search/searchdownloadhandler.h"
|
||||||
#include "base/search/searchhandler.h"
|
#include "base/search/searchhandler.h"
|
||||||
#include "base/search/searchpluginmanager.h"
|
#include "base/search/searchpluginmanager.h"
|
||||||
#include "base/utils/misc.h"
|
#include "base/utils/misc.h"
|
||||||
#include "gui/addnewtorrentdialog.h"
|
#include "gui/interfaces/iguiapplication.h"
|
||||||
#include "gui/lineedit.h"
|
#include "gui/lineedit.h"
|
||||||
#include "gui/uithememanager.h"
|
#include "gui/uithememanager.h"
|
||||||
#include "gui/utils.h"
|
|
||||||
#include "searchsortmodel.h"
|
#include "searchsortmodel.h"
|
||||||
#include "ui_searchjobwidget.h"
|
#include "ui_searchjobwidget.h"
|
||||||
|
|
||||||
SearchJobWidget::SearchJobWidget(SearchHandler *searchHandler, QWidget *parent)
|
SearchJobWidget::SearchJobWidget(SearchHandler *searchHandler, IGUIApplication *app, QWidget *parent)
|
||||||
: QWidget(parent)
|
: GUIApplicationComponent(app, parent)
|
||||||
, m_ui(new Ui::SearchJobWidget)
|
, m_ui {new Ui::SearchJobWidget}
|
||||||
, m_searchHandler(searchHandler)
|
, m_searchHandler {searchHandler}
|
||||||
, m_nameFilteringMode(u"Search/FilteringMode"_s)
|
, m_nameFilteringMode {u"Search/FilteringMode"_s}
|
||||||
{
|
{
|
||||||
m_ui->setupUi(this);
|
m_ui->setupUi(this);
|
||||||
|
|
||||||
|
@ -289,12 +287,7 @@ void SearchJobWidget::downloadTorrent(const QModelIndex &rowIndex, const AddTorr
|
||||||
|
|
||||||
void SearchJobWidget::addTorrentToSession(const QString &source, const AddTorrentOption option)
|
void SearchJobWidget::addTorrentToSession(const QString &source, const AddTorrentOption option)
|
||||||
{
|
{
|
||||||
if (source.isEmpty()) return;
|
app()->addTorrentManager()->addTorrent(source, {}, option);
|
||||||
|
|
||||||
if ((option == AddTorrentOption::ShowDialog) || ((option == AddTorrentOption::Default) && AddNewTorrentDialog::isEnabled()))
|
|
||||||
AddNewTorrentDialog::show(source, this);
|
|
||||||
else
|
|
||||||
BitTorrent::Session::instance()->addTorrent(source);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SearchJobWidget::updateResultsCount()
|
void SearchJobWidget::updateResultsCount()
|
||||||
|
|
|
@ -32,6 +32,8 @@
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
#include "base/settingvalue.h"
|
#include "base/settingvalue.h"
|
||||||
|
#include "gui/guiaddtorrentmanager.h"
|
||||||
|
#include "gui/guiapplicationcomponent.h"
|
||||||
|
|
||||||
#define ENGINE_URL_COLUMN 4
|
#define ENGINE_URL_COLUMN 4
|
||||||
#define URL_COLUMN 5
|
#define URL_COLUMN 5
|
||||||
|
@ -52,7 +54,7 @@ namespace Ui
|
||||||
class SearchJobWidget;
|
class SearchJobWidget;
|
||||||
}
|
}
|
||||||
|
|
||||||
class SearchJobWidget final : public QWidget
|
class SearchJobWidget final : public GUIApplicationComponent<QWidget>
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_DISABLE_COPY_MOVE(SearchJobWidget)
|
Q_DISABLE_COPY_MOVE(SearchJobWidget)
|
||||||
|
@ -74,7 +76,7 @@ public:
|
||||||
NoResults
|
NoResults
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit SearchJobWidget(SearchHandler *searchHandler, QWidget *parent = nullptr);
|
SearchJobWidget(SearchHandler *searchHandler, IGUIApplication *app, QWidget *parent = nullptr);
|
||||||
~SearchJobWidget() override;
|
~SearchJobWidget() override;
|
||||||
|
|
||||||
Status status() const;
|
Status status() const;
|
||||||
|
@ -94,13 +96,6 @@ private slots:
|
||||||
void displayColumnHeaderMenu();
|
void displayColumnHeaderMenu();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum class AddTorrentOption
|
|
||||||
{
|
|
||||||
Default,
|
|
||||||
ShowDialog,
|
|
||||||
SkipDialog,
|
|
||||||
};
|
|
||||||
|
|
||||||
void loadSettings();
|
void loadSettings();
|
||||||
void saveSettings() const;
|
void saveSettings() const;
|
||||||
void updateFilter();
|
void updateFilter();
|
||||||
|
|
|
@ -53,6 +53,7 @@
|
||||||
#include "base/search/searchpluginmanager.h"
|
#include "base/search/searchpluginmanager.h"
|
||||||
#include "base/utils/foreignapps.h"
|
#include "base/utils/foreignapps.h"
|
||||||
#include "gui/desktopintegration.h"
|
#include "gui/desktopintegration.h"
|
||||||
|
#include "gui/interfaces/iguiapplication.h"
|
||||||
#include "gui/mainwindow.h"
|
#include "gui/mainwindow.h"
|
||||||
#include "gui/uithememanager.h"
|
#include "gui/uithememanager.h"
|
||||||
#include "pluginselectdialog.h"
|
#include "pluginselectdialog.h"
|
||||||
|
@ -84,8 +85,7 @@ namespace
|
||||||
}
|
}
|
||||||
|
|
||||||
SearchWidget::SearchWidget(IGUIApplication *app, MainWindow *mainWindow)
|
SearchWidget::SearchWidget(IGUIApplication *app, MainWindow *mainWindow)
|
||||||
: QWidget(mainWindow)
|
: GUIApplicationComponent(app, mainWindow)
|
||||||
, GUIApplicationComponent(app)
|
|
||||||
, m_ui {new Ui::SearchWidget()}
|
, m_ui {new Ui::SearchWidget()}
|
||||||
, m_mainWindow {mainWindow}
|
, m_mainWindow {mainWindow}
|
||||||
{
|
{
|
||||||
|
@ -347,7 +347,7 @@ void SearchWidget::on_searchButton_clicked()
|
||||||
auto *searchHandler = SearchPluginManager::instance()->startSearch(pattern, selectedCategory(), plugins);
|
auto *searchHandler = SearchPluginManager::instance()->startSearch(pattern, selectedCategory(), plugins);
|
||||||
|
|
||||||
// Tab Addition
|
// Tab Addition
|
||||||
auto *newTab = new SearchJobWidget(searchHandler, this);
|
auto *newTab = new SearchJobWidget(searchHandler, app(), this);
|
||||||
m_allTabs.append(newTab);
|
m_allTabs.append(newTab);
|
||||||
|
|
||||||
QString tabName = pattern;
|
QString tabName = pattern;
|
||||||
|
|
|
@ -48,7 +48,7 @@ namespace Ui
|
||||||
class SearchWidget;
|
class SearchWidget;
|
||||||
}
|
}
|
||||||
|
|
||||||
class SearchWidget : public QWidget, public GUIApplicationComponent
|
class SearchWidget : public GUIApplicationComponent<QWidget>
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_DISABLE_COPY_MOVE(SearchWidget)
|
Q_DISABLE_COPY_MOVE(SearchWidget)
|
||||||
|
|
|
@ -38,8 +38,7 @@
|
||||||
#include "apierror.h"
|
#include "apierror.h"
|
||||||
|
|
||||||
APIController::APIController(IApplication *app, QObject *parent)
|
APIController::APIController(IApplication *app, QObject *parent)
|
||||||
: QObject(parent)
|
: ApplicationComponent(app, parent)
|
||||||
, ApplicationComponent(app)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ class QString;
|
||||||
using DataMap = QHash<QString, QByteArray>;
|
using DataMap = QHash<QString, QByteArray>;
|
||||||
using StringMap = QHash<QString, QString>;
|
using StringMap = QHash<QString, QString>;
|
||||||
|
|
||||||
class APIController : public QObject, public ApplicationComponent
|
class APIController : public ApplicationComponent<QObject>
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_DISABLE_COPY_MOVE(APIController)
|
Q_DISABLE_COPY_MOVE(APIController)
|
||||||
|
|
|
@ -47,6 +47,7 @@
|
||||||
#include "base/bittorrent/torrent.h"
|
#include "base/bittorrent/torrent.h"
|
||||||
#include "base/bittorrent/torrentdescriptor.h"
|
#include "base/bittorrent/torrentdescriptor.h"
|
||||||
#include "base/bittorrent/trackerentry.h"
|
#include "base/bittorrent/trackerentry.h"
|
||||||
|
#include "base/interfaces/iapplication.h"
|
||||||
#include "base/global.h"
|
#include "base/global.h"
|
||||||
#include "base/logger.h"
|
#include "base/logger.h"
|
||||||
#include "base/net/downloadmanager.h"
|
#include "base/net/downloadmanager.h"
|
||||||
|
@ -732,7 +733,7 @@ void TorrentsController::addAction()
|
||||||
if (!url.isEmpty())
|
if (!url.isEmpty())
|
||||||
{
|
{
|
||||||
Net::DownloadManager::instance()->setCookiesFromUrl(cookies, QUrl::fromEncoded(url.toUtf8()));
|
Net::DownloadManager::instance()->setCookiesFromUrl(cookies, QUrl::fromEncoded(url.toUtf8()));
|
||||||
partialSuccess |= BitTorrent::Session::instance()->addTorrent(url, addTorrentParams);
|
partialSuccess |= app()->addTorrentManager()->addTorrent(url, addTorrentParams);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -143,8 +143,7 @@ namespace
|
||||||
}
|
}
|
||||||
|
|
||||||
WebApplication::WebApplication(IApplication *app, QObject *parent)
|
WebApplication::WebApplication(IApplication *app, QObject *parent)
|
||||||
: QObject(parent)
|
: ApplicationComponent(app, parent)
|
||||||
, ApplicationComponent(app)
|
|
||||||
, m_cacheID {QString::number(Utils::Random::rand(), 36)}
|
, m_cacheID {QString::number(Utils::Random::rand(), 36)}
|
||||||
, m_authController {new AuthController(this, app, this)}
|
, m_authController {new AuthController(this, app, this)}
|
||||||
{
|
{
|
||||||
|
|
|
@ -58,7 +58,7 @@ class APIController;
|
||||||
class AuthController;
|
class AuthController;
|
||||||
class WebApplication;
|
class WebApplication;
|
||||||
|
|
||||||
class WebSession final : public QObject, public ApplicationComponent, public ISession
|
class WebSession final : public ApplicationComponent<QObject>, public ISession
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit WebSession(const QString &sid, IApplication *app);
|
explicit WebSession(const QString &sid, IApplication *app);
|
||||||
|
@ -83,8 +83,7 @@ private:
|
||||||
QMap<QString, APIController *> m_apiControllers;
|
QMap<QString, APIController *> m_apiControllers;
|
||||||
};
|
};
|
||||||
|
|
||||||
class WebApplication final
|
class WebApplication final : public ApplicationComponent<QObject>
|
||||||
: public QObject, public ApplicationComponent
|
|
||||||
, public Http::IRequestHandler, public ISessionManager
|
, public Http::IRequestHandler, public ISessionManager
|
||||||
, private Http::ResponseBuilder
|
, private Http::ResponseBuilder
|
||||||
{
|
{
|
||||||
|
|
|
@ -45,7 +45,7 @@ namespace Net
|
||||||
|
|
||||||
class WebApplication;
|
class WebApplication;
|
||||||
|
|
||||||
class WebUI : public QObject, public ApplicationComponent
|
class WebUI final : public ApplicationComponent<QObject>
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_DISABLE_COPY_MOVE(WebUI)
|
Q_DISABLE_COPY_MOVE(WebUI)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue