diff --git a/Transmission.xcodeproj/project.pbxproj b/Transmission.xcodeproj/project.pbxproj index 8129648e4..47fc51d31 100644 --- a/Transmission.xcodeproj/project.pbxproj +++ b/Transmission.xcodeproj/project.pbxproj @@ -205,6 +205,7 @@ A29DF8B90DB2544C00D04E5A /* resume.cc in Sources */ = {isa = PBXBuildFile; fileRef = A29DF8B60DB2544C00D04E5A /* resume.cc */; }; A29DF8BA0DB2544C00D04E5A /* resume.h in Headers */ = {isa = PBXBuildFile; fileRef = A29DF8B70DB2544C00D04E5A /* resume.h */; }; A29DF8BB0DB2544C00D04E5A /* torrent.h in Headers */ = {isa = PBXBuildFile; fileRef = A29DF8B80DB2544C00D04E5A /* torrent.h */; }; + 2B9BA6C508B488FE586A0AB2 /* torrents.h in Sources */ = {isa = PBXBuildFile; fileRef = 2B9BA6C508B488FE586A0AB3 /* torrents.h */; }; A29DF8BE0DB2545F00D04E5A /* verify.h in Headers */ = {isa = PBXBuildFile; fileRef = A2D22A110D65EED100007D5F /* verify.h */; }; A29E653613F1603100048D71 /* evutil_rand.c in Sources */ = {isa = PBXBuildFile; fileRef = A29E653513F1603100048D71 /* evutil_rand.c */; }; A2A1CB7A0BF29D5500AE959F /* PeerProgressIndicatorCell.mm in Sources */ = {isa = PBXBuildFile; fileRef = A2A1CB780BF29D5500AE959F /* PeerProgressIndicatorCell.mm */; }; @@ -308,6 +309,7 @@ BEFC1E2D0C07861A00B0BB3C /* upnp.cc in Sources */ = {isa = PBXBuildFile; fileRef = BEFC1DF40C07861A00B0BB3C /* upnp.cc */; }; BEFC1E2F0C07861A00B0BB3C /* session.cc in Sources */ = {isa = PBXBuildFile; fileRef = BEFC1DF60C07861A00B0BB3C /* session.cc */; }; BEFC1E320C07861A00B0BB3C /* torrent.cc in Sources */ = {isa = PBXBuildFile; fileRef = BEFC1DF90C07861A00B0BB3C /* torrent.cc */; }; + 2B9BA6C508B488FE586A0AB0 /* torrents.cc in Sources */ = {isa = PBXBuildFile; fileRef = 2B9BA6C508B488FE586A0AB1 /* torrents.cc */; }; BEFC1E350C07861A00B0BB3C /* port-forwarding.h in Headers */ = {isa = PBXBuildFile; fileRef = BEFC1DFC0C07861A00B0BB3C /* port-forwarding.h */; }; BEFC1E360C07861A00B0BB3C /* port-forwarding.cc in Sources */ = {isa = PBXBuildFile; fileRef = BEFC1DFD0C07861A00B0BB3C /* port-forwarding.cc */; }; BEFC1E3B0C07861A00B0BB3C /* platform.h in Headers */ = {isa = PBXBuildFile; fileRef = BEFC1E020C07861A00B0BB3C /* platform.h */; }; @@ -886,6 +888,7 @@ A29DF8B60DB2544C00D04E5A /* resume.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = resume.cc; sourceTree = ""; }; A29DF8B70DB2544C00D04E5A /* resume.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = resume.h; sourceTree = ""; }; A29DF8B80DB2544C00D04E5A /* torrent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = torrent.h; sourceTree = ""; }; + 2B9BA6C508B488FE586A0AB3 /* torrents.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = torrents.h; sourceTree = ""; }; A29E653513F1603100048D71 /* evutil_rand.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = evutil_rand.c; sourceTree = ""; }; A29EBE520DC01FC9006CEE80 /* web.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = web.cc; sourceTree = ""; }; A29EBE530DC01FC9006CEE80 /* web.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = web.h; sourceTree = ""; }; @@ -1032,6 +1035,7 @@ BEFC1DF50C07861A00B0BB3C /* transmission.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = transmission.h; sourceTree = ""; }; BEFC1DF60C07861A00B0BB3C /* session.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = session.cc; sourceTree = ""; }; BEFC1DF90C07861A00B0BB3C /* torrent.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = torrent.cc; sourceTree = ""; }; + 2B9BA6C508B488FE586A0AB1 /* torrents.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = torrents.cc; sourceTree = ""; }; BEFC1DFC0C07861A00B0BB3C /* port-forwarding.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "port-forwarding.h"; sourceTree = ""; }; BEFC1DFD0C07861A00B0BB3C /* port-forwarding.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = "port-forwarding.cc"; sourceTree = ""; }; BEFC1E020C07861A00B0BB3C /* platform.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = platform.h; sourceTree = ""; }; @@ -1552,6 +1556,7 @@ A29DF8B60DB2544C00D04E5A /* resume.cc */, A29DF8B70DB2544C00D04E5A /* resume.h */, A29DF8B80DB2544C00D04E5A /* torrent.h */, + 2B9BA6C508B488FE586A0AB3 /* torrents.h */, C1033E031A3279B800EF44D8 /* crypto-utils-fallback.cc */, C1033E041A3279B800EF44D8 /* crypto-utils-ccrypto.cc */, C1033E051A3279B800EF44D8 /* crypto-utils.cc */, @@ -1598,6 +1603,7 @@ A23F29A0132A447400E9A83B /* announcer-http.cc */, A2AA9BE0132CAC8D00FA131E /* announcer-udp.cc */, BEFC1DF90C07861A00B0BB3C /* torrent.cc */, + 2B9BA6C508B488FE586A0AB1 /* torrents.cc */, BEFC1DFC0C07861A00B0BB3C /* port-forwarding.h */, BEFC1DFD0C07861A00B0BB3C /* port-forwarding.cc */, A21FBBA90EDA78C300BC3C51 /* bandwidth.h */, @@ -2085,6 +2091,7 @@ C17740D6273A002C00E455D2 /* web-utils.h in Headers */, A29DF8BA0DB2544C00D04E5A /* resume.h in Headers */, A29DF8BB0DB2544C00D04E5A /* torrent.h in Headers */, + 2B9BA6C508B488FE586A0AB2 /* torrents.h in Headers */, A29DF8BE0DB2545F00D04E5A /* verify.h in Headers */, C1FEE57B1C3223CC00D62832 /* watchdir.h in Headers */, A2AAB6650DE0D08B00E04DDA /* blocklist.h in Headers */, @@ -2760,6 +2767,7 @@ ED8A16402735A8AA000D61F9 /* peer-mgr-active-requests.cc in Sources */, BEFC1E2F0C07861A00B0BB3C /* session.cc in Sources */, BEFC1E320C07861A00B0BB3C /* torrent.cc in Sources */, + 2B9BA6C508B488FE586A0AB0 /* torrents.cc in Sources */, BEFC1E360C07861A00B0BB3C /* port-forwarding.cc in Sources */, BEFC1E3C0C07861A00B0BB3C /* platform.cc in Sources */, BEFC1E460C07861A00B0BB3C /* net.cc in Sources */, diff --git a/libtransmission/CMakeLists.txt b/libtransmission/CMakeLists.txt index e6d26f5fd..452ff4e26 100644 --- a/libtransmission/CMakeLists.txt +++ b/libtransmission/CMakeLists.txt @@ -60,6 +60,7 @@ set(PROJECT_FILES torrent-magnet.cc torrent-metainfo.cc torrent.cc + torrents.cc tr-assert.cc tr-assert.mm tr-dht.cc @@ -194,6 +195,7 @@ set(${PROJECT_NAME}_PRIVATE_HEADERS torrent-magnet.h torrent-metainfo.h torrent.h + torrents.h tr-dht.h tr-lpd.h tr-udp.h diff --git a/libtransmission/announcer.cc b/libtransmission/announcer.cc index 92b4c32ce..3e4da366d 100644 --- a/libtransmission/announcer.cc +++ b/libtransmission/announcer.cc @@ -598,7 +598,7 @@ static tr_tier* getTier(tr_announcer* announcer, tr_sha1_digest_t const& info_ha return nullptr; } - auto* const tor = announcer->session->getTorrent(info_hash); + auto* const tor = announcer->session->torrents().get(info_hash); if (tor == nullptr || tor->torrent_announcer == nullptr) { return nullptr; @@ -1305,7 +1305,7 @@ static void on_scrape_done(tr_scrape_response const* response, void* vsession) for (int i = 0; i < response->row_count; ++i) { auto const& row = response->rows[i]; - auto* const tor = session->getTorrent(row.info_hash); + auto* const tor = session->torrents().get(row.info_hash); if (tor != nullptr) { @@ -1535,7 +1535,7 @@ static void scrapeAndAnnounceMore(tr_announcer* announcer) /* build a list of tiers that need to be announced */ auto announce_me = std::vector{}; auto scrape_me = std::vector{}; - for (auto* tor : announcer->session->torrents) + for (auto* const tor : announcer->session->torrents()) { for (auto& tier : tor->torrent_announcer->tiers) { diff --git a/libtransmission/handshake.cc b/libtransmission/handshake.cc index 42ffb0fec..0e12eaa61 100644 --- a/libtransmission/handshake.cc +++ b/libtransmission/handshake.cc @@ -175,7 +175,7 @@ static void setReadState(tr_handshake* handshake, handshake_state_t state) static bool buildHandshakeMessage(tr_handshake* handshake, uint8_t* buf) { auto const torrent_hash = tr_cryptoGetTorrentHash(handshake->crypto); - auto* const tor = torrent_hash ? handshake->session->getTorrent(*torrent_hash) : nullptr; + auto* const tor = torrent_hash ? handshake->session->torrents().get(*torrent_hash) : nullptr; bool const success = tor != nullptr; if (success) @@ -257,7 +257,7 @@ static handshake_parse_err_t parseHandshake(tr_handshake* handshake, struct evbu /* peer id */ dbgmsg(handshake, "peer-id is [%" TR_PRIsv "]", TR_PRIsv_ARG(peer_id)); - if (auto* const tor = handshake->session->getTorrent(hash); peer_id == tr_torrentGetPeerId(tor)) + if (auto* const tor = handshake->session->torrents().get(hash); peer_id == tr_torrentGetPeerId(tor)) { dbgmsg(handshake, "streuth! we've connected to ourselves."); return HANDSHAKE_PEER_IS_SELF; @@ -647,7 +647,7 @@ static ReadState readHandshake(tr_handshake* handshake, struct evbuffer* inbuf) if (tr_peerIoIsIncoming(handshake->io)) { - if (!handshake->session->contains(hash)) + if (!handshake->session->torrents().contains(hash)) { dbgmsg(handshake, "peer is trying to connect to us for a torrent we don't have."); return tr_handshakeDone(handshake, false); @@ -703,7 +703,7 @@ static ReadState readPeerId(tr_handshake* handshake, struct evbuffer* inbuf) // if we've somehow connected to ourselves, don't keep the connection auto const hash = tr_peerIoGetTorrentHash(handshake->io); - auto* const tor = hash ? handshake->session->getTorrent(*hash) : nullptr; + auto* const tor = hash ? handshake->session->torrents().get(*hash) : nullptr; bool const connected_to_self = peer_id == tr_torrentGetPeerId(tor); return tr_handshakeDone(handshake, !connected_to_self); @@ -1134,7 +1134,7 @@ static void gotError(tr_peerIo* io, short what, void* vhandshake) /* This peer probably doesn't speak uTP. */ auto const hash = tr_peerIoGetTorrentHash(io); - auto* const tor = hash ? handshake->session->getTorrent(*hash) : nullptr; + auto* const tor = hash ? handshake->session->torrents().get(*hash) : nullptr; /* Don't mark a peer as non-uTP unless it's really a connect failure. */ if ((errcode == ETIMEDOUT || errcode == ECONNREFUSED) && tr_isTorrent(tor)) diff --git a/libtransmission/peer-mgr.cc b/libtransmission/peer-mgr.cc index 29e7eaa15..312ce4107 100644 --- a/libtransmission/peer-mgr.cc +++ b/libtransmission/peer-mgr.cc @@ -294,7 +294,7 @@ tr_address const* tr_peerAddress(tr_peer const* peer) static tr_swarm* getExistingSwarm(tr_peerMgr* manager, tr_sha1_digest_t const& hash) { - tr_torrent* tor = manager->session->getTorrent(hash); + auto* const tor = manager->session->torrents().get(hash); return tor == nullptr ? nullptr : tor->swarm; } @@ -414,7 +414,7 @@ void tr_peerMgrOnBlocklistChanged(tr_peerMgr* mgr) { /* we cache whether or not a peer is blocklisted... since the blocklist has changed, erase that cached value */ - for (auto* tor : mgr->session->torrents) + for (auto* const tor : mgr->session->torrents()) { tr_swarm* s = tor->swarm; @@ -657,7 +657,7 @@ static void refillUpkeep(evutil_socket_t /*fd*/, short /*what*/, void* vmgr) auto* mgr = static_cast(vmgr); auto const lock = mgr->unique_lock(); - auto& torrents = mgr->session->torrents; + auto& torrents = mgr->session->torrents(); std::for_each(std::begin(torrents), std::end(torrents), [](auto* tor) { tr_swarmCancelOldRequests(tor->swarm); }); tr_timerAddMsec(mgr->refillUpkeepTimer, RefillUpkeepPeriodMsec); @@ -2224,7 +2224,7 @@ static void rechokePulse(evutil_socket_t /*fd*/, short /*what*/, void* vmgr) auto const lock = mgr->unique_lock(); uint64_t const now = tr_time_msec(); - for (auto* tor : mgr->session->torrents) + for (auto* const tor : mgr->session->torrents()) { if (tor->isRunning) { @@ -2479,9 +2479,10 @@ static void enforceTorrentPeerLimit(tr_swarm* s) static void enforceSessionPeerLimit(tr_session* session) { // do we have too many peers? + auto const& torrents = session->torrents(); size_t const n_peers = std::accumulate( - std::begin(session->torrents), - std::end(session->torrents), + std::begin(torrents), + std::end(torrents), size_t{}, [](size_t sum, tr_torrent const* tor) { return sum + tr_ptrArraySize(&tor->swarm->peers); }); size_t const max = tr_sessionGetPeerLimit(session); @@ -2493,7 +2494,7 @@ static void enforceSessionPeerLimit(tr_session* session) // make a list of all the peers auto peers = std::vector{}; peers.reserve(n_peers); - for (auto* tor : session->torrents) + for (auto* const tor : session->torrents()) { size_t const n = tr_ptrArraySize(&tor->swarm->peers); auto** base = (tr_peer**)tr_ptrArrayBase(&tor->swarm->peers); @@ -2513,7 +2514,7 @@ static void reconnectPulse(evutil_socket_t /*fd*/, short /*what*/, void* vmgr) time_t const now_sec = tr_time(); // remove crappy peers - for (auto* tor : mgr->session->torrents) + for (auto* const tor : mgr->session->torrents()) { if (!tor->swarm->isRunning) { @@ -2526,7 +2527,7 @@ static void reconnectPulse(evutil_socket_t /*fd*/, short /*what*/, void* vmgr) } // if we're over the per-torrent peer limits, cull some peers - for (auto* tor : mgr->session->torrents) + for (auto* const tor : mgr->session->torrents()) { if (tor->isRunning) { @@ -2550,7 +2551,7 @@ static void reconnectPulse(evutil_socket_t /*fd*/, short /*what*/, void* vmgr) static void pumpAllPeers(tr_peerMgr* mgr) { - for (auto* tor : mgr->session->torrents) + for (auto* const tor : mgr->session->torrents()) { tr_swarm* s = tor->swarm; @@ -2595,7 +2596,7 @@ static void bandwidthPulse(evutil_socket_t /*fd*/, short /*what*/, void* vmgr) session->bandwidth->allocate(TR_DOWN, BandwidthPeriodMsec); /* torrent upkeep */ - for (auto* tor : session->torrents) + for (auto* const tor : session->torrents()) { /* possibly stop torrents that have seeded enough */ tr_torrentCheckSeedLimit(tor); @@ -2693,7 +2694,7 @@ static void atomPulse(evutil_socket_t /*fd*/, short /*what*/, void* vmgr) auto* mgr = static_cast(vmgr); auto const lock = mgr->unique_lock(); - for (auto* tor : mgr->session->torrents) + for (auto* const tor : mgr->session->torrents()) { tr_swarm* s = tor->swarm; int const maxAtomCount = getMaxAtomCount(tor); @@ -2920,7 +2921,7 @@ static std::vector getPeerCandidates(tr_session* session, size_t /* count how many peers and atoms we've got */ int atomCount = 0; int peerCount = 0; - for (auto const* tor : session->torrents) + for (auto const* tor : session->torrents()) { atomCount += tr_ptrArraySize(&tor->swarm->pool); peerCount += tr_ptrArraySize(&tor->swarm->peers); @@ -2936,7 +2937,7 @@ static std::vector getPeerCandidates(tr_session* session, size_t candidates.reserve(atomCount); /* populate the candidate array */ - for (auto* tor : session->torrents) + for (auto* tor : session->torrents()) { if (!tor->swarm->isRunning) { diff --git a/libtransmission/rpcimpl.cc b/libtransmission/rpcimpl.cc index 3c681938c..e1ece7a82 100644 --- a/libtransmission/rpcimpl.cc +++ b/libtransmission/rpcimpl.cc @@ -133,7 +133,7 @@ static auto getTorrents(tr_session* session, tr_variant* args) } else if (tr_variantGetStrView(node, &sv)) { - tor = session->getTorrent(sv); + tor = session->torrents().get(sv); } if (tor != nullptr) @@ -155,16 +155,16 @@ static auto getTorrents(tr_session* session, tr_variant* args) { time_t const cutoff = tr_time() - RecentlyActiveSeconds; - torrents.reserve(std::size(session->torrents)); + torrents.reserve(std::size(session->torrents())); std::copy_if( - std::begin(session->torrents), - std::end(session->torrents), + std::begin(session->torrents()), + std::end(session->torrents()), std::back_inserter(torrents), [&cutoff](auto const* tor) { return tor->anyDate >= cutoff; }); } else { - auto* const tor = session->getTorrent(sv); + auto* const tor = session->torrents().get(sv); if (tor != nullptr) { torrents.push_back(tor); @@ -173,8 +173,8 @@ static auto getTorrents(tr_session* session, tr_variant* args) } else // all of them { - torrents.reserve(std::size(session->torrents)); - std::copy(std::begin(session->torrents), std::end(session->torrents), std::back_inserter(torrents)); + torrents.reserve(std::size(session->torrents())); + std::copy(std::begin(session->torrents()), std::end(session->torrents()), std::back_inserter(torrents)); } return torrents; @@ -1977,11 +1977,9 @@ static char const* sessionStats( auto currentStats = tr_session_stats{}; auto cumulativeStats = tr_session_stats{}; - int const total = std::size(session->torrents); - int const running = std::count_if( - std::begin(session->torrents), - std::end(session->torrents), - [](auto const* tor) { return tor->isRunning; }); + auto const& torrents = session->torrents(); + int const total = std::size(torrents); + int const running = std::count_if(std::begin(torrents), std::end(torrents), [](auto const* tor) { return tor->isRunning; }); tr_sessionGetStats(session, ¤tStats); tr_sessionGetCumulativeStats(session, &cumulativeStats); diff --git a/libtransmission/session.cc b/libtransmission/session.cc index 59bcafbea..70b64e0d1 100644 --- a/libtransmission/session.cc +++ b/libtransmission/session.cc @@ -144,17 +144,17 @@ std::optional tr_session::WebMediator::publicAddress() const unsigned int tr_session::WebMediator::clamp(int torrent_id, unsigned int byte_count) const { auto const lock = session_->unique_lock(); - auto const it = session_->torrentsById.find(torrent_id); - return it == std::end(session_->torrentsById) ? 0U : it->second->bandwidth->clamp(TR_DOWN, byte_count); + auto const* const tor = session_->torrents().get(torrent_id); + return tor == nullptr ? 0U : tor->bandwidth->clamp(TR_DOWN, byte_count); } void tr_session::WebMediator::notifyBandwidthConsumed(int torrent_id, size_t byte_count) { auto const lock = session_->unique_lock(); - auto const it = session_->torrentsById.find(torrent_id); - if (it != std::end(session_->torrentsById)) + auto const* const tor = session_->torrents().get(torrent_id); + if (tor != nullptr) { - it->second->bandwidth->notifyBandwidthConsumed(TR_DOWN, byte_count, true, tr_time_msec()); + tor->bandwidth->notifyBandwidthConsumed(TR_DOWN, byte_count, true, tr_time_msec()); } } @@ -574,7 +574,7 @@ static void onSaveTimer(evutil_socket_t /*fd*/, short /*what*/, void* vsession) tr_logAddError("Error while flushing completed pieces from cache"); } - for (auto* tor : session->torrents) + for (auto* const tor : session->torrents()) { tr_torrentSave(tor); } @@ -671,7 +671,7 @@ static void onNowTimer(evutil_socket_t /*fd*/, short /*what*/, void* vsession) // TODO: this seems a little silly. Why do we increment this // every second instead of computing the value as needed by // subtracting the current time from a start time? - for (auto* tor : session->torrents) + for (auto* const tor : session->torrents()) { if (tor->isRunning) { @@ -1233,7 +1233,7 @@ static void peerPortChanged(void* vsession) open_incoming_peer_port(session); tr_sharedPortChanged(session); - for (auto* tor : session->torrents) + for (auto* const tor : session->torrents()) { tr_torrentChangeMyPort(tor); } @@ -1805,17 +1805,16 @@ double tr_sessionGetRawSpeed_KBps(tr_session const* session, tr_direction dir) int tr_sessionCountTorrents(tr_session const* session) { - return tr_isSession(session) ? std::size(session->torrents) : 0; + return tr_isSession(session) ? std::size(session->torrents()) : 0; } std::vector tr_sessionGetTorrents(tr_session* session) { TR_ASSERT(tr_isSession(session)); - auto const& src = session->torrents; - auto const n = std::size(src); + auto const n = std::size(session->torrents()); auto torrents = std::vector{ n }; - std::copy(std::begin(src), std::end(src), std::begin(torrents)); + std::copy(std::begin(session->torrents()), std::end(session->torrents()), std::begin(torrents)); return torrents; } @@ -2275,7 +2274,7 @@ void tr_session::setDefaultTrackers(std::string_view trackers) // if the list changed, update all the public torrents if (default_trackers_ != oldval) { - for (auto* tor : torrents) + for (auto* const tor : torrents()) { if (tor->isPublic()) { @@ -2808,7 +2807,7 @@ std::vector tr_sessionGetNextQueuedTorrents(tr_session* session, tr // build an array of the candidates auto candidates = std::vector{}; candidates.reserve(tr_sessionCountTorrents(session)); - for (auto* tor : session->torrents) + for (auto* const tor : session->torrents()) { if (tor->isQueued() && (direction == tor->queueDirection())) { @@ -2846,7 +2845,7 @@ int tr_sessionCountQueueFreeSlots(tr_session* session, tr_direction dir) bool const stalled_enabled = tr_sessionGetQueueStalledEnabled(session); int const stalled_if_idle_for_n_seconds = tr_sessionGetQueueStalledMinutes(session) * 60; time_t const now = tr_time(); - for (auto const* tor : session->torrents) + for (auto const* const tor : session->torrents()) { /* is it the right activity? */ if (activity != tr_torrentGetActivity(tor)) @@ -2875,23 +2874,3 @@ int tr_sessionCountQueueFreeSlots(tr_session* session, tr_direction dir) return max - active_count; } - -void tr_sessionAddTorrent(tr_session* session, tr_torrent* tor) -{ - session->torrents.insert(tor); - session->torrentsById.insert_or_assign(tor->uniqueId, tor); - session->torrentsByHash.insert_or_assign(tor->infoHash(), tor); -} - -void tr_sessionRemoveTorrent(tr_session* session, tr_torrent* tor) -{ - session->torrents.erase(tor); - session->torrentsById.erase(tor->uniqueId); - session->torrentsByHash.erase(tor->infoHash()); -} - -tr_torrent* tr_session::getTorrent(std::string_view info_dict_hash_string) -{ - auto const info_hash = tr_sha1_from_string(info_dict_hash_string); - return info_hash ? this->getTorrent(*info_hash) : nullptr; -} diff --git a/libtransmission/session.h b/libtransmission/session.h index db3919360..26a01185c 100644 --- a/libtransmission/session.h +++ b/libtransmission/session.h @@ -30,6 +30,7 @@ #include "announce-list.h" #include "net.h" // tr_socket_t #include "quark.h" +#include "torrents.h" #include "web.h" enum tr_auto_switch_state_t @@ -108,6 +109,16 @@ struct tr_turtle_info struct tr_session { public: + [[nodiscard]] auto const& torrents() const + { + return torrents_; + } + + [[nodiscard]] auto& torrents() + { + return torrents_; + } + [[nodiscard]] auto unique_lock() const { return std::unique_lock(session_mutex_); @@ -118,27 +129,6 @@ public: return is_closing_; } - [[nodiscard]] auto const* getTorrent(tr_sha1_digest_t const& info_dict_hash) const - { - auto& src = this->torrentsByHash; - auto it = src.find(info_dict_hash); - return it == std::end(src) ? nullptr : it->second; - } - - [[nodiscard]] auto* getTorrent(tr_sha1_digest_t const& info_dict_hash) - { - auto& src = this->torrentsByHash; - auto it = src.find(info_dict_hash); - return it == std::end(src) ? nullptr : it->second; - } - - [[nodiscard]] tr_torrent* getTorrent(std::string_view info_dict_hash_string); - - [[nodiscard]] auto contains(tr_sha1_digest_t const& info_dict_hash) const - { - return getTorrent(info_dict_hash) != nullptr; - } - // download dir std::string const& downloadDir() const @@ -348,10 +338,6 @@ public: tr_port randomPortLow; tr_port randomPortHigh; - std::unordered_set torrents; - std::map torrentsById; - std::map torrentsByHash; - std::string config_dir; std::string resume_dir; std::string torrent_dir; @@ -423,6 +409,8 @@ public: private: static std::recursive_mutex session_mutex_; + tr_torrents torrents_; + std::array scripts_; std::string blocklist_url_; std::string download_dir_; @@ -492,6 +480,3 @@ bool tr_sessionGetActiveSpeedLimit_Bps(tr_session const* session, tr_direction d std::vector tr_sessionGetNextQueuedTorrents(tr_session* session, tr_direction dir, size_t numwanted); int tr_sessionCountQueueFreeSlots(tr_session* session, tr_direction); - -void tr_sessionAddTorrent(tr_session* session, tr_torrent* tor); -void tr_sessionRemoveTorrent(tr_session* session, tr_torrent* tor); diff --git a/libtransmission/torrent.cc b/libtransmission/torrent.cc index ec8cbebac..8dda9168a 100644 --- a/libtransmission/torrent.cc +++ b/libtransmission/torrent.cc @@ -88,14 +88,12 @@ int tr_torrentId(tr_torrent const* tor) tr_torrent* tr_torrentFindFromId(tr_session* session, int id) { - auto& src = session->torrentsById; - auto it = src.find(id); - return it == std::end(src) ? nullptr : it->second; + return session->torrents().get(id); } tr_torrent* tr_torrentFindFromHash(tr_session* session, tr_sha1_digest_t const* hash) { - return hash == nullptr ? nullptr : session->getTorrent(*hash); + return hash == nullptr ? nullptr : session->torrents().get(*hash); } tr_torrent* tr_torrentFindFromMetainfo(tr_session* session, tr_torrent_metainfo const* metainfo) @@ -105,18 +103,17 @@ tr_torrent* tr_torrentFindFromMetainfo(tr_session* session, tr_torrent_metainfo return nullptr; } - return tr_torrentFindFromHash(session, &metainfo->infoHash()); + return session->torrents().get(metainfo->infoHash()); } tr_torrent* tr_torrentFindFromMagnetLink(tr_session* session, char const* magnet_link) { - auto mm = tr_magnet_metainfo{}; - return mm.parseMagnet(magnet_link != nullptr ? magnet_link : "") ? session->getTorrent(mm.infoHash()) : nullptr; + return magnet_link == nullptr ? nullptr : session->torrents().get(magnet_link); } tr_torrent* tr_torrentFindFromObfuscatedHash(tr_session* session, tr_sha1_digest_t const& obfuscated_hash) { - for (auto* tor : session->torrents) + for (auto* const tor : session->torrents()) { if (tor->obfuscated_hash == obfuscated_hash) { @@ -675,14 +672,12 @@ static void refreshCurrentDir(tr_torrent* tor); static void torrentInit(tr_torrent* tor, tr_ctor const* ctor) { - static auto next_unique_id = int{ 1 }; auto const lock = tor->unique_lock(); tr_session* session = tr_ctorGetSession(ctor); TR_ASSERT(session != nullptr); tor->session = session; - tor->uniqueId = next_unique_id++; tor->queuePosition = tr_sessionCountTorrents(session); torrentInitFromInfoDict(tor); @@ -765,7 +760,7 @@ static void torrentInit(tr_torrent* tor, tr_ctor const* ctor) tr_torrentSetIdleLimit(tor, tr_sessionGetIdleLimit(tor->session)); } - tr_sessionAddTorrent(session, tor); + tor->uniqueId = session->torrents().add(tor); // if we don't have a local .torrent or .magnet file already, assume the torrent is new auto const filename = tor->hasMetadata() ? tor->torrentFile() : tor->magnetFile(); @@ -845,7 +840,7 @@ tr_torrent* tr_torrentNew(tr_ctor* ctor, tr_torrent** setme_duplicate_of) } // is it a duplicate? - if (auto* const duplicate_of = session->getTorrent(metainfo.infoHash()); duplicate_of != nullptr) + if (auto* const duplicate_of = session->torrents().get(metainfo.infoHash()); duplicate_of != nullptr) { if (setme_duplicate_of != nullptr) { @@ -1298,13 +1293,13 @@ static void freeTorrent(tr_torrent* tor) tr_announcerRemoveTorrent(session->announcer, tor); - tr_sessionRemoveTorrent(session, tor); + session->torrents().remove(tor, tr_time()); if (!session->isClosing()) { // "so you die, captain, and we all move up in rank." // resequence the queue positions - for (auto* t : session->torrents) + for (auto* t : session->torrents()) { if (t->queuePosition > tor->queuePosition) { @@ -2768,7 +2763,7 @@ void tr_torrentSetQueuePosition(tr_torrent* tor, int pos) tor->queuePosition = -1; - for (auto* walk : tor->session->torrents) + for (auto* const walk : tor->session->torrents()) { if ((old_pos < pos) && (old_pos <= walk->queuePosition) && (walk->queuePosition <= pos)) { diff --git a/libtransmission/torrents.cc b/libtransmission/torrents.cc new file mode 100644 index 000000000..66cc6eec4 --- /dev/null +++ b/libtransmission/torrents.cc @@ -0,0 +1,141 @@ +// This file Copyright © 2022 Mnemosyne LLC. +// It may be used under GPLv2 (SPDX: GPL-2.0), GPLv3 (SPDX: GPL-3.0), +// or any future license endorsed by Mnemosyne LLC. +// License text can be found in the licenses/ folder. + +#include +#include +#include + +#include "transmission.h" + +#include "magnet-metainfo.h" +#include "torrent.h" +#include "torrents.h" +#include "tr-assert.h" + +namespace +{ + +struct CompareTorrentByHash +{ + bool operator()(tr_sha1_digest_t const& a, tr_sha1_digest_t const& b) const + { + return a < b; + } + + bool operator()(tr_torrent const* a, tr_torrent const* b) const + { + return (*this)(a->infoHash(), b->infoHash()); + } + + bool operator()(tr_torrent const* a, tr_sha1_digest_t const& b) const + { + return (*this)(a->infoHash(), b); + } + + bool operator()(tr_sha1_digest_t const& a, tr_torrent const* b) const + { + return (*this)(a, b->infoHash()); + } +}; + +} // namespace + +tr_torrents::tr_torrents() + // Insert an empty pointer at by_id_[0] to ensure that the first added + // torrent doesn't get an ID of 0; ie, that every torrent has a positive + // ID number. This constraint isn't needed by libtransmission code but + // the ID is exported in the RPC API to 3rd party clients that may be + // testing for >0 as a validity check. + : by_id_{ nullptr } +{ +} + +tr_torrent const* tr_torrents::get(int id) const +{ + TR_ASSERT(0 <= id); + TR_ASSERT(static_cast(id) < std::size(by_id_)); + if (static_cast(id) >= std::size(by_id_)) + { + return nullptr; + } + + auto const* tor = by_id_.at(id); + TR_ASSERT(tor == nullptr || tor->uniqueId == id); + TR_ASSERT(removed_.count(id) == (tor == nullptr ? 1 : 0)); + return tor; +} + +tr_torrent* tr_torrents::get(int id) +{ + TR_ASSERT(0 <= id); + TR_ASSERT(static_cast(id) < std::size(by_id_)); + if (static_cast(id) >= std::size(by_id_)) + { + return nullptr; + } + + auto* tor = by_id_.at(id); + TR_ASSERT(tor == nullptr || tor->uniqueId == id); + TR_ASSERT(removed_.count(id) == (tor == nullptr ? 1 : 0)); + return tor; +} + +tr_torrent const* tr_torrents::get(std::string_view magnet_link) const +{ + auto magnet = tr_magnet_metainfo{}; + return magnet.parseMagnet(magnet_link) ? get(magnet.infoHash()) : nullptr; +} + +tr_torrent* tr_torrents::get(std::string_view magnet_link) +{ + auto magnet = tr_magnet_metainfo{}; + return magnet.parseMagnet(magnet_link) ? get(magnet.infoHash()) : nullptr; +} + +tr_torrent* tr_torrents::get(tr_sha1_digest_t const& hash) +{ + auto [begin, end] = std::equal_range(std::begin(by_hash_), std::end(by_hash_), hash, CompareTorrentByHash{}); + return begin == end ? nullptr : *begin; +} + +tr_torrent const* tr_torrents::get(tr_sha1_digest_t const& hash) const +{ + auto [begin, end] = std::equal_range(std::cbegin(by_hash_), std::cend(by_hash_), hash, CompareTorrentByHash{}); + return begin == end ? nullptr : *begin; +} + +int tr_torrents::add(tr_torrent* tor) +{ + int const id = static_cast(std::size(by_id_)); + by_id_.push_back(tor); + by_hash_.insert(std::lower_bound(std::begin(by_hash_), std::end(by_hash_), tor, CompareTorrentByHash{}), tor); + return id; +} + +void tr_torrents::remove(tr_torrent const* tor, time_t timestamp) +{ + TR_ASSERT(tor != nullptr); + TR_ASSERT(get(tor->uniqueId) == tor); + + by_id_[tor->uniqueId] = nullptr; + auto const [begin, end] = std::equal_range(std::begin(by_hash_), std::end(by_hash_), tor, CompareTorrentByHash{}); + by_hash_.erase(begin, end); + removed_.insert_or_assign(tor->uniqueId, timestamp); +} + +std::set tr_torrents::removedSince(time_t timestamp) const +{ + auto ret = std::set{}; + + for (auto const& [id, removed_at] : removed_) + { + if (removed_at >= timestamp) + { + ret.insert(id); + } + } + + return ret; +} diff --git a/libtransmission/torrents.h b/libtransmission/torrents.h new file mode 100644 index 000000000..75077569b --- /dev/null +++ b/libtransmission/torrents.h @@ -0,0 +1,118 @@ +// This file Copyright © 2022 Mnemosyne LLC. +// It may be used under GPLv2 (SPDX: GPL-2.0), GPLv3 (SPDX: GPL-3.0), +// or any future license endorsed by Mnemosyne LLC. +// License text can be found in the licenses/ folder. + +#pragma once + +#ifndef __TRANSMISSION__ +#error only libtransmission should #include this header. +#endif + +#include +#include +#include +#include +#include + +#include "transmission.h" + +#include "torrent-metainfo.h" + +struct tr_torrent; +struct tr_torrent_metainfo; + +// A helper class to manage tracking sets of tr_torrent objects. +class tr_torrents +{ +public: + tr_torrents(); + + // returns a fast lookup id for `tor` + [[nodiscard]] int add(tr_torrent* tor); + + void remove(tr_torrent const* tor, time_t current_time); + + // O(1) + [[nodiscard]] tr_torrent* get(int id); + [[nodiscard]] tr_torrent const* get(int id) const; + + // O(log n) + [[nodiscard]] tr_torrent const* get(tr_sha1_digest_t const& hash) const; + [[nodiscard]] tr_torrent* get(tr_sha1_digest_t const& hash); + + [[nodiscard]] tr_torrent const* get(tr_torrent_metainfo const& metainfo) const + { + return get(metainfo.infoHash()); + } + + [[nodiscard]] tr_torrent* get(tr_torrent_metainfo const& metainfo) + { + return get(metainfo.infoHash()); + } + + // These convenience functions use get(tr_sha1_digest_t const&) + // after parsing the magnet link to get the info hash. If you have + // the info hash already, use get() instead to avoid excess parsing. + [[nodiscard]] tr_torrent const* get(std::string_view magnet_link) const; + [[nodiscard]] tr_torrent* get(std::string_view magnet_link); + + template + [[nodiscard]] bool contains(T const& key) const + { + return get(key) != nullptr; + } + + [[nodiscard]] std::set removedSince(time_t) const; + + [[nodiscard]] auto cbegin() const + { + return std::cbegin(by_hash_); + } + [[nodiscard]] auto begin() const + { + return cbegin(); + } + [[nodiscard]] auto begin() + { + return std::begin(by_hash_); + } + + [[nodiscard]] auto cend() const + { + return std::cend(by_hash_); + } + + [[nodiscard]] auto end() const + { + return cend(); + } + + [[nodiscard]] auto end() + { + return std::end(by_hash_); + } + + [[nodiscard]] auto size() const + { + return std::size(by_hash_); + } + + [[nodiscard]] auto empty() const + { + return std::empty(by_hash_); + } + +private: + std::vector by_hash_; + + // This is a lookup table where by_id_[id]->uniqueId == id. + // There is a small tradeoff here -- lookup is O(1) at the cost + // of a wasted slot in the lookup table whenever a torrent is + // removed. This improves speed for all use cases at the cost of + // wasting a small amount of memory in uncommon use cases, e.g. a + // long-lived session where thousands of torrents are removed + std::vector by_id_; + + std::map removed_; +}; diff --git a/libtransmission/tr-dht.cc b/libtransmission/tr-dht.cc index fe39d4c3c..20f28cf18 100644 --- a/libtransmission/tr-dht.cc +++ b/libtransmission/tr-dht.cc @@ -600,7 +600,7 @@ static void callback(void* /*ignore*/, int event, unsigned char const* info_hash auto hash = tr_sha1_digest_t{}; std::copy_n(reinterpret_cast(info_hash), std::size(hash), std::data(hash)); auto const lock = session_->unique_lock(); - tr_torrent* const tor = session_->getTorrent(hash); + auto* const tor = session_->torrents().get(hash); if (event == DHT_EVENT_VALUES || event == DHT_EVENT_VALUES6) { @@ -700,7 +700,7 @@ void tr_dhtUpkeep(tr_session* session) { time_t const now = tr_time(); - for (auto* tor : session->torrents) + for (auto* const tor : session->torrents()) { if (!tor->isRunning || !tor->allowsDht()) { diff --git a/libtransmission/tr-lpd.cc b/libtransmission/tr-lpd.cc index cfaac13a0..f16d39b4a 100644 --- a/libtransmission/tr-lpd.cc +++ b/libtransmission/tr-lpd.cc @@ -533,7 +533,7 @@ static int tr_lpdConsiderAnnounce(tr_pex* peer, char const* const msg) return res; } - tor = session->getTorrent(hashString); + tor = session->torrents().get(hashString); if (tr_isTorrent(tor) && tor->allowsLpd()) { @@ -576,7 +576,7 @@ static int tr_lpdAnnounceMore(time_t const now, int const interval) if (tr_sessionAllowsLPD(session)) { - for (auto* tor : session->torrents) + for (auto* const tor : session->torrents()) { int announcePrio = 0; diff --git a/tests/libtransmission/CMakeLists.txt b/tests/libtransmission/CMakeLists.txt index ef02d3a28..b7481760e 100644 --- a/tests/libtransmission/CMakeLists.txt +++ b/tests/libtransmission/CMakeLists.txt @@ -30,6 +30,7 @@ add_executable(libtransmission-test subprocess-test.cc test-fixtures.h torrent-metainfo-test.cc + torrents-test.cc utils-test.cc variant-test.cc watchdir-test.cc diff --git a/tests/libtransmission/assets/debian-11.2.0-amd64-DVD-1.iso.torrent b/tests/libtransmission/assets/debian-11.2.0-amd64-DVD-1.iso.torrent new file mode 100644 index 000000000..d06b0045c Binary files /dev/null and b/tests/libtransmission/assets/debian-11.2.0-amd64-DVD-1.iso.torrent differ diff --git a/tests/libtransmission/assets/ubuntu-18.04.6-desktop-amd64.iso.torrent b/tests/libtransmission/assets/ubuntu-18.04.6-desktop-amd64.iso.torrent new file mode 100644 index 000000000..8439cee1c Binary files /dev/null and b/tests/libtransmission/assets/ubuntu-18.04.6-desktop-amd64.iso.torrent differ diff --git a/tests/libtransmission/assets/ubuntu-20.04.4-desktop-amd64.iso.torrent b/tests/libtransmission/assets/ubuntu-20.04.4-desktop-amd64.iso.torrent new file mode 100644 index 000000000..fb5793417 Binary files /dev/null and b/tests/libtransmission/assets/ubuntu-20.04.4-desktop-amd64.iso.torrent differ diff --git a/tests/libtransmission/torrents-test.cc b/tests/libtransmission/torrents-test.cc new file mode 100644 index 000000000..d00baed5a --- /dev/null +++ b/tests/libtransmission/torrents-test.cc @@ -0,0 +1,122 @@ +// This file Copyright (C) 2022 Mnemosyne LLC. +// It may be used under GPLv2 (SPDX: GPL-2.0), GPLv3 (SPDX: GPL-3.0), +// or any future license endorsed by Mnemosyne LLC. +// License text can be found in the licenses/ folder. + +#include "transmission.h" + +#include "torrent.h" +#include "torrents.h" +#include "utils.h" + +#include "gtest/gtest.h" + +#include +#include + +using namespace std::literals; + +using TorrentsTest = ::testing::Test; + +TEST_F(TorrentsTest, simpleTests) +{ + auto constexpr* const TorrentFile = LIBTRANSMISSION_TEST_ASSETS_DIR "/Android-x86 8.1 r6 iso.torrent"; + auto tm = tr_torrent_metainfo{}; + EXPECT_TRUE(tm.parseTorrentFile(TorrentFile)); + auto* tor = new tr_torrent(std::move(tm)); + EXPECT_NE(nullptr, tor); + + auto torrents = tr_torrents{}; + EXPECT_TRUE(std::empty(torrents)); + EXPECT_EQ(0U, std::size(torrents)); + + auto const id = torrents.add(tor); + EXPECT_GT(id, 0); + tor->uniqueId = id; + + EXPECT_TRUE(std::empty(torrents.removedSince(0))); + EXPECT_FALSE(std::empty(torrents)); + EXPECT_EQ(1U, std::size(torrents)); + + EXPECT_EQ(tor, torrents.get(id)); + EXPECT_EQ(tor, torrents.get(tor->infoHash())); + EXPECT_EQ(tor, torrents.get(tor->magnet())); + + tm = tr_torrent_metainfo{}; + EXPECT_TRUE(tm.parseTorrentFile(TorrentFile)); + EXPECT_EQ(tor, torrents.get(tm)); + + // cleanup + torrents.remove(tor, time(nullptr)); + delete tor; +} + +TEST_F(TorrentsTest, rangedLoop) +{ + auto constexpr Filenames = std::array{ "Android-x86 8.1 r6 iso.torrent"sv, + "debian-11.2.0-amd64-DVD-1.iso.torrent"sv, + "ubuntu-18.04.6-desktop-amd64.iso.torrent"sv, + "ubuntu-20.04.4-desktop-amd64.iso.torrent"sv }; + + auto torrents = tr_torrents{}; + auto torrents_set = std::set{}; + + for (auto const& name : Filenames) + { + auto const path = tr_strvJoin(LIBTRANSMISSION_TEST_ASSETS_DIR, "/"sv, name); + auto tm = tr_torrent_metainfo{}; + EXPECT_TRUE(tm.parseTorrentFile(path)); + auto* const tor = new tr_torrent(std::move(tm)); + tor->uniqueId = torrents.add(tor); + EXPECT_EQ(tor, torrents.get(tor->uniqueId)); + torrents_set.insert(tor); + } + + for (auto const* tor : torrents) + { + EXPECT_EQ(1U, torrents_set.erase(tor)); + } + EXPECT_EQ(0U, std::size(torrents_set)); + EXPECT_EQ(0U, std::size(torrents_set)); +} + +TEST_F(TorrentsTest, removedSince) +{ + auto constexpr Filenames = std::array{ "Android-x86 8.1 r6 iso.torrent"sv, + "debian-11.2.0-amd64-DVD-1.iso.torrent"sv, + "ubuntu-18.04.6-desktop-amd64.iso.torrent"sv, + "ubuntu-20.04.4-desktop-amd64.iso.torrent"sv }; + + auto torrents = tr_torrents{}; + auto torrents_v = std::vector{}; + torrents_v.reserve(std::size(Filenames)); + + // setup: add the torrents + for (auto const& name : Filenames) + { + auto const path = tr_strvJoin(LIBTRANSMISSION_TEST_ASSETS_DIR, "/"sv, name); + auto tm = tr_torrent_metainfo{}; + auto* const tor = new tr_torrent(std::move(tm)); + tor->uniqueId = torrents.add(tor); + torrents_v.push_back(tor); + } + + // setup: remove them at the given timestamp + auto constexpr TimeRemoved = std::array{ 100, 200, 200, 300 }; + for (size_t i = 0; i < 4; ++i) + { + auto* const tor = torrents_v[i]; + EXPECT_EQ(tor, torrents.get(tor->uniqueId)); + torrents.remove(torrents_v[i], TimeRemoved[i]); + EXPECT_EQ(nullptr, torrents.get(tor->uniqueId)); + } + + auto remove = std::set{}; + remove = { torrents_v[3]->uniqueId }; + EXPECT_EQ(remove, torrents.removedSince(300)); + EXPECT_EQ(remove, torrents.removedSince(201)); + remove = { torrents_v[1]->uniqueId, torrents_v[2]->uniqueId, torrents_v[3]->uniqueId }; + EXPECT_EQ(remove, torrents.removedSince(200)); + remove = { torrents_v[0]->uniqueId, torrents_v[1]->uniqueId, torrents_v[2]->uniqueId, torrents_v[3]->uniqueId }; + EXPECT_EQ(remove, torrents.removedSince(50)); +}