feat: allow downloading sequentially from a specific piece (#7502)

* feat: allow downloading sequentially from a specific piece

* Apply review changes

Co-authored-by: Yat Ho <lagoho7@gmail.com>

* Use sequential_download_from_piece param in torrent-add, and save it to resume file

* fix: change observable type bool -> tr_piece_index_t

* fix: run code_style.sh

* fix: improved test and missing comment

Co-authored-by: Yat Ho <lagoho7@gmail.com>

* fix: apply similar changes to sequentialDownloadFromPiece test

* docs: change parameter type boolean -> number

---------

Co-authored-by: Yat Ho <lagoho7@gmail.com>
This commit is contained in:
Geoffrey Bonneville
2025-11-11 16:32:57 +01:00
committed by GitHub
parent af92dc5d80
commit 0f7f460c55
12 changed files with 295 additions and 45 deletions

View File

@@ -141,32 +141,33 @@ Request arguments:
| Key | Value Type | Value Description
|:--|:--|:--
| `bandwidthPriority` | number | this torrent's bandwidth tr_priority_t
| `downloadLimit` | number | maximum download speed (kB/s)
| `downloadLimited` | boolean | true if `downloadLimit` is honored
| `files-unwanted` | array | indices of file(s) to not download
| `files-wanted` | array | indices of file(s) to download
| `group` | string | The name of this torrent's bandwidth group
| `honorsSessionLimits` | boolean | true if session upload limits are honored
| `ids` | array | torrent list, as described in 3.1
| `labels` | array | array of string labels
| `location` | string | new location of the torrent's content
| `peer-limit` | number | maximum number of peers
| `priority-high` | array | indices of high-priority file(s)
| `priority-low` | array | indices of low-priority file(s)
| `priority-normal` | array | indices of normal-priority file(s)
| `queuePosition` | number | position of this torrent in its queue [0...n)
| `seedIdleLimit` | number | torrent-level number of minutes of seeding inactivity
| `seedIdleMode` | number | which seeding inactivity to use. See tr_idlelimit
| `seedRatioLimit` | double | torrent-level seeding ratio
| `seedRatioMode` | number | which ratio to use. See tr_ratiolimit
| `sequential_download` | boolean | download torrent pieces sequentially
| `trackerAdd` | array | **DEPRECATED** use trackerList instead
| `trackerList` | string | string of announce URLs, one per line, and a blank line between [tiers](https://www.bittorrent.org/beps/bep_0012.html).
| `trackerRemove` | array | **DEPRECATED** use trackerList instead
| `trackerReplace` | array | **DEPRECATED** use trackerList instead
| `uploadLimit` | number | maximum upload speed (kB/s)
| `uploadLimited` | boolean | true if `uploadLimit` is honored
| `bandwidthPriority` | number | this torrent's bandwidth tr_priority_t
| `downloadLimit` | number | maximum download speed (kB/s)
| `downloadLimited` | boolean | true if `downloadLimit` is honored
| `files-unwanted` | array | indices of file(s) to not download
| `files-wanted` | array | indices of file(s) to download
| `group` | string | The name of this torrent's bandwidth group
| `honorsSessionLimits` | boolean | true if session upload limits are honored
| `ids` | array | torrent list, as described in 3.1
| `labels` | array | array of string labels
| `location` | string | new location of the torrent's content
| `peer-limit` | number | maximum number of peers
| `priority-high` | array | indices of high-priority file(s)
| `priority-low` | array | indices of low-priority file(s)
| `priority-normal` | array | indices of normal-priority file(s)
| `queuePosition` | number | position of this torrent in its queue [0...n)
| `seedIdleLimit` | number | torrent-level number of minutes of seeding inactivity
| `seedIdleMode` | number | which seeding inactivity to use. See tr_idlelimit
| `seedRatioLimit` | double | torrent-level seeding ratio
| `seedRatioMode` | number | which ratio to use. See tr_ratiolimit
| `sequential_download` | boolean | download torrent pieces sequentially
| `sequential_download_from_piece` | number | download from a specific piece when sequential download is enabled
| `trackerAdd` | array | **DEPRECATED** use trackerList instead
| `trackerList` | string | string of announce URLs, one per line, and a blank line between [tiers](https://www.bittorrent.org/beps/bep_0012.html).
| `trackerRemove` | array | **DEPRECATED** use trackerList instead
| `trackerReplace` | array | **DEPRECATED** use trackerList instead
| `uploadLimit` | number | maximum upload speed (kB/s)
| `uploadLimited` | boolean | true if `uploadLimit` is honored
Just as an empty `ids` value is shorthand for "all ids", using an empty array
for `files-wanted`, `files-unwanted`, `priority-high`, `priority-low`, or
@@ -273,6 +274,7 @@ The 'source' column here corresponds to the data structure there.
| `seedRatioLimit`| double| tr_torrent
| `seedRatioMode`| number| tr_ratiolimit
| `sequential_download`| boolean| tr_torrent
| `sequential_download_from_piece`| number| tr_torrent
| `sizeWhenDone`| number| tr_stat
| `startDate`| number| tr_stat
| `status`| number (see below)| tr_stat
@@ -462,20 +464,21 @@ Request arguments:
| Key | Value Type | Description
|:--|:--|:--
| `cookies` | string | pointer to a string of one or more cookies.
| `download-dir` | string | path to download the torrent to
| `filename` | string | filename or URL of the .torrent file
| `labels` | array | array of string labels
| `metainfo` | string | base64-encoded .torrent content
| `paused` | boolean | if true, don't start the torrent
| `peer-limit` | number | maximum number of peers
| `bandwidthPriority` | number | torrent's bandwidth tr_priority_t
| `files-wanted` | array | indices of file(s) to download
| `files-unwanted` | array | indices of file(s) to not download
| `priority-high` | array | indices of high-priority file(s)
| `priority-low` | array | indices of low-priority file(s)
| `priority-normal` | array | indices of normal-priority file(s)
| `sequential_download` | boolean | download torrent pieces sequentially
| `cookies` | string | pointer to a string of one or more cookies.
| `download-dir` | string | path to download the torrent to
| `filename` | string | filename or URL of the .torrent file
| `labels` | array | array of string labels
| `metainfo` | string | base64-encoded .torrent content
| `paused` | boolean | if true, don't start the torrent
| `peer-limit` | number | maximum number of peers
| `bandwidthPriority` | number | torrent's bandwidth tr_priority_t
| `files-wanted` | array | indices of file(s) to download
| `files-unwanted` | array | indices of file(s) to not download
| `priority-high` | array | indices of high-priority file(s)
| `priority-low` | array | indices of low-priority file(s)
| `priority-normal` | array | indices of normal-priority file(s)
| `sequential_download` | boolean | download torrent pieces sequentially
| `sequential_download_from_piece` | number | download from a specific piece when sequential download is enabled
Either `filename` **or** `metainfo` **must** be included. All other arguments are optional.
@@ -1040,6 +1043,9 @@ Transmission 4.1.0 (`rpc-version-semver` 5.4.0, `rpc-version`: 18)
| `torrent-add` | new arg `sequential_download`
| `torrent-get` | new arg `sequential_download`
| `torrent-set` | new arg `sequential_download`
| `torrent-add` | new arg `sequential_download_from_piece`
| `torrent-get` | new arg `sequential_download_from_piece`
| `torrent-set` | new arg `sequential_download_from_piece`
| `torrent-get` | new arg `files.begin_piece`
| `torrent-get` | new arg `files.end_piece`
| `port-test` | new arg `ip_protocol`

View File

@@ -276,7 +276,8 @@ private:
tr_piece_index_t const piece,
tr_piece_index_t const n_pieces,
tr_piece_index_t const random_salt,
bool const is_sequential)
bool const is_sequential,
tr_piece_index_t const sequential_download_from_piece)
{
if (!is_sequential)
{
@@ -294,7 +295,19 @@ private:
return 1U;
}
return piece + 1U;
if (sequential_download_from_piece <= 1)
{
return piece + 1U;
}
// Rotate remaining pieces
// 1 2 3 4 5 -> 3 4 5 1 2 if sequential_download_from_piece is 3
if (piece < sequential_download_from_piece)
{
return n_pieces - (sequential_download_from_piece - piece);
}
return piece - sequential_download_from_piece + 2U;
}
// ---
@@ -304,6 +317,7 @@ private:
auto n_old_c = std::size(candidates_);
auto salter = tr_salt_shaker<tr_piece_index_t>{};
auto const is_sequential = mediator_.is_sequential_download();
auto const sequential_download_from_piece = mediator_.sequential_download_from_piece();
auto const n_pieces = mediator_.piece_count();
std::sort(
@@ -322,7 +336,7 @@ private:
}
else
{
auto const salt = get_salt(piece, n_pieces, salter(), is_sequential);
auto const salt = get_salt(piece, n_pieces, salter(), is_sequential, sequential_download_from_piece);
candidates_.emplace_back(piece, salt, &mediator_);
}
}
@@ -352,10 +366,11 @@ private:
{
auto salter = tr_salt_shaker<tr_piece_index_t>{};
auto const is_sequential = mediator_.is_sequential_download();
auto const sequential_download_from_piece = mediator_.sequential_download_from_piece();
auto const n_pieces = mediator_.piece_count();
for (auto& candidate : candidates_)
{
candidate.salt = get_salt(candidate.piece, n_pieces, salter(), is_sequential);
candidate.salt = get_salt(candidate.piece, n_pieces, salter(), is_sequential, sequential_download_from_piece);
}
std::sort(std::begin(candidates_), std::end(candidates_));
@@ -431,11 +446,15 @@ Wishlist::Impl::Impl(Mediator& mediator_in)
mediator_in.observe_sent_request([this](tr_torrent*, tr_peer*, tr_block_span_t bs) { requested_block_span(bs); }),
// salt
mediator_in.observe_sequential_download_changed([this](tr_torrent*, bool) { recalculate_salt(); }),
// salt
mediator_in.observe_sequential_download_from_piece_changed([this](tr_torrent*, tr_piece_index_t)
{ recalculate_salt(); }),
} }
, mediator_{ mediator_in }
{
auto salter = tr_salt_shaker<tr_piece_index_t>{};
auto const is_sequential = mediator_.is_sequential_download();
auto const sequential_download_from_piece = mediator_.sequential_download_from_piece();
auto const n_pieces = mediator_.piece_count();
candidates_.reserve(n_pieces);
for (tr_piece_index_t piece = 0U; piece < n_pieces; ++piece)
@@ -445,7 +464,7 @@ Wishlist::Impl::Impl(Mediator& mediator_in)
continue;
}
auto const salt = get_salt(piece, n_pieces, salter(), is_sequential);
auto const salt = get_salt(piece, n_pieces, salter(), is_sequential, sequential_download_from_piece);
candidates_.emplace_back(piece, salt, &mediator_);
}
std::sort(std::begin(candidates_), std::end(candidates_));

View File

@@ -34,6 +34,7 @@ public:
[[nodiscard]] virtual bool client_has_piece(tr_piece_index_t piece) const = 0;
[[nodiscard]] virtual bool client_wants_piece(tr_piece_index_t piece) const = 0;
[[nodiscard]] virtual bool is_sequential_download() const = 0;
[[nodiscard]] virtual tr_piece_index_t sequential_download_from_piece() const = 0;
[[nodiscard]] virtual size_t count_piece_replication(tr_piece_index_t piece) const = 0;
[[nodiscard]] virtual tr_block_span_t block_span(tr_piece_index_t piece) const = 0;
[[nodiscard]] virtual tr_piece_index_t piece_count() const = 0;
@@ -66,6 +67,8 @@ public:
libtransmission::SimpleObservable<tr_torrent*, tr_peer*, tr_block_span_t>::Observer observer) = 0;
[[nodiscard]] virtual libtransmission::ObserverTag observe_sequential_download_changed(
libtransmission::SimpleObservable<tr_torrent*, bool>::Observer observer) = 0;
[[nodiscard]] virtual libtransmission::ObserverTag observe_sequential_download_from_piece_changed(
libtransmission::SimpleObservable<tr_torrent*, tr_piece_index_t>::Observer observer) = 0;
virtual ~Mediator() = default;
};

