mirror of
https://github.com/transmission/transmission.git
synced 2025-12-20 02:18:42 +00:00
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:
committed by
GitHub
parent
af92dc5d80
commit
0f7f460c55
@@ -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`
|
||||
|
||||
@@ -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_));
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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 };
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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_;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user