mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2025-06-27 17:09:53 -04:00
commit
794310dca9
6 changed files with 426 additions and 13 deletions
|
@ -1,5 +1,14 @@
|
|||
# WebAPI Changelog
|
||||
|
||||
## 2.11.9
|
||||
|
||||
* [#21015](https://github.com/qbittorrent/qBittorrent/pull/21015)
|
||||
* Add `torrents/fetchMetadata` endpoint for retrieving torrent metadata associated with a URL
|
||||
* Add `torrents/parseMetadata` endpoint for retrieving torrent metadata associated with a .torrent file
|
||||
* Add `torrents/saveMetadata` endpoint for saving retrieved torrent metadata to a .torrent file
|
||||
* `torrents/add` allows adding a torrent with metadata previously retrieved via `torrents/fetchMetadata` or `torrents/parseMetadata`
|
||||
* `torrents/add` allows specifying a torrent's file priorities
|
||||
|
||||
## 2.11.8
|
||||
|
||||
* [#21349](https://github.com/qbittorrent/qBittorrent/pull/21349)
|
||||
|
|
|
@ -141,6 +141,22 @@ catch (const lt::system_error &err)
|
|||
return nonstd::make_unexpected(QString::fromLocal8Bit(err.what()));
|
||||
}
|
||||
|
||||
nonstd::expected<QByteArray, QString> BitTorrent::TorrentDescriptor::saveToBuffer() const
|
||||
try
|
||||
{
|
||||
const lt::entry torrentEntry = lt::write_torrent_file(m_ltAddTorrentParams);
|
||||
// usually torrent size should be smaller than 1 MB,
|
||||
// however there are >100 MB v2/hybrid torrent files out in the wild
|
||||
QByteArray buffer;
|
||||
buffer.reserve(1024 * 1024);
|
||||
lt::bencode(std::back_inserter(buffer), torrentEntry);
|
||||
return buffer;
|
||||
}
|
||||
catch (const lt::system_error &err)
|
||||
{
|
||||
return nonstd::make_unexpected(QString::fromLocal8Bit(err.what()));
|
||||
}
|
||||
|
||||
BitTorrent::TorrentDescriptor::TorrentDescriptor(lt::add_torrent_params ltAddTorrentParams)
|
||||
: m_ltAddTorrentParams {std::move(ltAddTorrentParams)}
|
||||
{
|
||||
|
|
|
@ -69,6 +69,7 @@ namespace BitTorrent
|
|||
static nonstd::expected<TorrentDescriptor, QString> loadFromFile(const Path &path) noexcept;
|
||||
static nonstd::expected<TorrentDescriptor, QString> parse(const QString &str) noexcept;
|
||||
nonstd::expected<void, QString> saveToFile(const Path &path) const;
|
||||
nonstd::expected<QByteArray, QString> saveToBuffer() const;
|
||||
|
||||
const lt::add_torrent_params <AddTorrentParams() const;
|
||||
|
||||
|
|
|
@ -54,12 +54,15 @@
|
|||
#include "base/interfaces/iapplication.h"
|
||||
#include "base/global.h"
|
||||
#include "base/logger.h"
|
||||
#include "base/net/downloadmanager.h"
|
||||
#include "base/preferences.h"
|
||||
#include "base/torrentfilter.h"
|
||||
#include "base/utils/datetime.h"
|
||||
#include "base/utils/fs.h"
|
||||
#include "base/utils/sslkey.h"
|
||||
#include "base/utils/string.h"
|
||||
#include "apierror.h"
|
||||
#include "apistatus.h"
|
||||
#include "serialize/serialize_torrent.h"
|
||||
|
||||
// Tracker keys
|
||||
|
@ -133,6 +136,16 @@ const QString KEY_FILE_IS_SEED = u"is_seed"_s;
|
|||
const QString KEY_FILE_PIECE_RANGE = u"piece_range"_s;
|
||||
const QString KEY_FILE_AVAILABILITY = u"availability"_s;
|
||||
|
||||
// Torrent info
|
||||
const QString KEY_TORRENTINFO_FILE_LENGTH = u"length"_s;
|
||||
const QString KEY_TORRENTINFO_FILE_PATH = u"path"_s;
|
||||
const QString KEY_TORRENTINFO_FILES = u"files"_s;
|
||||
const QString KEY_TORRENTINFO_INFO = u"info"_s;
|
||||
const QString KEY_TORRENTINFO_LENGTH = u"length"_s;
|
||||
const QString KEY_TORRENTINFO_PIECE_LENGTH = u"piece_length"_s;
|
||||
const QString KEY_TORRENTINFO_TRACKERS = u"trackers"_s;
|
||||
const QString KEY_TORRENTINFO_WEBSEEDS = u"webseeds"_s;
|
||||
|
||||
namespace
|
||||
{
|
||||
using Utils::String::parseBool;
|
||||
|
@ -343,6 +356,123 @@ namespace
|
|||
|
||||
return url;
|
||||
}
|
||||
|
||||
QJsonObject serializeInfoHash(const BitTorrent::InfoHash &infoHash)
|
||||
{
|
||||
return {
|
||||
{KEY_TORRENT_INFOHASHV1, infoHash.v1().toString()},
|
||||
{KEY_TORRENT_INFOHASHV2, infoHash.v2().toString()},
|
||||
{KEY_TORRENT_ID, infoHash.toTorrentID().toString()},
|
||||
};
|
||||
}
|
||||
|
||||
QJsonObject serializeTorrentInfo(const BitTorrent::TorrentInfo &info)
|
||||
{
|
||||
qlonglong torrentSize = 0;
|
||||
QJsonArray files;
|
||||
for (int fileIndex = 0; fileIndex < info.filesCount(); ++fileIndex)
|
||||
{
|
||||
const qlonglong fileSize = info.fileSize(fileIndex);
|
||||
torrentSize += fileSize;
|
||||
files << QJsonObject
|
||||
{
|
||||
// use platform-independent separators
|
||||
{KEY_TORRENTINFO_FILE_PATH, info.filePath(fileIndex).data()},
|
||||
{KEY_TORRENTINFO_FILE_LENGTH, fileSize}
|
||||
};
|
||||
}
|
||||
|
||||
const BitTorrent::InfoHash infoHash = info.infoHash();
|
||||
|
||||
return {
|
||||
{KEY_TORRENT_INFOHASHV1, infoHash.v1().toString()},
|
||||
{KEY_TORRENT_INFOHASHV2, infoHash.v2().toString()},
|
||||
{KEY_TORRENT_ID, infoHash.toTorrentID().toString()},
|
||||
{KEY_TORRENTINFO_INFO, QJsonObject {
|
||||
{KEY_TORRENTINFO_FILES, files},
|
||||
{KEY_TORRENTINFO_LENGTH, torrentSize},
|
||||
{KEY_TORRENT_NAME, info.name()},
|
||||
{KEY_TORRENTINFO_PIECE_LENGTH, info.pieceLength()},
|
||||
{KEY_PROP_PIECES_NUM, info.piecesCount()},
|
||||
{KEY_PROP_PRIVATE, info.isPrivate()},
|
||||
}},
|
||||
};
|
||||
}
|
||||
|
||||
QJsonObject serializeTorrentInfo(const BitTorrent::TorrentDescriptor &torrentDescr)
|
||||
{
|
||||
QJsonObject info = serializeTorrentInfo(torrentDescr.info().value());
|
||||
|
||||
QJsonArray trackers;
|
||||
for (const BitTorrent::TrackerEntry &tracker : asConst(torrentDescr.trackers()))
|
||||
{
|
||||
trackers << QJsonObject
|
||||
{
|
||||
{KEY_TRACKER_URL, tracker.url},
|
||||
{KEY_TRACKER_TIER, tracker.tier}
|
||||
};
|
||||
}
|
||||
info.insert(KEY_TORRENTINFO_TRACKERS, trackers);
|
||||
|
||||
QJsonArray webseeds;
|
||||
for (const QUrl &webseed : asConst(torrentDescr.urlSeeds()))
|
||||
{
|
||||
webseeds << webseed.toString();
|
||||
}
|
||||
info.insert(KEY_TORRENTINFO_WEBSEEDS, webseeds);
|
||||
|
||||
info.insert(KEY_PROP_CREATED_BY, torrentDescr.creator());
|
||||
info.insert(KEY_PROP_CREATION_DATE, Utils::DateTime::toSecsSinceEpoch(torrentDescr.creationDate()));
|
||||
info.insert(KEY_PROP_COMMENT, torrentDescr.comment());
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
QJsonObject serializeTorrentInfo(const BitTorrent::Torrent &torrent)
|
||||
{
|
||||
QJsonObject info = serializeTorrentInfo(torrent.info());
|
||||
|
||||
QJsonArray trackers;
|
||||
for (const BitTorrent::TrackerEntryStatus &tracker : asConst(torrent.trackers()))
|
||||
{
|
||||
trackers << QJsonObject
|
||||
{
|
||||
{KEY_TRACKER_URL, tracker.url},
|
||||
{KEY_TRACKER_TIER, tracker.tier}
|
||||
};
|
||||
}
|
||||
info.insert(KEY_TORRENTINFO_TRACKERS, trackers);
|
||||
|
||||
QJsonArray webseeds;
|
||||
for (const QUrl &webseed : asConst(torrent.urlSeeds()))
|
||||
{
|
||||
webseeds << webseed.toString();
|
||||
}
|
||||
info.insert(KEY_TORRENTINFO_WEBSEEDS, webseeds);
|
||||
|
||||
info.insert(KEY_PROP_CREATED_BY, torrent.creator());
|
||||
info.insert(KEY_PROP_CREATION_DATE, Utils::DateTime::toSecsSinceEpoch(torrent.creationDate()));
|
||||
info.insert(KEY_PROP_COMMENT, torrent.comment());
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
nonstd::expected<BitTorrent::DownloadPriority, QString> parseDownloadPriority(const QString &priorityStr)
|
||||
{
|
||||
bool ok = false;
|
||||
const auto priority = static_cast<BitTorrent::DownloadPriority>(priorityStr.toInt(&ok));
|
||||
if (!ok)
|
||||
return nonstd::make_unexpected(TorrentsController::tr("Priority must be an integer"));
|
||||
if (!isValidDownloadPriority(priority))
|
||||
return nonstd::make_unexpected(TorrentsController::tr("Priority is not valid"));
|
||||
return priority;
|
||||
}
|
||||
}
|
||||
|
||||
TorrentsController::TorrentsController(IApplication *app, QObject *parent)
|
||||
: APIController(app, parent)
|
||||
{
|
||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::metadataDownloaded, this, &TorrentsController::onMetadataDownloaded);
|
||||
}
|
||||
|
||||
void TorrentsController::countAction()
|
||||
|
@ -839,7 +969,7 @@ void TorrentsController::pieceStatesAction()
|
|||
|
||||
void TorrentsController::addAction()
|
||||
{
|
||||
const QString urls = params()[u"urls"_s];
|
||||
const QStringList urls = params()[u"urls"_s].split(u'\n', Qt::SkipEmptyParts);
|
||||
|
||||
const bool skipChecking = parseBool(params()[u"skip_checking"_s]).value_or(false);
|
||||
const bool seqDownload = parseBool(params()[u"sequentialDownload"_s]).value_or(false);
|
||||
|
@ -871,7 +1001,28 @@ void TorrentsController::addAction()
|
|||
? Utils::String::toEnum(contentLayoutParam, BitTorrent::TorrentContentLayout::Original)
|
||||
: std::optional<BitTorrent::TorrentContentLayout> {});
|
||||
|
||||
const BitTorrent::AddTorrentParams addTorrentParams
|
||||
const DataMap &torrents = data();
|
||||
|
||||
QList<BitTorrent::DownloadPriority> filePriorities;
|
||||
const QStringList filePrioritiesParam = params()[u"filePriorities"_s].split(u',', Qt::SkipEmptyParts);
|
||||
if (!filePrioritiesParam.isEmpty())
|
||||
{
|
||||
if (urls.size() > 1)
|
||||
throw APIError(APIErrorType::BadParams, tr("Cannot specify filePriorities when adding multiple torrents"));
|
||||
if (!torrents.isEmpty())
|
||||
throw APIError(APIErrorType::BadParams, tr("Cannot specify filePriorities when uploading torrent files"));
|
||||
|
||||
filePriorities.reserve(filePrioritiesParam.size());
|
||||
for (const QString &priorityStr : filePrioritiesParam)
|
||||
{
|
||||
const nonstd::expected<BitTorrent::DownloadPriority, QString> result = parseDownloadPriority(priorityStr);
|
||||
if (!result)
|
||||
throw APIError(APIErrorType::BadParams, result.error());
|
||||
filePriorities << result.value();
|
||||
}
|
||||
}
|
||||
|
||||
BitTorrent::AddTorrentParams addTorrentParams
|
||||
{
|
||||
// TODO: Check if destination actually exists
|
||||
.name = torrentName,
|
||||
|
@ -905,17 +1056,45 @@ void TorrentsController::addAction()
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
bool partialSuccess = false;
|
||||
for (QString url : asConst(urls.split(u'\n')))
|
||||
for (QString url : urls)
|
||||
{
|
||||
url = url.trimmed();
|
||||
if (!url.isEmpty())
|
||||
if (url.isEmpty())
|
||||
continue;
|
||||
|
||||
BitTorrent::InfoHash infoHash;
|
||||
if (const auto iter = m_torrentSourceCache.constFind(url); iter != m_torrentSourceCache.constEnd())
|
||||
infoHash = iter.value();
|
||||
else if (const auto sourceTorrentDescr = BitTorrent::TorrentDescriptor::parse(url))
|
||||
infoHash = sourceTorrentDescr.value().infoHash();
|
||||
|
||||
if (const BitTorrent::TorrentDescriptor &torrentDescr = m_torrentMetadataCache.value(infoHash); torrentDescr.info().has_value())
|
||||
{
|
||||
if (!filePriorities.isEmpty())
|
||||
{
|
||||
const BitTorrent::TorrentInfo &info = torrentDescr.info().value();
|
||||
if (filePriorities.size() != info.filesCount())
|
||||
throw APIError(APIErrorType::BadParams, tr("Length of filePriorities must equal number of files in torrent"));
|
||||
|
||||
addTorrentParams.filePriorities = filePriorities;
|
||||
}
|
||||
|
||||
partialSuccess |= BitTorrent::Session::instance()->addTorrent(torrentDescr, addTorrentParams);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!filePriorities.isEmpty())
|
||||
throw APIError(APIErrorType::BadParams, tr("filePriorities may only be specified when metadata has already been fetched"));
|
||||
|
||||
partialSuccess |= app()->addTorrentManager()->addTorrent(url, addTorrentParams);
|
||||
}
|
||||
m_torrentSourceCache.remove(url);
|
||||
m_torrentMetadataCache.remove(infoHash);
|
||||
}
|
||||
|
||||
const DataMap &torrents = data();
|
||||
// process uploaded .torrent files
|
||||
for (auto it = torrents.constBegin(); it != torrents.constEnd(); ++it)
|
||||
{
|
||||
if (const auto loadResult = BitTorrent::TorrentDescriptor::load(it.value()))
|
||||
|
@ -1103,13 +1282,11 @@ void TorrentsController::filePrioAction()
|
|||
requireParams({u"hash"_s, u"id"_s, u"priority"_s});
|
||||
|
||||
const auto id = BitTorrent::TorrentID::fromString(params()[u"hash"_s]);
|
||||
bool ok = false;
|
||||
const auto priority = static_cast<BitTorrent::DownloadPriority>(params()[u"priority"_s].toInt(&ok));
|
||||
if (!ok)
|
||||
throw APIError(APIErrorType::BadParams, tr("Priority must be an integer"));
|
||||
const nonstd::expected<BitTorrent::DownloadPriority, QString> result = parseDownloadPriority(params()[u"priority"_s]);
|
||||
if (!result)
|
||||
throw APIError(APIErrorType::BadParams, result.error());
|
||||
|
||||
if (!BitTorrent::isValidDownloadPriority(priority))
|
||||
throw APIError(APIErrorType::BadParams, tr("Priority is not valid"));
|
||||
const BitTorrent::DownloadPriority priority = result.value();
|
||||
|
||||
BitTorrent::Torrent *const torrent = BitTorrent::Session::instance()->getTorrent(id);
|
||||
if (!torrent)
|
||||
|
@ -1122,6 +1299,7 @@ void TorrentsController::filePrioAction()
|
|||
bool priorityChanged = false;
|
||||
for (const QString &fileID : params()[u"id"_s].split(u'|'))
|
||||
{
|
||||
bool ok = false;
|
||||
const int id = fileID.toInt(&ok);
|
||||
if (!ok)
|
||||
throw APIError(APIErrorType::BadParams, tr("File IDs must be integers"));
|
||||
|
@ -1768,3 +1946,186 @@ void TorrentsController::setSSLParametersAction()
|
|||
|
||||
setResult(QString());
|
||||
}
|
||||
|
||||
void TorrentsController::fetchMetadataAction()
|
||||
{
|
||||
requireParams({u"source"_s});
|
||||
|
||||
const QString sourceParam = params()[u"source"_s].trimmed();
|
||||
// must provide some value to parse
|
||||
if (sourceParam.isEmpty())
|
||||
throw APIError(APIErrorType::BadParams, tr("Must specify URI or hash"));
|
||||
|
||||
const QString source = QUrl::fromPercentEncoding(sourceParam.toLatin1());
|
||||
const auto sourceTorrentDescr = BitTorrent::TorrentDescriptor::parse(source);
|
||||
|
||||
const BitTorrent::InfoHash infoHash = sourceTorrentDescr ? sourceTorrentDescr.value().infoHash() : m_torrentSourceCache.value(source);
|
||||
if (infoHash.isValid())
|
||||
{
|
||||
// check metadata cache
|
||||
if (const BitTorrent::TorrentDescriptor &torrentDescr = m_torrentMetadataCache.value(infoHash);
|
||||
torrentDescr.info().has_value())
|
||||
{
|
||||
setResult(serializeTorrentInfo(torrentDescr));
|
||||
}
|
||||
// check transfer list
|
||||
else if (const BitTorrent::Torrent *const torrent = BitTorrent::Session::instance()->findTorrent(infoHash);
|
||||
torrent && torrent->info().isValid())
|
||||
{
|
||||
setResult(serializeTorrentInfo(*torrent));
|
||||
}
|
||||
// check request cache
|
||||
else if (BitTorrent::Session::instance()->isKnownTorrent(infoHash))
|
||||
{
|
||||
setResult(serializeInfoHash(infoHash));
|
||||
setStatus(APIStatus::Async);
|
||||
}
|
||||
// request torrent's metadata
|
||||
else
|
||||
{
|
||||
if (!BitTorrent::Session::instance()->downloadMetadata(sourceTorrentDescr.value())) [[unlikely]]
|
||||
throw APIError(APIErrorType::BadParams, tr("Unable to download metadata for '%1'").arg(infoHash.toTorrentID().toString()));
|
||||
|
||||
m_torrentMetadataCache.insert(infoHash, sourceTorrentDescr.value());
|
||||
|
||||
setResult(serializeInfoHash(infoHash));
|
||||
setStatus(APIStatus::Async);
|
||||
}
|
||||
}
|
||||
// http(s) url
|
||||
else if (Net::DownloadManager::hasSupportedScheme(source))
|
||||
{
|
||||
if (!m_requestedTorrentSource.contains(source))
|
||||
{
|
||||
const auto *pref = Preferences::instance();
|
||||
|
||||
Net::DownloadManager::instance()->download(Net::DownloadRequest(source).limit(pref->getTorrentFileSizeLimit())
|
||||
, pref->useProxyForGeneralPurposes(), this, &TorrentsController::onDownloadFinished);
|
||||
|
||||
m_requestedTorrentSource.insert(source);
|
||||
}
|
||||
|
||||
setResult(QJsonObject {});
|
||||
setStatus(APIStatus::Async);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw APIError(APIErrorType::BadParams, tr("Unable to parse '%1'").arg(source));
|
||||
}
|
||||
}
|
||||
|
||||
void TorrentsController::parseMetadataAction()
|
||||
{
|
||||
const DataMap &uploadedTorrents = data();
|
||||
// must provide some value to parse
|
||||
if (uploadedTorrents.isEmpty())
|
||||
throw APIError(APIErrorType::BadParams, tr("Must specify torrent file(s)"));
|
||||
|
||||
QJsonObject result;
|
||||
for (auto it = uploadedTorrents.constBegin(); it != uploadedTorrents.constEnd(); ++it)
|
||||
{
|
||||
if (const auto loadResult = BitTorrent::TorrentDescriptor::load(it.value()))
|
||||
{
|
||||
const BitTorrent::TorrentDescriptor &torrentDescr = loadResult.value();
|
||||
m_torrentMetadataCache.insert(torrentDescr.infoHash(), torrentDescr);
|
||||
|
||||
const QString &fileName = it.key();
|
||||
result.insert(fileName, serializeTorrentInfo(torrentDescr));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw APIError(APIErrorType::BadData, tr("'%1' is not a valid torrent file.").arg(it.key()));
|
||||
}
|
||||
}
|
||||
|
||||
setResult(result);
|
||||
}
|
||||
|
||||
void TorrentsController::saveMetadataAction()
|
||||
{
|
||||
requireParams({u"source"_s});
|
||||
|
||||
const QString sourceParam = params()[u"source"_s].trimmed();
|
||||
if (sourceParam.isEmpty())
|
||||
throw APIError(APIErrorType::BadParams, tr("Must specify URI or hash"));
|
||||
|
||||
const QString source = QUrl::fromPercentEncoding(sourceParam.toLatin1());
|
||||
|
||||
BitTorrent::InfoHash infoHash;
|
||||
if (const auto iter = m_torrentSourceCache.constFind(source); iter != m_torrentSourceCache.constEnd())
|
||||
infoHash = iter.value();
|
||||
else if (const auto sourceTorrentDescr = BitTorrent::TorrentDescriptor::parse(source))
|
||||
infoHash = sourceTorrentDescr.value().infoHash();
|
||||
|
||||
if (!infoHash.isValid())
|
||||
throw APIError(APIErrorType::NotFound);
|
||||
|
||||
const BitTorrent::TorrentDescriptor &torrentDescr = m_torrentMetadataCache.value(infoHash);
|
||||
if (!torrentDescr.info().has_value())
|
||||
throw APIError(APIErrorType::Conflict, tr("Metadata is not yet available"));
|
||||
|
||||
const nonstd::expected<QByteArray, QString> result = torrentDescr.saveToBuffer();
|
||||
if (!result)
|
||||
throw APIError(APIErrorType::Conflict, tr("Unable to export torrent metadata. Error: %1").arg(result.error()));
|
||||
|
||||
setResult(result.value(), u"application/x-bittorrent"_s, (infoHash.toTorrentID().toString() + u".torrent"));
|
||||
}
|
||||
|
||||
void TorrentsController::onDownloadFinished(const Net::DownloadResult &result)
|
||||
{
|
||||
const QString source = result.url;
|
||||
m_requestedTorrentSource.remove(source);
|
||||
|
||||
switch (result.status)
|
||||
{
|
||||
case Net::DownloadStatus::Success:
|
||||
// use the info directly from the .torrent file
|
||||
if (const auto loadResult = BitTorrent::TorrentDescriptor::load(result.data))
|
||||
{
|
||||
const BitTorrent::TorrentDescriptor &torrentDescr = loadResult.value();
|
||||
const BitTorrent::InfoHash infoHash = torrentDescr.infoHash();
|
||||
m_torrentSourceCache.insert(source, infoHash);
|
||||
m_torrentMetadataCache.insert(infoHash, torrentDescr);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogMsg(tr("Parse torrent failed. URL: \"%1\". Error: \"%2\".").arg(source, loadResult.error()), Log::WARNING);
|
||||
m_torrentSourceCache.remove(source);
|
||||
}
|
||||
break;
|
||||
|
||||
case Net::DownloadStatus::RedirectedToMagnet:
|
||||
if (const auto parseResult = BitTorrent::TorrentDescriptor::parse(result.magnetURI))
|
||||
{
|
||||
const BitTorrent::TorrentDescriptor &torrentDescr = parseResult.value();
|
||||
const BitTorrent::InfoHash infoHash = torrentDescr.infoHash();
|
||||
m_torrentSourceCache.insert(source, infoHash);
|
||||
|
||||
if (!m_torrentMetadataCache.contains(infoHash) && !BitTorrent::Session::instance()->isKnownTorrent(infoHash))
|
||||
{
|
||||
if (BitTorrent::Session::instance()->downloadMetadata(torrentDescr))
|
||||
m_torrentMetadataCache.insert(infoHash, torrentDescr);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogMsg(tr("Parse magnet URI failed. URI: \"%1\". Error: \"%2\".").arg(result.magnetURI, parseResult.error()), Log::WARNING);
|
||||
m_torrentSourceCache.remove(source);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void TorrentsController::onMetadataDownloaded(const BitTorrent::TorrentInfo &info)
|
||||
{
|
||||
Q_ASSERT(info.isValid());
|
||||
if (!info.isValid()) [[unlikely]]
|
||||
return;
|
||||
|
||||
const BitTorrent::InfoHash infoHash = info.infoHash();
|
||||
if (auto iter = m_torrentMetadataCache.find(infoHash); iter != m_torrentMetadataCache.end())
|
||||
iter.value().setTorrentInfo(info);
|
||||
}
|
||||
|
|
|
@ -28,15 +28,30 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <QHash>
|
||||
#include <QSet>
|
||||
|
||||
#include "base/bittorrent/torrentdescriptor.h"
|
||||
#include "apicontroller.h"
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
class InfoHash;
|
||||
class TorrentInfo;
|
||||
}
|
||||
|
||||
namespace Net
|
||||
{
|
||||
struct DownloadResult;
|
||||
}
|
||||
|
||||
class TorrentsController : public APIController
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(TorrentsController)
|
||||
|
||||
public:
|
||||
using APIController::APIController;
|
||||
explicit TorrentsController(IApplication *app, QObject *parent = nullptr);
|
||||
|
||||
private slots:
|
||||
void countAction();
|
||||
|
@ -95,4 +110,15 @@ private slots:
|
|||
void exportAction();
|
||||
void SSLParametersAction();
|
||||
void setSSLParametersAction();
|
||||
void fetchMetadataAction();
|
||||
void parseMetadataAction();
|
||||
void saveMetadataAction();
|
||||
|
||||
private:
|
||||
void onDownloadFinished(const Net::DownloadResult &result);
|
||||
void onMetadataDownloaded(const BitTorrent::TorrentInfo &info);
|
||||
|
||||
QHash<QString, BitTorrent::InfoHash> m_torrentSourceCache;
|
||||
QHash<BitTorrent::InfoHash, BitTorrent::TorrentDescriptor> m_torrentMetadataCache;
|
||||
QSet<QString> m_requestedTorrentSource;
|
||||
};
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
#include "base/utils/version.h"
|
||||
#include "api/isessionmanager.h"
|
||||
|
||||
inline const Utils::Version<3, 2> API_VERSION {2, 11, 8};
|
||||
inline const Utils::Version<3, 2> API_VERSION {2, 11, 9};
|
||||
|
||||
class APIController;
|
||||
class AuthController;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue