Implement gateway for adding new torrents

PR #19355.
This commit is contained in:
Vladimir Golovnev
2023-08-14 18:17:56 +03:00
committed by GitHub
parent e4313d6651
commit dcf3e97291
42 changed files with 933 additions and 639 deletions

View File

@@ -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

View File

@@ -0,0 +1,217 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2023 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*/
#include "addtorrentmanager.h"
#include "base/bittorrent/infohash.h"
#include "base/bittorrent/session.h"
#include "base/bittorrent/torrentdescriptor.h"
#include "base/logger.h"
#include "base/net/downloadmanager.h"
#include "base/preferences.h"
AddTorrentManager::AddTorrentManager(IApplication *app, BitTorrent::Session *btSession, QObject *parent)
: ApplicationComponent(app, parent)
, m_btSession {btSession}
{
Q_ASSERT(btSession);
connect(btSession, &BitTorrent::Session::torrentAdded, this, &AddTorrentManager::onSessionTorrentAdded);
connect(btSession, &BitTorrent::Session::addTorrentFailed, this, &AddTorrentManager::onSessionAddTorrentFailed);
}
BitTorrent::Session *AddTorrentManager::btSession() const
{
return m_btSession;
}
bool AddTorrentManager::addTorrent(const QString &source, const BitTorrent::AddTorrentParams &params)
{
// `source`: .torrent file path, magnet URI or URL
if (source.isEmpty())
return false;
if (Net::DownloadManager::hasSupportedScheme(source))
{
LogMsg(tr("Downloading torrent... Source: \"%1\"").arg(source));
const auto *pref = Preferences::instance();
// Launch downloader
Net::DownloadManager::instance()->download(Net::DownloadRequest(source).limit(pref->getTorrentFileSizeLimit())
, pref->useProxyForGeneralPurposes(), this, &AddTorrentManager::onDownloadFinished);
m_downloadedTorrents[source] = params;
return true;
}
if (const auto parseResult = BitTorrent::TorrentDescriptor::parse(source))
{
return processTorrent(source, parseResult.value(), params);
}
else if (source.startsWith(u"magnet:", Qt::CaseInsensitive))
{
handleAddTorrentFailed(source, parseResult.error());
return false;
}
const Path decodedPath {source.startsWith(u"file://", Qt::CaseInsensitive)
? QUrl::fromEncoded(source.toLocal8Bit()).toLocalFile() : source};
auto torrentFileGuard = std::make_shared<TorrentFileGuard>(decodedPath);
if (const auto loadResult = BitTorrent::TorrentDescriptor::loadFromFile(decodedPath))
{
setTorrentFileGuard(source, torrentFileGuard);
return processTorrent(source, loadResult.value(), params);
}
else
{
handleAddTorrentFailed(source, loadResult.error());
return false;
}
return false;
}
bool AddTorrentManager::addTorrentToSession(const QString &source, const BitTorrent::TorrentDescriptor &torrentDescr
, const BitTorrent::AddTorrentParams &addTorrentParams)
{
const bool result = btSession()->addTorrent(torrentDescr, addTorrentParams);
if (result)
m_sourcesByInfoHash[torrentDescr.infoHash()] = source;
return result;
}
void AddTorrentManager::onDownloadFinished(const Net::DownloadResult &result)
{
const QString &source = result.url;
const BitTorrent::AddTorrentParams addTorrentParams = m_downloadedTorrents.take(source);
switch (result.status)
{
case Net::DownloadStatus::Success:
if (const auto loadResult = BitTorrent::TorrentDescriptor::load(result.data))
processTorrent(source, loadResult.value(), addTorrentParams);
else
handleAddTorrentFailed(source, loadResult.error());
break;
case Net::DownloadStatus::RedirectedToMagnet:
if (const auto parseResult = BitTorrent::TorrentDescriptor::parse(result.magnetURI))
processTorrent(source, parseResult.value(), addTorrentParams);
else
handleAddTorrentFailed(source, parseResult.error());
break;
default:
handleAddTorrentFailed(source, result.errorString);
}
}
void AddTorrentManager::onSessionTorrentAdded(BitTorrent::Torrent *torrent)
{
if (const QString source = m_sourcesByInfoHash.take(torrent->infoHash()); !source.isEmpty())
{
auto torrentFileGuard = m_guardedTorrentFiles.take(source);
if (torrentFileGuard)
torrentFileGuard->markAsAddedToSession();
emit torrentAdded(source, torrent);
}
}
void AddTorrentManager::onSessionAddTorrentFailed(const BitTorrent::InfoHash &infoHash, const QString &reason)
{
if (const QString source = m_sourcesByInfoHash.take(infoHash); !source.isEmpty())
{
auto torrentFileGuard = m_guardedTorrentFiles.take(source);
if (torrentFileGuard)
torrentFileGuard->setAutoRemove(false);
emit addTorrentFailed(source, reason);
}
}
void AddTorrentManager::handleAddTorrentFailed(const QString &source, const QString &reason)
{
LogMsg(tr("Failed to add torrent. Source: \"%1\". Reason: \"%2\"").arg(source, reason), Log::WARNING);
emit addTorrentFailed(source, reason);
}
void AddTorrentManager::handleDuplicateTorrent(const QString &source, BitTorrent::Torrent *torrent, const QString &message)
{
LogMsg(tr("Detected an attempt to add a duplicate torrent. Source: %1. Existing torrent: %2. Result: %3")
.arg(source, torrent->name(), message));
emit addTorrentFailed(source, message);
}
void AddTorrentManager::setTorrentFileGuard(const QString &source, std::shared_ptr<TorrentFileGuard> torrentFileGuard)
{
m_guardedTorrentFiles.emplace(source, std::move(torrentFileGuard));
}
void AddTorrentManager::releaseTorrentFileGuard(const QString &source)
{
auto torrentFileGuard = m_guardedTorrentFiles.take(source);
if (torrentFileGuard)
torrentFileGuard->setAutoRemove(false);
}
bool AddTorrentManager::processTorrent(const QString &source, const BitTorrent::TorrentDescriptor &torrentDescr
, const BitTorrent::AddTorrentParams &addTorrentParams)
{
const BitTorrent::InfoHash infoHash = torrentDescr.infoHash();
if (BitTorrent::Torrent *torrent = btSession()->findTorrent(infoHash))
{
// a duplicate torrent is being added
const bool hasMetadata = torrentDescr.info().has_value();
if (hasMetadata)
{
// Trying to set metadata to existing torrent in case if it has none
torrent->setMetadata(*torrentDescr.info());
}
if (!btSession()->isMergeTrackersEnabled())
{
handleDuplicateTorrent(source, torrent, tr("Merging of trackers is disabled"));
return false;
}
const bool isPrivate = torrent->isPrivate() || (hasMetadata && torrentDescr.info()->isPrivate());
if (isPrivate)
{
handleDuplicateTorrent(source, torrent, tr("Trackers cannot be merged because it is a private torrent"));
return false;
}
// merge trackers and web seeds
torrent->addTrackers(torrentDescr.trackers());
torrent->addUrlSeeds(torrentDescr.urlSeeds());
handleDuplicateTorrent(source, torrent, tr("Trackers are merged from new source"));
return false;
}
return addTorrentToSession(source, torrentDescr, addTorrentParams);
}