View File

@@ -384,6 +384,7 @@ public:
[[nodiscard]] bool client_has_piece(tr_piece_index_t piece) const override;
[[nodiscard]] bool client_wants_piece(tr_piece_index_t piece) const override;
[[nodiscard]] bool is_sequential_download() const override;
[[nodiscard]] tr_piece_index_t sequential_download_from_piece() const override;
[[nodiscard]] size_t count_piece_replication(tr_piece_index_t piece) const override;
[[nodiscard]] tr_block_span_t block_span(tr_piece_index_t piece) const override;
[[nodiscard]] tr_piece_index_t piece_count() const override;
@@ -417,6 +418,8 @@ public:
libtransmission::SimpleObservable<tr_torrent*, tr_peer*, tr_block_span_t>::Observer observer) override;
[[nodiscard]] libtransmission::ObserverTag observe_sequential_download_changed(
libtransmission::SimpleObservable<tr_torrent*, bool>::Observer observer) override;
[[nodiscard]] libtransmission::ObserverTag observe_sequential_download_from_piece_changed(
libtransmission::SimpleObservable<tr_torrent*, tr_piece_index_t>::Observer observer) override;
private:
tr_torrent& tor_;
@@ -1020,6 +1023,11 @@ bool tr_swarm::WishlistMediator::is_sequential_download() const
return tor_.is_sequential_download();
}
tr_piece_index_t tr_swarm::WishlistMediator::sequential_download_from_piece() const
{
return tor_.sequential_download_from_piece();
}
size_t tr_swarm::WishlistMediator::count_piece_replication(tr_piece_index_t piece) const
{
auto const op = [piece](size_t acc, auto const& peer)
@@ -1133,6 +1141,12 @@ libtransmission::ObserverTag tr_swarm::WishlistMediator::observe_sequential_down
return tor_.sequential_download_changed_.observe(std::move(observer));
}
libtransmission::ObserverTag tr_swarm::WishlistMediator::observe_sequential_download_from_piece_changed(
libtransmission::SimpleObservable<tr_torrent*, tr_piece_index_t>::Observer observer)
{
return tor_.sequential_download_from_piece_changed_.observe(std::move(observer));
}
// ---
struct tr_peerMgr

