refactor: convert `tr_incomplete_metadata` to c++ class (#6383)
* refactor: unset peer BEP-9 support if size hint is invalid * fix: open torrent file in binary mode * refactor: move metadata size check to method * refactor: remove duplicate checks * refactor: reduce temp variable scope in `parseLtepHandshake()` * refactor: convert `get_piece_length()` to method * refactor: convert `tr_torrentSetMetadataSizeHint()` to method * refactor: convert `tr_torrentGetMetadataPiece()` to method * refactor: convert `tr_torrentUseMetainfoFromFile()` to method * refactor: convert `tr_torrentSetMetadataPiece()` to method * refactor: convert `tr_torrentGetNextMetadataRequest()` to method * refactor: convert `tr_torrentGetMetadataPercent()` to method * refactor: add basic framework for MagnetMediator * refactor: initialise `tr_incomplete_metadata` fields in constructor * refactor: check metadata transfer completion in `set_metadata_piece()` * refactor: convert `use_new_metainfo()` and `on_have_all_metainfo()` to methods * refactor: move parts of `tr_torrent::set_metadata_piece()` into `tr_incomplete_metadata` * refactor: move parts of `tr_torrent::get_next_metadata_request()` into `tr_incomplete_metadata` * refactor: move parts of `tr_torrent::get_metadata_percent()` into `tr_incomplete_metadata` * refactor: hide all `tr_incomplete_metadata` fields * refactor: move `incomplete_metadata` to private * feat: add test for `set_metadata_piece()` * refactor: unify integer types * refactor: rename `tr_incomplete_metadata` to `tr_metadata_download` * chore: make clang-tidy happy libtransmission/torrent-magnet.cc:117:68: warning: comparison of integers of different signs: 'long' and 'const uint64_t' (aka 'const unsigned long') [clang-diagnostic-sign-compare] * refactor: pass log name to `tr_metadata_download` constructor * chore: iwyu * fix: thread-safe `TorrentMagnetTest.setMetadataPiece` * chore: housekeeping * Revert "fix: thread-safe `TorrentMagnetTest.setMetadataPiece`" This reverts commit 2a7fcd93a262888f9f55d542b1a9a2da9ca72cea. * fix: stop soon instead of stop now in `on_metainfo_completed()` This is unreachable code now, but if it is ever reached, Transmission will very likely crash. * fix: maybe fix OpenBSD test failure
This commit is contained in:
parent
5000edef01
commit
10d047005a
|
@ -2256,7 +2256,6 @@ void tr_peerMgr::bandwidth_pulse()
|
|||
for (auto* const tor : torrents_)
|
||||
{
|
||||
tor->do_idle_work();
|
||||
tr_torrentMagnetDoIdleWork(tor);
|
||||
}
|
||||
|
||||
reconnect_pulse();
|
||||
|
|
|
@ -1015,12 +1015,11 @@ void parseLtepHandshake(tr_peerMsgsImpl* msgs, MessageReader& payload)
|
|||
logtrace(msgs, fmt::format(FMT_STRING("here is the base64-encoded handshake: [{:s}]"), tr_base64_encode(handshake_sv)));
|
||||
|
||||
/* does the peer prefer encrypted connections? */
|
||||
auto i = int64_t{};
|
||||
auto pex = tr_pex{};
|
||||
auto& [addr, port] = pex.socket_address;
|
||||
if (tr_variantDictFindInt(&*var, TR_KEY_e, &i))
|
||||
if (auto e = int64_t{}; tr_variantDictFindInt(&*var, TR_KEY_e, &e))
|
||||
{
|
||||
msgs->encryption_preference = i != 0 ? EncryptionPreference::Yes : EncryptionPreference::No;
|
||||
msgs->encryption_preference = e != 0 ? EncryptionPreference::Yes : EncryptionPreference::No;
|
||||
|
||||
if (msgs->encryption_preference == EncryptionPreference::Yes)
|
||||
{
|
||||
|
@ -1034,21 +1033,21 @@ void parseLtepHandshake(tr_peerMsgsImpl* msgs, MessageReader& payload)
|
|||
|
||||
if (tr_variant* sub = nullptr; tr_variantDictFindDict(&*var, TR_KEY_m, &sub))
|
||||
{
|
||||
if (tr_variantDictFindInt(sub, TR_KEY_ut_pex, &i))
|
||||
if (auto ut_pex = int64_t{}; tr_variantDictFindInt(sub, TR_KEY_ut_pex, &ut_pex))
|
||||
{
|
||||
msgs->peerSupportsPex = i != 0;
|
||||
msgs->ut_pex_id = static_cast<uint8_t>(i);
|
||||
msgs->peerSupportsPex = ut_pex != 0;
|
||||
msgs->ut_pex_id = static_cast<uint8_t>(ut_pex);
|
||||
logtrace(msgs, fmt::format(FMT_STRING("msgs->ut_pex is {:d}"), static_cast<int>(msgs->ut_pex_id)));
|
||||
}
|
||||
|
||||
if (tr_variantDictFindInt(sub, TR_KEY_ut_metadata, &i))
|
||||
if (auto ut_metadata = int64_t{}; tr_variantDictFindInt(sub, TR_KEY_ut_metadata, &ut_metadata))
|
||||
{
|
||||
msgs->peerSupportsMetadataXfer = i != 0;
|
||||
msgs->ut_metadata_id = static_cast<uint8_t>(i);
|
||||
msgs->peerSupportsMetadataXfer = ut_metadata != 0;
|
||||
msgs->ut_metadata_id = static_cast<uint8_t>(ut_metadata);
|
||||
logtrace(msgs, fmt::format(FMT_STRING("msgs->ut_metadata_id is {:d}"), static_cast<int>(msgs->ut_metadata_id)));
|
||||
}
|
||||
|
||||
if (tr_variantDictFindInt(sub, TR_KEY_ut_holepunch, &i))
|
||||
if (auto ut_holepunch = int64_t{}; tr_variantDictFindInt(sub, TR_KEY_ut_holepunch, &ut_holepunch))
|
||||
{
|
||||
// Transmission doesn't support this extension yet.
|
||||
// But its presence does indicate µTP supports,
|
||||
|
@ -1058,13 +1057,20 @@ void parseLtepHandshake(tr_peerMsgsImpl* msgs, MessageReader& payload)
|
|||
}
|
||||
|
||||
/* look for metainfo size (BEP 9) */
|
||||
if (tr_variantDictFindInt(&*var, TR_KEY_metadata_size, &i))
|
||||
if (auto metadata_size = int64_t{}; tr_variantDictFindInt(&*var, TR_KEY_metadata_size, &metadata_size))
|
||||
{
|
||||
tr_torrentSetMetadataSizeHint(msgs->torrent, i);
|
||||
if (!tr_metadata_download::is_valid_metadata_size(metadata_size))
|
||||
{
|
||||
msgs->peerSupportsMetadataXfer = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
msgs->torrent->maybe_start_metadata_transfer(metadata_size);
|
||||
}
|
||||
}
|
||||
|
||||
/* look for upload_only (BEP 21) */
|
||||
if (tr_variantDictFindInt(&*var, TR_KEY_upload_only, &i))
|
||||
if (auto upload_only = int64_t{}; tr_variantDictFindInt(&*var, TR_KEY_upload_only, &upload_only))
|
||||
{
|
||||
pex.flags |= ADDED_F_SEED_FLAG;
|
||||
}
|
||||
|
@ -1079,11 +1085,11 @@ void parseLtepHandshake(tr_peerMsgsImpl* msgs, MessageReader& payload)
|
|||
}
|
||||
|
||||
/* get peer's listening port */
|
||||
if (tr_variantDictFindInt(&*var, TR_KEY_p, &i) && i > 0)
|
||||
if (auto p = int64_t{}; tr_variantDictFindInt(&*var, TR_KEY_p, &p) && p > 0)
|
||||
{
|
||||
port.set_host(i);
|
||||
port.set_host(p);
|
||||
msgs->publish(tr_peer_event::GotPort(port));
|
||||
logtrace(msgs, fmt::format(FMT_STRING("peer's port is now {:d}"), i));
|
||||
logtrace(msgs, fmt::format(FMT_STRING("peer's port is now {:d}"), p));
|
||||
}
|
||||
|
||||
std::byte const* addr_compact = nullptr;
|
||||
|
@ -1103,9 +1109,9 @@ void parseLtepHandshake(tr_peerMsgsImpl* msgs, MessageReader& payload)
|
|||
}
|
||||
|
||||
/* get peer's maximum request queue size */
|
||||
if (tr_variantDictFindInt(&*var, TR_KEY_reqq, &i))
|
||||
if (auto reqq = int64_t{}; tr_variantDictFindInt(&*var, TR_KEY_reqq, &reqq))
|
||||
{
|
||||
msgs->reqq = i;
|
||||
msgs->reqq = reqq;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1135,13 +1141,10 @@ void parseUtMetadata(tr_peerMsgsImpl* msgs, MessageReader& payload_in)
|
|||
/* NOOP */
|
||||
}
|
||||
|
||||
auto const* const benc_end = serde.end();
|
||||
|
||||
if (msg_type == MetadataMsgType::Data && !msgs->torrent->has_metainfo() && msg_end - benc_end <= MetadataPieceSize &&
|
||||
piece * MetadataPieceSize + (msg_end - benc_end) <= total_size)
|
||||
if (auto const piece_len = msg_end - serde.end();
|
||||
msg_type == MetadataMsgType::Data && piece * MetadataPieceSize + piece_len <= total_size)
|
||||
{
|
||||
size_t const piece_len = msg_end - benc_end;
|
||||
tr_torrentSetMetadataPiece(msgs->torrent, piece, benc_end, piece_len);
|
||||
msgs->torrent->set_metadata_piece(piece, serde.end(), piece_len);
|
||||
}
|
||||
|
||||
if (msg_type == MetadataMsgType::Request)
|
||||
|
@ -1745,7 +1748,7 @@ void updateMetadataRequests(tr_peerMsgsImpl* msgs, time_t now)
|
|||
return;
|
||||
}
|
||||
|
||||
if (auto const piece = tr_torrentGetNextMetadataRequest(msgs->torrent, now); piece)
|
||||
if (auto const piece = msgs->torrent->get_next_metadata_request(now); piece)
|
||||
{
|
||||
auto tmp = tr_variant{};
|
||||
tr_variantInitDict(&tmp, 3);
|
||||
|
@ -1796,8 +1799,8 @@ namespace peer_pulse_helpers
|
|||
return {};
|
||||
}
|
||||
|
||||
auto data = tr_metadata_piece{};
|
||||
if (!tr_torrentGetMetadataPiece(msgs->torrent, *piece, data))
|
||||
auto data = msgs->torrent->get_metadata_piece(*piece);
|
||||
if (!data)
|
||||
{
|
||||
// send a reject
|
||||
auto tmp = tr_variant{};
|
||||
|
@ -1813,7 +1816,7 @@ namespace peer_pulse_helpers
|
|||
tr_variantDictAddInt(&tmp, TR_KEY_msg_type, MetadataMsgType::Data);
|
||||
tr_variantDictAddInt(&tmp, TR_KEY_piece, *piece);
|
||||
tr_variantDictAddInt(&tmp, TR_KEY_total_size, msgs->torrent->info_dict_size());
|
||||
return protocol_send_message(msgs, BtPeerMsgs::Ltep, msgs->ut_metadata_id, tr_variant_serde::benc().to_string(tmp), data);
|
||||
return protocol_send_message(msgs, BtPeerMsgs::Ltep, msgs->ut_metadata_id, tr_variant_serde::benc().to_string(tmp), *data);
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t add_next_piece(tr_peerMsgsImpl* msgs, uint64_t now)
|
||||
|
|
|
@ -4,11 +4,9 @@
|
|||
// License text can be found in the licenses/ folder.
|
||||
|
||||
#include <algorithm>
|
||||
#include <climits> /* INT_MAX */
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <ctime>
|
||||
#include <deque>
|
||||
#include <fstream>
|
||||
#include <ios>
|
||||
#include <memory>
|
||||
|
@ -16,7 +14,6 @@
|
|||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility> // std::move
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
|
@ -33,120 +30,114 @@
|
|||
#include "libtransmission/utils.h"
|
||||
#include "libtransmission/variant.h"
|
||||
|
||||
#define tr_logAddDebugMagnet(magnet, msg) tr_logAddDebug(msg, (magnet)->log_name())
|
||||
|
||||
namespace
|
||||
{
|
||||
// don't ask for the same metadata piece more than this often
|
||||
auto constexpr MinRepeatIntervalSecs = int{ 3 };
|
||||
auto constexpr MinRepeatIntervalSecs = time_t{ 3 };
|
||||
|
||||
[[nodiscard]] auto create_all_needed(int n_pieces)
|
||||
[[nodiscard]] int64_t div_ceil(int64_t numerator, int64_t denominator)
|
||||
{
|
||||
auto ret = std::deque<tr_incomplete_metadata::metadata_node>{};
|
||||
|
||||
ret.resize(n_pieces);
|
||||
|
||||
for (int i = 0; i < n_pieces; ++i)
|
||||
{
|
||||
ret[i].piece = i;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
[[nodiscard]] int div_ceil(int numerator, int denominator)
|
||||
{
|
||||
auto const [quot, rem] = std::div(numerator, denominator);
|
||||
auto const [quot, rem] = std::lldiv(numerator, denominator);
|
||||
return quot + (rem == 0 ? 0 : 1);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
bool tr_torrentSetMetadataSizeHint(tr_torrent* tor, int64_t size)
|
||||
void tr_metadata_download::create_all_needed(int64_t n_pieces) noexcept
|
||||
{
|
||||
if (tor->has_metainfo())
|
||||
pieces_needed_.clear();
|
||||
pieces_needed_.resize(n_pieces);
|
||||
|
||||
for (int64_t i = 0; i < n_pieces; ++i)
|
||||
{
|
||||
return false;
|
||||
pieces_needed_[i].piece = i;
|
||||
}
|
||||
|
||||
if (tor->incomplete_metadata)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int const n = (size <= 0 || size > INT_MAX) ? -1 : div_ceil(size, MetadataPieceSize);
|
||||
tr_logAddDebugTor(tor, fmt::format("metadata is {} bytes in {} pieces", size, n));
|
||||
if (n <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto m = std::make_unique<tr_incomplete_metadata>();
|
||||
m->piece_count = n;
|
||||
m->metadata.resize(size);
|
||||
m->pieces_needed = create_all_needed(n);
|
||||
|
||||
if (std::empty(m->metadata) || std::empty(m->pieces_needed))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
tor->incomplete_metadata = std::move(m);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool tr_torrentGetMetadataPiece(tr_torrent const* tor, int piece, tr_metadata_piece& setme)
|
||||
tr_metadata_download::tr_metadata_download(std::string_view log_name, int64_t const size)
|
||||
: log_name_{ std::string{ log_name } }
|
||||
{
|
||||
TR_ASSERT(is_valid_metadata_size(size));
|
||||
|
||||
auto const n = div_ceil(size, MetadataPieceSize);
|
||||
tr_logAddDebugMagnet(this, fmt::format("metadata is {} bytes in {} pieces", size, n));
|
||||
|
||||
piece_count_ = n;
|
||||
metadata_.resize(size);
|
||||
create_all_needed(n);
|
||||
}
|
||||
|
||||
void tr_torrent::maybe_start_metadata_transfer(int64_t const size) noexcept
|
||||
{
|
||||
if (has_metainfo() || metadata_download_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!tr_metadata_download::is_valid_metadata_size(size))
|
||||
{
|
||||
TR_ASSERT(false);
|
||||
return;
|
||||
}
|
||||
|
||||
metadata_download_ = std::make_unique<tr_metadata_download>(name(), size);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<tr_metadata_piece> tr_torrent::get_metadata_piece(int64_t const piece) const
|
||||
{
|
||||
TR_ASSERT(tr_isTorrent(tor));
|
||||
TR_ASSERT(piece >= 0);
|
||||
|
||||
if (!tor->has_metainfo())
|
||||
if (!has_metainfo())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
auto const n_pieces = std::max(1, div_ceil(tor->info_dict_size(), MetadataPieceSize));
|
||||
if (piece < 0 || piece >= n_pieces)
|
||||
auto const info_dict_size = this->info_dict_size();
|
||||
TR_ASSERT(info_dict_size > 0);
|
||||
if (auto const n_pieces = std::max(int64_t{ 1 }, div_ceil(info_dict_size, MetadataPieceSize));
|
||||
piece < 0 || piece >= n_pieces)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
auto in = std::ifstream{ tor->torrent_file(), std::ios_base::in };
|
||||
auto in = std::ifstream{ torrent_file(), std::ios_base::in | std::ios_base::binary };
|
||||
if (!in.is_open())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
auto const info_dict_size = tor->info_dict_size();
|
||||
TR_ASSERT(info_dict_size > 0);
|
||||
auto const offset_in_info_dict = static_cast<uint64_t>(piece) * MetadataPieceSize;
|
||||
if (auto const offset_in_file = tor->info_dict_offset() + offset_in_info_dict; !in.seekg(offset_in_file))
|
||||
auto const offset_in_info_dict = piece * MetadataPieceSize;
|
||||
if (auto const offset_in_file = info_dict_offset() + offset_in_info_dict; !in.seekg(offset_in_file))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
auto const piece_len = offset_in_info_dict + MetadataPieceSize <= info_dict_size ? MetadataPieceSize :
|
||||
info_dict_size - offset_in_info_dict;
|
||||
setme.resize(piece_len);
|
||||
return !!in.read(reinterpret_cast<char*>(std::data(setme)), std::size(setme));
|
||||
auto const piece_len = static_cast<size_t>(offset_in_info_dict + MetadataPieceSize) <= info_dict_size ?
|
||||
MetadataPieceSize :
|
||||
info_dict_size - offset_in_info_dict;
|
||||
if (auto ret = tr_metadata_piece(piece_len); in.read(reinterpret_cast<char*>(std::data(ret)), std::size(ret)))
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
bool tr_torrentUseMetainfoFromFile(
|
||||
tr_torrent* tor,
|
||||
tr_torrent_metainfo const* metainfo,
|
||||
char const* filename_in,
|
||||
tr_error* error)
|
||||
bool tr_torrent::use_metainfo_from_file(tr_torrent_metainfo const* metainfo, char const* filename_in, tr_error* error)
|
||||
{
|
||||
// add .torrent file
|
||||
if (!tr_sys_path_copy(filename_in, tor->torrent_file().c_str(), error))
|
||||
if (!tr_sys_path_copy(filename_in, torrent_file().c_str(), error))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// remove .magnet file
|
||||
tr_sys_path_remove(tor->magnet_file());
|
||||
tr_sys_path_remove(magnet_file());
|
||||
|
||||
// tor should keep this metainfo
|
||||
tor->set_metainfo(*metainfo);
|
||||
set_metainfo(*metainfo);
|
||||
|
||||
tor->incomplete_metadata.reset();
|
||||
metadata_download_.reset();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -157,13 +148,6 @@ namespace
|
|||
{
|
||||
namespace set_metadata_piece_helpers
|
||||
{
|
||||
[[nodiscard]] constexpr size_t get_piece_length(tr_incomplete_metadata const& m, int piece)
|
||||
{
|
||||
return piece + 1 == m.piece_count ? // last piece
|
||||
std::size(m.metadata) - (piece * MetadataPieceSize) :
|
||||
MetadataPieceSize;
|
||||
}
|
||||
|
||||
tr_variant build_metainfo_except_info_dict(tr_torrent_metainfo const& tm)
|
||||
{
|
||||
auto top = tr_variant::Map{ 8U };
|
||||
|
@ -205,21 +189,25 @@ tr_variant build_metainfo_except_info_dict(tr_torrent_metainfo const& tm)
|
|||
|
||||
return tr_variant{ std::move(top) };
|
||||
}
|
||||
} // namespace set_metadata_piece_helpers
|
||||
} // namespace
|
||||
|
||||
bool use_new_metainfo(tr_torrent* tor, tr_error* error)
|
||||
[[nodiscard]] bool tr_torrent::use_new_metainfo(tr_error* error)
|
||||
{
|
||||
auto const& m = tor->incomplete_metadata;
|
||||
using namespace set_metadata_piece_helpers;
|
||||
|
||||
auto const& m = metadata_download_;
|
||||
TR_ASSERT(m);
|
||||
|
||||
// test the info_dict checksum
|
||||
if (tr_sha1::digest(m->metadata) != tor->info_hash())
|
||||
if (tr_sha1::digest(m->get_metadata()) != info_hash())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// checksum passed; now try to parse it as benc
|
||||
auto serde = tr_variant_serde::benc().inplace();
|
||||
auto info_dict_v = serde.parse(m->metadata);
|
||||
auto info_dict_v = serde.parse(m->get_metadata());
|
||||
if (!info_dict_v)
|
||||
{
|
||||
if (error != nullptr)
|
||||
|
@ -232,7 +220,7 @@ bool use_new_metainfo(tr_torrent* tor, tr_error* error)
|
|||
}
|
||||
|
||||
// yay we have an info dict. Let's make a torrent file
|
||||
auto top_var = build_metainfo_except_info_dict(tor->metainfo());
|
||||
auto top_var = build_metainfo_except_info_dict(metainfo());
|
||||
tr_variantMergeDicts(tr_variantDictAddDict(&top_var, TR_KEY_info, 0), &*info_dict_v);
|
||||
auto const benc = serde.to_string(top_var);
|
||||
|
||||
|
@ -244,111 +232,90 @@ bool use_new_metainfo(tr_torrent* tor, tr_error* error)
|
|||
}
|
||||
|
||||
// save it
|
||||
if (!tr_file_save(tor->torrent_file(), benc, error))
|
||||
if (!tr_file_save(torrent_file(), benc, error))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// remove .magnet file
|
||||
tr_sys_path_remove(tor->magnet_file());
|
||||
tr_sys_path_remove(magnet_file());
|
||||
|
||||
// tor should keep this metainfo
|
||||
tor->set_metainfo(metainfo);
|
||||
set_metainfo(metainfo);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void on_have_all_metainfo(tr_torrent* tor)
|
||||
void tr_torrent::on_have_all_metainfo()
|
||||
{
|
||||
auto error = tr_error{};
|
||||
auto& m = tor->incomplete_metadata;
|
||||
auto& m = metadata_download_;
|
||||
TR_ASSERT(m);
|
||||
|
||||
if (!use_new_metainfo(tor, &error)) /* drat. */
|
||||
if (auto error = tr_error{}; !use_new_metainfo(&error)) /* drat. */
|
||||
{
|
||||
auto msg = std::string_view{ error && !std::empty(error.message()) ? error.message() : "unknown error" };
|
||||
tr_logAddWarnTor(
|
||||
tor,
|
||||
this,
|
||||
fmt::format("Couldn't parse magnet metainfo: '{error}'. Redownloading metadata", fmt::arg("error", msg)));
|
||||
}
|
||||
|
||||
m.reset();
|
||||
}
|
||||
} // namespace set_metadata_piece_helpers
|
||||
} // namespace
|
||||
|
||||
void tr_torrentMagnetDoIdleWork(tr_torrent* const tor)
|
||||
bool tr_metadata_download::set_metadata_piece(int64_t const piece, void const* const data, size_t const len)
|
||||
{
|
||||
using namespace set_metadata_piece_helpers;
|
||||
|
||||
TR_ASSERT(tr_isTorrent(tor));
|
||||
|
||||
if (auto const& m = tor->incomplete_metadata; m && std::empty(m->pieces_needed))
|
||||
{
|
||||
tr_logAddDebugTor(tor, fmt::format("we now have all the metainfo!"));
|
||||
on_have_all_metainfo(tor);
|
||||
}
|
||||
}
|
||||
|
||||
void tr_torrentSetMetadataPiece(tr_torrent* tor, int piece, void const* data, size_t len)
|
||||
{
|
||||
using namespace set_metadata_piece_helpers;
|
||||
|
||||
TR_ASSERT(tr_isTorrent(tor));
|
||||
TR_ASSERT(data != nullptr);
|
||||
|
||||
tr_logAddDebugTor(tor, fmt::format("got metadata piece {} of {} bytes", piece, len));
|
||||
|
||||
// are we set up to download metadata?
|
||||
auto& m = tor->incomplete_metadata;
|
||||
if (!m)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// sanity test: is `piece` in range?
|
||||
if ((piece < 0) || (piece >= m->piece_count))
|
||||
if (piece < 0 || piece >= piece_count_)
|
||||
{
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// sanity test: is `len` the right size?
|
||||
if (get_piece_length(*m, piece) != len)
|
||||
if (get_piece_length(piece) != len)
|
||||
{
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// do we need this piece?
|
||||
auto& needed = m->pieces_needed;
|
||||
auto& needed = pieces_needed_;
|
||||
auto const iter = std::find_if(
|
||||
std::begin(needed),
|
||||
std::end(needed),
|
||||
[piece](auto const& item) { return item.piece == piece; });
|
||||
if (iter == std::end(needed))
|
||||
{
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t const offset = piece * MetadataPieceSize;
|
||||
std::copy_n(reinterpret_cast<char const*>(data), len, std::begin(m->metadata) + offset);
|
||||
auto const offset = piece * MetadataPieceSize;
|
||||
std::copy_n(reinterpret_cast<char const*>(data), len, std::begin(metadata_) + offset);
|
||||
|
||||
needed.erase(iter);
|
||||
tr_logAddDebugTor(tor, fmt::format("saving metainfo piece {}... {} remain", piece, std::size(needed)));
|
||||
tr_logAddDebugMagnet(this, fmt::format("saving metainfo piece {}... {} remain", piece, std::size(needed)));
|
||||
|
||||
return std::empty(needed);
|
||||
}
|
||||
|
||||
void tr_torrent::set_metadata_piece(int64_t const piece, void const* const data, size_t const len)
|
||||
{
|
||||
TR_ASSERT(data != nullptr);
|
||||
|
||||
tr_logAddDebugTor(this, fmt::format("got metadata piece {} of {} bytes", piece, len));
|
||||
|
||||
if (auto& m = metadata_download_; m && m->set_metadata_piece(piece, data, len))
|
||||
{
|
||||
tr_logAddDebugTor(this, fmt::format("we now have all the metainfo!"));
|
||||
on_have_all_metainfo();
|
||||
}
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
std::optional<int> tr_torrentGetNextMetadataRequest(tr_torrent* tor, time_t now)
|
||||
[[nodiscard]] std::optional<int64_t> tr_metadata_download::get_next_metadata_request(time_t const now) noexcept
|
||||
{
|
||||
TR_ASSERT(tr_isTorrent(tor));
|
||||
|
||||
auto& m = tor->incomplete_metadata;
|
||||
if (!m)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
auto& needed = m->pieces_needed;
|
||||
auto& needed = pieces_needed_;
|
||||
if (std::empty(needed) || needed.front().requested_at + MinRepeatIntervalSecs >= now)
|
||||
{
|
||||
return {};
|
||||
|
@ -358,28 +325,47 @@ std::optional<int> tr_torrentGetNextMetadataRequest(tr_torrent* tor, time_t now)
|
|||
needed.pop_front();
|
||||
req.requested_at = now;
|
||||
needed.push_back(req);
|
||||
tr_logAddDebugTor(tor, fmt::format("next piece to request: {}", req.piece));
|
||||
tr_logAddDebugMagnet(this, fmt::format("next piece to request: {}", req.piece));
|
||||
return req.piece;
|
||||
}
|
||||
|
||||
double tr_torrentGetMetadataPercent(tr_torrent const* tor)
|
||||
[[nodiscard]] std::optional<int64_t> tr_torrent::get_next_metadata_request(time_t const now) noexcept
|
||||
{
|
||||
if (tor->has_metainfo())
|
||||
if (auto& m = metadata_download_; m)
|
||||
{
|
||||
return 1.0;
|
||||
return m->get_next_metadata_request(now);
|
||||
}
|
||||
|
||||
if (auto const& m = tor->incomplete_metadata; m)
|
||||
return {};
|
||||
}
|
||||
|
||||
[[nodiscard]] double tr_metadata_download::get_metadata_percent() const noexcept
|
||||
{
|
||||
if (auto const n = piece_count_; n != 0)
|
||||
{
|
||||
if (auto const n = m->piece_count; n != 0)
|
||||
{
|
||||
return (n - std::size(m->pieces_needed)) / static_cast<double>(n);
|
||||
}
|
||||
return (n - std::size(pieces_needed_)) / static_cast<double>(n);
|
||||
}
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
[[nodiscard]] double tr_torrent::get_metadata_percent() const noexcept
|
||||
{
|
||||
if (has_metainfo())
|
||||
{
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
if (auto const& m = metadata_download_; m)
|
||||
{
|
||||
return m->get_metadata_percent();
|
||||
}
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
std::string tr_torrentGetMagnetLink(tr_torrent const* tor)
|
||||
{
|
||||
return tor->magnet();
|
||||
|
|
|
@ -13,46 +13,67 @@
|
|||
#include <cstdint> // int64_t
|
||||
#include <ctime> // time_t
|
||||
#include <deque>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <small/vector.hpp>
|
||||
|
||||
struct tr_error;
|
||||
struct tr_torrent;
|
||||
struct tr_torrent_metainfo;
|
||||
#include "libtransmission/tr-macros.h"
|
||||
|
||||
// defined by BEP #9
|
||||
inline constexpr int MetadataPieceSize = 1024 * 16;
|
||||
inline constexpr auto MetadataPieceSize = 1024 * 16;
|
||||
|
||||
using tr_metadata_piece = small::max_size_vector<std::byte, MetadataPieceSize>;
|
||||
|
||||
struct tr_incomplete_metadata
|
||||
class tr_metadata_download
|
||||
{
|
||||
public:
|
||||
tr_metadata_download(std::string_view log_name, int64_t size);
|
||||
|
||||
[[nodiscard]] static constexpr auto is_valid_metadata_size(int64_t const size) noexcept
|
||||
{
|
||||
return size > 0 && size <= std::numeric_limits<int>::max();
|
||||
}
|
||||
|
||||
bool set_metadata_piece(int64_t piece, void const* data, size_t len);
|
||||
|
||||
[[nodiscard]] std::optional<int64_t> get_next_metadata_request(time_t now) noexcept;
|
||||
|
||||
[[nodiscard]] double get_metadata_percent() const noexcept;
|
||||
|
||||
[[nodiscard]] constexpr auto const& get_metadata() const noexcept
|
||||
{
|
||||
return metadata_;
|
||||
}
|
||||
|
||||
[[nodiscard]] TR_CONSTEXPR20 std::string_view log_name() const noexcept
|
||||
{
|
||||
return log_name_;
|
||||
}
|
||||
|
||||
private:
|
||||
struct metadata_node
|
||||
{
|
||||
time_t requested_at = 0U;
|
||||
int piece = 0;
|
||||
time_t requested_at = {};
|
||||
int64_t piece = {};
|
||||
};
|
||||
|
||||
std::vector<char> metadata;
|
||||
[[nodiscard]] constexpr size_t get_piece_length(int64_t const piece) const noexcept
|
||||
{
|
||||
return piece + 1 == piece_count_ ? // last piece
|
||||
std::size(metadata_) - (piece * MetadataPieceSize) :
|
||||
MetadataPieceSize;
|
||||
}
|
||||
|
||||
/** sorted from least to most recently requested */
|
||||
std::deque<metadata_node> pieces_needed;
|
||||
void create_all_needed(int64_t n_pieces) noexcept;
|
||||
|
||||
int piece_count = 0;
|
||||
std::vector<char> metadata_;
|
||||
std::deque<metadata_node> pieces_needed_;
|
||||
int64_t piece_count_ = {};
|
||||
|
||||
std::string log_name_;
|
||||
};
|
||||
|
||||
bool tr_torrentGetMetadataPiece(tr_torrent const* tor, int piece, tr_metadata_piece& setme);
|
||||
|
||||
void tr_torrentSetMetadataPiece(tr_torrent* tor, int piece, void const* data, size_t len);
|
||||
|
||||
std::optional<int> tr_torrentGetNextMetadataRequest(tr_torrent* tor, time_t now);
|
||||
|
||||
bool tr_torrentSetMetadataSizeHint(tr_torrent* tor, int64_t metadata_size);
|
||||
|
||||
double tr_torrentGetMetadataPercent(tr_torrent const* tor);
|
||||
|
||||
void tr_torrentMagnetDoIdleWork(tr_torrent* tor);
|
||||
|
||||
bool tr_torrentUseMetainfoFromFile(tr_torrent* tor, tr_torrent_metainfo const* metainfo, char const* filename, tr_error* error);
|
||||
|
|
|
@ -131,7 +131,7 @@ bool tr_torrentSetMetainfoFromFile(tr_torrent* tor, tr_torrent_metainfo const* m
|
|||
}
|
||||
|
||||
auto error = tr_error{};
|
||||
tr_torrentUseMetainfoFromFile(tor, metainfo, filename, &error);
|
||||
tor->use_metainfo_from_file(metainfo, filename, &error);
|
||||
if (error)
|
||||
{
|
||||
tor->error().set_local_error(fmt::format(
|
||||
|
@ -890,7 +890,7 @@ void tr_torrent::on_metainfo_completed()
|
|||
// Potentially, we are in `tr_torrent::init`,
|
||||
// and we don't want any file created before `tr_torrent::start`
|
||||
// so we Verify but we don't Create files.
|
||||
tr_torrentVerify(this);
|
||||
session->queue_session_thread(tr_torrentVerify, this);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -904,7 +904,7 @@ void tr_torrent::on_metainfo_completed()
|
|||
}
|
||||
else if (is_running())
|
||||
{
|
||||
tr_torrentStop(this);
|
||||
stop_soon();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1315,7 +1315,7 @@ tr_stat tr_torrent::stats() const
|
|||
stats.pieceDownloadSpeed_KBps = piece_download_speed.count(Speed::Units::KByps);
|
||||
|
||||
stats.percentComplete = this->completion_.percent_complete();
|
||||
stats.metadataPercentComplete = tr_torrentGetMetadataPercent(this);
|
||||
stats.metadataPercentComplete = get_metadata_percent();
|
||||
|
||||
stats.percentDone = this->completion_.percent_done();
|
||||
stats.leftUntilDone = this->completion_.left_until_done();
|
||||
|
|
|
@ -148,7 +148,6 @@ struct tr_torrent final : public tr_completion::torrent_view
|
|||
uint64_t cur_ = {};
|
||||
};
|
||||
|
||||
public:
|
||||
using labels_t = std::vector<tr_interned_string>;
|
||||
|
||||
using VerifyDoneCallback = std::function<void(tr_torrent*)>;
|
||||
|
@ -603,6 +602,18 @@ public:
|
|||
|
||||
[[nodiscard]] bool ensure_piece_is_checked(tr_piece_index_t piece);
|
||||
|
||||
/// METAINFO - MAGNET
|
||||
|
||||
void maybe_start_metadata_transfer(int64_t size) noexcept;
|
||||
|
||||
[[nodiscard]] std::optional<tr_metadata_piece> get_metadata_piece(int64_t piece) const;
|
||||
|
||||
void set_metadata_piece(int64_t piece, void const* data, size_t len);
|
||||
|
||||
[[nodiscard]] std::optional<int64_t> get_next_metadata_request(time_t now) noexcept;
|
||||
|
||||
[[nodiscard]] double get_metadata_percent() const noexcept;
|
||||
|
||||
///
|
||||
|
||||
[[nodiscard]] tr_stat stats() const;
|
||||
|
@ -949,11 +960,6 @@ public:
|
|||
CumulativeCount bytes_downloaded_;
|
||||
CumulativeCount bytes_uploaded_;
|
||||
|
||||
/* Used when the torrent has been created with a magnet link
|
||||
* and we're in the process of downloading the metainfo from
|
||||
* other peers */
|
||||
std::unique_ptr<tr_incomplete_metadata> incomplete_metadata;
|
||||
|
||||
tr_session* session = nullptr;
|
||||
|
||||
tr_torrent_announcer* torrent_announcer = nullptr;
|
||||
|
@ -963,6 +969,7 @@ public:
|
|||
time_t lpdAnnounceAt = 0;
|
||||
|
||||
private:
|
||||
friend bool tr_torrentSetMetainfoFromFile(tr_torrent* tor, tr_torrent_metainfo const* metainfo, char const* filename);
|
||||
friend tr_file_view tr_torrentFile(tr_torrent const* tor, tr_file_index_t file);
|
||||
friend tr_stat const* tr_torrentStat(tr_torrent* tor);
|
||||
friend tr_torrent* tr_torrentNew(tr_ctor* ctor, tr_torrent** setme_duplicate_of);
|
||||
|
@ -1200,6 +1207,8 @@ private:
|
|||
return fpm_.byte_span_for_file(file);
|
||||
}
|
||||
|
||||
bool use_metainfo_from_file(tr_torrent_metainfo const* metainfo, char const* filename, tr_error* error);
|
||||
|
||||
// ---
|
||||
|
||||
void set_has_piece(tr_piece_index_t piece, bool has)
|
||||
|
@ -1224,6 +1233,7 @@ private:
|
|||
|
||||
void on_metainfo_updated();
|
||||
void on_metainfo_completed();
|
||||
void on_have_all_metainfo();
|
||||
void on_piece_completed(tr_piece_index_t piece);
|
||||
void on_piece_failed(tr_piece_index_t piece);
|
||||
void on_file_completed(tr_file_index_t file);
|
||||
|
@ -1232,6 +1242,8 @@ private:
|
|||
void create_empty_files() const;
|
||||
void recheck_completeness();
|
||||
|
||||
[[nodiscard]] bool use_new_metainfo(tr_error* error);
|
||||
|
||||
void set_location_in_session_thread(std::string_view path, bool move_from_old_path, int volatile* setme_state);
|
||||
|
||||
void rename_path_in_session_thread(
|
||||
|
@ -1261,6 +1273,11 @@ private:
|
|||
|
||||
tr_torrent_metainfo metainfo_;
|
||||
|
||||
/* Used when the torrent has been created with a magnet link
|
||||
* and we're in the process of downloading the metainfo from
|
||||
* other peers */
|
||||
std::unique_ptr<tr_metadata_download> metadata_download_;
|
||||
|
||||
tr_bandwidth bandwidth_;
|
||||
|
||||
tr_completion completion_;
|
||||
|
|
|
@ -383,7 +383,7 @@ protected:
|
|||
// 1048576 files-filled-with-zeroes/1048576
|
||||
// 4096 files-filled-with-zeroes/4096
|
||||
// 512 files-filled-with-zeroes/512
|
||||
char const* benc_base64 =
|
||||
static auto constexpr BencBase64 =
|
||||
"ZDg6YW5ub3VuY2UzMTpodHRwOi8vd3d3LmV4YW1wbGUuY29tL2Fubm91bmNlMTA6Y3JlYXRlZCBi"
|
||||
"eTI1OlRyYW5zbWlzc2lvbi8yLjYxICgxMzQwNykxMzpjcmVhdGlvbiBkYXRlaTEzNTg3MDQwNzVl"
|
||||
"ODplbmNvZGluZzU6VVRGLTg0OmluZm9kNTpmaWxlc2xkNjpsZW5ndGhpMTA0ODU3NmU0OnBhdGhs"
|
||||
|
@ -404,7 +404,7 @@ protected:
|
|||
"OnByaXZhdGVpMGVlZQ==";
|
||||
|
||||
// create the torrent ctor
|
||||
auto const benc = tr_base64_decode(benc_base64);
|
||||
auto const benc = tr_base64_decode(BencBase64);
|
||||
EXPECT_LT(0U, std::size(benc));
|
||||
auto* ctor = tr_ctorNew(session_);
|
||||
auto error = tr_error{};
|
||||
|
@ -448,6 +448,20 @@ protected:
|
|||
return tor;
|
||||
}
|
||||
|
||||
[[nodiscard]] tr_torrent* zeroTorrentMagnetInit()
|
||||
{
|
||||
static auto constexpr V1Hash = "fa5794674a18241bec985ddc3390e3cb171345e4";
|
||||
|
||||
auto ctor = tr_ctorNew(session_);
|
||||
ctor->set_metainfo_from_magnet_link(V1Hash);
|
||||
tr_ctorSetPaused(ctor, TR_FORCE, true);
|
||||
|
||||
auto* const tor = tr_torrentNew(ctor, nullptr);
|
||||
EXPECT_NE(nullptr, tor);
|
||||
tr_ctorFree(ctor);
|
||||
return tor;
|
||||
}
|
||||
|
||||
void blockingTorrentVerify(tr_torrent* tor)
|
||||
{
|
||||
EXPECT_NE(nullptr, tor->session);
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
// License text can be found in the licenses/ folder.
|
||||
|
||||
#include <cstddef> // size_t
|
||||
#include <future>
|
||||
#include <string>
|
||||
|
||||
#include <libtransmission/error.h>
|
||||
|
@ -28,16 +29,16 @@ TEST_F(TorrentMagnetTest, getMetadataPiece)
|
|||
};
|
||||
auto piece = int{ 0 };
|
||||
auto info_dict_size = size_t{ 0U };
|
||||
auto data = tr_metadata_piece{};
|
||||
for (;;)
|
||||
{
|
||||
if (!tr_torrentGetMetadataPiece(tor, piece++, data))
|
||||
auto data = tor->get_metadata_piece(piece++);
|
||||
if (!data)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
benc.append(reinterpret_cast<char const*>(std::data(data)), std::size(data));
|
||||
info_dict_size += std::size(data);
|
||||
benc.append(reinterpret_cast<char const*>(std::data(*data)), std::size(*data));
|
||||
info_dict_size += std::size(*data);
|
||||
}
|
||||
benc.append("e");
|
||||
EXPECT_EQ(tor->info_dict_size(), info_dict_size);
|
||||
|
@ -50,4 +51,47 @@ TEST_F(TorrentMagnetTest, getMetadataPiece)
|
|||
EXPECT_EQ(tor->piece_hash(0), torrent_metainfo.piece_hash(0));
|
||||
}
|
||||
|
||||
TEST_F(TorrentMagnetTest, setMetadataPiece)
|
||||
{
|
||||
static auto constexpr InfoDictBase64 =
|
||||
"ZDU6ZmlsZXNsZDY6bGVuZ3RoaTEwNDg1NzZlNDpwYXRobDc6MTA0ODU3NmVlZDY6bGVuZ3RoaTQw"
|
||||
"OTZlNDpwYXRobDQ6NDA5NmVlZDY6bGVuZ3RoaTUxMmU0OnBhdGhsMzo1MTJlZWU0Om5hbWUyNDpm"
|
||||
"aWxlcy1maWxsZWQtd2l0aC16ZXJvZXMxMjpwaWVjZSBsZW5ndGhpMzI3NjhlNjpwaWVjZXM2NjA6"
|
||||
"UYhDGEm0YTFS/XvbpqP/Ck/WQktRiEMYSbRhMVL9e9umo/8KT9ZCS1GIQxhJtGExUv1726aj/wpP"
|
||||
"1kJLUYhDGEm0YTFS/XvbpqP/Ck/WQktRiEMYSbRhMVL9e9umo/8KT9ZCS1GIQxhJtGExUv1726aj"
|
||||
"/wpP1kJLUYhDGEm0YTFS/XvbpqP/Ck/WQktRiEMYSbRhMVL9e9umo/8KT9ZCS1GIQxhJtGExUv17"
|
||||
"26aj/wpP1kJLUYhDGEm0YTFS/XvbpqP/Ck/WQktRiEMYSbRhMVL9e9umo/8KT9ZCS1GIQxhJtGEx"
|
||||
"Uv1726aj/wpP1kJLUYhDGEm0YTFS/XvbpqP/Ck/WQktRiEMYSbRhMVL9e9umo/8KT9ZCS1GIQxhJ"
|
||||
"tGExUv1726aj/wpP1kJLUYhDGEm0YTFS/XvbpqP/Ck/WQktRiEMYSbRhMVL9e9umo/8KT9ZCS1GI"
|
||||
"QxhJtGExUv1726aj/wpP1kJLUYhDGEm0YTFS/XvbpqP/Ck/WQktRiEMYSbRhMVL9e9umo/8KT9ZC"
|
||||
"S1GIQxhJtGExUv1726aj/wpP1kJLUYhDGEm0YTFS/XvbpqP/Ck/WQktRiEMYSbRhMVL9e9umo/8K"
|
||||
"T9ZCS1GIQxhJtGExUv1726aj/wpP1kJLUYhDGEm0YTFS/XvbpqP/Ck/WQktRiEMYSbRhMVL9e9um"
|
||||
"o/8KT9ZCS1GIQxhJtGExUv1726aj/wpP1kJLUYhDGEm0YTFS/XvbpqP/Ck/WQktRiEMYSbRhMVL9"
|
||||
"e9umo/8KT9ZCS1GIQxhJtGExUv1726aj/wpP1kJLUYhDGEm0YTFS/XvbpqP/Ck/WQktRiEMYSbRh"
|
||||
"MVL9e9umo/8KT9ZCSzpX+QPk899JzAVbjTNoaVd8IP9dNzpwcml2YXRlaTBlZQ==";
|
||||
|
||||
auto* const tor = zeroTorrentMagnetInit();
|
||||
EXPECT_NE(nullptr, tor);
|
||||
EXPECT_FALSE(tor->has_metainfo());
|
||||
|
||||
auto promise = std::promise<void>{};
|
||||
auto future = promise.get_future();
|
||||
session_->run_in_session_thread(
|
||||
[tor, &promise]()
|
||||
{
|
||||
auto const metainfo_benc = tr_base64_decode(InfoDictBase64);
|
||||
auto const metainfo_size = std::size(metainfo_benc);
|
||||
EXPECT_LE(metainfo_size, MetadataPieceSize);
|
||||
|
||||
tor->maybe_start_metadata_transfer(metainfo_size);
|
||||
tor->set_metadata_piece(0, std::data(metainfo_benc), metainfo_size);
|
||||
EXPECT_TRUE(tor->has_metainfo());
|
||||
EXPECT_EQ(tor->info_dict_size(), metainfo_size);
|
||||
EXPECT_EQ(tor->get_metadata_percent(), 1.0);
|
||||
|
||||
promise.set_value();
|
||||
});
|
||||
future.wait();
|
||||
}
|
||||
|
||||
} // namespace libtransmission::test
|
||||
|
|
Loading…
Reference in New Issue