View File

@@ -0,0 +1,86 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2023 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*/
#pragma once
#include <memory>
#include <QHash>
#include <QObject>
#include "base/applicationcomponent.h"
#include "base/bittorrent/addtorrentparams.h"
#include "base/torrentfileguard.h"
namespace Net
{
struct DownloadResult;
}
namespace BitTorrent
{
class Session;
class TorrentDescriptor;
}
class AddTorrentManager : public ApplicationComponent<QObject>
{
Q_OBJECT
Q_DISABLE_COPY_MOVE(AddTorrentManager)
public:
AddTorrentManager(IApplication *app, BitTorrent::Session *btSession, QObject *parent = nullptr);
BitTorrent::Session *btSession() const;
bool addTorrent(const QString &source, const BitTorrent::AddTorrentParams &params = {});
signals:
void torrentAdded(const QString &source, BitTorrent::Torrent *torrent);
void addTorrentFailed(const QString &source, const QString &reason);
protected:
bool addTorrentToSession(const QString &source, const BitTorrent::TorrentDescriptor &torrentDescr
, const BitTorrent::AddTorrentParams &addTorrentParams);
void handleAddTorrentFailed(const QString &source, const QString &reason);
void handleDuplicateTorrent(const QString &source, BitTorrent::Torrent *torrent, const QString &message);
void setTorrentFileGuard(const QString &source, std::shared_ptr<TorrentFileGuard> torrentFileGuard);
void releaseTorrentFileGuard(const QString &source);
private:
void onDownloadFinished(const Net::DownloadResult &result);
void onSessionTorrentAdded(BitTorrent::Torrent *torrent);
void onSessionAddTorrentFailed(const BitTorrent::InfoHash &infoHash, const QString &reason);
bool processTorrent(const QString &source, const BitTorrent::TorrentDescriptor &torrentDescr
, const BitTorrent::AddTorrentParams &addTorrentParams);
BitTorrent::Session *m_btSession = nullptr;
QHash<QString, BitTorrent::AddTorrentParams> m_downloadedTorrents;
QHash<BitTorrent::InfoHash, QString> m_sourcesByInfoHash;
QHash<QString, std::shared_ptr<TorrentFileGuard>> m_guardedTorrentFiles;
};

