feat: torrent_get.wanted is now boolean (#7997)

This commit is contained in:
Yat Ho
2025-12-26 01:42:43 +08:00
committed by GitHub
parent 11ead87f8d
commit a1184061a0
4 changed files with 226 additions and 6 deletions

View File

@@ -336,7 +336,7 @@ Files are returned in the order they are laid out in the torrent. References to
| Key | Value Type | transmission.h source
|:--|:--|:--
| `bytes_completed` | number | tr_file_view
| `wanted` | boolean | tr_file_view (**Note:** Not to be confused with `torrent_get.wanted`, which is an array of 0/1 instead of boolean)
| `wanted` | boolean | tr_file_view
| `priority` | number | tr_file_view
`peers`: an array of objects, each containing:
@@ -437,10 +437,9 @@ Files are returned in the order they are laid out in the torrent. References to
| `tier` | number | tr_tracker_view
`wanted`: An array of `tr_torrentFileCount()` 0/1, 1 (true) if the corresponding file is to be downloaded. (Source: `tr_file_view`)
`wanted`: An array of `tr_torrentFileCount()` booleans, true if the corresponding file is to be downloaded. (Source: `tr_file_view`)
**Note:** For backwards compatibility, in `4.x.x`, `wanted` is serialized as an array of `0` or `1` that should be treated as booleans.
This will be fixed in `5.0.0` to return an array of booleans.
**Note:** For backwards compatibility, in the old bespoke API, `wanted` is serialized as an array of `0` or `1` that should be treated as booleans.
Example:

View File

@@ -670,9 +670,96 @@ void convert_keys(tr_variant& var, State& state)
});
}
namespace convert_jsonrpc_helpers
{
void convert_files_wanted(tr_variant::Vector& wanted, State const& state)
{
auto ret = tr_variant::Vector{};
ret.reserve(std::size(wanted));
for (auto const& var : wanted)
{
if (state.style == Style::Tr5)
{
if (auto const val = var.value_if<bool>())
{
ret.emplace_back(*val);
}
else
{
return;
}
}
else
{
if (auto const val = var.value_if<int64_t>(); val == 0 || val == 1)
{
ret.emplace_back(*val);
}
else
{
return;
}
}
}
wanted = std::move(ret);
}
void convert_files_wanted_response(tr_variant::Map& top, State const& state)
{
if (auto* const args = top.find_if<tr_variant::Map>(state.style == Style::Tr5 ? TR_KEY_result : TR_KEY_arguments))
{
if (auto* const torrents = args->find_if<tr_variant::Vector>(TR_KEY_torrents);
torrents != nullptr && !std::empty(*torrents))
{
// TrFormat::Table
if (auto* const first_vec = torrents->front().get_if<tr_variant::Vector>();
first_vec != nullptr && !std::empty(*first_vec))
{
if (auto const wanted_iter = std::find_if(
std::begin(*first_vec),
std::end(*first_vec),
[](tr_variant const& v)
{ return v.value_if<std::string_view>() == tr_quark_get_string_view(TR_KEY_wanted); });
wanted_iter != std::end(*first_vec))
{
auto const wanted_idx = static_cast<size_t>(wanted_iter - std::begin(*first_vec));
for (auto it = std::next(std::begin(*torrents)); it != std::end(*torrents); ++it)
{
if (auto* const row = it->get_if<tr_variant::Vector>(); row != nullptr && wanted_idx < std::size(*row))
{
if (auto* const wanted = (*row)[wanted_idx].get_if<tr_variant::Vector>())
{
convert_files_wanted(*wanted, state);
}
}
}
}
}
// TrFormat::Object
else if (torrents->front().index() == tr_variant::MapIndex)
{
for (auto& var : *torrents)
{
if (auto* const map = var.get_if<tr_variant::Map>())
{
if (auto* const wanted = map->find_if<tr_variant::Vector>(TR_KEY_wanted))
{
convert_files_wanted(*wanted, state);
}
}
}
}
}
}
}
} // namespace convert_jsonrpc_helpers
// jsonrpc <-> legacy rpc conversion
void convert_jsonrpc(tr_variant::Map& top, State const& state)
{
using namespace convert_jsonrpc_helpers;
if (!state.is_rpc)
{
return;
@@ -705,6 +792,8 @@ void convert_jsonrpc(tr_variant::Map& top, State const& state)
// - add `result: "success"`
top.replace_key(TR_KEY_result, TR_KEY_arguments);
top.try_emplace(TR_KEY_result, tr_variant::unmanaged_string("success"));
convert_files_wanted_response(top, state);
}
if (state.is_response && is_legacy && !state.is_success)
@@ -751,6 +840,8 @@ void convert_jsonrpc(tr_variant::Map& top, State const& state)
{
top.erase(TR_KEY_result);
top.replace_key(TR_KEY_arguments, TR_KEY_result);
convert_files_wanted_response(top, state);
}
if (state.is_response && is_jsonrpc && !state.is_success && state.was_legacy)

View File

@@ -458,7 +458,7 @@ namespace make_torrent_field_helpers
vec.reserve(n_files);
for (tr_file_index_t idx = 0U; idx != n_files; ++idx)
{
vec.emplace_back(tr_torrentFile(&tor, idx).wanted ? 1 : 0);
vec.emplace_back(tr_torrentFile(&tor, idx).wanted);
}
return tr_variant{ std::move(vec) };
}

