diff --git a/cli/cli.cc b/cli/cli.cc index c347f3ffd..48106e234 100644 --- a/cli/cli.cc +++ b/cli/cli.cc @@ -68,7 +68,7 @@ static sig_atomic_t manualUpdate = false; static char const* torrentPath = nullptr; -static auto constexpr Options = std::array{ +static auto constexpr Options = std::array{ { { '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, "" }, @@ -93,6 +93,8 @@ static auto constexpr Options = std::array{ { '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, "" }, + { 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) { diff --git a/docs/rpc-spec.md b/docs/rpc-spec.md index 69c37a463..9bfb8af76 100644 --- a/docs/rpc-spec.md +++ b/docs/rpc-spec.md @@ -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` diff --git a/libtransmission/peer-mgr-wishlist.cc b/libtransmission/peer-mgr-wishlist.cc index c415b3e6f..b0adbbc61 100644 --- a/libtransmission/peer-mgr-wishlist.cc +++ b/libtransmission/peer-mgr-wishlist.cc @@ -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 getCandidates(Wishlist::Mediator const& mediator) } // transform them into candidates - auto salter = tr_salt_shaker{}; + auto salter = tr_salt_shaker{}; auto const n = std::size(wanted_pieces); auto candidates = std::vector{}; + 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 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)); diff --git a/libtransmission/peer-mgr-wishlist.h b/libtransmission/peer-mgr-wishlist.h index c8eee0e9c..c99ac0eab 100644 --- a/libtransmission/peer-mgr-wishlist.h +++ b/libtransmission/peer-mgr-wishlist.h @@ -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; diff --git a/libtransmission/peer-mgr.cc b/libtransmission/peer-mgr.cc index 82ed63fd9..fda8ed918 100644 --- a/libtransmission/peer-mgr.cc +++ b/libtransmission/peer-mgr.cc @@ -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_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_; diff --git a/libtransmission/quark.cc b/libtransmission/quark.cc index cd0d5f5a6..e0a06fdc6 100644 --- a/libtransmission/quark.cc +++ b/libtransmission/quark.cc @@ -18,7 +18,7 @@ using namespace std::literals; namespace { -auto constexpr MyStatic = std::array{ ""sv, +auto constexpr MyStatic = std::array{ ""sv, "activeTorrentCount"sv, "activity-date"sv, "activityDate"sv, @@ -329,6 +329,7 @@ auto constexpr MyStatic = std::array{ ""sv, "seedRatioMode"sv, "seederCount"sv, "seeding-time-seconds"sv, + "sequentialDownload"sv, "session-count"sv, "session-id"sv, "sessionCount"sv, diff --git a/libtransmission/quark.h b/libtransmission/quark.h index eb9fdb098..c3db9ce1a 100644 --- a/libtransmission/quark.h +++ b/libtransmission/quark.h @@ -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, diff --git a/libtransmission/resume.cc b/libtransmission/resume.cc index 7370e06df..2ac53724e 100644 --- a/libtransmission/resume.cc +++ b/libtransmission/resume.cc @@ -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()) diff --git a/libtransmission/rpcimpl.cc b/libtransmission/rpcimpl.cc index 7ebdb470e..99ef5c059 100644 --- a/libtransmission/rpcimpl.cc +++ b/libtransmission/rpcimpl.cc @@ -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); diff --git a/libtransmission/torrent.h b/libtransmission/torrent.h index 4c75daabb..b4bf07d17 100644 --- a/libtransmission/torrent.h +++ b/libtransmission/torrent.h @@ -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; }; // --- diff --git a/qt/DetailsDialog.cc b/qt/DetailsDialog.cc index 12a6eec12..3de247134 100644 --- a/qt/DetailsDialog.cc +++ b/qt/DetailsDialog.cc @@ -1392,7 +1392,9 @@ void DetailsDialog::onAddTrackerClicked() auto urls_list = QList{}; 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); } diff --git a/tests/libtransmission/peer-mgr-wishlist-test.cc b/tests/libtransmission/peer-mgr-wishlist-test.cc index f64c86030..51bc31a06 100644 --- a/tests/libtransmission/peer-mgr-wishlist-test.cc +++ b/tests/libtransmission/peer-mgr-wishlist-test.cc @@ -28,6 +28,7 @@ protected: mutable std::set 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]; diff --git a/utils/remote.cc b/utils/remote.cc index 2a7d57cd2..753b93851 100644 --- a/utils/remote.cc +++ b/utils/remote.cc @@ -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));