View File

@@ -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;
}

View File

@@ -28,20 +28,41 @@
#pragma once
#include <concepts>
#include <utility>
#include <QtClassHelperMacros>
#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;
virtual IApplication *app() const;
IApplication *app() const;
protected:
explicit ApplicationComponentBase(IApplication *app);
private:
IApplication *m_app = nullptr;
};
template <typename T>
concept IsApplicationComponent = std::derived_from<T, ApplicationComponentBase>;
template <typename Base>
requires (!IsApplicationComponent<Base>)
class ApplicationComponent : public Base, public ApplicationComponentBase
{
public:
template <typename... Args>
explicit ApplicationComponent(IApplication *app, Args&&... args)
: Base(std::forward<Args>(args)...)
, ApplicationComponentBase(app)
{
}
};

View File

@@ -116,6 +116,11 @@ std::size_t BitTorrent::qHash(const BitTorrent::TorrentID &key, const std::size_
return ::qHash(static_cast<TorrentID::BaseType>(key), seed);
}
std::size_t BitTorrent::qHash(const InfoHash &key, const std::size_t seed)
{
return qHashMulti(seed, key.v1(), key.v2());
}
bool BitTorrent::operator==(const BitTorrent::InfoHash &left, const BitTorrent::InfoHash &right)
{
return (static_cast<InfoHash::WrappedType>(left) == static_cast<InfoHash::WrappedType>(right));

View File

@@ -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);
}

View File

@@ -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 &params = {}) = 0;
virtual bool addTorrent(const TorrentDescriptor &torrentDescr, const AddTorrentParams &params = {}) = 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<TorrentID> &ids) = 0;
virtual void decreaseTorrentsQueuePos(const QVector<TorrentID> &ids) = 0;
virtual void topTorrentsQueuePos(const QVector<TorrentID> &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();

View File

@@ -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 &params)
{
// `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 &params)
{
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<lt::alert *> &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))

View File

@@ -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 &params = {}) override;
bool addTorrent(const TorrentDescriptor &torrentDescr, const AddTorrentParams &params = {}) 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<TorrentID> &ids) override;
void decreaseTorrentsQueuePos(const QVector<TorrentID> &ids) override;
void topTorrentsQueuePos(const QVector<TorrentID> &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<TorrentID, TorrentImpl *> m_torrents;
QHash<TorrentID, TorrentImpl *> m_hybridTorrentsByAltID;
QHash<TorrentID, LoadTorrentParams> m_loadingTorrents;
QHash<QString, AddTorrentParams> m_downloadedTorrents;
QHash<TorrentID, RemovingTorrentData> m_removingTorrents;
QSet<TorrentID> m_needSaveResumeDataTorrents;
QHash<TorrentID, TorrentID> m_changedTorrentIDs;

View File

@@ -33,6 +33,8 @@
#include <QtSystemDetection>
#include <QMetaObject>
#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;
};

View File

@@ -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())

View File

@@ -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);

View File

@@ -41,14 +41,16 @@
#include <QVariant>
#include <QVector>
#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<ProcessingJob> &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))
{

View File

@@ -36,6 +36,7 @@
#include <QRegularExpression>
#include <QSharedPointer>
#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<QObject>
{
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);