diff --git a/src/app/application.cpp b/src/app/application.cpp index ab58c1bb7..7667dc287 100644 --- a/src/app/application.cpp +++ b/src/app/application.cpp @@ -50,6 +50,7 @@ #include #ifndef DISABLE_GUI +#include #include #include #include @@ -63,6 +64,7 @@ #endif // Q_OS_MACOS #endif +#include "base/addtorrentmanager.h" #include "base/bittorrent/infohash.h" #include "base/bittorrent/session.h" #include "base/bittorrent/torrent.h" @@ -81,7 +83,6 @@ #include "base/search/searchpluginmanager.h" #include "base/settingsstorage.h" #include "base/torrentfileswatcher.h" -#include "base/utils/compare.h" #include "base/utils/fs.h" #include "base/utils/misc.h" #include "base/utils/string.h" @@ -91,7 +92,7 @@ #include "upgrade.h" #ifndef DISABLE_GUI -#include "gui/addnewtorrentdialog.h" +#include "gui/guiaddtorrentmanager.h" #include "gui/desktopintegration.h" #include "gui/mainwindow.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())); 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() @@ -744,18 +760,15 @@ void Application::processParams(const QBtCommandLineParameters ¶ms) // be shown and skipTorrentDialog is undefined. The other is when // skipTorrentDialog is false, meaning that the application setting // should be overridden. - const bool showDialog = !params.skipDialog.value_or(!AddNewTorrentDialog::isEnabled()); - if (showDialog) - { - for (const QString &torrentSource : params.torrentSources) - AddNewTorrentDialog::show(torrentSource, params.addTorrentParams, m_window); - } - else + AddTorrentOption addTorrentOption = AddTorrentOption::Default; + if (params.skipDialog.has_value()) + addTorrentOption = params.skipDialog.value() ? AddTorrentOption::SkipDialog : AddTorrentOption::ShowDialog; + for (const QString &torrentSource : params.torrentSources) + m_addTorrentManager->addTorrent(torrentSource, params.addTorrentParams, addTorrentOption); +#else + for (const QString &torrentSource : params.torrentSources) + m_addTorrentManager->addTorrent(torrentSource, params.addTorrentParams); #endif - { - for (const QString &torrentSource : params.torrentSources) - BitTorrent::Session::instance()->addTorrent(torrentSource, params.addTorrentParams); - } } 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::allTorrentsFinished, this, &Application::allTorrentsFinished, Qt::QueuedConnection); + m_addTorrentManager = new AddTorrentManagerImpl(this, BitTorrent::Session::instance(), this); + Net::GeoIPManager::initInstance(); TorrentFilesWatcher::initInstance(); 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 const auto *btSession = BitTorrent::Session::instance(); @@ -834,30 +849,25 @@ int Application::exec() , [this](const BitTorrent::Torrent *torrent, const QString &msg) { m_desktopIntegration->showNotification(tr("I/O Error", "i.e: Input/Output Error") - , 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)); + , 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)); }); - connect(btSession, &BitTorrent::Session::loadTorrentFailed, this - , [this](const QString &error) + connect(btSession, &BitTorrent::Session::torrentFinished, this + , [this](const BitTorrent::Torrent *torrent) { - m_desktopIntegration->showNotification(tr("Error"), tr("Failed to add torrent: %1").arg(error)); + 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::torrentAdded, this - , [this](const BitTorrent::Torrent *torrent) + connect(m_addTorrentManager, &AddTorrentManager::torrentAdded, this + , [this]([[maybe_unused]] const QString &source, 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 - , [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())); - }); - connect(btSession, &BitTorrent::Session::downloadFromUrlFailed, this - , [this](const QString &url, const QString &reason) + connect(m_addTorrentManager, &AddTorrentManager::addTorrentFailed, this + , [this](const QString &source, const QString &reason) { - m_desktopIntegration->showNotification(tr("URL download error") - , tr("Couldn't download file at URL '%1', reason: %2.").arg(url, 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); @@ -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 bool Application::event(QEvent *ev) { @@ -1018,7 +1079,7 @@ void Application::initializeTranslation() const QString localeStr = pref->getLocale(); if (m_qtTranslator.load((u"qtbase_" + localeStr), QLibraryInfo::path(QLibraryInfo::TranslationsPath)) - || m_qtTranslator.load((u"qt_" + localeStr), QLibraryInfo::path(QLibraryInfo::TranslationsPath))) + || m_qtTranslator.load((u"qt_" + localeStr), QLibraryInfo::path(QLibraryInfo::TranslationsPath))) { qDebug("Qt %s locale recognized, using translation.", qUtf8Printable(localeStr)); } @@ -1262,6 +1323,7 @@ void Application::cleanup() delete RSS::Session::instance(); TorrentFilesWatcher::freeInstance(); + delete m_addTorrentManager; BitTorrent::Session::freeInstance(); Net::GeoIPManager::freeInstance(); Net::DownloadManager::freeInstance(); @@ -1296,3 +1358,8 @@ void Application::cleanup() Utils::Misc::shutdownComputer(m_shutdownAct); } } + +AddTorrentManagerImpl *Application::addTorrentManager() const +{ + return m_addTorrentManager; +} diff --git a/src/app/application.h b/src/app/application.h index c08d7ef94..e57348ee3 100644 --- a/src/app/application.h +++ b/src/app/application.h @@ -72,6 +72,7 @@ class QProgressDialog; class DesktopIntegration; class MainWindow; +using AddTorrentManagerImpl = GUIAddTorrentManager; using BaseApplication = QApplication; using BaseIApplication = IGUIApplication; @@ -79,6 +80,7 @@ using BaseIApplication = IGUIApplication; class QSessionManager; #endif #else // DISABLE_GUI +using AddTorrentManagerImpl = AddTorrentManager; using BaseApplication = QCoreApplication; using BaseIApplication = IApplication; #endif // DISABLE_GUI @@ -149,6 +151,8 @@ private slots: #endif private: + AddTorrentManagerImpl *addTorrentManager() const override; + void initializeTranslation(); void processParams(const QBtCommandLineParameters ¶ms); void runExternalProgram(const QString &programTemplate, const BitTorrent::Torrent *torrent) const; @@ -168,6 +172,8 @@ private: #ifdef Q_OS_MACOS bool event(QEvent *) override; #endif + void askRecursiveTorrentDownloadConfirmation(const BitTorrent::Torrent *torrent); + void recursiveTorrentDownload(const BitTorrent::TorrentID &torrentID); #endif ApplicationInstanceManager *m_instanceManager = nullptr; @@ -197,6 +203,8 @@ private: SettingValue m_processMemoryPriority; #endif + AddTorrentManagerImpl *m_addTorrentManager = nullptr; + #ifndef DISABLE_GUI SettingValue m_startUpWindowState; SettingValue m_storeNotificationTorrentAdded; diff --git a/src/base/CMakeLists.txt b/src/base/CMakeLists.txt index 78283c276..36bb0f506 100644 --- a/src/base/CMakeLists.txt +++ b/src/base/CMakeLists.txt @@ -1,6 +1,7 @@ add_library(qbt_base STATIC # headers 3rdparty/expected.hpp + addtorrentmanager.h algorithm.h applicationcomponent.h asyncfilestorage.h @@ -106,6 +107,7 @@ add_library(qbt_base STATIC version.h # sources + addtorrentmanager.cpp applicationcomponent.cpp asyncfilestorage.cpp bittorrent/abstractfilestorage.cpp diff --git a/src/base/addtorrentmanager.cpp b/src/base/addtorrentmanager.cpp new file mode 100644 index 000000000..a094dbd85 --- /dev/null +++ b/src/base/addtorrentmanager.cpp @@ -0,0 +1,217 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2015-2023 Vladimir Golovnev + * Copyright (C) 2006 Christophe Dumez + * + * 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(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) +{ + 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); +} diff --git a/src/base/addtorrentmanager.h b/src/base/addtorrentmanager.h new file mode 100644 index 000000000..cb9f5346d --- /dev/null +++ b/src/base/addtorrentmanager.h @@ -0,0 +1,86 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2015-2023 Vladimir Golovnev + * Copyright (C) 2006 Christophe Dumez + * + * 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 + +#include +#include + +#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 +{ + 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); + 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 m_downloadedTorrents; + QHash m_sourcesByInfoHash; + QHash> m_guardedTorrentFiles; +}; diff --git a/src/base/applicationcomponent.cpp b/src/base/applicationcomponent.cpp index 6495cb9dc..5c60dbd82 100644 --- a/src/base/applicationcomponent.cpp +++ b/src/base/applicationcomponent.cpp @@ -28,12 +28,12 @@ #include "applicationcomponent.h" -ApplicationComponent::ApplicationComponent(IApplication *app) +ApplicationComponentBase::ApplicationComponentBase(IApplication *app) : m_app {app} { } -IApplication *ApplicationComponent::app() const +IApplication *ApplicationComponentBase::app() const { return m_app; } diff --git a/src/base/applicationcomponent.h b/src/base/applicationcomponent.h index 29d0fcbc7..2a762ab89 100644 --- a/src/base/applicationcomponent.h +++ b/src/base/applicationcomponent.h @@ -28,20 +28,41 @@ #pragma once +#include +#include + #include -#include "interfaces/iapplication.h" +class IApplication; -class ApplicationComponent +class ApplicationComponentBase { - Q_DISABLE_COPY_MOVE(ApplicationComponent) + Q_DISABLE_COPY_MOVE(ApplicationComponentBase) public: - explicit ApplicationComponent(IApplication *app); - virtual ~ApplicationComponent() = default; + virtual ~ApplicationComponentBase() = default; + + IApplication *app() const; - virtual IApplication *app() const; +protected: + explicit ApplicationComponentBase(IApplication *app); private: IApplication *m_app = nullptr; }; + +template +concept IsApplicationComponent = std::derived_from; + +template +requires (!IsApplicationComponent) +class ApplicationComponent : public Base, public ApplicationComponentBase +{ +public: + template + explicit ApplicationComponent(IApplication *app, Args&&... args) + : Base(std::forward(args)...) + , ApplicationComponentBase(app) + { + } +}; diff --git a/src/base/bittorrent/infohash.cpp b/src/base/bittorrent/infohash.cpp index 9558c4209..c4b514e8c 100644 --- a/src/base/bittorrent/infohash.cpp +++ b/src/base/bittorrent/infohash.cpp @@ -116,6 +116,11 @@ std::size_t BitTorrent::qHash(const BitTorrent::TorrentID &key, const std::size_ return ::qHash(static_cast(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) { return (static_cast(left) == static_cast(right)); diff --git a/src/base/bittorrent/infohash.h b/src/base/bittorrent/infohash.h index c79b34968..d870e0fd2 100644 --- a/src/base/bittorrent/infohash.h +++ b/src/base/bittorrent/infohash.h @@ -87,6 +87,7 @@ namespace BitTorrent }; 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); } diff --git a/src/base/bittorrent/session.h b/src/base/bittorrent/session.h index 81370375c..a6ff616f2 100644 --- a/src/base/bittorrent/session.h +++ b/src/base/bittorrent/session.h @@ -440,13 +440,11 @@ namespace BitTorrent virtual void banIP(const QString &ip) = 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 deleteTorrent(const TorrentID &id, DeleteOption deleteOption = DeleteOption::DeleteTorrent) = 0; virtual bool downloadMetadata(const TorrentDescriptor &torrentDescr) = 0; virtual bool cancelDownloadMetadata(const TorrentID &id) = 0; - virtual void recursiveTorrentDownload(const TorrentID &id) = 0; virtual void increaseTorrentsQueuePos(const QVector &ids) = 0; virtual void decreaseTorrentsQueuePos(const QVector &ids) = 0; virtual void topTorrentsQueuePos(const QVector &ids) = 0; @@ -454,17 +452,15 @@ namespace BitTorrent signals: void startupProgressUpdated(int progress); + void addTorrentFailed(const InfoHash &infoHash, const QString &reason); void allTorrentsFinished(); void categoryAdded(const QString &categoryName); void categoryRemoved(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 IPFilterParsed(bool error, int ruleCount); void loadTorrentFailed(const QString &error); void metadataDownloaded(const TorrentInfo &info); - void recursiveTorrentDownloadPossible(Torrent *torrent); void restored(); void speedLimitModeChanged(bool alternative); void statsUpdated(); diff --git a/src/base/bittorrent/sessionimpl.cpp b/src/base/bittorrent/sessionimpl.cpp index 821f4ee06..60b6ab561 100644 --- a/src/base/bittorrent/sessionimpl.cpp +++ b/src/base/bittorrent/sessionimpl.cpp @@ -77,11 +77,9 @@ #include "base/algorithm.h" #include "base/global.h" #include "base/logger.h" -#include "base/net/downloadmanager.h" #include "base/net/proxyconfigurationmanager.h" #include "base/preferences.h" #include "base/profile.h" -#include "base/torrentfileguard.h" #include "base/torrentfilter.h" #include "base/unicodestrings.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) { TorrentImpl *torrent = m_torrents.value(id); @@ -2593,40 +2562,6 @@ qsizetype SessionImpl::torrentsCount() const 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) { 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))) return false; - if (Torrent *torrent = 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; - } - - 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())); + if (findTorrent(infoHash)) return false; - } // It looks illogical that we don't just use an existing handle, // 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()) 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) { return !(torrent->isFinished() || torrent->isPaused() || torrent->isErrored()); @@ -5263,37 +5161,6 @@ void SessionImpl::disableIPFilter() 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 { return m_status; @@ -5406,6 +5273,7 @@ void SessionImpl::handleAddTorrentAlerts(const std::vector &alerts) if (const auto loadingTorrentsIter = m_loadingTorrents.find(TorrentID::fromInfoHash(infoHash)) ; loadingTorrentsIter != m_loadingTorrents.end()) { + emit addTorrentFailed(infoHash, msg); m_loadingTorrents.erase(loadingTorrentsIter); } else if (const auto downloadedMetadataIter = m_downloadedMetadata.find(TorrentID::fromInfoHash(infoHash)) diff --git a/src/base/bittorrent/sessionimpl.h b/src/base/bittorrent/sessionimpl.h index b1754dccb..24513a8e8 100644 --- a/src/base/bittorrent/sessionimpl.h +++ b/src/base/bittorrent/sessionimpl.h @@ -67,11 +67,6 @@ class FileSearcher; class FilterParserThread; class NativeSessionExtension; -namespace Net -{ - struct DownloadResult; -} - namespace BitTorrent { class InfoHash; @@ -414,13 +409,11 @@ namespace BitTorrent void banIP(const QString &ip) 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 deleteTorrent(const TorrentID &id, DeleteOption deleteOption = DeleteTorrent) override; bool downloadMetadata(const TorrentDescriptor &torrentDescr) override; bool cancelDownloadMetadata(const TorrentID &id) override; - void recursiveTorrentDownload(const TorrentID &id) override; void increaseTorrentsQueuePos(const QVector &ids) override; void decreaseTorrentsQueuePos(const QVector &ids) override; void topTorrentsQueuePos(const QVector &ids) override; @@ -477,7 +470,6 @@ namespace BitTorrent void generateResumeData(); void handleIPFilterParsed(int ruleCount); void handleIPFilterError(); - void handleDownloadFinished(const Net::DownloadResult &result); void fileSearchFinished(const TorrentID &id, const Path &savePath, const PathList &fileNames); private: @@ -746,7 +738,6 @@ namespace BitTorrent QHash m_torrents; QHash m_hybridTorrentsByAltID; QHash m_loadingTorrents; - QHash m_downloadedTorrents; QHash m_removingTorrents; QSet m_needSaveResumeDataTorrents; QHash m_changedTorrentIDs; diff --git a/src/base/interfaces/iapplication.h b/src/base/interfaces/iapplication.h index 8b1a156c2..0b751cb4f 100644 --- a/src/base/interfaces/iapplication.h +++ b/src/base/interfaces/iapplication.h @@ -33,6 +33,8 @@ #include #include +#include "base/addtorrentmanager.h" + class QString; class Path; @@ -83,4 +85,6 @@ public: virtual MemoryPriority processMemoryPriority() const = 0; virtual void setProcessMemoryPriority(MemoryPriority priority) = 0; #endif + + virtual AddTorrentManager *addTorrentManager() const = 0; }; diff --git a/src/base/preferences.cpp b/src/base/preferences.cpp index df121e9c8..c4ab472c6 100644 --- a/src/base/preferences.cpp +++ b/src/base/preferences.cpp @@ -2086,6 +2086,50 @@ void Preferences::setSpeedWidgetGraphEnable(const int id, const bool 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() { if (SettingsStorage::instance()->save()) diff --git a/src/base/preferences.h b/src/base/preferences.h index fbf763eea..28de28d83 100644 --- a/src/base/preferences.h +++ b/src/base/preferences.h @@ -424,6 +424,14 @@ public: bool getSpeedWidgetGraphEnable(int id) const; 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: void setStatusFilterState(bool checked); void setCategoryFilterState(bool checked); diff --git a/src/base/rss/rss_autodownloader.cpp b/src/base/rss/rss_autodownloader.cpp index 5e877ad78..3c2c7f2f3 100644 --- a/src/base/rss/rss_autodownloader.cpp +++ b/src/base/rss/rss_autodownloader.cpp @@ -41,14 +41,16 @@ #include #include -#include "../bittorrent/session.h" -#include "../bittorrent/torrentdescriptor.h" -#include "../asyncfilestorage.h" -#include "../global.h" -#include "../logger.h" -#include "../profile.h" -#include "../utils/fs.h" -#include "../utils/io.h" +#include "base/addtorrentmanager.h" +#include "base/asyncfilestorage.h" +#include "base/bittorrent/session.h" +#include "base/bittorrent/torrentdescriptor.h" +#include "base/global.h" +#include "base/interfaces/iapplication.h" +#include "base/logger.h" +#include "base/profile.h" +#include "base/utils/fs.h" +#include "base/utils/io.h" #include "rss_article.h" #include "rss_autodownloadrule.h" #include "rss_feed.h" @@ -100,12 +102,13 @@ QString computeSmartFilterRegex(const QStringList &filters) return u"(?:_|\\b)(?:%1)(?:_|\\b)"_s.arg(filters.join(u")|(?:")); } -AutoDownloader::AutoDownloader() - : m_storeProcessingEnabled(u"RSS/AutoDownloader/EnableProcessing"_s, false) - , m_storeSmartEpisodeFilter(u"RSS/AutoDownloader/SmartEpisodeFilter"_s) - , m_storeDownloadRepacks(u"RSS/AutoDownloader/DownloadRepacks"_s) - , m_processingTimer(new QTimer(this)) - , m_ioThread(new QThread) +AutoDownloader::AutoDownloader(IApplication *app) + : ApplicationComponent(app) + , m_storeProcessingEnabled {u"RSS/AutoDownloader/EnableProcessing"_s, false} + , m_storeSmartEpisodeFilter {u"RSS/AutoDownloader/SmartEpisodeFilter"_s} + , m_storeDownloadRepacks {u"RSS/AutoDownloader/DownloadRepacks"_s} + , m_processingTimer {new QTimer(this)} + , m_ioThread {new QThread} { Q_ASSERT(!m_instance); // only one instance is allowed m_instance = this; @@ -122,17 +125,17 @@ AutoDownloader::AutoDownloader() m_ioThread->start(); - connect(BitTorrent::Session::instance(), &BitTorrent::Session::downloadFromUrlFinished - , this, &AutoDownloader::handleTorrentDownloadFinished); - connect(BitTorrent::Session::instance(), &BitTorrent::Session::downloadFromUrlFailed - , this, &AutoDownloader::handleTorrentDownloadFailed); + connect(app->addTorrentManager(), &AddTorrentManager::torrentAdded + , this, &AutoDownloader::handleTorrentAdded); + connect(app->addTorrentManager(), &AddTorrentManager::addTorrentFailed + , this, &AutoDownloader::handleAddTorrentFailed); // initialise the smart episode regex const QString regex = computeSmartFilterRegex(smartEpisodeFilters()); - m_smartEpisodeRegex = QRegularExpression(regex, - QRegularExpression::CaseInsensitiveOption - | QRegularExpression::ExtendedPatternSyntaxOption - | QRegularExpression::UseUnicodePropertiesOption); + m_smartEpisodeRegex = QRegularExpression(regex + , QRegularExpression::CaseInsensitiveOption + | QRegularExpression::ExtendedPatternSyntaxOption + | QRegularExpression::UseUnicodePropertiesOption); load(); @@ -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) 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. } @@ -472,7 +475,7 @@ void AutoDownloader::processJob(const QSharedPointer &job) .arg(job->articleData.value(Article::KeyTitle).toString(), rule.name())); 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)) { diff --git a/src/base/rss/rss_autodownloader.h b/src/base/rss/rss_autodownloader.h index b00d9d58f..c70bdbf94 100644 --- a/src/base/rss/rss_autodownloader.h +++ b/src/base/rss/rss_autodownloader.h @@ -36,6 +36,7 @@ #include #include +#include "base/applicationcomponent.h" #include "base/exceptions.h" #include "base/settingvalue.h" #include "base/utils/thread.h" @@ -61,14 +62,14 @@ namespace RSS using RuntimeError::RuntimeError; }; - class AutoDownloader final : public QObject + class AutoDownloader final : public ApplicationComponent { Q_OBJECT Q_DISABLE_COPY_MOVE(AutoDownloader) friend class ::Application; - AutoDownloader(); + explicit AutoDownloader(IApplication *app); ~AutoDownloader() override; public: @@ -110,8 +111,8 @@ namespace RSS private slots: void process(); - void handleTorrentDownloadFinished(const QString &url); - void handleTorrentDownloadFailed(const QString &url); + void handleTorrentAdded(const QString &source); + void handleAddTorrentFailed(const QString &url); void handleNewArticle(const Article *article); void handleFeedURLChanged(Feed *feed, const QString &oldURL); diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 8bff5528a..17cef5d1f 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -54,6 +54,7 @@ add_library(qbt_gui STATIC flowlayout.h fspathedit.h fspathedit_p.h + guiaddtorrentmanager.h guiapplicationcomponent.h hidabletabwidget.h interfaces/iguiapplication.h @@ -148,7 +149,7 @@ add_library(qbt_gui STATIC flowlayout.cpp fspathedit.cpp fspathedit_p.cpp - guiapplicationcomponent.cpp + guiaddtorrentmanager.cpp hidabletabwidget.cpp ipsubnetwhitelistoptionsdialog.cpp lineedit.cpp diff --git a/src/gui/addnewtorrentdialog.cpp b/src/gui/addnewtorrentdialog.cpp index 36b1eda3f..96ec8b254 100644 --- a/src/gui/addnewtorrentdialog.cpp +++ b/src/gui/addnewtorrentdialog.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -50,7 +51,6 @@ #include "base/bittorrent/torrentcontenthandler.h" #include "base/bittorrent/torrentcontentlayout.h" #include "base/global.h" -#include "base/net/downloadmanager.h" #include "base/preferences.h" #include "base/settingsstorage.h" #include "base/torrentfileguard.h" @@ -58,19 +58,16 @@ #include "base/utils/fs.h" #include "base/utils/misc.h" #include "lineedit.h" -#include "raisedmessagebox.h" #include "torrenttagsdialog.h" -#include "ui_addnewtorrentdialog.h" #include "uithememanager.h" +#include "ui_addnewtorrentdialog.h" + namespace { #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_DOWNLOADPATHHISTORY = SETTINGS_KEY(u"DownloadPathHistory"_s); - const QString KEY_SAVEPATHHISTORYLENGTH = SETTINGS_KEY(u"SavePathHistoryLength"_s); // just a shortcut inline SettingsStorage *settings() @@ -267,19 +264,18 @@ private: BitTorrent::TorrentContentLayout m_currentContentLayout; }; -const int AddNewTorrentDialog::minPathHistoryLength; -const int AddNewTorrentDialog::maxPathHistoryLength; - -AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::AddTorrentParams &inParams, QWidget *parent) +AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::TorrentDescriptor &torrentDescr + , const BitTorrent::AddTorrentParams &inParams, QWidget *parent) : QDialog(parent) - , m_ui(new Ui::AddNewTorrentDialog) - , m_filterLine(new LineEdit(this)) - , m_torrentParams(inParams) - , m_storeDialogSize(SETTINGS_KEY(u"DialogSize"_s)) - , m_storeDefaultCategory(SETTINGS_KEY(u"DefaultCategory"_s)) - , m_storeRememberLastSavePath(SETTINGS_KEY(u"RememberLastSavePath"_s)) - , m_storeTreeHeaderState(u"GUI/Qt6/" SETTINGS_KEY(u"TreeHeaderState"_s)) - , m_storeSplitterState(u"GUI/Qt6/" SETTINGS_KEY(u"SplitterState"_s)) + , m_ui {new Ui::AddNewTorrentDialog} + , m_torrentDescr {torrentDescr} + , m_torrentParams {inParams} + , m_filterLine {new LineEdit(this)} + , m_storeDialogSize {SETTINGS_KEY(u"DialogSize"_s)} + , m_storeDefaultCategory {SETTINGS_KEY(u"DefaultCategory"_s)} + , m_storeRememberLastSavePath {SETTINGS_KEY(u"RememberLastSavePath"_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 m_ui->setupUi(this); @@ -384,8 +380,6 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::AddTorrentParams &inP loadState(); - connect(m_ui->doNotDeleteTorrentCheckBox, &QCheckBox::clicked, this, &AddNewTorrentDialog::doNotDeleteTorrentClicked); - connect(m_ui->buttonSelectAll, &QPushButton::clicked, m_ui->contentTreeView, &TorrentContentWidget::checkAll); connect(m_ui->buttonSelectNone, &QPushButton::clicked, m_ui->contentTreeView, &TorrentContentWidget::checkNone); @@ -408,51 +402,54 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::AddTorrentParams &inP m_ui->savePath->setFocus(); else m_ui->categoryComboBox->setFocus(); -} -AddNewTorrentDialog::~AddNewTorrentDialog() -{ - saveState(); - delete m_ui; -} + connect(Preferences::instance(), &Preferences::changed, [] + { + const int length = Preferences::instance()->addNewTorrentDialogSavePathHistoryLength(); + settings()->storeValue(KEY_SAVEPATHHISTORY + , QStringList(settings()->loadValue(KEY_SAVEPATHHISTORY).mid(0, length))); + }); -bool AddNewTorrentDialog::isEnabled() -{ - return settings()->loadValue(KEY_ENABLED, true); -} + const BitTorrent::InfoHash infoHash = m_torrentDescr.infoHash(); -void AddNewTorrentDialog::setEnabled(const bool value) -{ - settings()->storeValue(KEY_ENABLED, value); + 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()); } -bool AddNewTorrentDialog::isTopLevel() +AddNewTorrentDialog::~AddNewTorrentDialog() { - return settings()->loadValue(KEY_TOPLEVEL, true); + saveState(); + delete m_ui; } -void AddNewTorrentDialog::setTopLevel(const bool value) +BitTorrent::TorrentDescriptor AddNewTorrentDialog::torrentDescriptor() const { - settings()->storeValue(KEY_TOPLEVEL, value); + return m_torrentDescr; } -int AddNewTorrentDialog::savePathHistoryLength() +BitTorrent::AddTorrentParams AddNewTorrentDialog::addTorrentParams() const { - const int defaultHistoryLength = 8; - const int value = settings()->loadValue(KEY_SAVEPATHHISTORYLENGTH, defaultHistoryLength); - return std::clamp(value, minPathHistoryLength, maxPathHistoryLength); + return m_torrentParams; } -void AddNewTorrentDialog::setSavePathHistoryLength(const int value) +bool AddNewTorrentDialog::isDoNotDeleteTorrentChecked() const { - 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(KEY_SAVEPATHHISTORY).mid(0, clampedValue))); + return m_ui->doNotDeleteTorrentCheckBox->isChecked(); } void AddNewTorrentDialog::loadState() @@ -471,140 +468,10 @@ void AddNewTorrentDialog::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(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) { QDialog::showEvent(event); - if (!isTopLevel()) + if (!Preferences::instance()->isAddNewTorrentDialogTopLevel()) return; activateWindow(); @@ -818,16 +685,17 @@ void AddNewTorrentDialog::accept() m_torrentParams.useAutoTMM = useAutoTMM; if (!useAutoTMM) { + const int savePathHistoryLength = Preferences::instance()->addNewTorrentDialogSavePathHistoryLength(); const Path savePath = m_ui->savePath->selectedPath(); m_torrentParams.savePath = savePath; - updatePathHistory(KEY_SAVEPATHHISTORY, savePath, savePathHistoryLength()); + updatePathHistory(KEY_SAVEPATHHISTORY, savePath, savePathHistoryLength); m_torrentParams.useDownloadPath = m_ui->groupBoxDownloadPath->isChecked(); if (m_torrentParams.useDownloadPath) { const Path downloadPath = m_ui->downloadPath->selectedPath(); m_torrentParams.downloadPath = downloadPath; - updatePathHistory(KEY_DOWNLOADPATHHISTORY, downloadPath, savePathHistoryLength()); + updatePathHistory(KEY_DOWNLOADPATHHISTORY, downloadPath, savePathHistoryLength); } else { @@ -843,11 +711,6 @@ void AddNewTorrentDialog::accept() setEnabled(!m_ui->checkBoxNeverShow->isChecked()); - // Add torrent - BitTorrent::Session::instance()->addTorrent(m_torrentDescr, m_torrentParams); - - if (m_torrentGuard) - m_torrentGuard->markAsAddedToSession(); QDialog::accept(); } @@ -865,13 +728,13 @@ void AddNewTorrentDialog::reject() void AddNewTorrentDialog::updateMetadata(const BitTorrent::TorrentInfo &metadata) { Q_ASSERT(metadata.isValid()); - - if (!metadata.matchesInfoHash(m_torrentDescr.infoHash())) + if (!metadata.isValid()) [[unlikely]] 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); setMetadataProgressIndicator(true, tr("Parsing metadata...")); @@ -941,50 +804,6 @@ void AddNewTorrentDialog::setupTreeview() 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) { if (index != 1) @@ -1014,9 +833,3 @@ void AddNewTorrentDialog::TMMChanged(int index) updateDiskSpaceLabel(); } - -void AddNewTorrentDialog::doNotDeleteTorrentClicked(bool checked) -{ - if (m_torrentGuard) - m_torrentGuard->setAutoRemove(!checked); -} diff --git a/src/gui/addnewtorrentdialog.h b/src/gui/addnewtorrentdialog.h index c36735d32..211e542ab 100644 --- a/src/gui/addnewtorrentdialog.h +++ b/src/gui/addnewtorrentdialog.h @@ -29,8 +29,6 @@ #pragma once -#include - #include #include "base/bittorrent/addtorrentparams.h" @@ -38,23 +36,12 @@ #include "base/path.h" #include "base/settingvalue.h" -namespace BitTorrent -{ - class InfoHash; -} - -namespace Net -{ - struct DownloadResult; -} - namespace Ui { class AddNewTorrentDialog; } class LineEdit; -class TorrentFileGuard; class AddNewTorrentDialog final : public QDialog { @@ -62,32 +49,24 @@ class AddNewTorrentDialog final : public QDialog Q_DISABLE_COPY_MOVE(AddNewTorrentDialog) public: - static const int minPathHistoryLength = 0; - static const int maxPathHistoryLength = 99; - + explicit AddNewTorrentDialog(const BitTorrent::TorrentDescriptor &torrentDescr + , const BitTorrent::AddTorrentParams &inParams, QWidget *parent); ~AddNewTorrentDialog() override; - static bool isEnabled(); - static void setEnabled(bool value); - static bool isTopLevel(); - static void setTopLevel(bool value); - static int savePathHistoryLength(); - static void setSavePathHistoryLength(int value); + BitTorrent::TorrentDescriptor torrentDescriptor() const; + BitTorrent::AddTorrentParams addTorrentParams() const; + bool isDoNotDeleteTorrentChecked() const; - static void show(const QString &source, const BitTorrent::AddTorrentParams &inParams, QWidget *parent); - static void show(const QString &source, QWidget *parent); + void updateMetadata(const BitTorrent::TorrentInfo &metadata); private slots: void updateDiskSpaceLabel(); void onSavePathChanged(const Path &newPath); void onDownloadPathChanged(const Path &newPath); void onUseDownloadPathChanged(bool checked); - void updateMetadata(const BitTorrent::TorrentInfo &metadata); - void handleDownloadFinished(const Net::DownloadResult &downloadResult); void TMMChanged(int index); void categoryChanged(int index); void contentLayoutChanged(); - void doNotDeleteTorrentClicked(bool checked); void accept() override; void reject() override; @@ -95,10 +74,6 @@ private slots: private: class TorrentContentAdaptor; - explicit AddNewTorrentDialog(const BitTorrent::AddTorrentParams &inParams, QWidget *parent); - - bool loadTorrent(const QString &source); - bool loadTorrentImpl(); void populateSavePaths(); void loadState(); void saveState(); @@ -112,12 +87,11 @@ private: Ui::AddNewTorrentDialog *m_ui = nullptr; TorrentContentAdaptor *m_contentAdaptor = nullptr; BitTorrent::TorrentDescriptor m_torrentDescr; + BitTorrent::AddTorrentParams m_torrentParams; int m_savePathIndex = -1; int m_downloadPathIndex = -1; bool m_useDownloadPath = false; LineEdit *m_filterLine = nullptr; - std::unique_ptr m_torrentGuard; - BitTorrent::AddTorrentParams m_torrentParams; SettingValue m_storeDialogSize; SettingValue m_storeDefaultCategory; diff --git a/src/gui/advancedsettings.cpp b/src/gui/advancedsettings.cpp index 0d0ce6642..71dbd5c7d 100644 --- a/src/gui/advancedsettings.cpp +++ b/src/gui/advancedsettings.cpp @@ -168,8 +168,7 @@ namespace } AdvancedSettings::AdvancedSettings(IGUIApplication *app, QWidget *parent) - : QTableWidget(parent) - , GUIApplicationComponent(app) + : GUIApplicationComponent(app, parent) { // column setColumnCount(COL_COUNT); @@ -306,7 +305,7 @@ void AdvancedSettings::saveAdvancedSettings() const session->setReannounceWhenAddressChangedEnabled(m_checkBoxReannounceWhenAddressChanged.isChecked()); // Misc GUI properties app()->mainWindow()->setDownloadTrackerFavicon(m_checkBoxTrackerFavicon.isChecked()); - AddNewTorrentDialog::setSavePathHistoryLength(m_spinBoxSavePathHistoryLength.value()); + pref->setAddNewTorrentDialogSavePathHistoryLength(m_spinBoxSavePathHistoryLength.value()); pref->setSpeedWidgetEnabled(m_checkBoxSpeedWidgetEnabled.isChecked()); #ifndef Q_OS_MACOS pref->setIconsInMenusEnabled(m_checkBoxIconsInMenusEnabled.isChecked()); @@ -786,8 +785,8 @@ void AdvancedSettings::loadAdvancedSettings() m_checkBoxTrackerFavicon.setChecked(app()->mainWindow()->isDownloadTrackerFavicon()); addRow(DOWNLOAD_TRACKER_FAVICON, tr("Download tracker's favicon"), &m_checkBoxTrackerFavicon); // Save path history length - m_spinBoxSavePathHistoryLength.setRange(AddNewTorrentDialog::minPathHistoryLength, AddNewTorrentDialog::maxPathHistoryLength); - m_spinBoxSavePathHistoryLength.setValue(AddNewTorrentDialog::savePathHistoryLength()); + m_spinBoxSavePathHistoryLength.setRange(0, 99); + m_spinBoxSavePathHistoryLength.setValue(pref->addNewTorrentDialogSavePathHistoryLength()); addRow(SAVE_PATH_HISTORY_LENGTH, tr("Save path history length"), &m_spinBoxSavePathHistoryLength); // Enable speed graphs m_checkBoxSpeedWidgetEnabled.setChecked(pref->isSpeedWidgetEnabled()); diff --git a/src/gui/advancedsettings.h b/src/gui/advancedsettings.h index 1cb869e9e..82ff74ca6 100644 --- a/src/gui/advancedsettings.h +++ b/src/gui/advancedsettings.h @@ -38,7 +38,7 @@ #include "guiapplicationcomponent.h" -class AdvancedSettings final : public QTableWidget, public GUIApplicationComponent +class AdvancedSettings final : public GUIApplicationComponent { Q_OBJECT Q_DISABLE_COPY_MOVE(AdvancedSettings) diff --git a/src/gui/guiaddtorrentmanager.cpp b/src/gui/guiaddtorrentmanager.cpp new file mode 100644 index 000000000..2bccaad48 --- /dev/null +++ b/src/gui/guiaddtorrentmanager.cpp @@ -0,0 +1,200 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2015-2023 Vladimir Golovnev + * Copyright (C) 2006 Christophe Dumez + * + * 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(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; +} diff --git a/src/gui/guiapplicationcomponent.cpp b/src/gui/guiaddtorrentmanager.h similarity index 52% rename from src/gui/guiapplicationcomponent.cpp rename to src/gui/guiaddtorrentmanager.h index a10f28621..fa48dda30 100644 --- a/src/gui/guiapplicationcomponent.cpp +++ b/src/gui/guiaddtorrentmanager.h @@ -1,6 +1,7 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2022 Vladimir Golovnev + * Copyright (C) 2015-2023 Vladimir Golovnev + * Copyright (C) 2006 Christophe Dumez * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -26,14 +27,48 @@ * exception statement from your version. */ +#pragma once + +#include "base/addtorrentmanager.h" +#include "base/bittorrent/infohash.h" #include "guiapplicationcomponent.h" -GUIApplicationComponent::GUIApplicationComponent(IGUIApplication *app) - : ApplicationComponent(app) +#include + +namespace BitTorrent { + class TorrentDescriptor; } -IGUIApplication *GUIApplicationComponent::app() const +namespace Net { - return static_cast(ApplicationComponent::app()); + struct DownloadResult; } + +class AddNewTorrentDialog; + +enum class AddTorrentOption +{ + Default, + ShowDialog, + SkipDialog, +}; + +class GUIAddTorrentManager : public GUIApplicationComponent +{ + 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 m_downloadedTorrents; + QHash m_dialogs; +}; diff --git a/src/gui/guiapplicationcomponent.h b/src/gui/guiapplicationcomponent.h index 3f31d671d..2c3155417 100644 --- a/src/gui/guiapplicationcomponent.h +++ b/src/gui/guiapplicationcomponent.h @@ -29,14 +29,38 @@ #pragma once #include "base/applicationcomponent.h" -#include "interfaces/iguiapplication.h" -class GUIApplicationComponent : public ApplicationComponent +class IGUIApplication; + +template +class GUIApplicationComponent : public Base, public ApplicationComponentBase { - Q_DISABLE_COPY_MOVE(GUIApplicationComponent) +public: + template + explicit GUIApplicationComponent(IGUIApplication *app, Args&&... args) + : Base(std::forward(args)...) + , ApplicationComponentBase(reinterpret_cast(app)) + { + } + IGUIApplication *app() const + { + return reinterpret_cast(ApplicationComponentBase::app()); + } +}; + +template +class GUIApplicationComponent : public Base +{ public: - explicit GUIApplicationComponent(IGUIApplication *app); + template + explicit GUIApplicationComponent(IGUIApplication *app, Args&&... args) + : Base(reinterpret_cast(app), std::forward(args)...) + { + } - IGUIApplication *app() const override; + IGUIApplication *app() const + { + return reinterpret_cast(ApplicationComponentBase::app()); + } }; diff --git a/src/gui/interfaces/iguiapplication.h b/src/gui/interfaces/iguiapplication.h index a444aaae4..d6ca641eb 100644 --- a/src/gui/interfaces/iguiapplication.h +++ b/src/gui/interfaces/iguiapplication.h @@ -31,6 +31,7 @@ #pragma once #include "base/interfaces/iapplication.h" +#include "gui/guiaddtorrentmanager.h" #include "gui/windowstate.h" class DesktopIntegration; @@ -41,6 +42,8 @@ class IGUIApplication : public IApplication public: ~IGUIApplication() override = default; + GUIAddTorrentManager *addTorrentManager() const override = 0; + virtual DesktopIntegration *desktopIntegration() = 0; virtual MainWindow *mainWindow() = 0; diff --git a/src/gui/mainwindow.cpp b/src/gui/mainwindow.cpp index 31feea966..a43d11477 100644 --- a/src/gui/mainwindow.cpp +++ b/src/gui/mainwindow.cpp @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2022 Vladimir Golovnev + * Copyright (C) 2022-2023 Vladimir Golovnev * Copyright (C) 2006 Christophe Dumez * * This program is free software; you can redistribute it and/or @@ -75,13 +75,13 @@ #include "base/utils/password.h" #include "base/version.h" #include "aboutdialog.h" -#include "addnewtorrentdialog.h" #include "autoexpandabledialog.h" #include "cookiesdialog.h" #include "desktopintegration.h" #include "downloadfromurldialog.h" #include "executionlogwidget.h" #include "hidabletabwidget.h" +#include "interfaces/iguiapplication.h" #include "lineedit.h" #include "optionsdialog.h" #include "powermanagement/powermanagement.h" @@ -184,7 +184,6 @@ MainWindow::MainWindow(IGUIApplication *app, WindowState initialState) updateAltSpeedsBtn(BitTorrent::Session::instance()->isAltGlobalSpeedLimitEnabled()); connect(BitTorrent::Session::instance(), &BitTorrent::Session::speedLimitModeChanged, this, &MainWindow::updateAltSpeedsBtn); - connect(BitTorrent::Session::instance(), &BitTorrent::Session::recursiveTorrentDownloadPossible, this, &MainWindow::askRecursiveTorrentDownloadConfirmation); qDebug("create tabWidget"); m_tabs = new HidableTabWidget(this); @@ -673,7 +672,7 @@ void MainWindow::displayRSSTab(bool enable) // RSS tab 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); #ifdef Q_OS_MACOS 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 -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() { auto *dialog = new SpeedLimitDialog {this}; @@ -1124,7 +1092,6 @@ void MainWindow::keyPressEvent(QKeyEvent *event) if (mimeData->hasText()) { - const bool useTorrentAdditionDialog = AddNewTorrentDialog::isEnabled(); const QStringList lines = mimeData->text().split(u'\n', Qt::SkipEmptyParts); for (QString line : lines) @@ -1134,10 +1101,7 @@ void MainWindow::keyPressEvent(QKeyEvent *event) if (!isTorrentLink(line)) continue; - if (useTorrentAdditionDialog) - AddNewTorrentDialog::show(line, this); - else - BitTorrent::Session::instance()->addTorrent(line); + app()->addTorrentManager()->addTorrent(line); } return; @@ -1320,14 +1284,8 @@ void MainWindow::dropEvent(QDropEvent *event) } // Download torrents - const bool useTorrentAdditionDialog = AddNewTorrentDialog::isEnabled(); for (const QString &file : asConst(torrentFiles)) - { - if (useTorrentAdditionDialog) - AddNewTorrentDialog::show(file, this); - else - BitTorrent::Session::instance()->addTorrent(file); - } + app()->addTorrentManager()->addTorrent(file); if (!torrentFiles.isEmpty()) return; // Create torrent @@ -1364,22 +1322,14 @@ void MainWindow::on_actionOpen_triggered() Preferences *const pref = Preferences::instance(); // Open File Open Dialog // Note: it is possible to select more than one file - const QStringList pathsList = - QFileDialog::getOpenFileNames(this, tr("Open Torrent Files"), pref->getMainLastDir().data(), - tr("Torrent Files") + u" (*" + TORRENT_FILE_EXTENSION + u')'); + const QStringList pathsList = QFileDialog::getOpenFileNames(this, tr("Open Torrent Files") + , pref->getMainLastDir().data(), tr("Torrent Files") + u" (*" + TORRENT_FILE_EXTENSION + u')'); if (pathsList.isEmpty()) return; - const bool useTorrentAdditionDialog = AddNewTorrentDialog::isEnabled(); - for (const QString &file : pathsList) - { - if (useTorrentAdditionDialog) - AddNewTorrentDialog::show(file, this); - else - BitTorrent::Session::instance()->addTorrent(file); - } + app()->addTorrentManager()->addTorrent(file); // Save last dir to remember it const Path topDir {pathsList.at(0)}; @@ -1576,14 +1526,8 @@ void MainWindow::reloadTorrentStats(const QVector &torren void MainWindow::downloadFromURLList(const QStringList &urlList) { - const bool useTorrentAdditionDialog = AddNewTorrentDialog::isEnabled(); for (const QString &url : urlList) - { - if (useTorrentAdditionDialog) - AddNewTorrentDialog::show(url, this); - else - BitTorrent::Session::instance()->addTorrent(url); - } + app()->addTorrentManager()->addTorrent(url); } /***************************************************** diff --git a/src/gui/mainwindow.h b/src/gui/mainwindow.h index 5b7e389e5..c70f4c8e8 100644 --- a/src/gui/mainwindow.h +++ b/src/gui/mainwindow.h @@ -71,7 +71,7 @@ namespace Ui class MainWindow; } -class MainWindow final : public QMainWindow, public GUIApplicationComponent +class MainWindow final : public GUIApplicationComponent { Q_OBJECT Q_DISABLE_COPY_MOVE(MainWindow) @@ -122,7 +122,6 @@ private slots: void reloadSessionStats(); void reloadTorrentStats(const QVector &torrents); void loadPreferences(); - void askRecursiveTorrentDownloadConfirmation(const BitTorrent::Torrent *torrent); void optionsSaved(); void toggleAlternativeSpeeds(); diff --git a/src/gui/optionsdialog.cpp b/src/gui/optionsdialog.cpp index d043dfbb6..ac8d4760c 100644 --- a/src/gui/optionsdialog.cpp +++ b/src/gui/optionsdialog.cpp @@ -110,8 +110,7 @@ namespace // Constructor OptionsDialog::OptionsDialog(IGUIApplication *app, QWidget *parent) - : QDialog(parent) - , GUIApplicationComponent(app) + : GUIApplicationComponent(app, parent) , m_ui {new Ui::OptionsDialog} , m_storeDialogSize {SETTINGS_KEY(u"Size"_s)} , m_storeHSplitterSize {SETTINGS_KEY(u"HorizontalSplitterSizes"_s)} @@ -484,8 +483,8 @@ void OptionsDialog::loadDownloadsTabOptions() const auto *pref = Preferences::instance(); const auto *session = BitTorrent::Session::instance(); - m_ui->checkAdditionDialog->setChecked(AddNewTorrentDialog::isEnabled()); - m_ui->checkAdditionDialogFront->setChecked(AddNewTorrentDialog::isTopLevel()); + m_ui->checkAdditionDialog->setChecked(pref->isAddNewTorrentDialogEnabled()); + m_ui->checkAdditionDialogFront->setChecked(pref->isAddNewTorrentDialogTopLevel()); m_ui->contentLayoutComboBox->setCurrentIndex(static_cast(session->torrentContentLayout())); m_ui->checkAddToQueueTop->setChecked(session->isAddTorrentToQueueTop()); @@ -686,8 +685,8 @@ void OptionsDialog::saveDownloadsTabOptions() const auto *pref = Preferences::instance(); auto *session = BitTorrent::Session::instance(); - AddNewTorrentDialog::setEnabled(useAdditionDialog()); - AddNewTorrentDialog::setTopLevel(m_ui->checkAdditionDialogFront->isChecked()); + pref->setAddNewTorrentDialogEnabled(useAdditionDialog()); + pref->setAddNewTorrentDialogTopLevel(m_ui->checkAdditionDialogFront->isChecked()); session->setTorrentContentLayout(static_cast(m_ui->contentLayoutComboBox->currentIndex())); diff --git a/src/gui/optionsdialog.h b/src/gui/optionsdialog.h index dfb5a75c8..6f8c83166 100644 --- a/src/gui/optionsdialog.h +++ b/src/gui/optionsdialog.h @@ -58,7 +58,7 @@ namespace Ui class OptionsDialog; } -class OptionsDialog final : public QDialog, public GUIApplicationComponent +class OptionsDialog final : public GUIApplicationComponent { Q_OBJECT Q_DISABLE_COPY_MOVE(OptionsDialog) diff --git a/src/gui/rss/rsswidget.cpp b/src/gui/rss/rsswidget.cpp index 09fb8c7cc..502d863bd 100644 --- a/src/gui/rss/rsswidget.cpp +++ b/src/gui/rss/rsswidget.cpp @@ -39,7 +39,6 @@ #include #include -#include "base/bittorrent/session.h" #include "base/global.h" #include "base/net/downloadmanager.h" #include "base/preferences.h" @@ -47,17 +46,17 @@ #include "base/rss/rss_feed.h" #include "base/rss/rss_folder.h" #include "base/rss/rss_session.h" -#include "gui/addnewtorrentdialog.h" #include "gui/autoexpandabledialog.h" +#include "gui/interfaces/iguiapplication.h" #include "gui/uithememanager.h" #include "articlelistwidget.h" #include "automatedrssdownloader.h" #include "feedlistwidget.h" #include "ui_rsswidget.h" -RSSWidget::RSSWidget(QWidget *parent) - : QWidget(parent) - , m_ui(new Ui::RSSWidget) +RSSWidget::RSSWidget(IGUIApplication *app, QWidget *parent) + : GUIApplicationComponent(app, parent) + , m_ui {new Ui::RSSWidget} { m_ui->setupUi(this); @@ -370,13 +369,7 @@ void RSSWidget::downloadSelectedTorrents() // Mark as read article->markAsRead(); - if (!article->torrentUrl().isEmpty()) - { - if (AddNewTorrentDialog::isEnabled()) - AddNewTorrentDialog::show(article->torrentUrl(), window()); - else - BitTorrent::Session::instance()->addTorrent(article->torrentUrl()); - } + app()->addTorrentManager()->addTorrent(article->torrentUrl()); } } diff --git a/src/gui/rss/rsswidget.h b/src/gui/rss/rsswidget.h index a2b44c5bf..5c5aaaa1b 100644 --- a/src/gui/rss/rsswidget.h +++ b/src/gui/rss/rsswidget.h @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2017 Vladimir Golovnev + * Copyright (C) 2017-2023 Vladimir Golovnev * Copyright (C) 2006 Christophe Dumez * Copyright (C) 2006 Arnaud Demaiziere * @@ -32,6 +32,8 @@ #include +#include "gui/guiapplicationcomponent.h" + class QListWidgetItem; class QTreeWidgetItem; @@ -43,14 +45,14 @@ namespace Ui class RSSWidget; } -class RSSWidget : public QWidget +class RSSWidget final : public GUIApplicationComponent { Q_OBJECT Q_DISABLE_COPY_MOVE(RSSWidget) public: - RSSWidget(QWidget *parent); - ~RSSWidget(); + explicit RSSWidget(IGUIApplication *app, QWidget *parent = nullptr); + ~RSSWidget() override; public slots: void deleteSelectedItems(); diff --git a/src/gui/search/searchjobwidget.cpp b/src/gui/search/searchjobwidget.cpp index c3a8c1815..99f06b6ac 100644 --- a/src/gui/search/searchjobwidget.cpp +++ b/src/gui/search/searchjobwidget.cpp @@ -39,24 +39,22 @@ #include #include -#include "base/bittorrent/session.h" #include "base/preferences.h" #include "base/search/searchdownloadhandler.h" #include "base/search/searchhandler.h" #include "base/search/searchpluginmanager.h" #include "base/utils/misc.h" -#include "gui/addnewtorrentdialog.h" +#include "gui/interfaces/iguiapplication.h" #include "gui/lineedit.h" #include "gui/uithememanager.h" -#include "gui/utils.h" #include "searchsortmodel.h" #include "ui_searchjobwidget.h" -SearchJobWidget::SearchJobWidget(SearchHandler *searchHandler, QWidget *parent) - : QWidget(parent) - , m_ui(new Ui::SearchJobWidget) - , m_searchHandler(searchHandler) - , m_nameFilteringMode(u"Search/FilteringMode"_s) +SearchJobWidget::SearchJobWidget(SearchHandler *searchHandler, IGUIApplication *app, QWidget *parent) + : GUIApplicationComponent(app, parent) + , m_ui {new Ui::SearchJobWidget} + , m_searchHandler {searchHandler} + , m_nameFilteringMode {u"Search/FilteringMode"_s} { 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) { - if (source.isEmpty()) return; - - if ((option == AddTorrentOption::ShowDialog) || ((option == AddTorrentOption::Default) && AddNewTorrentDialog::isEnabled())) - AddNewTorrentDialog::show(source, this); - else - BitTorrent::Session::instance()->addTorrent(source); + app()->addTorrentManager()->addTorrent(source, {}, option); } void SearchJobWidget::updateResultsCount() diff --git a/src/gui/search/searchjobwidget.h b/src/gui/search/searchjobwidget.h index 46a608864..f597c03af 100644 --- a/src/gui/search/searchjobwidget.h +++ b/src/gui/search/searchjobwidget.h @@ -32,6 +32,8 @@ #include #include "base/settingvalue.h" +#include "gui/guiaddtorrentmanager.h" +#include "gui/guiapplicationcomponent.h" #define ENGINE_URL_COLUMN 4 #define URL_COLUMN 5 @@ -52,7 +54,7 @@ namespace Ui class SearchJobWidget; } -class SearchJobWidget final : public QWidget +class SearchJobWidget final : public GUIApplicationComponent { Q_OBJECT Q_DISABLE_COPY_MOVE(SearchJobWidget) @@ -74,7 +76,7 @@ public: NoResults }; - explicit SearchJobWidget(SearchHandler *searchHandler, QWidget *parent = nullptr); + SearchJobWidget(SearchHandler *searchHandler, IGUIApplication *app, QWidget *parent = nullptr); ~SearchJobWidget() override; Status status() const; @@ -94,13 +96,6 @@ private slots: void displayColumnHeaderMenu(); private: - enum class AddTorrentOption - { - Default, - ShowDialog, - SkipDialog, - }; - void loadSettings(); void saveSettings() const; void updateFilter(); diff --git a/src/gui/search/searchwidget.cpp b/src/gui/search/searchwidget.cpp index a8764a3c4..fcf22528f 100644 --- a/src/gui/search/searchwidget.cpp +++ b/src/gui/search/searchwidget.cpp @@ -53,6 +53,7 @@ #include "base/search/searchpluginmanager.h" #include "base/utils/foreignapps.h" #include "gui/desktopintegration.h" +#include "gui/interfaces/iguiapplication.h" #include "gui/mainwindow.h" #include "gui/uithememanager.h" #include "pluginselectdialog.h" @@ -84,8 +85,7 @@ namespace } SearchWidget::SearchWidget(IGUIApplication *app, MainWindow *mainWindow) - : QWidget(mainWindow) - , GUIApplicationComponent(app) + : GUIApplicationComponent(app, mainWindow) , m_ui {new Ui::SearchWidget()} , m_mainWindow {mainWindow} { @@ -347,7 +347,7 @@ void SearchWidget::on_searchButton_clicked() auto *searchHandler = SearchPluginManager::instance()->startSearch(pattern, selectedCategory(), plugins); // Tab Addition - auto *newTab = new SearchJobWidget(searchHandler, this); + auto *newTab = new SearchJobWidget(searchHandler, app(), this); m_allTabs.append(newTab); QString tabName = pattern; diff --git a/src/gui/search/searchwidget.h b/src/gui/search/searchwidget.h index b3458a855..06544308c 100644 --- a/src/gui/search/searchwidget.h +++ b/src/gui/search/searchwidget.h @@ -48,7 +48,7 @@ namespace Ui class SearchWidget; } -class SearchWidget : public QWidget, public GUIApplicationComponent +class SearchWidget : public GUIApplicationComponent { Q_OBJECT Q_DISABLE_COPY_MOVE(SearchWidget) diff --git a/src/webui/api/apicontroller.cpp b/src/webui/api/apicontroller.cpp index ac3942fe1..2d0aeb8aa 100644 --- a/src/webui/api/apicontroller.cpp +++ b/src/webui/api/apicontroller.cpp @@ -38,8 +38,7 @@ #include "apierror.h" APIController::APIController(IApplication *app, QObject *parent) - : QObject(parent) - , ApplicationComponent(app) + : ApplicationComponent(app, parent) { } diff --git a/src/webui/api/apicontroller.h b/src/webui/api/apicontroller.h index e748bc99f..b81f7b642 100644 --- a/src/webui/api/apicontroller.h +++ b/src/webui/api/apicontroller.h @@ -39,7 +39,7 @@ class QString; using DataMap = QHash; using StringMap = QHash; -class APIController : public QObject, public ApplicationComponent +class APIController : public ApplicationComponent { Q_OBJECT Q_DISABLE_COPY_MOVE(APIController) diff --git a/src/webui/api/torrentscontroller.cpp b/src/webui/api/torrentscontroller.cpp index 6104f5289..8861d18c0 100644 --- a/src/webui/api/torrentscontroller.cpp +++ b/src/webui/api/torrentscontroller.cpp @@ -47,6 +47,7 @@ #include "base/bittorrent/torrent.h" #include "base/bittorrent/torrentdescriptor.h" #include "base/bittorrent/trackerentry.h" +#include "base/interfaces/iapplication.h" #include "base/global.h" #include "base/logger.h" #include "base/net/downloadmanager.h" @@ -732,7 +733,7 @@ void TorrentsController::addAction() if (!url.isEmpty()) { Net::DownloadManager::instance()->setCookiesFromUrl(cookies, QUrl::fromEncoded(url.toUtf8())); - partialSuccess |= BitTorrent::Session::instance()->addTorrent(url, addTorrentParams); + partialSuccess |= app()->addTorrentManager()->addTorrent(url, addTorrentParams); } } diff --git a/src/webui/webapplication.cpp b/src/webui/webapplication.cpp index c85bc9ca8..ca42458d9 100644 --- a/src/webui/webapplication.cpp +++ b/src/webui/webapplication.cpp @@ -143,8 +143,7 @@ namespace } WebApplication::WebApplication(IApplication *app, QObject *parent) - : QObject(parent) - , ApplicationComponent(app) + : ApplicationComponent(app, parent) , m_cacheID {QString::number(Utils::Random::rand(), 36)} , m_authController {new AuthController(this, app, this)} { diff --git a/src/webui/webapplication.h b/src/webui/webapplication.h index 364d5ee41..4d3cf28e4 100644 --- a/src/webui/webapplication.h +++ b/src/webui/webapplication.h @@ -58,7 +58,7 @@ class APIController; class AuthController; class WebApplication; -class WebSession final : public QObject, public ApplicationComponent, public ISession +class WebSession final : public ApplicationComponent, public ISession { public: explicit WebSession(const QString &sid, IApplication *app); @@ -83,8 +83,7 @@ private: QMap m_apiControllers; }; -class WebApplication final - : public QObject, public ApplicationComponent +class WebApplication final : public ApplicationComponent , public Http::IRequestHandler, public ISessionManager , private Http::ResponseBuilder { diff --git a/src/webui/webui.h b/src/webui/webui.h index e2c63b828..bc1de6c95 100644 --- a/src/webui/webui.h +++ b/src/webui/webui.h @@ -45,7 +45,7 @@ namespace Net class WebApplication; -class WebUI : public QObject, public ApplicationComponent +class WebUI final : public ApplicationComponent { Q_OBJECT Q_DISABLE_COPY_MOVE(WebUI)