diff --git a/gtk/DetailsDialog.cc b/gtk/DetailsDialog.cc index a1f384b44..779cdeb4b 100644 --- a/gtk/DetailsDialog.cc +++ b/gtk/DetailsDialog.cc @@ -213,18 +213,7 @@ guint DetailsDialog::Impl::last_page_ = 0; std::vector DetailsDialog::Impl::getTorrents() const { - std::vector torrents; - torrents.reserve(ids_.size()); - - for (auto const id : ids_) - { - if (auto* torrent = core_->find_torrent(id); torrent != nullptr) - { - torrents.push_back(torrent); - } - } - - return torrents; + return core_->find_torrents(ids_); } /**** @@ -581,14 +570,13 @@ void DetailsDialog::Impl::refreshInfo(std::vector const& torrents) Glib::ustring const no_torrent = _("No Torrents Selected"); Glib::ustring stateString; uint64_t sizeWhenDone = 0; - std::vector stats; - std::vector infos; - stats.reserve(torrents.size()); + auto const stats = tr_torrentStat(std::data(torrents), std::size(torrents)); + + std::vector infos; infos.reserve(torrents.size()); for (auto* const torrent : torrents) { - stats.push_back(tr_torrentStat(torrent)); infos.push_back(tr_torrentView(torrent)); } diff --git a/gtk/Dialogs.cc b/gtk/Dialogs.cc index f92ab87b3..c61a2ae58 100644 --- a/gtk/Dialogs.cc +++ b/gtk/Dialogs.cc @@ -27,20 +27,18 @@ void gtr_confirm_remove( std::vector const& torrent_ids, bool delete_files) { - int connected = 0; - int incomplete = 0; int const count = torrent_ids.size(); - if (count == 0) { return; } - for (auto const id : torrent_ids) + int connected = 0; + int incomplete = 0; + // TODO(c++20) remove `torrents` local when tr_torrentStat() takes a span + auto const torrents = core->find_torrents(torrent_ids); + for (auto const* stat : tr_torrentStat(std::data(torrents), std::size(torrents))) { - tr_torrent* tor = core->find_torrent(id); - tr_stat const* stat = tr_torrentStat(tor); - if (stat->leftUntilDone != 0) { ++incomplete; diff --git a/gtk/Session.cc b/gtk/Session.cc index afa29a10e..2328c76ba 100644 --- a/gtk/Session.cc +++ b/gtk/Session.cc @@ -1416,6 +1416,26 @@ size_t Session::Impl::get_active_torrent_count() const return activeCount; } +std::vector Session::find_torrents(std::vector const& ids) const +{ + auto ret = std::vector{}; + + if (auto* const session = impl_->get_session()) + { + ret.reserve(std::size(ids)); + + for (auto const& id : ids) + { + if (auto* const tor = tr_torrentFindFromId(session, id)) + { + ret.emplace_back(tor); + } + } + } + + return ret; +} + tr_torrent* Session::find_torrent(tr_torrent_id_t id) const { tr_torrent* tor = nullptr; diff --git a/gtk/Session.h b/gtk/Session.h index df1b11d20..dc3a69b55 100644 --- a/gtk/Session.h +++ b/gtk/Session.h @@ -72,6 +72,9 @@ public: tr_torrent* find_torrent(tr_torrent_id_t id) const; + // TODO(c++20) std::span + [[nodiscard]] std::vector find_torrents(std::vector const& ids) const; + transmission::app::FaviconCache>& favicon_cache() const; /****** diff --git a/libtransmission/session.cc b/libtransmission/session.cc index 430dffd16..b0a332606 100644 --- a/libtransmission/session.cc +++ b/libtransmission/session.cc @@ -461,13 +461,6 @@ tr_address tr_session::bind_address(tr_address_type type) const noexcept // --- -std::unique_lock tr_sessionLock(tr_session const* const session) -{ - return session->unique_lock(); -} - -// --- - tr_variant tr_sessionGetDefaultSettings() { auto ret = tr_variant::make_map(); diff --git a/libtransmission/torrent.cc b/libtransmission/torrent.cc index 90c8264a3..525df31be 100644 --- a/libtransmission/torrent.cc +++ b/libtransmission/torrent.cc @@ -1399,6 +1399,27 @@ tr_stat const* tr_torrentStat(tr_torrent* const tor) return &tor->stats_; } +std::vector tr_torrentStat(tr_torrent* const* torrents, size_t n_torrents) +{ + auto ret = std::vector{}; + + if (n_torrents != 0U) + { + ret.reserve(n_torrents); + + auto const lock = torrents[0]->unique_lock(); + + for (size_t idx = 0U; idx != n_torrents; ++idx) + { + tr_torrent* const tor = torrents[idx]; + tor->stats_ = tor->stats(); + ret.emplace_back(&tor->stats_); + } + } + + return ret; +} + // --- tr_file_view tr_torrentFile(tr_torrent const* tor, tr_file_index_t file) diff --git a/libtransmission/torrent.h b/libtransmission/torrent.h index 100c69564..2cef6059d 100644 --- a/libtransmission/torrent.h +++ b/libtransmission/torrent.h @@ -1035,6 +1035,7 @@ private: friend bool tr_torrentSetMetainfoFromFile(tr_torrent* tor, tr_torrent_metainfo const* metainfo, char const* filename); friend tr_file_view tr_torrentFile(tr_torrent const* tor, tr_file_index_t file); friend tr_stat const* tr_torrentStat(tr_torrent* tor); + friend std::vector tr_torrentStat(tr_torrent* const* torrents, size_t n_torrents); friend tr_torrent* tr_torrentNew(tr_ctor* ctor, tr_torrent** setme_duplicate_of); friend uint64_t tr_torrentGetBytesLeftToAllocate(tr_torrent const* tor); friend void tr_torrentFreeInSessionThread(tr_torrent* tor); diff --git a/libtransmission/transmission.h b/libtransmission/transmission.h index 88fd82c12..bf1df3109 100644 --- a/libtransmission/transmission.h +++ b/libtransmission/transmission.h @@ -14,9 +14,9 @@ #include #include #include -#include #include #include +#include #include "libtransmission/tr-macros.h" @@ -139,8 +139,6 @@ inline auto constexpr TrHttpServerDefaultBasePath = std::string_view{ "/transmis inline auto constexpr TrHttpServerRpcRelativePath = std::string_view{ "rpc" }; inline auto constexpr TrHttpServerWebRelativePath = std::string_view{ "web/" }; -std::unique_lock tr_sessionLock(tr_session const* session); - /** * Add libtransmission's default settings to the benc dictionary. * @@ -1591,6 +1589,11 @@ struct tr_stat second or so to get a new snapshot of the torrent's status. */ tr_stat const* tr_torrentStat(tr_torrent* torrent); +// Batch version of tr_torrentStat(). +// Prefer calling this over calling the single-torrent version in a loop. +// TODO(c++20) take a std::span argument +std::vector tr_torrentStat(tr_torrent* const* torrents, size_t n_torrents); + /** @} */ /** @brief Sanity checker to test that the direction is `TR_UP` or `TR_DOWN` */ diff --git a/macosx/Controller.mm b/macosx/Controller.mm index 415cd2818..c129a6d77 100644 --- a/macosx/Controller.mm +++ b/macosx/Controller.mm @@ -2367,20 +2367,16 @@ void onTorrentCompletenessChanged(tr_torrent* tor, tr_completeness status, bool BOOL anyCompleted = NO; BOOL anyActive = NO; + [Torrent updateTorrents:self.fTorrents]; + + for (Torrent* torrent in self.fTorrents) { - // avoid having to wait for the same lock multiple times in the same operation - auto const lock = tr_sessionLock(self.sessionHandle); - for (Torrent* torrent in self.fTorrents) - { - [torrent update]; + //pull the upload and download speeds - most consistent by using current stats + dlRate += torrent.downloadRate; + ulRate += torrent.uploadRate; - //pull the upload and download speeds - most consistent by using current stats - dlRate += torrent.downloadRate; - ulRate += torrent.uploadRate; - - anyCompleted |= torrent.finishedSeeding; - anyActive |= torrent.active && !torrent.stalled && !torrent.error; - } + anyCompleted |= torrent.finishedSeeding; + anyActive |= torrent.active && !torrent.stalled && !torrent.error; } PowerManager.shared.shouldPreventSleep = anyActive && [self.fDefaults boolForKey:@"SleepPrevent"]; @@ -3817,9 +3813,10 @@ void onTorrentCompletenessChanged(tr_torrent* tor, tr_completeness status, bool for (Torrent* torrent in self.fTorrents) { torrent.queuePosition = i++; - [torrent update]; } + [Torrent updateTorrents:self.fTorrents]; + //do the drag animation here so that the dragged torrents are the ones that are animated as moving, and not the torrents around them [self.fTableView beginUpdates]; @@ -5527,10 +5524,7 @@ void onTorrentCompletenessChanged(tr_torrent* tor, tr_completeness status, bool - (void)rpcUpdateQueue { - for (Torrent* torrent in self.fTorrents) - { - [torrent update]; - } + [Torrent updateTorrents:self.fTorrents]; NSSortDescriptor* descriptor = [NSSortDescriptor sortDescriptorWithKey:@"queuePosition" ascending:YES]; NSArray* descriptors = @[ descriptor ]; diff --git a/macosx/Torrent.h b/macosx/Torrent.h index bfe97801c..d524ac254 100644 --- a/macosx/Torrent.h +++ b/macosx/Torrent.h @@ -35,6 +35,10 @@ extern NSString* const kTorrentDidChangeGroupNotification; - (void)getAmountFinished:(float*)tab size:(int)size; @property(nonatomic) NSIndexSet* previousFinishedPieces; +// Updates one or more torrents by refreshing their libtransmission stats. +// Prefer using this batch method when updating many torrents at once. ++ (void)updateTorrents:(NSArray*)torrents; + - (void)update; - (void)startTransferIgnoringQueue:(BOOL)ignoreQueue; diff --git a/macosx/Torrent.mm b/macosx/Torrent.mm index b1fba9ff3..abd5384af 100644 --- a/macosx/Torrent.mm +++ b/macosx/Torrent.mm @@ -242,19 +242,59 @@ bool trashDataFile(char const* filename, void* /*user_data*/, tr_error* error) - (void)update { - //get previous stalled value before update - BOOL const wasTransmitting = self.fStat != NULL && self.transmitting; + [Torrent updateTorrents:@[ self ]]; +} - self.fStat = tr_torrentStat(self.fHandle); - - //make sure the "active" filter is updated when transmitting changes - if (wasTransmitting != self.transmitting) ++ (void)updateTorrents:(NSArray*)torrents +{ + if (torrents == nil || torrents.count == 0) { - //posting asynchronously with coalescing to prevent stack overflow on lots of torrents changing state at the same time - [NSNotificationQueue.defaultQueue enqueueNotification:[NSNotification notificationWithName:@"UpdateTorrentsState" object:nil] - postingStyle:NSPostASAP - coalesceMask:NSNotificationCoalescingOnName - forModes:nil]; + return; + } + + std::vector torrent_objects; + torrent_objects.reserve(torrents.count); + + std::vector torrent_handles; + torrent_handles.reserve(torrents.count); + + std::vector was_transmitting; + was_transmitting.reserve(torrents.count); + + for (Torrent* torrent in torrents) + { + if (torrent == nil || torrent.fHandle == nullptr) + { + continue; + } + + torrent_objects.emplace_back(torrent); + torrent_handles.emplace_back(torrent.fHandle); + was_transmitting.emplace_back(torrent.fStat != nullptr && torrent.transmitting); + } + + if (torrent_handles.empty()) + { + return; + } + + auto const stats = tr_torrentStat(torrent_handles.data(), torrent_handles.size()); + + // Assign stats and post notifications. + for (size_t i = 0, n = torrent_objects.size(); i < n; ++i) + { + Torrent* const torrent = torrent_objects[i]; + torrent.fStat = stats[i]; + + //make sure the "active" filter is updated when transmitting changes + if (was_transmitting[i] != torrent.transmitting) + { + //posting asynchronously with coalescing to prevent stack overflow on lots of torrents changing state at the same time + [NSNotificationQueue.defaultQueue enqueueNotification:[NSNotification notificationWithName:@"UpdateTorrentsState" object:nil] + postingStyle:NSPostASAP + coalesceMask:NSNotificationCoalescingOnName + forModes:nil]; + } } }