feat: sequential download (#4795)

This commit is contained in:
Pierre Dubouilh 2023-04-14 18:47:54 +02:00 committed by GitHub
parent afa9f64feb
commit ebfba686b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 72 additions and 11 deletions

View File

@ -68,7 +68,7 @@ static sig_atomic_t manualUpdate = false;
static char const* torrentPath = nullptr;
static auto constexpr Options = std::array<tr_option, 19>{
static auto constexpr Options = std::array<tr_option, 20>{
{ { 'b', "blocklist", "Enable peer blocklists", "b", false, nullptr },
{ 'B', "no-blocklist", "Disable peer blocklists", "B", false, nullptr },
{ 'd', "downlimit", "Set max download speed in " SPEED_K_STR, "d", true, "<speed>" },
@ -93,6 +93,8 @@ static auto constexpr Options = std::array<tr_option, 19>{
{ 'v', "verify", "Verify the specified torrent", "v", false, nullptr },
{ 'V', "version", "Show version number and exit", "V", false, nullptr },
{ 'w', "download-dir", "Where to save downloaded data", "w", true, "<path>" },
{ 500, "sequential-download", "Download pieces sequentially", "seq", false, nullptr },
{ 0, nullptr, nullptr, nullptr, false, nullptr } }
};
@ -445,6 +447,10 @@ static int parseCommandLine(tr_variant* d, int argc, char const** argv)
tr_variantDictAddInt(d, TR_KEY_encryption, TR_CLEAR_PREFERRED);
break;
case 500:
tr_variantDictAddBool(d, TR_KEY_sequentialDownload, true);
break;
case TR_OPT_UNK:
if (torrentPath == nullptr)
{

View File

@ -157,6 +157,7 @@ Request arguments:
| `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
| `sequentialDownload` | 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
@ -267,6 +268,7 @@ The 'source' column here corresponds to the data structure there.
| `seedIdleMode`| number| tr_inactivelimit
| `seedRatioLimit`| double| tr_torrent
| `seedRatioMode`| number| tr_ratiolimit
| `sequentialDownload`| boolean| tr_torrent
| `sizeWhenDone`| number| tr_stat
| `startDate`| number| tr_stat
| `status`| number (see below)| tr_stat
@ -1001,3 +1003,9 @@ Transmission 4.0.0 (`rpc-version-semver` 5.3.0, `rpc-version`: 17)
| `group-set` | new method
| `group-get` | new method
| `torrent-get` | :warning: old arg `wanted` was implemented as an array of `0` or `1` in Transmission 3.00 and older, despite being documented as an array of booleans. Transmission 4.0.0 and 4.0.1 "fixed" this by returning an array of booleans; but in practical terms, this change caused an unannounced breaking change for any 3rd party code that expected `0` or `1`. For this reason, 4.0.2 restored the 3.00 behavior and updated this spec to match the code.
Transmission 4.1.0 (`rpc-version-semver` 5.4.0, `rpc-version`: 18)
| Method | Description
|:---|:---
| `torrent-get` | new arg `sequentialDownload`
| `torrent-set` | new arg `sequentialDownload`

View File

@ -19,15 +19,16 @@
namespace
{
using SaltType = tr_piece_index_t;
struct Candidate
{
tr_piece_index_t piece;
size_t n_blocks_missing;
tr_priority_t priority;
uint8_t salt;
SaltType salt;
Candidate(tr_piece_index_t piece_in, size_t missing_in, tr_priority_t priority_in, uint8_t salt_in)
Candidate(tr_piece_index_t piece_in, size_t missing_in, tr_priority_t priority_in, SaltType salt_in)
: piece{ piece_in }
, n_blocks_missing{ missing_in }
, priority{ priority_in }
@ -86,14 +87,16 @@ std::vector<Candidate> getCandidates(Wishlist::Mediator const& mediator)
}
// transform them into candidates
auto salter = tr_salt_shaker{};
auto salter = tr_salt_shaker<SaltType>{};
auto const n = std::size(wanted_pieces);
auto candidates = std::vector<Candidate>{};
auto const is_sequential = mediator.isSequentialDownload();
candidates.reserve(n);
for (size_t i = 0; i < n; ++i)
{
auto const [piece, n_missing] = wanted_pieces[i];
candidates.emplace_back(piece, n_missing, mediator.priority(piece), salter());
auto const salt = is_sequential ? piece : salter();
candidates.emplace_back(piece, n_missing, mediator.priority(piece), salt);
}
return candidates;
@ -134,9 +137,10 @@ std::vector<tr_block_span_t> Wishlist::next(size_t n_wanted_blocks)
return {};
}
// We usually won't need all the candidates until endgame, so don't
// waste cycles sorting all of them here. partial sort is enough.
auto candidates = getCandidates(mediator_);
// We usually won't need all the candidates to be sorted until endgame, so don't
// waste cycles sorting all of them here. partial sort is enough.
auto constexpr MaxSortedPieces = size_t{ 30 };
auto const middle = std::min(std::size(candidates), MaxSortedPieces);
std::partial_sort(std::begin(candidates), std::begin(candidates) + middle, std::end(candidates));

View File

@ -27,6 +27,7 @@ public:
[[nodiscard]] virtual bool clientCanRequestBlock(tr_block_index_t block) const = 0;
[[nodiscard]] virtual bool clientCanRequestPiece(tr_piece_index_t piece) const = 0;
[[nodiscard]] virtual bool isEndgame() const = 0;
[[nodiscard]] virtual bool isSequentialDownload() const = 0;
[[nodiscard]] virtual size_t countActiveRequests(tr_block_index_t block) const = 0;
[[nodiscard]] virtual size_t countMissingBlocks(tr_piece_index_t piece) const = 0;
[[nodiscard]] virtual tr_block_span_t blockSpan(tr_piece_index_t) const = 0;

View File

@ -828,7 +828,7 @@ void tr_peerMgrSetUtpFailed(tr_torrent* tor, tr_address const& addr, bool failed
* REQUESTS
*
* There are two data structures associated with managing block requests:
*
*
* 1. tr_swarm::active_requests, an opaque class that tracks what requests
* we currently have, i.e. which blocks and from which peers.
* This is used for cancelling requests that have been waiting
@ -911,6 +911,11 @@ std::vector<tr_block_span_t> tr_peerMgrGetNextRequests(tr_torrent* torrent, tr_p
return torrent_->piecePriority(piece);
}
[[nodiscard]] bool isSequentialDownload() const override
{
return torrent_->isSequentialDownload();
}
private:
tr_torrent const* const torrent_;
tr_swarm const* const swarm_;

View File

@ -18,7 +18,7 @@ using namespace std::literals;
namespace
{
auto constexpr MyStatic = std::array<std::string_view, 401>{ ""sv,
auto constexpr MyStatic = std::array<std::string_view, 402>{ ""sv,
"activeTorrentCount"sv,
"activity-date"sv,
"activityDate"sv,
@ -329,6 +329,7 @@ auto constexpr MyStatic = std::array<std::string_view, 401>{ ""sv,
"seedRatioMode"sv,
"seederCount"sv,
"seeding-time-seconds"sv,
"sequentialDownload"sv,
"session-count"sv,
"session-id"sv,
"sessionCount"sv,

View File

@ -332,6 +332,7 @@ enum
TR_KEY_seedRatioMode,
TR_KEY_seederCount,
TR_KEY_seeding_time_seconds,
TR_KEY_sequentialDownload,
TR_KEY_session_count,
TR_KEY_session_id,
TR_KEY_sessionCount,

View File

@ -900,6 +900,7 @@ void save(tr_torrent* tor)
tr_variantDictAddInt(&top, TR_KEY_max_peers, tor->peerLimit());
tr_variantDictAddInt(&top, TR_KEY_bandwidth_priority, tor->getPriority());
tr_variantDictAddBool(&top, TR_KEY_paused, !tor->start_when_stable);
tr_variantDictAddBool(&top, TR_KEY_sequentialDownload, tor->isSequentialDownload());
savePeers(&top, tor);
if (tor->hasMetainfo())

View File

@ -47,9 +47,9 @@ using namespace std::literals;
namespace
{
auto constexpr RecentlyActiveSeconds = time_t{ 60 };
auto constexpr RpcVersion = int64_t{ 17 };
auto constexpr RpcVersion = int64_t{ 18 };
auto constexpr RpcVersionMin = int64_t{ 14 };
auto constexpr RpcVersionSemver = "5.3.0"sv;
auto constexpr RpcVersionSemver = "5.4.0"sv;
enum class TrFormat
{
@ -610,6 +610,10 @@ void initField(tr_torrent const* const tor, tr_stat const* const st, tr_variant*
tr_variantInitInt(initme, st->haveUnchecked);
break;
case TR_KEY_sequentialDownload:
tr_variantDictAddBool(initme, TR_KEY_sequentialDownload, tor->isSequentialDownload());
break;
case TR_KEY_haveValid:
tr_variantInitInt(initme, st->haveValid);
break;
@ -1226,6 +1230,11 @@ char const* torrentSet(tr_session* session, tr_variant* args_in, tr_variant* /*a
tr_torrentSetSpeedLimit_KBps(tor, TR_DOWN, tmp);
}
if (auto val = bool{}; tr_variantDictFindBool(args_in, TR_KEY_sequentialDownload, &val))
{
tor->setSequentialDownload(val);
}
if (auto val = bool{}; tr_variantDictFindBool(args_in, TR_KEY_downloadLimited, &val))
{
tor->useSpeedLimit(TR_DOWN, val);

View File

@ -664,6 +664,16 @@ public:
torrent's content than any other mime-type. */
[[nodiscard]] std::string_view primaryMimeType() const;
constexpr void setSequentialDownload(bool is_sequential) noexcept
{
this->sequential_download_ = is_sequential;
}
[[nodiscard]] constexpr auto isSequentialDownload() const noexcept
{
return this->sequential_download_;
}
constexpr void setDirty() noexcept
{
this->isDirty = true;
@ -948,6 +958,8 @@ private:
tr_interned_string bandwidth_group_;
bool needs_completeness_check_ = true;
bool sequential_download_ = false;
};
// ---

View File

@ -1392,7 +1392,9 @@ void DetailsDialog::onAddTrackerClicked()
auto urls_list = QList<QString>{};
urls_list.reserve(std::size(urls));
for (auto const& url : urls)
{
urls_list << url;
}
torrentSet(torrent_ids_t{ std::begin(ids), std::end(ids) }, TR_KEY_trackerAdd, urls_list);
}

View File

@ -28,6 +28,7 @@ protected:
mutable std::set<tr_piece_index_t> can_request_piece_;
tr_piece_index_t piece_count_ = 0;
bool is_endgame_ = false;
bool is_sequential_download_ = false;
[[nodiscard]] bool clientCanRequestBlock(tr_block_index_t block) const final
{
@ -44,6 +45,11 @@ protected:
return is_endgame_;
}
[[nodiscard]] bool isSequentialDownload() const final
{
return is_sequential_download_;
}
[[nodiscard]] size_t countActiveRequests(tr_block_index_t block) const final
{
return active_request_count_[block];

View File

@ -975,6 +975,11 @@ static void printDetails(tr_variant* top)
fmt::print(" Location: {:s}\n", sv);
}
if (tr_variantDictFindBool(t, TR_KEY_sequentialDownload, &boolVal))
{
fmt::print(" Sequential Download: {:s}\n", (boolVal ? "Yes" : "No"));
}
if (tr_variantDictFindInt(t, TR_KEY_sizeWhenDone, &i) && tr_variantDictFindInt(t, TR_KEY_leftUntilDone, &j))
{
fmt::print(" Percent Done: {:s}%\n", strlpercent(100.0 * (i - j) / i));