View File

@@ -321,6 +321,7 @@ auto constexpr MyStatic = std::array<std::string_view, TR_N_KEYS>{
"seederCount"sv,
"seeding-time-seconds"sv,
"sequential_download"sv,
"sequential_download_from_piece"sv,
"session-count"sv,
"session-id"sv,
"sessionCount"sv,

View File

@@ -323,6 +323,7 @@ enum // NOLINT(performance-enum-size)
TR_KEY_seederCount,
TR_KEY_seeding_time_seconds,
TR_KEY_sequential_download,
TR_KEY_sequential_download_from_piece,
TR_KEY_session_count,
TR_KEY_session_id,
TR_KEY_sessionCount,

View File

@@ -771,6 +771,15 @@ tr_resume::fields_t load_from_file(tr_torrent* tor, tr_torrent::ResumeHelper& he
}
}
if ((fields_to_load & tr_resume::SequentialDownloadFromPiece) != 0)
{
if (auto i = map.value_if<int64_t>(TR_KEY_sequential_download_from_piece); i)
{
tor->set_sequential_download_from_piece(*i);
fields_loaded |= tr_resume::SequentialDownloadFromPiece;
}
}
if ((fields_to_load & tr_resume::Peers) != 0)
{
fields_loaded |= load_peers(map, tor);
@@ -880,6 +889,15 @@ auto set_from_ctor(
}
}
if ((fields & tr_resume::SequentialDownloadFromPiece) != 0)
{
if (auto const& val = ctor.sequential_download_from_piece(mode); val)
{
tor->set_sequential_download_from_piece(*val);
ret |= tr_resume::SequentialDownloadFromPiece;
}
}
return ret;
}
@@ -945,6 +963,7 @@ void save(tr_torrent* const tor, tr_torrent::ResumeHelper const& helper)
map.try_emplace(TR_KEY_bandwidth_priority, tor->get_priority());
map.try_emplace(TR_KEY_paused, !helper.start_when_stable());
map.try_emplace(TR_KEY_sequential_download, tor->is_sequential_download());
map.try_emplace(TR_KEY_sequential_download_from_piece, tor->sequential_download_from_piece());
save_peers(map, tor);
if (tor->has_metainfo())

