Skip to content

Commit

Permalink
Auto-set a torrent's parameters based on metadata
Browse files Browse the repository at this point in the history
Adds a system for configuring a set of rules that modify torrent
parameters based on the torrent's metadata. Rules are specified
via a JSON file loaded on startup.

Closes qbittorrent#5779.
  • Loading branch information
tgregerson committed Mar 5, 2024
1 parent 0114610 commit 205c901
Show file tree
Hide file tree
Showing 12 changed files with 980 additions and 32 deletions.
2 changes: 2 additions & 0 deletions src/base/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ add_library(qbt_base STATIC
bittorrent/torrentdescriptor.h
bittorrent/torrentimpl.h
bittorrent/torrentinfo.h
bittorrent/torrentparamrules.h
bittorrent/tracker.h
bittorrent/trackerentry.h
concepts/explicitlyconvertibleto.h
Expand Down Expand Up @@ -148,6 +149,7 @@ add_library(qbt_base STATIC
bittorrent/torrentdescriptor.cpp
bittorrent/torrentimpl.cpp
bittorrent/torrentinfo.cpp
bittorrent/torrentparamrules.cpp
bittorrent/tracker.cpp
bittorrent/trackerentry.cpp
exceptions.cpp
Expand Down
9 changes: 7 additions & 2 deletions src/base/addtorrentmanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include "base/bittorrent/infohash.h"
#include "base/bittorrent/session.h"
#include "base/bittorrent/torrentdescriptor.h"
#include "base/bittorrent/torrentparamrules.h"
#include "base/logger.h"
#include "base/net/downloadmanager.h"
#include "base/preferences.h"
Expand Down Expand Up @@ -177,15 +178,15 @@ void AddTorrentManager::releaseTorrentFileGuard(const QString &source)
}

bool AddTorrentManager::processTorrent(const QString &source, const BitTorrent::TorrentDescriptor &torrentDescr
, const BitTorrent::AddTorrentParams &addTorrentParams)
, BitTorrent::AddTorrentParams addTorrentParams)
{
const BitTorrent::InfoHash infoHash = torrentDescr.infoHash();

const bool hasMetadata = torrentDescr.info().has_value();
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
Expand Down Expand Up @@ -213,5 +214,9 @@ bool AddTorrentManager::processTorrent(const QString &source, const BitTorrent::
return false;
}

// If metadata is not available now, rules will be applied after it is downloaded.
if (hasMetadata)
btSession()->applyTorrentParamRules(torrentDescr, &addTorrentParams);

return addTorrentToSession(source, torrentDescr, addTorrentParams);
}
2 changes: 1 addition & 1 deletion src/base/addtorrentmanager.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ class AddTorrentManager : public ApplicationComponent<QObject>
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::AddTorrentParams addTorrentParams);

BitTorrent::Session *m_btSession = nullptr;
QHash<QString, BitTorrent::AddTorrentParams> m_downloadedTorrents;
Expand Down
2 changes: 2 additions & 0 deletions src/base/bittorrent/session.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include <QtContainerFwd>
#include <QObject>

#include "base/bittorrent/torrentparamrules.h"
#include "base/pathfwd.h"
#include "base/tagset.h"
#include "addtorrentparams.h"
Expand Down Expand Up @@ -452,6 +453,7 @@ namespace BitTorrent
virtual void banIP(const QString &ip) = 0;

virtual bool isKnownTorrent(const InfoHash &infoHash) const = 0;
virtual void applyTorrentParamRules(const TorrentDescriptor &torrentDescr, AddTorrentParams *params) const = 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;
Expand Down
50 changes: 50 additions & 0 deletions src/base/bittorrent/sessionimpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ using namespace std::chrono_literals;
using namespace BitTorrent;

const Path CATEGORIES_FILE_NAME {u"categories.json"_s};
const Path TORRENT_PARAM_RULES_FILE_NAME {u"auto_torrent_customizer_rules.json"_s};
const int MAX_PROCESSING_RESUMEDATA_COUNT = 50;
const int STATISTICS_SAVE_INTERVAL = std::chrono::milliseconds(15min).count();

Expand Down Expand Up @@ -580,6 +581,9 @@ SessionImpl::SessionImpl(QObject *parent)
connect(m_ioThread.get(), &QThread::finished, m_fileSearcher, &QObject::deleteLater);
connect(m_fileSearcher, &FileSearcher::searchFinished, this, &SessionImpl::fileSearchFinished);