View File

@@ -300,6 +300,128 @@ constexpr std::string_view CurrentTorrentGetJson = R"json({
}
})json";
constexpr std::string_view CurrentFilesWantedResponseObjectJson = R"json({
"id": 6,
"jsonrpc": "2.0",
"result": {
"torrents": [
{
"wanted": [
false,
true,
true,
false
]
},
{
"wanted": [
true,
false,
true,
false,
true
]
}
]
}
})json";
constexpr std::string_view LegacyFilesWantedResponseObjectJson = R"json({
"arguments": {
"torrents": [
{
"wanted": [
0,
1,
1,
0
]
},
{
"wanted": [
1,
0,
1,
0,
1
]
}
]
},
"result": "success",
"tag": 6
})json";
constexpr std::string_view CurrentFilesWantedResponseArrayJson = R"json({
"id": 6,
"jsonrpc": "2.0",
"result": {
"torrents": [
[
"comment",
"wanted",
"id"
],
[
"id 1",
[
false,
true,
true,
false
],
1
],
[
"id 2",
[
true,
false,
true,
false,
true
],
2
]
]
}
})json";
constexpr std::string_view LegacyFilesWantedResponseArrayJson = R"json({
"arguments": {
"torrents": [
[
"comment",
"wanted",
"id"
],
[
"id 1",
[
0,
1,
1,
0
],
1
],
[
"id 2",
[
1,
0,
1,
0,
1
],
2
]
]
},
"result": "success",
"tag": 6
})json";
constexpr std::string_view CurrentPortTestErrorResponse = R"json({
"error": {
"code": 8,
@@ -963,7 +1085,7 @@ TEST_F(ApiCompatTest, canConvertRpc)
using TestCase = std::tuple<std::string_view, std::string_view, Style, std::string_view>;
// clang-format off
static auto constexpr TestCases = std::array<TestCase, 42U>{ {
static auto constexpr TestCases = std::array<TestCase, 50U>{ {
{ "free_space tr5 -> tr5", BadFreeSpaceRequest, Style::Tr5, BadFreeSpaceRequest },
{ "free_space tr5 -> tr4", BadFreeSpaceRequest, Style::Tr4, BadFreeSpaceRequestLegacy },
{ "free_space tr4 -> tr5", BadFreeSpaceRequestLegacy, Style::Tr5, BadFreeSpaceRequest },
@@ -1006,6 +1128,14 @@ TEST_F(ApiCompatTest, canConvertRpc)
{ "unrecognised info tr4 -> tr4", UnrecognisedInfoLegacyResponse, Style::Tr4, UnrecognisedInfoLegacyResponse},
{ "non-int tag tr4 -> tr5", LegacyNonIntTagRequest, Style::Tr5, LegacyNonIntTagRequestResult },
{ "non-int tag tr4 -> tr4", LegacyNonIntTagRequest, Style::Tr4, LegacyNonIntTagRequest },
{ "files wanted response object tr5 -> tr5", CurrentFilesWantedResponseObjectJson, Style::Tr5, CurrentFilesWantedResponseObjectJson },
{ "files wanted response object tr5 -> tr4", CurrentFilesWantedResponseObjectJson, Style::Tr4, LegacyFilesWantedResponseObjectJson },
{ "files wanted response object tr4 -> tr5", LegacyFilesWantedResponseObjectJson, Style::Tr5, CurrentFilesWantedResponseObjectJson },
{ "files wanted response object tr5 -> tr4", LegacyFilesWantedResponseObjectJson, Style::Tr4, LegacyFilesWantedResponseObjectJson },
{ "files wanted response array tr5 -> tr5", CurrentFilesWantedResponseArrayJson, Style::Tr5, CurrentFilesWantedResponseArrayJson },
{ "files wanted response array tr5 -> tr4", CurrentFilesWantedResponseArrayJson, Style::Tr4, LegacyFilesWantedResponseArrayJson },
{ "files wanted response array tr4 -> tr5", LegacyFilesWantedResponseArrayJson, Style::Tr5, CurrentFilesWantedResponseArrayJson },
{ "files wanted response array tr5 -> tr4", LegacyFilesWantedResponseArrayJson, Style::Tr4, LegacyFilesWantedResponseArrayJson },
// TODO(ckerr): torrent-get with 'table'
} };