feat: sequential download (#4795)
This commit is contained in:
parent
afa9f64feb
commit
ebfba686b0
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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_;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
// ---
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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));
|
||||
|
|
Loading…
Reference in New Issue