m_torrentParamRules = new TorrentParamRules(this);
loadTorrentParamRules();

m_ioThread->start();

initMetrics();
Expand Down Expand Up @@ -4842,6 +4846,11 @@ void SessionImpl::setMaxRatioAction(const MaxRatioAction act)
m_maxRatioAction = static_cast<int>(act);
}

void SessionImpl::applyTorrentParamRules(const TorrentDescriptor &torrentDescr, AddTorrentParams *params) const
{
m_torrentParamRules->apply(torrentDescr, params);
}

bool SessionImpl::isKnownTorrent(const InfoHash &infoHash) const
{
const bool isHybrid = infoHash.isHybrid();
Expand Down Expand Up @@ -4939,6 +4948,8 @@ void SessionImpl::handleTorrentUrlSeedsRemoved(TorrentImpl *const torrent, const

void SessionImpl::handleTorrentMetadataReceived(TorrentImpl *const torrent)
{
qDebug() << "Metadata received for " << torrent->name();
m_torrentParamRules->apply(torrent);
if (!torrentExportDirectory().isEmpty())
exportTorrentFile(torrent, torrentExportDirectory());

Expand Down Expand Up @@ -5200,6 +5211,45 @@ void SessionImpl::loadCategories()
}
}

void SessionImpl::loadTorrentParamRules()
{
const Path path = specialFolderLocation(SpecialFolder::Config) / TORRENT_PARAM_RULES_FILE_NAME;
const QString pathStr = path.toString();
if (!path.exists())
{
LogMsg(tr("Auto torrent customizer rules not found at \"%1\"").arg(pathStr), Log::INFO);
return;
}

constexpr int fileMaxSize = 1024 * 1024;
const auto readResult = Utils::IO::readFile(path, fileMaxSize);
if (!readResult)
{
LogMsg(tr("Failed to read auto torrent customizer rules file \"%1\"").arg(readResult.error().message), Log::WARNING);
return;
}

QJsonParseError jsonError;
const auto jsonDoc = QJsonDocument::fromJson(readResult.value(), &jsonError);
if (jsonError.error != QJsonParseError::NoError)
{
LogMsg(tr("Failed to parse auto torrent customizer rules file: \"%1\". Error: \"%2\"")
.arg(pathStr, jsonError.errorString()), Log::WARNING);
return;
}

if (!jsonDoc.isObject())
{
LogMsg(tr("Failed to load auto torrent customizer rules from \"%1\". Error: \"Invalid data format\"")
.arg(pathStr), Log::WARNING);
return;
}

m_torrentParamRules->clearRules();
const size_t numRules = m_torrentParamRules->loadRulesFromJson(jsonDoc.object());
LogMsg(tr("Loaded %1 auto torrent customizer rule(s) from \"%2\"", nullptr, numRules).arg(numRules).arg(pathStr), Log::INFO);
}

bool SessionImpl::hasPerTorrentRatioLimit() const
{
return std::any_of(m_torrents.cbegin(), m_torrents.cend(), [](const TorrentImpl *torrent)
Expand Down
4 changes: 4 additions & 0 deletions src/base/bittorrent/sessionimpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,7 @@ namespace BitTorrent
void banIP(const QString &ip) override;

bool isKnownTorrent(const InfoHash &infoHash) const override;
void applyTorrentParamRules(const TorrentDescriptor &torrentDescr, AddTorrentParams *params) const 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;
Expand Down Expand Up @@ -584,6 +585,8 @@ namespace BitTorrent
void upgradeCategories();
DownloadPathOption resolveCategoryDownloadPathOption(const QString &categoryName, const std::optional<DownloadPathOption> &option) const;

void loadTorrentParamRules();

void saveStatistics() const;
void loadStatistics();

Expand Down Expand Up @@ -753,6 +756,7 @@ namespace BitTorrent
QThreadPool *m_asyncWorker = nullptr;
ResumeDataStorage *m_resumeDataStorage = nullptr;
FileSearcher *m_fileSearcher = nullptr;
TorrentParamRules *m_torrentParamRules = nullptr;

QHash<TorrentID, lt::torrent_handle> m_downloadedMetadata;

Expand Down

0 comments on commit 205c901

Please sign in to comment.