diff --git a/4890.patch b/4890.patch new file mode 100644 index 0000000..2941f42 --- /dev/null +++ b/4890.patch @@ -0,0 +1,363 @@ +From ba97fb792c248320edd6353593feaff91b1155ef Mon Sep 17 00:00:00 2001 +From: Charles Kerr +Date: Tue, 14 Feb 2023 10:30:54 -0600 +Subject: [PATCH 1/3] fix: processing of block fragments that arrive + out-of-order from request + +Fixes 4.0.0 regression on torrents whose pieces did not align on block boundaries. +--- + libtransmission/cache.cc | 2 +- + libtransmission/cache.h | 2 +- + libtransmission/peer-msgs.cc | 96 ++++++++++++++++++------------------ + libtransmission/torrent.cc | 11 ++++- + libtransmission/webseed.cc | 2 +- + 5 files changed, 60 insertions(+), 53 deletions(-) + +diff --git a/libtransmission/cache.cc b/libtransmission/cache.cc +index ef446b244c..f7ae03dcc1 100644 +--- a/libtransmission/cache.cc ++++ b/libtransmission/cache.cc +@@ -140,7 +140,7 @@ Cache::Cache(tr_torrents& torrents, int64_t max_bytes) + + // --- + +-int Cache::writeBlock(tr_torrent_id_t tor_id, tr_block_index_t block, std::unique_ptr>& writeme) ++int Cache::writeBlock(tr_torrent_id_t tor_id, tr_block_index_t block, std::unique_ptr> writeme) + { + auto const key = Key{ tor_id, block }; + auto iter = std::lower_bound(std::begin(blocks_), std::end(blocks_), key, CompareCacheBlockByKey{}); +diff --git a/libtransmission/cache.h b/libtransmission/cache.h +index e25deb2328..9d6455610b 100644 +--- a/libtransmission/cache.h ++++ b/libtransmission/cache.h +@@ -36,7 +36,7 @@ class Cache + } + + // @return any error code from cacheTrim() +- int writeBlock(tr_torrent_id_t tor, tr_block_index_t block, std::unique_ptr>& writeme); ++ int writeBlock(tr_torrent_id_t tor, tr_block_index_t block, std::unique_ptr> writeme); + + int readBlock(tr_torrent* torrent, tr_block_info::Location loc, uint32_t len, uint8_t* setme); + int prefetchBlock(tr_torrent* torrent, tr_block_info::Location loc, uint32_t len); +diff --git a/libtransmission/peer-msgs.cc b/libtransmission/peer-msgs.cc +index 837dbb4b76..05864fea20 100644 +--- a/libtransmission/peer-msgs.cc ++++ b/libtransmission/peer-msgs.cc +@@ -188,7 +188,20 @@ struct tr_incoming + uint8_t id = 0; // the protocol message, e.g. BtPeerMsgs::Piece + uint32_t length = 0; // the full message payload length. Includes the +1 for id length + std::optional block_req; // metadata for incoming blocks +- std::map>> block_buf; // piece data for incoming blocks ++ ++ struct incoming_piece_data ++ { ++ explicit incoming_piece_data(uint32_t block_size) ++ : buf{ std::make_unique>(block_size) } ++ { ++ } ++ ++ uint32_t n_bytes_received = 0; ++ ++ std::unique_ptr> buf; ++ }; ++ ++ std::map blocks; + }; + + class tr_peerMsgsImpl; +@@ -1397,7 +1410,7 @@ bool messageLengthIsCorrect(tr_peerMsgsImpl const* msg, uint8_t id, uint32_t len + } + } + +-int clientGotBlock(tr_peerMsgsImpl* msgs, std::unique_ptr>& block_data, tr_block_index_t block); ++int clientGotBlock(tr_peerMsgsImpl* msgs, std::unique_ptr> block_data, tr_block_index_t block); + + ReadState readBtPiece(tr_peerMsgsImpl* msgs, size_t inlen, size_t* setme_piece_bytes_read) + { +@@ -1406,7 +1419,8 @@ ReadState readBtPiece(tr_peerMsgsImpl* msgs, size_t inlen, size_t* setme_piece_b + logtrace(msgs, "In readBtPiece"); + + // If this is the first we've seen of the piece data, parse out the header +- if (!msgs->incoming.block_req) ++ auto& incoming = msgs->incoming; ++ if (!incoming.block_req) + { + if (inlen < 8) + { +@@ -1416,67 +1430,55 @@ ReadState readBtPiece(tr_peerMsgsImpl* msgs, size_t inlen, size_t* setme_piece_b + auto req = peer_request{}; + msgs->io->read_uint32(&req.index); + msgs->io->read_uint32(&req.offset); +- req.length = msgs->incoming.length - 9; ++ req.length = incoming.length - 9; + logtrace(msgs, fmt::format(FMT_STRING("got incoming block header {:d}:{:d}->{:d}"), req.index, req.offset, req.length)); +- msgs->incoming.block_req = req; ++ incoming.block_req = req; + return READ_NOW; + } + +- auto& req = msgs->incoming.block_req; ++ auto& req = incoming.block_req; + auto const loc = msgs->torrent->pieceLoc(req->index, req->offset); + auto const block = loc.block; + auto const block_size = msgs->torrent->blockSize(block); +- auto& block_buf = msgs->incoming.block_buf[block]; +- if (!block_buf) +- { +- block_buf = std::make_unique>(); +- block_buf->reserve(block_size); +- } + +- // read in another chunk of data +- auto const n_left_in_block = block_size - std::size(*block_buf); +- auto const n_left_in_req = size_t{ req->length }; +- auto const n_to_read = std::min({ n_left_in_block, n_left_in_req, inlen }); +- auto const old_length = std::size(*block_buf); +- block_buf->resize(old_length + n_to_read); +- msgs->io->read_bytes(&((*block_buf)[old_length]), n_to_read); ++ auto const n_this_pass = std::min(size_t{ req->length }, inlen); ++ TR_ASSERT(loc.block_offset + n_this_pass <= block_size); + +- msgs->publish(tr_peer_event::GotPieceData(n_to_read)); +- *setme_piece_bytes_read += n_to_read; +- logtrace( +- msgs, +- fmt::format( +- FMT_STRING("got {:d} bytes for block {:d}:{:d}->{:d} ... {:d} remain in req, {:d} remain in block"), +- n_to_read, +- req->index, +- req->offset, +- req->length, +- req->length, +- block_size - std::size(*block_buf))); +- +- // if we didn't read enough to finish off the request, +- // update the table and wait for more +- if (n_to_read < n_left_in_req) +- { +- auto new_loc = msgs->torrent->byteLoc(loc.byte + n_to_read); ++ auto& incoming_block = incoming.blocks.try_emplace(block, block_size).first->second; ++ msgs->io->read_bytes(std::data(*incoming_block.buf) + loc.block_offset, n_this_pass); ++ ++ msgs->publish(tr_peer_event::GotPieceData(n_this_pass)); ++ *setme_piece_bytes_read += n_this_pass; ++ incoming_block.n_bytes_received += n_this_pass; ++ TR_ASSERT(incoming_block.n_bytes_received <= block_size); ++ logtrace(msgs, fmt::format("got {:d} bytes for req {:d}:{:d}->{:d}", n_this_pass, req->index, req->offset, req->length)); ++ ++ // if we haven't gotten the full response yet, ++ // update what part of `req` is unfulfilled and wait for more ++ if (n_this_pass < req->length) ++ { ++ req->length -= n_this_pass; ++ auto const new_loc = msgs->torrent->byteLoc(loc.byte + n_this_pass); + req->index = new_loc.piece; + req->offset = new_loc.piece_offset; +- req->length -= n_to_read; + return READ_LATER; + } + +- // we've fully read this message ++ // we've got the entire response message + req.reset(); + msgs->state = AwaitingBt::Length; + +- // if we didn't read enough to finish off the block, +- // update the table and wait for more +- if (std::size(*block_buf) < block_size) ++ // if we haven't gotten the entire block yet, wait for more ++ if (incoming_block.n_bytes_received < block_size) + { + return READ_LATER; + } + +- return clientGotBlock(msgs, block_buf, block) != 0 ? READ_ERR : READ_NOW; ++ // we've got the entire block, so send it along. ++ auto block_buf = std::move(incoming_block.buf); ++ incoming.blocks.erase(block); // note: invalidates `incoming_block` local ++ auto const ok = clientGotBlock(msgs, std::move(block_buf), block) == 0; ++ return ok ? READ_NOW : READ_ERR; + } + + ReadState readBtMessage(tr_peerMsgsImpl* msgs, size_t inlen) +@@ -1744,7 +1746,7 @@ ReadState readBtMessage(tr_peerMsgsImpl* msgs, size_t inlen) + } + + /* returns 0 on success, or an errno on failure */ +-int clientGotBlock(tr_peerMsgsImpl* msgs, std::unique_ptr>& block_data, tr_block_index_t const block) ++int clientGotBlock(tr_peerMsgsImpl* msgs, std::unique_ptr> block_data, tr_block_index_t const block) + { + TR_ASSERT(msgs != nullptr); + +@@ -1760,7 +1762,6 @@ int clientGotBlock(tr_peerMsgsImpl* msgs, std::unique_ptr>& + if (std::size(*block_data) != msgs->torrent->blockSize(block)) + { + logdbg(msgs, fmt::format("wrong block size: expected {:d}, got {:d}", n_expected, std::size(*block_data))); +- block_data->clear(); + return EMSGSIZE; + } + +@@ -1769,7 +1770,6 @@ int clientGotBlock(tr_peerMsgsImpl* msgs, std::unique_ptr>& + if (!tr_peerMgrDidPeerRequest(msgs->torrent, msgs, block)) + { + logdbg(msgs, "we didn't ask for this message..."); +- block_data->clear(); + return 0; + } + +@@ -1777,19 +1777,17 @@ int clientGotBlock(tr_peerMsgsImpl* msgs, std::unique_ptr>& + if (msgs->torrent->hasPiece(loc.piece)) + { + logtrace(msgs, "we did ask for this message, but the piece is already complete..."); +- block_data->clear(); + return 0; + } + + // NB: if writeBlock() fails the torrent may be paused. + // If this happens, `msgs` will be a dangling pointer and must no longer be used. +- if (auto const err = msgs->session->cache->writeBlock(tor->id(), block, block_data); err != 0) ++ if (auto const err = msgs->session->cache->writeBlock(tor->id(), block, std::move(block_data)); err != 0) + { + return err; + } + + msgs->blame.set(loc.piece); +- msgs->incoming.block_buf.erase(block); + msgs->publish(tr_peer_event::GotBlock(tor->blockInfo(), block)); + + return 0; +diff --git a/libtransmission/torrent.cc b/libtransmission/torrent.cc +index 4b5a0b1dea..cd96bac2c3 100644 +--- a/libtransmission/torrent.cc ++++ b/libtransmission/torrent.cc +@@ -2400,8 +2400,17 @@ void tr_torrentGotBlock(tr_torrent* tor, tr_block_index_t block) + tor->setDirty(); + + tor->completion.addBlock(block); +- if (auto const piece = tor->blockLoc(block).piece; tor->hasPiece(piece)) ++ ++ auto const block_loc = tor->blockLoc(block); ++ auto const first_piece = block_loc.piece; ++ auto const last_piece = tor->byteLoc(block_loc.byte + tor->blockSize(block) - 1).piece; ++ for (auto piece = first_piece; piece <= last_piece; ++piece) + { ++ if (!tor->hasPiece(piece)) ++ { ++ continue; ++ } ++ + if (tor->checkPiece(piece)) + { + onPieceCompleted(tor, piece); +diff --git a/libtransmission/webseed.cc b/libtransmission/webseed.cc +index 7f07dd149e..8136139d87 100644 +--- a/libtransmission/webseed.cc ++++ b/libtransmission/webseed.cc +@@ -352,7 +352,7 @@ struct write_block_data + { + if (auto const* const tor = tr_torrentFindFromId(session_, tor_id_); tor != nullptr) + { +- session_->cache->writeBlock(tor_id_, block_, data_); ++ session_->cache->writeBlock(tor_id_, block_, std::move(data_)); + webseed_->publish(tr_peer_event::GotBlock(tor->blockInfo(), block_)); + } + + +From 8a3418aae23e37d446a86b11a34679db0bfa99f7 Mon Sep 17 00:00:00 2001 +From: Charles Kerr +Date: Tue, 14 Feb 2023 10:59:16 -0600 +Subject: [PATCH 2/3] fixup! fix: processing of block fragments that arrive + out-of-order from request + +refactor: be more paranoid about duplicate requests at endgame. +--- + libtransmission/peer-msgs.cc | 16 ++++++++++------ + 1 file changed, 10 insertions(+), 6 deletions(-) + +diff --git a/libtransmission/peer-msgs.cc b/libtransmission/peer-msgs.cc +index 05864fea20..24e3359d65 100644 +--- a/libtransmission/peer-msgs.cc ++++ b/libtransmission/peer-msgs.cc +@@ -22,6 +22,7 @@ + + #include "transmission.h" + ++#include "bitfield.h" + #include "cache.h" + #include "completion.h" + #include "crypto-utils.h" +@@ -193,12 +194,12 @@ struct tr_incoming + { + explicit incoming_piece_data(uint32_t block_size) + : buf{ std::make_unique>(block_size) } ++ , have{ block_size } + { + } + +- uint32_t n_bytes_received = 0; +- + std::unique_ptr> buf; ++ tr_bitfield have; + }; + + std::map blocks; +@@ -1443,19 +1444,22 @@ ReadState readBtPiece(tr_peerMsgsImpl* msgs, size_t inlen, size_t* setme_piece_b + + auto const n_this_pass = std::min(size_t{ req->length }, inlen); + TR_ASSERT(loc.block_offset + n_this_pass <= block_size); ++ if (n_this_pass == 0) ++ { ++ return READ_LATER; ++ } + + auto& incoming_block = incoming.blocks.try_emplace(block, block_size).first->second; + msgs->io->read_bytes(std::data(*incoming_block.buf) + loc.block_offset, n_this_pass); + + msgs->publish(tr_peer_event::GotPieceData(n_this_pass)); + *setme_piece_bytes_read += n_this_pass; +- incoming_block.n_bytes_received += n_this_pass; +- TR_ASSERT(incoming_block.n_bytes_received <= block_size); ++ incoming_block.have.setSpan(loc.block_offset, loc.block_offset + n_this_pass); + logtrace(msgs, fmt::format("got {:d} bytes for req {:d}:{:d}->{:d}", n_this_pass, req->index, req->offset, req->length)); + + // if we haven't gotten the full response yet, + // update what part of `req` is unfulfilled and wait for more +- if (n_this_pass < req->length) ++ if (req->length > n_this_pass) + { + req->length -= n_this_pass; + auto const new_loc = msgs->torrent->byteLoc(loc.byte + n_this_pass); +@@ -1469,7 +1473,7 @@ ReadState readBtPiece(tr_peerMsgsImpl* msgs, size_t inlen, size_t* setme_piece_b + msgs->state = AwaitingBt::Length; + + // if we haven't gotten the entire block yet, wait for more +- if (incoming_block.n_bytes_received < block_size) ++ if (!incoming_block.have.hasAll()) + { + return READ_LATER; + } + +From d75445758c3e7dc666aba781111275d62acbf3b8 Mon Sep 17 00:00:00 2001 +From: Charles Kerr +Date: Tue, 14 Feb 2023 11:57:35 -0600 +Subject: [PATCH 3/3] fixup! fix: processing of block fragments that arrive + out-of-order from request + +chore: sync tests +--- + tests/libtransmission/move-test.cc | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/tests/libtransmission/move-test.cc b/tests/libtransmission/move-test.cc +index cc5dc05ce6..e06d1daefd 100644 +--- a/tests/libtransmission/move-test.cc ++++ b/tests/libtransmission/move-test.cc +@@ -84,7 +84,7 @@ TEST_P(IncompleteDirTest, incompleteDir) + + auto const test_incomplete_dir_threadfunc = [](TestIncompleteDirData* data) noexcept + { +- data->session->cache->writeBlock(data->tor->id(), data->block, data->buf); ++ data->session->cache->writeBlock(data->tor->id(), data->block, std::move(data->buf)); + tr_torrentGotBlock(data->tor, data->block); + data->done = true; + }; diff --git a/f551b4adbff0d59557d61867d0b6518c50f5a73f.patch b/f551b4adbff0d59557d61867d0b6518c50f5a73f.patch new file mode 100644 index 0000000..fcc68e2 --- /dev/null +++ b/f551b4adbff0d59557d61867d0b6518c50f5a73f.patch @@ -0,0 +1,500 @@ +From f551b4adbff0d59557d61867d0b6518c50f5a73f Mon Sep 17 00:00:00 2001 +From: Charles Kerr +Date: Mon, 13 Feb 2023 12:33:33 -0600 +Subject: [PATCH] fix: magnet links are always paused when added (#4856) + +--- + libtransmission/peer-mgr.cc | 8 +- + libtransmission/resume.cc | 6 +- + libtransmission/torrent-magnet.cc | 34 +++---- + libtransmission/torrent-magnet.h | 2 + + libtransmission/torrent.cc | 148 ++++++++++++++---------------- + libtransmission/torrent.h | 9 +- + libtransmission/transmission.h | 2 +- + macosx/Torrent.mm | 2 +- + 8 files changed, 101 insertions(+), 110 deletions(-) + +diff --git a/libtransmission/peer-mgr.cc b/libtransmission/peer-mgr.cc +index 4cf1d332c8..8e826dc3fa 100644 +--- a/libtransmission/peer-mgr.cc ++++ b/libtransmission/peer-mgr.cc +@@ -43,6 +43,7 @@ + #include "session.h" + #include "timer.h" + #include "torrent.h" ++#include "torrent-magnet.h" + #include "tr-assert.h" + #include "tr-utp.h" + #include "utils.h" +@@ -2391,8 +2392,11 @@ void tr_peerMgr::bandwidthPulse() + session->top_bandwidth_.allocate(Msec); + + // torrent upkeep +- auto& torrents = session->torrents(); +- std::for_each(std::begin(torrents), std::end(torrents), [](auto* tor) { tor->do_idle_work(); }); ++ for (auto* const tor : session->torrents()) ++ { ++ tor->do_idle_work(); ++ tr_torrentMagnetDoIdleWork(tor); ++ } + + /* pump the queues */ + queuePulse(session, TR_UP); +diff --git a/libtransmission/resume.cc b/libtransmission/resume.cc +index 80c00dbd28..9716ae876c 100644 +--- a/libtransmission/resume.cc ++++ b/libtransmission/resume.cc +@@ -712,7 +712,7 @@ auto loadFromFile(tr_torrent* tor, tr_resume::fields_t fields_to_load, bool* did + + if (auto val = bool{}; (fields_to_load & tr_resume::Run) != 0 && tr_variantDictFindBool(&top, TR_KEY_paused, &val)) + { +- tor->isRunning = !val; ++ tor->start_when_stable = !val; + fields_loaded |= tr_resume::Run; + } + +@@ -831,7 +831,7 @@ auto setFromCtor(tr_torrent* tor, tr_resume::fields_t fields, tr_ctor const* cto + { + if (auto is_paused = bool{}; tr_ctorGetPaused(ctor, mode, &is_paused)) + { +- tor->isRunning = !is_paused; ++ tor->start_when_stable = !is_paused; + ret |= tr_resume::Run; + } + } +@@ -907,7 +907,7 @@ void save(tr_torrent* tor) + tr_variantDictAddInt(&top, TR_KEY_uploaded, tor->uploadedPrev + tor->uploadedCur); + 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->isRunning && !tor->isQueued()); ++ tr_variantDictAddBool(&top, TR_KEY_paused, !tor->start_when_stable); + savePeers(&top, tor); + + if (tor->hasMetainfo()) +diff --git a/libtransmission/torrent-magnet.cc b/libtransmission/torrent-magnet.cc +index 1d91cbfef1..c32defb433 100644 +--- a/libtransmission/torrent-magnet.cc ++++ b/libtransmission/torrent-magnet.cc +@@ -176,13 +176,6 @@ bool tr_torrentUseMetainfoFromFile( + delete tor->incompleteMetadata; + tor->incompleteMetadata = nullptr; + } +- tor->isStopping = true; +- tor->magnetVerify = true; +- if (tor->session->shouldPauseAddedTorrents()) +- { +- tor->startAfterVerify = false; +- } +- tor->markEdited(); + + return true; + } +@@ -310,13 +303,6 @@ void on_have_all_metainfo(tr_torrent* tor, tr_incomplete_metadata* m) + { + delete tor->incompleteMetadata; + tor->incompleteMetadata = nullptr; +- tor->isStopping = true; +- tor->magnetVerify = true; +- if (tor->session->shouldPauseAddedTorrents() && !tor->magnetStartAfterVerify) +- { +- tor->startAfterVerify = false; +- } +- tor->markEdited(); + } + else /* drat. */ + { +@@ -340,6 +326,19 @@ void on_have_all_metainfo(tr_torrent* tor, tr_incomplete_metadata* m) + } // namespace set_metadata_piece_helpers + } // namespace + ++void tr_torrentMagnetDoIdleWork(tr_torrent* const tor) ++{ ++ using namespace set_metadata_piece_helpers; ++ ++ TR_ASSERT(tr_isTorrent(tor)); ++ ++ if (auto* const m = tor->incompleteMetadata; m != nullptr && std::empty(m->pieces_needed)) ++ { ++ tr_logAddDebugTor(tor, fmt::format("we now have all the metainfo!")); ++ on_have_all_metainfo(tor, m); ++ } ++} ++ + void tr_torrentSetMetadataPiece(tr_torrent* tor, int piece, void const* data, size_t len) + { + using namespace set_metadata_piece_helpers; +@@ -384,13 +383,6 @@ void tr_torrentSetMetadataPiece(tr_torrent* tor, int piece, void const* data, si + + needed.erase(iter); + tr_logAddDebugTor(tor, fmt::format("saving metainfo piece {}... {} remain", piece, std::size(needed))); +- +- // are we done? +- if (std::empty(needed)) +- { +- tr_logAddDebugTor(tor, fmt::format("metainfo piece {} was the last one", piece)); +- on_have_all_metainfo(tor, m); +- } + } + + // --- +diff --git a/libtransmission/torrent-magnet.h b/libtransmission/torrent-magnet.h +index 8af953bec9..cb5d7e1fcb 100644 +--- a/libtransmission/torrent-magnet.h ++++ b/libtransmission/torrent-magnet.h +@@ -33,6 +33,8 @@ bool tr_torrentSetMetadataSizeHint(tr_torrent* tor, int64_t metadata_size); + + double tr_torrentGetMetadataPercent(tr_torrent const* tor); + ++void tr_torrentMagnetDoIdleWork(tr_torrent* tor); ++ + bool tr_torrentUseMetainfoFromFile( + tr_torrent* tor, + tr_torrent_metainfo const* metainfo, +diff --git a/libtransmission/torrent.cc b/libtransmission/torrent.cc +index 1e74f8152b..4b5a0b1dea 100644 +--- a/libtransmission/torrent.cc ++++ b/libtransmission/torrent.cc +@@ -830,6 +830,8 @@ void torrentStart(tr_torrent* tor, torrent_start_opts opts) + { + using namespace start_stop_helpers; + ++ auto const lock = tor->unique_lock(); ++ + switch (tor->activity()) + { + case TR_STATUS_SEED: +@@ -849,7 +851,6 @@ void torrentStart(tr_torrent* tor, torrent_start_opts opts) + case TR_STATUS_CHECK_WAIT: + /* verifying right now... wait until that's done so + * we'll know what completeness to use/announce */ +- tor->startAfterVerify = true; + return; + + case TR_STATUS_STOPPED: +@@ -868,9 +869,6 @@ void torrentStart(tr_torrent* tor, torrent_start_opts opts) + return; + } + +- /* otherwise, start it now... */ +- auto const lock = tor->unique_lock(); +- + /* allow finished torrents to be resumed */ + if (tr_torrentIsSeedRatioDone(tor)) + { +@@ -895,6 +893,9 @@ void torrentStop(tr_torrent* const tor) + TR_ASSERT(tor->session->amInSessionThread()); + auto const lock = tor->unique_lock(); + ++ tor->isRunning = false; ++ tor->isStopping = false; ++ + if (!tor->session->isClosing()) + { + tr_logAddInfoTor(tor, _("Pausing torrent")); +@@ -913,16 +914,6 @@ void torrentStop(tr_torrent* const tor) + } + + torrentSetQueued(tor, false); +- +- if (tor->magnetVerify) +- { +- tor->magnetVerify = false; +- tr_logAddTraceTor(tor, "Magnet Verify"); +- tor->refreshCurrentDir(); +- tr_torrentVerify(tor); +- +- callScriptIfEnabled(tor, TR_SCRIPT_ON_TORRENT_ADDED); +- } + } + } // namespace + +@@ -935,8 +926,7 @@ void tr_torrentStop(tr_torrent* tor) + + auto const lock = tor->unique_lock(); + +- tor->isRunning = false; +- tor->isStopping = false; ++ tor->start_when_stable = false; + tor->setDirty(); + tor->session->runInSessionThread(torrentStop, tor); + } +@@ -965,7 +955,6 @@ void tr_torrentFreeInSessionThread(tr_torrent* tor) + tr_logAddInfoTor(tor, _("Removing torrent")); + } + +- tor->magnetVerify = false; + torrentStop(tor); + + if (tor->isDeleting) +@@ -975,7 +964,6 @@ void tr_torrentFreeInSessionThread(tr_torrent* tor) + tr_torrent_metainfo::removeFile(tor->session->resumeDir(), tor->name(), tor->infoHashString(), ".resume"sv); + } + +- tor->isRunning = false; + freeTorrent(tor); + } + +@@ -1036,6 +1024,34 @@ void torrentInitFromInfoDict(tr_torrent* tor) + tor->checked_pieces_ = tr_bitfield{ size_t(tor->pieceCount()) }; + } + ++void on_metainfo_completed(tr_torrent* tor) ++{ ++ // we can look for files now that we know what files are in the torrent ++ tor->refreshCurrentDir(); ++ ++ callScriptIfEnabled(tor, TR_SCRIPT_ON_TORRENT_ADDED); ++ ++ if (tor->session->shouldFullyVerifyAddedTorrents() || !isNewTorrentASeed(tor)) ++ { ++ tr_torrentVerify(tor); ++ } ++ else ++ { ++ tor->completion.setHasAll(); ++ tor->doneDate = tor->addedDate; ++ tor->recheckCompleteness(); ++ ++ if (tor->start_when_stable) ++ { ++ torrentStart(tor, {}); ++ } ++ else if (tor->isRunning) ++ { ++ tr_torrentStop(tor); ++ } ++ } ++} ++ + void torrentInit(tr_torrent* tor, tr_ctor const* ctor) + { + tr_session* session = tr_ctorGetSession(ctor); +@@ -1081,7 +1097,7 @@ void torrentInit(tr_torrent* tor, tr_ctor const* ctor) + tor->anyDate = now; + + tr_resume::fields_t loaded = {}; +- if (tor->hasMetainfo()) ++ + { + // tr_resume::load() calls a lot of tr_torrentSetFoo() methods + // that set things as dirty, but... these settings being loaded are +@@ -1106,9 +1122,6 @@ void torrentInit(tr_torrent* tor, tr_ctor const* ctor) + + tor->refreshCurrentDir(); + +- bool const do_start = tor->isRunning; +- tor->isRunning = false; +- + if ((loaded & tr_resume::Speedlimit) == 0) + { + tor->useSpeedLimit(TR_UP, false); +@@ -1174,37 +1187,14 @@ void torrentInit(tr_torrent* tor, tr_ctor const* ctor) + + tor->torrent_announcer = session->announcer_->addTorrent(tor, &tr_torrent::onTrackerResponse); + +- if (is_new_torrent) ++ if (auto const has_metainfo = tor->hasMetainfo(); is_new_torrent && has_metainfo) + { +- if (tor->hasMetainfo()) +- { +- callScriptIfEnabled(tor, TR_SCRIPT_ON_TORRENT_ADDED); +- } +- +- if (!tor->hasMetainfo() && !do_start) +- { +- auto opts = torrent_start_opts{}; +- opts.bypass_queue = true; +- opts.has_local_data = has_local_data; +- torrentStart(tor, opts); +- } +- else if (!session->shouldFullyVerifyAddedTorrents() && isNewTorrentASeed(tor)) +- { +- tor->completion.setHasAll(); +- tor->doneDate = tor->addedDate; +- tor->recheckCompleteness(); +- } +- else +- { +- tor->startAfterVerify = do_start; +- tr_torrentVerify(tor); +- } ++ on_metainfo_completed(tor); + } +- else if (do_start) ++ else if (tor->start_when_stable || !has_metainfo) + { +- // if checked_pieces_ got populated from the loading the resume +- // file above, then torrentStart doesn't need to check again + auto opts = torrent_start_opts{}; ++ opts.bypass_queue = !has_metainfo; // to fetch metainfo from peers + opts.has_local_data = has_local_data; + torrentStart(tor, opts); + } +@@ -1216,16 +1206,20 @@ void torrentInit(tr_torrent* tor, tr_ctor const* ctor) + } // namespace torrent_init_helpers + } // namespace + +-void tr_torrent::setMetainfo(tr_torrent_metainfo const& tm) ++void tr_torrent::setMetainfo(tr_torrent_metainfo tm) + { + using namespace torrent_init_helpers; + +- metainfo_ = tm; ++ TR_ASSERT(!hasMetainfo()); ++ metainfo_ = std::move(tm); + + torrentInitFromInfoDict(this); + tr_peerMgrOnTorrentGotMetainfo(this); + session->onMetadataCompleted(this); + this->setDirty(); ++ this->markEdited(); ++ ++ on_metainfo_completed(this); + } + + tr_torrent* tr_torrentNew(tr_ctor* ctor, tr_torrent** setme_duplicate_of) +@@ -1763,33 +1757,35 @@ void tr_torrentAmountFinished(tr_torrent const* tor, float* tabs, int n_tabs) + + // --- Start/Stop Callback + +-void tr_torrentStart(tr_torrent* tor) ++namespace ++{ ++void tr_torrentStartImpl(tr_torrent* tor, bool bypass_queue) + { +- if (tr_isTorrent(tor)) ++ if (!tr_isTorrent(tor)) + { +- tor->startAfterVerify = true; +- torrentStart(tor, {}); ++ return; + } ++ ++ tor->start_when_stable = true; ++ auto opts = torrent_start_opts{}; ++ opts.bypass_queue = bypass_queue; ++ torrentStart(tor, opts); ++} ++} // namespace ++ ++void tr_torrentStart(tr_torrent* tor) ++{ ++ tr_torrentStartImpl(tor, false); + } + + void tr_torrentStartNow(tr_torrent* tor) + { +- if (tr_isTorrent(tor)) +- { +- auto opts = torrent_start_opts{}; +- opts.bypass_queue = true; +- torrentStart(tor, opts); +- } ++ tr_torrentStartImpl(tor, true); + } + + void tr_torrentStartMagnet(tr_torrent* tor) + { +- if (tr_isTorrent(tor)) +- { +- tor->magnetStartAfterVerify = true; +- tor->startAfterVerify = true; +- torrentStart(tor, {}); +- } ++ tr_torrentStart(tor); + } + + // --- +@@ -1809,10 +1805,8 @@ void onVerifyDoneThreadFunc(tr_torrent* const tor) + + tor->recheckCompleteness(); + +- if (tor->startAfterVerify) ++ if (tor->start_when_stable) + { +- tor->startAfterVerify = false; +- + auto opts = torrent_start_opts{}; + opts.has_local_data = !tor->checked_pieces_.hasNone(); + torrentStart(tor, opts); +@@ -1832,20 +1826,18 @@ void verifyTorrent(tr_torrent* const tor) + /* if the torrent's already being verified, stop it */ + tor->session->verifyRemove(tor); + +- bool const start_after = (tor->isRunning || tor->startAfterVerify) && !tor->isStopping; +- +- if (tor->isRunning) ++ if (!tor->hasMetainfo()) + { +- tr_torrentStop(tor); ++ return; + } + +- if (setLocalErrorIfFilesDisappeared(tor)) ++ if (tor->isRunning) + { +- tor->startAfterVerify = false; ++ torrentStop(tor); + } +- else ++ ++ if (!setLocalErrorIfFilesDisappeared(tor)) + { +- tor->startAfterVerify = start_after; + tor->session->verifyAdd(tor); + } + } +diff --git a/libtransmission/torrent.h b/libtransmission/torrent.h +index deebb77337..a1a3031aa8 100644 +--- a/libtransmission/torrent.h ++++ b/libtransmission/torrent.h +@@ -109,7 +109,8 @@ struct tr_torrent final : public tr_completion::torrent_view + // but more refactoring is needed before that can happen + // because much of tr_torrent's impl is in the non-member C bindings + +- void setMetainfo(tr_torrent_metainfo const& tm); ++ // Used to add metainfo to a magnet torrent. ++ void setMetainfo(tr_torrent_metainfo tm); + + [[nodiscard]] auto unique_lock() const + { +@@ -903,10 +904,10 @@ struct tr_torrent final : public tr_completion::torrent_view + bool is_queued = false; + bool isRunning = false; + bool isStopping = false; +- bool startAfterVerify = false; +- bool magnetStartAfterVerify = false; + +- bool magnetVerify = false; ++ // 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; + + private: + [[nodiscard]] constexpr bool isPieceTransferAllowed(tr_direction direction) const noexcept +diff --git a/libtransmission/transmission.h b/libtransmission/transmission.h +index 11bb8743dd..82bf1d5e54 100644 +--- a/libtransmission/transmission.h ++++ b/libtransmission/transmission.h +@@ -601,7 +601,7 @@ void tr_sessionSetAntiBruteForceEnabled(tr_session* session, bool enabled); + /** @brief Like `tr_torrentStart()`, but resumes right away regardless of the queues. */ + void tr_torrentStartNow(tr_torrent* tor); + +-/** @brief Like tr_torrentStart(), but sets magnetStartAfterVerify to true. */ ++/** @brief DEPRECATED. Equivalent to tr_torrentStart(). Use that instead. */ + void tr_torrentStartMagnet(tr_torrent*); + + /** @brief Return the queued torrent's position in the queue it's in. [0...n) */ +diff --git a/macosx/Torrent.mm b/macosx/Torrent.mm +index bd5ad7972f..98e69882d3 100644 +--- a/macosx/Torrent.mm ++++ b/macosx/Torrent.mm +@@ -280,7 +280,7 @@ - (void)startMagnetTransferAfterMetaDownload + { + if ([self alertForRemainingDiskSpace]) + { +- tr_torrentStartMagnet(self.fHandle); ++ tr_torrentStart(self.fHandle); + [self update]; + + //capture, specifically, stop-seeding settings changing to unlimited diff --git a/transmission.spec b/transmission.spec index d233412..64c664d 100644 --- a/transmission.spec +++ b/transmission.spec @@ -2,7 +2,7 @@ Name: transmission Version: 4.0.0 -Release: 1%{?dist} +Release: 2%{?dist} Summary: A lightweight GTK+ BitTorrent client # See COPYING. This licensing situation is... special. License: MIT and GPLv2 @@ -11,6 +11,9 @@ URL: http://www.transmissionbt.com Source0: https://github.com/transmission/transmission/releases/download/%{version}/transmission-%{version}.tar.xz # https://bugzilla.redhat.com/show_bug.cgi?id=1221292 Source1: https://raw.githubusercontent.com/gnome-design-team/gnome-icons/master/apps-symbolic/Adwaita/scalable/apps/transmission-symbolic.svg +Patch0: f551b4adbff0d59557d61867d0b6518c50f5a73f.patch +Patch1: 4890.patch + BuildRequires: make BuildRequires: cmake @@ -87,7 +90,7 @@ useradd -r -g transmission -d %{_sharedstatedir}/transmission -s /sbin/nologin \ exit 0 %prep -%autosetup -p0 +%autosetup -p1 # fix icon location for Transmission Qt sed -i 's|Icon=%{name}-qt|Icon=%{name}|g' qt/%{name}-qt.desktop @@ -176,6 +179,9 @@ desktop-file-install \ %doc %{_mandir}/man1/transmission-qt.* %changelog +* Tue Feb 14 2023 Gwyn Ciesla - 4.0.0-2 +- Patches for crash. + * Wed Feb 08 2023 Gwyn Ciesla - 4.0.0-1 - 4.0.0, moved to qt6.