View File

@@ -45,6 +45,7 @@ auto inline constexpr Name = fields_t{ 1 << 21 };
auto inline constexpr Labels = fields_t{ 1 << 22 };
auto inline constexpr Group = fields_t{ 1 << 23 };
auto inline constexpr SequentialDownload = fields_t{ 1 << 24 };
auto inline constexpr SequentialDownloadFromPiece = fields_t{ 1 << 25 };
auto inline constexpr All = ~fields_t{ 0 };

View File

@@ -581,6 +581,7 @@ namespace make_torrent_field_helpers
case TR_KEY_seedRatioLimit:
case TR_KEY_seedRatioMode:
case TR_KEY_sequential_download:
case TR_KEY_sequential_download_from_piece:
case TR_KEY_sizeWhenDone:
case TR_KEY_source:
case TR_KEY_startDate:
@@ -676,6 +677,7 @@ namespace make_torrent_field_helpers
case TR_KEY_seedRatioLimit: return tor.seed_ratio();
case TR_KEY_seedRatioMode: return tor.seed_ratio_mode();
case TR_KEY_sequential_download: return tor.is_sequential_download();
case TR_KEY_sequential_download_from_piece: return tor.sequential_download_from_piece();
case TR_KEY_sizeWhenDone: return st.sizeWhenDone;
case TR_KEY_source: return tor.source();
case TR_KEY_startDate: return st.startDate;
@@ -887,6 +889,17 @@ char const* set_file_priorities(tr_torrent* tor, tr_priority_t priority, tr_vari
return nullptr; // no error
}
char const* set_sequential_download_from_piece(tr_torrent& tor, tr_piece_index_t piece)
{
if (piece >= tor.piece_count())
{
return "piece to sequentially download from is outside pieces range";
}
tor.set_sequential_download_from_piece(piece);
return nullptr; // no error
}
[[nodiscard]] char const* set_file_dls(tr_torrent* tor, bool wanted, tr_variant::Vector const& files_vec)
{
auto const [indices, errmsg] = get_file_indices(tor, files_vec);
@@ -1031,6 +1044,11 @@ char const* torrentSet(tr_session* session, tr_variant::Map const& args_in, tr_v
tor->set_sequential_download(*val);
}
if (auto const val = args_in.value_if<int64_t>(TR_KEY_sequential_download_from_piece); val && errmsg == nullptr)
{
errmsg = set_sequential_download_from_piece(*tor, *val);
}
if (auto const val = args_in.value_if<bool>(TR_KEY_downloadLimited))
{
tor->use_speed_limit(TR_DOWN, *val);
@@ -1475,6 +1493,11 @@ char const* torrentAdd(tr_session* session, tr_variant::Map const& args_in, tr_r
ctor.set_sequential_download(TR_FORCE, *val);
}
if (auto const val = args_in.value_if<int64_t>(TR_KEY_sequential_download_from_piece); val)
{
ctor.set_sequential_download_from_piece(TR_FORCE, *val);
}
tr_logAddTrace(fmt::format("torrentAdd: filename is '{}'", filename));
if (isCurlURL(filename))

View File

@@ -218,11 +218,22 @@ public:
optional_args_[mode].sequential_download_ = seq;
}
[[nodiscard]] constexpr auto const& sequential_download_from_piece(tr_ctorMode const mode) const noexcept
{
return optional_args_[mode].sequential_download_from_piece_;
}
constexpr void set_sequential_download_from_piece(tr_ctorMode const mode, tr_piece_index_t const piece) noexcept
{
optional_args_[mode].sequential_download_from_piece_ = piece;
}
private:
struct OptionalArgs
{
std::optional<bool> paused_;
std::optional<bool> sequential_download_;
std::optional<tr_piece_index_t> sequential_download_from_piece_;
std::optional<uint16_t> peer_limit_;
std::string download_dir_;
};

View File

@@ -773,6 +773,23 @@ struct tr_torrent
return sequential_download_;
}
bool set_sequential_download_from_piece(tr_piece_index_t piece) noexcept
{
auto const is_valid = piece < piece_count();
if (is_valid && piece != sequential_download_from_piece_)
{
sequential_download_from_piece_ = piece;
sequential_download_from_piece_changed_.emit(this, piece);
return true;
}
return false;
}
[[nodiscard]] constexpr auto sequential_download_from_piece() const noexcept
{
return sequential_download_from_piece_;
}
[[nodiscard]] constexpr bool is_running() const noexcept
{
return is_running_;
@@ -995,6 +1012,7 @@ struct tr_torrent
libtransmission::SimpleObservable<tr_torrent*, tr_file_index_t const*, tr_file_index_t, bool> files_wanted_changed_;
libtransmission::SimpleObservable<tr_torrent*, tr_file_index_t const*, tr_file_index_t, tr_priority_t> priority_changed_;
libtransmission::SimpleObservable<tr_torrent*, bool> sequential_download_changed_;
libtransmission::SimpleObservable<tr_torrent*, tr_piece_index_t> sequential_download_from_piece_changed_;
CumulativeCount bytes_corrupt_;
CumulativeCount bytes_downloaded_;
@@ -1413,6 +1431,8 @@ private:
bool sequential_download_ = false;
tr_piece_index_t sequential_download_from_piece_ = 0;
// start the torrent after all the startup scaffolding is done,
// e.g. fetching metadata from peers and/or verifying the torrent
bool start_when_stable_ = false;

View File

@@ -32,6 +32,7 @@ protected:
mutable std::set<tr_piece_index_t> client_wants_piece_;
tr_piece_index_t piece_count_ = 0;
bool is_sequential_download_ = false;
tr_piece_index_t sequential_download_from_piece_ = 0;
PeerMgrWishlistTest& parent_;
@@ -60,6 +61,11 @@ protected:
return is_sequential_download_;
}
[[nodiscard]] tr_piece_index_t sequential_download_from_piece() const override
{
return sequential_download_from_piece_;
}
[[nodiscard]] size_t count_piece_replication(tr_piece_index_t piece) const override
{
return piece_replication_[piece];
@@ -159,6 +165,12 @@ protected:
{
return parent_.sequential_download_changed_.observe(std::move(observer));
}
[[nodiscard]] libtransmission::ObserverTag observe_sequential_download_from_piece_changed(
libtransmission::SimpleObservable<tr_torrent*, tr_piece_index_t>::Observer observer) override
{
return parent_.sequential_download_from_piece_changed_.observe(std::move(observer));
}
};
libtransmission::SimpleObservable<tr_torrent*, tr_file_index_t const*, tr_file_index_t, bool> files_wanted_changed_;
@@ -174,6 +186,7 @@ protected:
libtransmission::SimpleObservable<tr_torrent*, tr_piece_index_t> piece_completed_;
libtransmission::SimpleObservable<tr_torrent*, tr_file_index_t const*, tr_file_index_t, tr_priority_t> priority_changed_;
libtransmission::SimpleObservable<tr_torrent*, bool> sequential_download_changed_;
libtransmission::SimpleObservable<tr_torrent*, tr_piece_index_t> sequential_download_from_piece_changed_;
static auto constexpr PeerHasAllPieces = [](tr_piece_index_t)
{
@@ -352,6 +365,62 @@ TEST_F(PeerMgrWishlistTest, sequentialDownload)
}
}
TEST_F(PeerMgrWishlistTest, sequentialDownloadFromPiece)
{
auto const get_spans = [this](size_t n_wanted)
{
auto mediator = MockMediator{ *this };
// setup: four pieces, all missing
mediator.piece_count_ = 4;
mediator.block_span_[0] = { 0, 100 };
mediator.block_span_[1] = { 100, 200 };
mediator.block_span_[2] = { 200, 300 };
mediator.block_span_[3] = { 300, 400 };
// peer has all pieces
mediator.piece_replication_[0] = 1;
mediator.piece_replication_[1] = 1;
mediator.piece_replication_[2] = 1;
mediator.piece_replication_[3] = 1;
// and we want all pieces
for (tr_piece_index_t i = 0; i < 4; ++i)
{
mediator.client_wants_piece_.insert(i);
}
// we enabled sequential download, from piece 2
mediator.is_sequential_download_ = true;
mediator.sequential_download_from_piece_ = 2;
return Wishlist{ mediator }.next(n_wanted, PeerHasAllPieces);
};
// First and last piece come first in sequential download mode regardless
// of "sequential download from piece", piece 2 comes next.
// NB: when all other things are equal in the wishlist, pieces are
// picked at random so this test -could- pass even if there's a bug.
// So test several times to shake out any randomness
static auto constexpr NumRuns = 1000;
for (int run = 0; run < NumRuns; ++run)
{
auto requested = tr_bitfield{ 400 };
auto const spans = get_spans(300);
for (auto const& [begin, end] : spans)
{
requested.set_span(begin, end);
}
EXPECT_EQ(300U, requested.count());
EXPECT_EQ(100U, requested.count(0, 100));
EXPECT_EQ(0U, requested.count(100, 200));
// piece 2 should be downloaded before piece 1
EXPECT_EQ(100U, requested.count(200, 300));
EXPECT_EQ(100U, requested.count(300, 400));
}
}
TEST_F(PeerMgrWishlistTest, doesNotRequestTooManyBlocks)
{
auto mediator = MockMediator{ *this };
@@ -1453,6 +1522,69 @@ TEST_F(PeerMgrWishlistTest, settingSequentialDownloadResortsCandidates)
}
}
TEST_F(PeerMgrWishlistTest, sequentialDownloadFromPieceResortsCandidates)
{
auto const get_spans = [this](size_t n_wanted)
{
auto mediator = MockMediator{ *this };
// setup: four pieces, all missing
mediator.piece_count_ = 4;
mediator.block_span_[0] = { 0, 100 };
mediator.block_span_[1] = { 100, 200 };
mediator.block_span_[2] = { 200, 300 };
mediator.block_span_[3] = { 300, 400 };
// peer has all pieces
mediator.piece_replication_[0] = 1;
mediator.piece_replication_[1] = 1;
mediator.piece_replication_[2] = 1;
mediator.piece_replication_[3] = 1;
// and we want all pieces
for (tr_piece_index_t i = 0; i < 4; ++i)
{
mediator.client_wants_piece_.insert(i);
}
// allow the wishlist to build its cache
auto wishlist = Wishlist{ mediator };
// we enabled sequential download, from piece 2
mediator.is_sequential_download_ = true;
sequential_download_changed_.emit(nullptr, true);
mediator.sequential_download_from_piece_ = 2;
sequential_download_from_piece_changed_.emit(nullptr, 2);
// the sequential download setting was changed,
// the candidate list should be resorted
return wishlist.next(n_wanted, PeerHasAllPieces);
};
// First and last piece come first in sequential download mode regardless
// of "sequential download from piece", piece 2 comes next.
// NB: when all other things are equal in the wishlist, pieces are
// picked at random so this test -could- pass even if there's a bug.
// So test several times to shake out any randomness
static auto constexpr NumRuns = 1000;
for (int run = 0; run < NumRuns; ++run)
{
auto requested = tr_bitfield{ 400 };
auto const spans = get_spans(300);
for (auto const& [begin, end] : spans)
{
requested.set_span(begin, end);
}
EXPECT_EQ(300U, requested.count());
EXPECT_EQ(100U, requested.count(0, 100));
EXPECT_EQ(0U, requested.count(100, 200));
// piece 2 should be downloaded before piece 1
EXPECT_EQ(100U, requested.count(200, 300));
EXPECT_EQ(100U, requested.count(300, 400));
}
}
TEST_F(PeerMgrWishlistTest, setFileWantedUpdatesCandidateListAdd)
{
auto const get_spans = [this](size_t n_wanted)