From 89b0362bf2f08bcc709ec6957a5f60d4df97336a Mon Sep 17 00:00:00 2001 From: Mike Gelfand Date: Mon, 6 Dec 2021 21:39:16 +0000 Subject: [PATCH 1/6] Include SVG support in Windows builds --- dist/msi/components/QtClient.wxs | 12 ++++++++---- release/windows/build-qt.ps1 | 1 + release/windows/build-transmission.ps1 | 10 +++++++++- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/dist/msi/components/QtClient.wxs b/dist/msi/components/QtClient.wxs index 6382d5d63..010610c37 100644 --- a/dist/msi/components/QtClient.wxs +++ b/dist/msi/components/QtClient.wxs @@ -97,6 +97,9 @@ + + + @@ -109,7 +112,6 @@ - @@ -120,8 +122,10 @@ + + + - @@ -161,15 +165,15 @@ + - - + diff --git a/release/windows/build-qt.ps1 b/release/windows/build-qt.ps1 index bc2c980c8..f6d0ab9cd 100644 --- a/release/windows/build-qt.ps1 +++ b/release/windows/build-qt.ps1 @@ -16,6 +16,7 @@ function global:Build-Qt([string] $PrefixDir, [string] $Arch, [string] $DepsPref $UnpackFlags = @( (Join-Path $ArchiveBase qtactiveqt '*') (Join-Path $ArchiveBase qtbase '*') + (Join-Path $ArchiveBase qtsvg '*') (Join-Path $ArchiveBase qttools '*') (Join-Path $ArchiveBase qttranslations '*') (Join-Path $ArchiveBase qtwinextras '*') diff --git a/release/windows/build-transmission.ps1 b/release/windows/build-transmission.ps1 index 8434dbb8f..0da5ccaf5 100644 --- a/release/windows/build-transmission.ps1 +++ b/release/windows/build-transmission.ps1 @@ -36,13 +36,21 @@ function global:Build-Transmission([string] $PrefixDir, [string] $Arch, [string] Copy-Item -Path (Join-Path $DepsPrefixDir bin "${x}.pdb") -Destination $DebugSymbolsDir } - foreach ($x in @('Core', 'DBus', 'Gui', 'Network', 'Widgets', 'WinExtras')) { + foreach ($x in @('Core', 'DBus', 'Gui', 'Network', 'Svg', 'Widgets', 'WinExtras')) { if ($DepsPrefixDir -ne $PrefixDir) { Copy-Item -Path (Join-Path $DepsPrefixDir bin "Qt5${x}.dll") -Destination (Join-Path $PrefixDir bin) } Copy-Item -Path (Join-Path $DepsPrefixDir bin "Qt5${x}.pdb") -Destination $DebugSymbolsDir } + foreach ($x in @('gif', 'ico', 'jpeg', 'svg')) { + if ($DepsPrefixDir -ne $PrefixDir) { + New-Item -Path (Join-Path $PrefixDir plugins imageformats) -ItemType Directory -ErrorAction Ignore | Out-Null + Copy-Item -Path (Join-Path $DepsPrefixDir plugins imageformats "q${x}.dll") -Destination (Join-Path $PrefixDir plugins imageformats) + } + Copy-Item -Path (Join-Path $DepsPrefixDir plugins imageformats "q${x}.pdb") -Destination $DebugSymbolsDir + } + if ($DepsPrefixDir -ne $PrefixDir) { New-Item -Path (Join-Path $PrefixDir plugins platforms) -ItemType Directory -ErrorAction Ignore | Out-Null Copy-Item -Path (Join-Path $DepsPrefixDir plugins platforms qwindows.dll) -Destination (Join-Path $PrefixDir plugins platforms) From c46ce2da0688da60234e14e10a77a765990b8373 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Tue, 7 Dec 2021 12:11:28 -0600 Subject: [PATCH 2/6] refactor: add tr_torrentWebseed(), tr_torrentWebseedCount() (#2277) * refactor: add tr_torrentWebseedCount() --- gtk/DetailsDialog.cc | 36 ++++++++++++++-------------------- libtransmission/peer-mgr.cc | 32 +++++------------------------- libtransmission/peer-mgr.h | 2 +- libtransmission/torrent.cc | 15 +++++++++----- libtransmission/torrent.h | 21 ++++++++++++++++++++ libtransmission/transmission.h | 27 +++++++++++++------------ libtransmission/webseed.cc | 13 ++++++++++++ libtransmission/webseed.h | 2 ++ macosx/Torrent.mm | 18 ++++++++--------- 9 files changed, 90 insertions(+), 76 deletions(-) diff --git a/gtk/DetailsDialog.cc b/gtk/DetailsDialog.cc index 00973ddd7..aa445d905 100644 --- a/gtk/DetailsDialog.cc +++ b/gtk/DetailsDialog.cc @@ -1403,7 +1403,7 @@ void DetailsDialog::Impl::refreshPeerList(std::vector const& torren void DetailsDialog::Impl::refreshWebseedList(std::vector const& torrents) { - int total = 0; + auto has_any_webseeds = bool{ false }; auto& hash = webseed_hash_; auto& store = webseed_store_; @@ -1416,13 +1416,11 @@ void DetailsDialog::Impl::refreshWebseedList(std::vector const& tor /* step 2: add any new webseeds */ for (auto const* const tor : torrents) { - auto const* inf = tr_torrentInfo(tor); - - total += inf->webseedCount; - - for (unsigned int j = 0; j < inf->webseedCount; ++j) + for (size_t j = 0, n = tr_torrentWebseedCount(tor); j < n; ++j) { - char const* url = inf->webseeds[j]; + has_any_webseeds = true; + + auto const* const url = tr_torrentWebseed(tor, j).url; auto const key = gtr_sprintf("%d.%s", tr_torrentId(tor), url); if (hash.find(key) == hash.end()) @@ -1438,27 +1436,23 @@ void DetailsDialog::Impl::refreshWebseedList(std::vector const& tor /* step 3: update the webseeds */ for (auto const* const tor : torrents) { - auto const* inf = tr_torrentInfo(tor); - double* speeds_KBps = tr_torrentWebSpeeds_KBps(tor); - - for (unsigned int j = 0; j < inf->webseedCount; ++j) + for (size_t j = 0, n = tr_torrentWebseedCount(tor); j < n; ++j) { - char const* const url = inf->webseeds[j]; - auto const key = gtr_sprintf("%d.%s", tr_torrentId(tor), url); + auto const webseed = tr_torrentWebseed(tor, j); + auto const key = gtr_sprintf("%d.%s", tr_torrentId(tor), webseed.url); auto const iter = store->get_iter(hash.at(key).get_path()); - char buf[128] = { 0 }; - if (speeds_KBps[j] > 0) + auto const KBps = double(webseed.download_bytes_per_second) / speed_K; + auto buf = std::array{}; + if (webseed.is_downloading) { - tr_formatter_speed_KBps(buf, speeds_KBps[j], sizeof(buf)); + tr_formatter_speed_KBps(std::data(buf), KBps, std::size(buf)); } - (*iter)[webseed_cols.download_rate_double] = speeds_KBps[j]; - (*iter)[webseed_cols.download_rate_string] = buf; + (*iter)[webseed_cols.download_rate_double] = KBps; + (*iter)[webseed_cols.download_rate_string] = std::data(buf); (*iter)[webseed_cols.was_updated] = true; } - - tr_free(speeds_KBps); } /* step 4: remove webseeds that have disappeared */ @@ -1481,7 +1475,7 @@ void DetailsDialog::Impl::refreshWebseedList(std::vector const& tor /* most of the time there are no webseeds... don't waste space showing an empty list */ - webseed_view_->set_visible(total > 0); + webseed_view_->set_visible(has_any_webseeds); } void DetailsDialog::Impl::refreshPeers(std::vector const& torrents) diff --git a/libtransmission/peer-mgr.cc b/libtransmission/peer-mgr.cc index 6549904a6..440f43e8a 100644 --- a/libtransmission/peer-mgr.cc +++ b/libtransmission/peer-mgr.cc @@ -1704,36 +1704,14 @@ uint64_t tr_peerMgrGetDesiredAvailable(tr_torrent const* tor) return desired_available; } -double* tr_peerMgrWebSpeeds_KBps(tr_torrent const* tor) +tr_webseed_view tr_peerMgrWebseed(tr_torrent const* tor, size_t i) { TR_ASSERT(tr_isTorrent(tor)); + TR_ASSERT(tor->swarm != nullptr); + size_t const n = tr_ptrArraySize(&tor->swarm->webseeds); + TR_ASSERT(i < n); - auto const now = tr_time_msec(); - - tr_swarm* const s = tor->swarm; - TR_ASSERT(s->manager != nullptr); - - unsigned int n = tr_ptrArraySize(&s->webseeds); - TR_ASSERT(n == tor->info.webseedCount); - - double* ret = tr_new0(double, n); - - for (unsigned int i = 0; i < n; ++i) - { - unsigned int Bps = 0; - auto const* const peer = static_cast(tr_ptrArrayNth(&s->webseeds, i)); - - if (peer->is_transferring_pieces(now, TR_DOWN, &Bps)) - { - ret[i] = Bps / (double)tr_speed_K; - } - else - { - ret[i] = -1.0; - } - } - - return ret; + return i >= n ? tr_webseed_view{} : tr_webseedView(static_cast(tr_ptrArrayNth(&tor->swarm->webseeds, i))); } static auto getPeerStats(tr_peerMsgs const* peer, time_t now, uint64_t now_msec) diff --git a/libtransmission/peer-mgr.h b/libtransmission/peer-mgr.h index 7f7be469a..0466ae23b 100644 --- a/libtransmission/peer-mgr.h +++ b/libtransmission/peer-mgr.h @@ -138,7 +138,7 @@ void tr_peerMgrOnBlocklistChanged(tr_peerMgr* manager); struct tr_peer_stat* tr_peerMgrPeerStats(tr_torrent const* tor, int* setmeCount); -double* tr_peerMgrWebSpeeds_KBps(tr_torrent const* tor); +tr_webseed_view tr_peerMgrWebseed(tr_torrent const* tor, size_t i); unsigned int tr_peerGetPieceSpeed_Bps(tr_peer const* peer, uint64_t now, tr_direction direction); diff --git a/libtransmission/torrent.cc b/libtransmission/torrent.cc index 281676c53..73fc87357 100644 --- a/libtransmission/torrent.cc +++ b/libtransmission/torrent.cc @@ -1241,17 +1241,22 @@ size_t tr_torrentFileCount(tr_torrent const* torrent) return torrent->fileCount(); } -/*** -**** -***/ +tr_webseed_view tr_torrentWebseed(tr_torrent const* tor, size_t i) +{ + return tr_peerMgrWebseed(tor, i); +} -double* tr_torrentWebSpeeds_KBps(tr_torrent const* tor) +size_t tr_torrentWebseedCount(tr_torrent const* tor) { TR_ASSERT(tr_isTorrent(tor)); - return tr_peerMgrWebSpeeds_KBps(tor); + return tor->webseedCount(); } +/*** +**** +***/ + tr_peer_stat* tr_torrentPeers(tr_torrent const* tor, int* peerCount) { TR_ASSERT(tr_isTorrent(tor)); diff --git a/libtransmission/torrent.h b/libtransmission/torrent.h index 7a556a98e..39c7ce30d 100644 --- a/libtransmission/torrent.h +++ b/libtransmission/torrent.h @@ -327,6 +327,27 @@ public: std::optional findFile(std::string& filename, tr_file_index_t i) const; + /// WEBSEEDS + + auto webseedCount() const + { + return info.webseedCount; + } + + auto const& webseed(size_t i) const + { + TR_ASSERT(i < webseedCount()); + + return info.webseeds[i]; + } + + auto& webseed(size_t i) + { + TR_ASSERT(i < webseedCount()); + + return info.webseeds[i]; + } + /// CHECKSUMS bool ensurePieceIsChecked(tr_piece_index_t piece) diff --git a/libtransmission/transmission.h b/libtransmission/transmission.h index b0fd1c762..33a52804d 100644 --- a/libtransmission/transmission.h +++ b/libtransmission/transmission.h @@ -1501,18 +1501,6 @@ tr_tracker_stat* tr_torrentTrackers(tr_torrent const* torrent, int* setmeTracker void tr_torrentTrackersFree(tr_tracker_stat* trackerStats, int trackerCount); -/** - * @brief get the download speeds for each of this torrent's webseed sources. - * - * @return an array of tor->info.webseedCount floats giving download speeds. - * Each speed in the array corresponds to the webseed at the same - * array index in tor->info.webseeds. - * To differentiate "idle" and "stalled" status, idle webseeds will - * return -1 instead of 0 KiB/s. - * NOTE: always free this array with tr_free() when you're done with it. - */ -double* tr_torrentWebSpeeds_KBps(tr_torrent const* torrent); - /* * This view structure is intended for short-term use. Its pointers are owned * by the torrent and may be invalidated if the torrent is edited or removed. @@ -1530,6 +1518,21 @@ tr_file_view tr_torrentFile(tr_torrent const* torrent, tr_file_index_t file); size_t tr_torrentFileCount(tr_torrent const* torrent); +/* + * This view structure is intended for short-term use. Its pointers are owned + * by the torrent and may be invalidated if the torrent is edited or removed. + */ +struct tr_webseed_view +{ + char const* url; // the url to download from + bool is_downloading; // can be true even if speed is 0, e.g. slow download + unsigned download_bytes_per_second; // current download speed +}; + +struct tr_webseed_view tr_torrentWebseed(tr_torrent const* torrent, size_t nth); + +size_t tr_torrentWebseedCount(tr_torrent const* torrent); + /*********************************************************************** * tr_torrentAvailability *********************************************************************** diff --git a/libtransmission/webseed.cc b/libtransmission/webseed.cc index 9dfd829ea..d3a1ac08f 100644 --- a/libtransmission/webseed.cc +++ b/libtransmission/webseed.cc @@ -125,6 +125,19 @@ public: } // namespace +tr_webseed_view tr_webseedView(tr_peer const* peer) +{ + auto const* w = dynamic_cast(peer); + if (w == nullptr) + { + return {}; + } + + auto bytes_per_second = unsigned{ 0 }; + auto const is_downloading = peer->is_transferring_pieces(tr_time_msec(), TR_DOWN, &bytes_per_second); + return { w->base_url.c_str(), is_downloading, bytes_per_second }; +} + /*** **** ***/ diff --git a/libtransmission/webseed.h b/libtransmission/webseed.h index 23a18b9c3..02bc4a0cd 100644 --- a/libtransmission/webseed.h +++ b/libtransmission/webseed.h @@ -17,3 +17,5 @@ #include "peer-common.h" tr_peer* tr_webseedNew(struct tr_torrent* torrent, std::string_view, tr_peer_callback callback, void* callback_data); + +tr_webseed_view tr_webseedView(tr_peer const* peer); diff --git a/macosx/Torrent.mm b/macosx/Torrent.mm index 75fe5bc41..cf9e28e48 100644 --- a/macosx/Torrent.mm +++ b/macosx/Torrent.mm @@ -1080,32 +1080,30 @@ bool trashDataFile(char const* filename, tr_error** error) - (NSUInteger)webSeedCount { - return fInfo->webseedCount; + return tr_torrentWebseedCount(fHandle); } - (NSArray*)webSeeds { - NSMutableArray* webSeeds = [NSMutableArray arrayWithCapacity:fInfo->webseedCount]; + NSUInteger n = tr_torrentWebseedCount(fHandle); + NSMutableArray* webSeeds = [NSMutableArray arrayWithCapacity:n]; - double* dlSpeeds = tr_torrentWebSpeeds_KBps(fHandle); - - for (NSInteger i = 0; i < fInfo->webseedCount; i++) + for (NSUInteger i = 0; i < n; ++i) { + auto const webseed = tr_torrentWebseed(fHandle, i); NSMutableDictionary* dict = [NSMutableDictionary dictionaryWithCapacity:3]; dict[@"Name"] = self.name; - dict[@"Address"] = @(fInfo->webseeds[i]); + dict[@"Address"] = @(webseed.url); - if (dlSpeeds[i] != -1.0) + if (webseed.is_downloading) { - dict[@"DL From Rate"] = @(dlSpeeds[i]); + dict[@"DL From Rate"] = @(double(webseed.download_bytes_per_second) / 1000); } [webSeeds addObject:dict]; } - tr_free(dlSpeeds); - return webSeeds; } From 76719bf34c255da4fca991c2ad3fa4b65d2154b1 Mon Sep 17 00:00:00 2001 From: Matt Joiner Date: Wed, 8 Dec 2021 09:18:56 +1100 Subject: [PATCH 3/6] Reject cancels when fast extension enabled (#2275) Co-authored-by: Charles Kerr --- libtransmission/peer-msgs.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libtransmission/peer-msgs.cc b/libtransmission/peer-msgs.cc index e74602517..70c684566 100644 --- a/libtransmission/peer-msgs.cc +++ b/libtransmission/peer-msgs.cc @@ -1750,6 +1750,11 @@ static ReadState readBtMessage(tr_peerMsgsImpl* msgs, struct evbuffer* inbuf, si { tr_removeElementFromArray(msgs->peerAskedFor, i, sizeof(struct peer_request), msgs->pendingReqsToClient); --msgs->pendingReqsToClient; + if (fext) + { + protocolSendReject(msgs, &r); + } + break; } } From 0a85c3aaa410b12f27e9bbdec3e0032922a836e5 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Tue, 7 Dec 2021 19:03:32 -0600 Subject: [PATCH 4/6] fixup! refactor: make parts of tr file private (#2241) (#2281) fix: crash regression in GTK client details dialog --- gtk/FileList.cc | 2 +- libtransmission/resume.cc | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/gtk/FileList.cc b/gtk/FileList.cc index c56a00ec5..d0b03207d 100644 --- a/gtk/FileList.cc +++ b/gtk/FileList.cc @@ -425,7 +425,7 @@ void buildTree(FileRowNode& node, build_data& build) auto const mime_type = isLeaf ? gtr_get_mime_type_from_filename(child_data.name) : DIRECTORY_MIME_TYPE; auto const icon = gtr_get_mime_type_icon(mime_type, Gtk::ICON_SIZE_MENU, *build.w); - auto const file = tr_torrentFile(build.tor, child_data.index); + auto const file = isLeaf ? tr_torrentFile(build.tor, child_data.index) : tr_file_view{}; int const priority = isLeaf ? file.priority : 0; bool const enabled = isLeaf ? file.wanted : true; auto name_esc = Glib::Markup::escape_text(child_data.name); diff --git a/libtransmission/resume.cc b/libtransmission/resume.cc index e431d9708..fa9bcf76f 100644 --- a/libtransmission/resume.cc +++ b/libtransmission/resume.cc @@ -375,6 +375,12 @@ static uint64_t loadName(tr_variant* dict, tr_torrent* tor) return 0; } + name = tr_strvDup(name); + if (std::empty(name)) + { + return 0; + } + if (name != tr_torrentName(tor)) { tr_free(tor->info.name); From ab0c49859e98920e092ca11931b3dfd2391b1ac9 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Wed, 8 Dec 2021 10:55:52 -0600 Subject: [PATCH 5/6] refactor: add tr_torrentTrackers() (#2282) * refactor: add tr_torrentTrackers() --- gtk/DetailsDialog.cc | 167 +++++++++++--------------- gtk/FilterBar.cc | 22 ++-- gtk/Session.cc | 56 ++------- gtk/TorrentCellRenderer.cc | 20 ++-- libtransmission/announcer.cc | 191 +++++++++++++++--------------- libtransmission/announcer.h | 4 +- libtransmission/file-piece-map.cc | 2 + libtransmission/rpcimpl.cc | 70 ++++++----- libtransmission/torrent.cc | 31 ++--- libtransmission/torrent.h | 7 ++ libtransmission/transmission.h | 119 ++++++------------- macosx/Torrent.mm | 41 +++---- macosx/TrackerNode.h | 2 +- macosx/TrackerNode.mm | 4 +- 14 files changed, 315 insertions(+), 421 deletions(-) diff --git a/gtk/DetailsDialog.cc b/gtk/DetailsDialog.cc index aa445d905..fba8c6594 100644 --- a/gtk/DetailsDialog.cc +++ b/gtk/DetailsDialog.cc @@ -1847,24 +1847,24 @@ Glib::ustring tr_strltime_rounded(time_t t) return tr_strltime(t); } -void appendAnnounceInfo(tr_tracker_stat const* const st, time_t const now, Gtk::TextDirection direction, std::ostream& gstr) +void appendAnnounceInfo(tr_tracker_view const& tracker, time_t const now, Gtk::TextDirection direction, std::ostream& gstr) { - if (st->hasAnnounced && st->announceState != TR_TRACKER_INACTIVE) + if (tracker.hasAnnounced && tracker.announceState != TR_TRACKER_INACTIVE) { gstr << '\n'; gstr << text_dir_mark[direction]; - auto const timebuf = tr_strltime_rounded(now - st->lastAnnounceTime); + auto const timebuf = tr_strltime_rounded(now - tracker.lastAnnounceTime); - if (st->lastAnnounceSucceeded) + if (tracker.lastAnnounceSucceeded) { gstr << gtr_sprintf( _("Got a list of %1$s%2$'d peers%3$s %4$s ago"), success_markup_begin, - st->lastAnnouncePeerCount, + tracker.lastAnnouncePeerCount, success_markup_end, timebuf); } - else if (st->lastAnnounceTimedOut) + else if (tracker.lastAnnounceTimedOut) { gstr << gtr_sprintf( _("Peer list request %1$stimed out%2$s %3$s ago; will retry"), @@ -1877,13 +1877,13 @@ void appendAnnounceInfo(tr_tracker_stat const* const st, time_t const now, Gtk:: gstr << gtr_sprintf( _("Got an error %1$s\"%2$s\"%3$s %4$s ago"), err_markup_begin, - st->lastAnnounceResult, + tracker.lastAnnounceResult, err_markup_end, timebuf); } } - switch (st->announceState) + switch (tracker.announceState) { case TR_TRACKER_INACTIVE: gstr << '\n'; @@ -1894,7 +1894,7 @@ void appendAnnounceInfo(tr_tracker_stat const* const st, time_t const now, Gtk:: case TR_TRACKER_WAITING: gstr << '\n'; gstr << text_dir_mark[direction]; - gstr << gtr_sprintf(_("Asking for more peers in %s"), tr_strltime_rounded(st->nextAnnounceTime - now)); + gstr << gtr_sprintf(_("Asking for more peers in %s"), tr_strltime_rounded(tracker.nextAnnounceTime - now)); break; case TR_TRACKER_QUEUED: @@ -1908,26 +1908,26 @@ void appendAnnounceInfo(tr_tracker_stat const* const st, time_t const now, Gtk:: gstr << text_dir_mark[direction]; gstr << gtr_sprintf( _("Asking for more peers now… %s"), - tr_strltime_rounded(now - st->lastAnnounceStartTime)); + tr_strltime_rounded(now - tracker.lastAnnounceStartTime)); break; } } -void appendScrapeInfo(tr_tracker_stat const* const st, time_t const now, Gtk::TextDirection direction, std::ostream& gstr) +void appendScrapeInfo(tr_tracker_view const& tracker, time_t const now, Gtk::TextDirection direction, std::ostream& gstr) { - if (st->hasScraped) + if (tracker.hasScraped) { gstr << '\n'; gstr << text_dir_mark[direction]; - auto const timebuf = tr_strltime_rounded(now - st->lastScrapeTime); + auto const timebuf = tr_strltime_rounded(now - tracker.lastScrapeTime); - if (st->lastScrapeSucceeded) + if (tracker.lastScrapeSucceeded) { gstr << gtr_sprintf( _("Tracker had %s%'d seeders and %'d leechers%s %s ago"), success_markup_begin, - st->seederCount, - st->leecherCount, + tracker.seederCount, + tracker.leecherCount, success_markup_end, timebuf); } @@ -1936,13 +1936,13 @@ void appendScrapeInfo(tr_tracker_stat const* const st, time_t const now, Gtk::Te gstr << gtr_sprintf( _("Got a scrape error \"%s%s%s\" %s ago"), err_markup_begin, - st->lastScrapeResult, + tracker.lastScrapeResult, err_markup_end, timebuf); } } - switch (st->scrapeState) + switch (tracker.scrapeState) { case TR_TRACKER_INACTIVE: break; @@ -1950,7 +1950,7 @@ void appendScrapeInfo(tr_tracker_stat const* const st, time_t const now, Gtk::Te case TR_TRACKER_WAITING: gstr << '\n'; gstr << text_dir_mark[direction]; - gstr << gtr_sprintf(_("Asking for peer counts in %s"), tr_strltime_rounded(st->nextScrapeTime - now)); + gstr << gtr_sprintf(_("Asking for peer counts in %s"), tr_strltime_rounded(tracker.nextScrapeTime - now)); break; case TR_TRACKER_QUEUED: @@ -1964,7 +1964,7 @@ void appendScrapeInfo(tr_tracker_stat const* const st, time_t const now, Gtk::Te gstr << text_dir_mark[direction]; gstr << gtr_sprintf( _("Asking for peer counts now… %s"), - tr_strltime_rounded(now - st->lastScrapeStartTime)); + tr_strltime_rounded(now - tracker.lastScrapeStartTime)); break; } } @@ -1972,25 +1972,25 @@ void appendScrapeInfo(tr_tracker_stat const* const st, time_t const now, Gtk::Te void buildTrackerSummary( std::ostream& gstr, std::string const& key, - tr_tracker_stat const* st, + tr_tracker_view const& tracker, bool showScrape, Gtk::TextDirection direction) { // hostname gstr << text_dir_mark[direction]; - gstr << (st->isBackup ? "" : ""); - gstr << Glib::Markup::escape_text(!key.empty() ? gtr_sprintf("%s - %s", st->host, key) : st->host); - gstr << (st->isBackup ? "" : ""); + gstr << (tracker.isBackup ? "" : ""); + gstr << Glib::Markup::escape_text(!key.empty() ? gtr_sprintf("%s - %s", tracker.host, key) : tracker.host); + gstr << (tracker.isBackup ? "" : ""); - if (!st->isBackup) + if (!tracker.isBackup) { time_t const now = time(nullptr); - appendAnnounceInfo(st, now, direction, gstr); + appendAnnounceInfo(tracker, now, direction, gstr); if (showScrape) { - appendScrapeInfo(st, now, direction, gstr); + appendScrapeInfo(tracker, now, direction, gstr); } } } @@ -2092,16 +2092,13 @@ void DetailsDialog::Impl::refreshTracker(std::vector const& torrent bool const showScrape = scrape_check_->get_active(); /* step 1: get all the trackers */ - std::vector statCount; - std::vector stats; - - statCount.reserve(torrents.size()); - stats.reserve(torrents.size()); - for (auto const* torrent : torrents) + auto trackers = std::multimap{}; + for (auto const* tor : torrents) { - int count = 0; - stats.push_back(tr_torrentTrackers(torrent, &count)); - statCount.push_back(count); + for (size_t i = 0, n = tr_torrentTrackerCount(tor); i < n; ++i) + { + trackers.emplace(tor, tr_torrentTracker(tor, i)); + } } /* step 2: mark all the trackers in the list as not-updated */ @@ -2110,61 +2107,49 @@ void DetailsDialog::Impl::refreshTracker(std::vector const& torrent row[tracker_cols.was_updated] = false; } - /* step 3: add any new trackers */ - for (size_t i = 0; i < statCount.size(); ++i) + /* step 3: add / update trackers */ + for (auto const& [tor, tracker] : trackers) { - int const jn = statCount.at(i); + auto const torrent_id = tr_torrentId(tor); - for (int j = 0; j < jn; ++j) + // build the key to find the row + gstr.str({}); + gstr << torrent_id << '\t' << tracker.tier << '\t' << tracker.announce; + if (hash.find(gstr.str()) == hash.end()) { - tr_torrent const* tor = torrents.at(i); - tr_tracker_stat const* st = &stats.at(i)[j]; - int const torrent_id = tr_torrentId(tor); + // if we didn't have that row, add it + auto const iter = store->append(); + (*iter)[tracker_cols.torrent_id] = torrent_id; + (*iter)[tracker_cols.tracker_id] = tracker.id; + (*iter)[tracker_cols.key] = gstr.str(); - /* build the key to find the row */ - gstr.str({}); - gstr << torrent_id << '\t' << st->tier << '\t' << st->announce; - - if (hash.find(gstr.str()) == hash.end()) - { - auto const iter = store->append(); - (*iter)[tracker_cols.torrent_id] = torrent_id; - (*iter)[tracker_cols.tracker_id] = st->id; - (*iter)[tracker_cols.key] = gstr.str(); - - auto const p = store->get_path(iter); - hash.emplace(gstr.str(), Gtk::TreeRowReference(store, p)); - gtr_get_favicon_from_url( - session, - st->announce, - [ref = Gtk::TreeRowReference(store, p)](auto const& pixbuf) mutable { favicon_ready_cb(pixbuf, ref); }); - } + auto const p = store->get_path(iter); + hash.emplace(gstr.str(), Gtk::TreeRowReference(store, p)); + gtr_get_favicon_from_url( + session, + tracker.announce, + [ref = Gtk::TreeRowReference(store, p)](auto const& pixbuf) mutable { favicon_ready_cb(pixbuf, ref); }); } } - /* step 4: update the peers */ - for (size_t i = 0; i < torrents.size(); ++i) + /* step 4: update the rows */ + auto const summary_name = std::string(std::size(torrents) == 1 ? tr_torrentName(torrents.front()) : ""); + for (auto const& [tor, tracker] : trackers) { - tr_torrent const* tor = torrents.at(i); - auto const summary_name = std::string(torrents.size() > 1 ? tr_torrentName(tor) : ""); + auto const torrent_id = tr_torrentId(tor); - for (int j = 0; j < statCount.at(i); ++j) - { - tr_tracker_stat const* st = &stats.at(i)[j]; + // build the key to find the row + gstr.str({}); + gstr << torrent_id << '\t' << tracker.tier << '\t' << tracker.announce; + auto const iter = store->get_iter(hash.at(gstr.str()).get_path()); - /* build the key to find the row */ - gstr.str({}); - gstr << tr_torrentId(tor) << '\t' << st->tier << '\t' << st->announce; - auto const iter = store->get_iter(hash.at(gstr.str()).get_path()); - - /* update the row */ - gstr.str({}); - buildTrackerSummary(gstr, summary_name, st, showScrape, dialog_.get_direction()); - (*iter)[tracker_cols.text] = gstr.str(); - (*iter)[tracker_cols.is_backup] = st->isBackup; - (*iter)[tracker_cols.tracker_id] = st->id; - (*iter)[tracker_cols.was_updated] = true; - } + // update the row + gstr.str({}); + buildTrackerSummary(gstr, summary_name, tracker, showScrape, dialog_.get_direction()); + (*iter)[tracker_cols.text] = gstr.str(); + (*iter)[tracker_cols.is_backup] = tracker.isBackup; + (*iter)[tracker_cols.tracker_id] = tracker.id; + (*iter)[tracker_cols.was_updated] = true; } /* step 5: remove trackers that have disappeared */ @@ -2186,12 +2171,6 @@ void DetailsDialog::Impl::refreshTracker(std::vector const& torrent } edit_trackers_button_->set_sensitive(tracker_list_get_current_torrent_id() >= 0); - - /* cleanup */ - for (size_t i = 0; i < stats.size(); ++i) - { - tr_torrentTrackersFree(stats[i], statCount[i]); - } } void DetailsDialog::Impl::onScrapeToggled() @@ -2274,19 +2253,18 @@ std::string get_editable_tracker_list(tr_torrent const* tor) { std::ostringstream gstr; int tier = 0; - tr_info const* inf = tr_torrentInfo(tor); - for (unsigned int i = 0; i < inf->trackerCount; ++i) + for (size_t i = 0, n = tr_torrentTrackerCount(tor); i < n; ++i) { - tr_tracker_info const* t = &inf->trackers[i]; + auto const tracker = tr_torrentTracker(tor, i); - if (tier != t->tier) + if (tier != tracker.tier) { - tier = t->tier; + tier = tracker.tier; gstr << '\n'; } - gstr << t->announce << '\n'; + gstr << tracker.announce << '\n'; } auto str = gstr.str(); @@ -2649,8 +2627,7 @@ void DetailsDialog::Impl::set_torrents(std::vector const& ids) { int const id = ids.front(); auto const* tor = core_->find_torrent(id); - auto const* inf = tr_torrentInfo(tor); - title = gtr_sprintf(_("%s Properties"), inf->name); + title = gtr_sprintf(_("%s Properties"), tr_torrentName(tor)); file_list_->set_torrent(id); file_list_->show(); diff --git a/gtk/FilterBar.cc b/gtk/FilterBar.cc index 36773cfa6..a30b35c9e 100644 --- a/gtk/FilterBar.cc +++ b/gtk/FilterBar.cc @@ -174,13 +174,12 @@ bool tracker_filter_model_update(Glib::RefPtr const& tracker_mod for (auto const& row : tmodel->children()) { auto const* tor = static_cast(row.get_value(torrent_cols.torrent)); - auto const* const inf = tr_torrentInfo(tor); std::set keys; - for (unsigned int i = 0; i < inf->trackerCount; ++i) + for (size_t i = 0, n = tr_torrentTrackerCount(tor); i < n; ++i) { - auto const* const key = &*strings.insert(gtr_get_host_from_url(inf->trackers[i].announce)).first; + auto const* const key = &*strings.insert(gtr_get_host_from_url(tr_torrentTracker(tor, i).announce)).first; if (auto const count = hosts_hash.find(key); count == hosts_hash.end()) { @@ -384,21 +383,20 @@ namespace bool test_tracker(tr_torrent const* tor, int active_tracker_type, Glib::ustring const& host) { - bool matches = true; - - if (active_tracker_type == TRACKER_FILTER_TYPE_HOST) + if (active_tracker_type != TRACKER_FILTER_TYPE_HOST) { - auto const* const inf = tr_torrentInfo(tor); + return true; + } - matches = false; - - for (unsigned int i = 0; !matches && i < inf->trackerCount; ++i) + for (size_t i = 0, n = tr_torrentTrackerCount(tor); i < n; ++i) + { + if (gtr_get_host_from_url(tr_torrentTracker(tor, i).announce) == host) { - matches = gtr_get_host_from_url(inf->trackers[i].announce) == host; + return true; } } - return matches; + return false; } /*** diff --git a/gtk/Session.cc b/gtk/Session.cc index c0aecb66c..4bef79049 100644 --- a/gtk/Session.cc +++ b/gtk/Session.cc @@ -456,15 +456,9 @@ int compare_by_activity(Gtk::TreeModel::iterator const& a, Gtk::TreeModel::itera int compare_by_age(Gtk::TreeModel::iterator const& a, Gtk::TreeModel::iterator const& b) { - int ret = 0; - auto* const ta = static_cast(a->get_value(torrent_cols.torrent)); auto* const tb = static_cast(b->get_value(torrent_cols.torrent)); - - if (ret == 0) - { - ret = compare_time(tr_torrentStatCached(ta)->addedDate, tr_torrentStatCached(tb)->addedDate); - } + int ret = compare_time(tr_torrentStatCached(ta)->addedDate, tr_torrentStatCached(tb)->addedDate); if (ret == 0) { @@ -476,15 +470,9 @@ int compare_by_age(Gtk::TreeModel::iterator const& a, Gtk::TreeModel::iterator c int compare_by_size(Gtk::TreeModel::iterator const& a, Gtk::TreeModel::iterator const& b) { - int ret = 0; - - auto const* const ia = tr_torrentInfo(static_cast(a->get_value(torrent_cols.torrent))); - auto const* const ib = tr_torrentInfo(static_cast(b->get_value(torrent_cols.torrent))); - - if (ret == 0) - { - ret = compare_uint64(ia->totalSize, ib->totalSize); - } + auto const size_a = tr_torrentInfo(static_cast(a->get_value(torrent_cols.torrent)))->totalSize; + auto const size_b = tr_torrentInfo(static_cast(b->get_value(torrent_cols.torrent)))->totalSize; + int ret = compare_uint64(size_a, size_b); if (ret == 0) { @@ -496,15 +484,9 @@ int compare_by_size(Gtk::TreeModel::iterator const& a, Gtk::TreeModel::iterator int compare_by_progress(Gtk::TreeModel::iterator const& a, Gtk::TreeModel::iterator const& b) { - int ret = 0; - auto const* const sa = tr_torrentStatCached(static_cast(a->get_value(torrent_cols.torrent))); auto const* const sb = tr_torrentStatCached(static_cast(b->get_value(torrent_cols.torrent))); - - if (ret == 0) - { - ret = compare_double(sa->percentComplete, sb->percentComplete); - } + int ret = compare_double(sa->percentComplete, sb->percentComplete); if (ret == 0) { @@ -521,15 +503,9 @@ int compare_by_progress(Gtk::TreeModel::iterator const& a, Gtk::TreeModel::itera int compare_by_eta(Gtk::TreeModel::iterator const& a, Gtk::TreeModel::iterator const& b) { - int ret = 0; - auto const* const sa = tr_torrentStatCached(static_cast(a->get_value(torrent_cols.torrent))); auto const* const sb = tr_torrentStatCached(static_cast(b->get_value(torrent_cols.torrent))); - - if (ret == 0) - { - ret = compare_eta(sa->eta, sb->eta); - } + int ret = compare_eta(sa->eta, sb->eta); if (ret == 0) { @@ -541,15 +517,9 @@ int compare_by_eta(Gtk::TreeModel::iterator const& a, Gtk::TreeModel::iterator c int compare_by_state(Gtk::TreeModel::iterator const& a, Gtk::TreeModel::iterator const& b) { - int ret = 0; - auto const sa = a->get_value(torrent_cols.activity); auto const sb = b->get_value(torrent_cols.activity); - - if (ret == 0) - { - ret = compare_int(sa, sb); - } + int ret = compare_int(sa, sb); if (ret == 0) { @@ -893,8 +863,7 @@ namespace Glib::ustring get_collated_name(tr_torrent const* tor) { - auto const* const inf = tr_torrentInfo(tor); - return gtr_sprintf("%s\t%s", Glib::ustring(tr_torrentName(tor)).lowercase(), inf->hashString); + return gtr_sprintf("%s\t%s", Glib::ustring(tr_torrentName(tor)).lowercase(), tr_torrentInfo(tor)->hashString); } struct metadata_callback_data @@ -951,14 +920,13 @@ namespace unsigned int build_torrent_trackers_hash(tr_torrent* tor) { - uint64_t hash = 0; - tr_info const* const inf = tr_torrentInfo(tor); + auto hash = uint64_t{}; - for (unsigned int i = 0; i < inf->trackerCount; ++i) + for (size_t i = 0, n = tr_torrentTrackerCount(tor); i < n; ++i) { - for (char const* pch = inf->trackers[i].announce; *pch != '\0'; ++pch) + for (auto const ch : std::string_view{ tr_torrentTracker(tor, i).announce }) { - hash = (hash << 4) ^ (hash >> 28) ^ *pch; + hash = (hash << 4) ^ (hash >> 28) ^ ch; } } diff --git a/gtk/TorrentCellRenderer.cc b/gtk/TorrentCellRenderer.cc index d2aa5d32a..2e6de9db0 100644 --- a/gtk/TorrentCellRenderer.cc +++ b/gtk/TorrentCellRenderer.cc @@ -33,13 +33,13 @@ namespace { -Glib::ustring getProgressString(tr_torrent const* tor, tr_info const* info, tr_stat const* st) +Glib::ustring getProgressString(tr_torrent const* tor, uint64_t total_size, tr_stat const* st) { Glib::ustring gstr; bool const isDone = st->leftUntilDone == 0; uint64_t const haveTotal = st->haveUnchecked + st->haveValid; - bool const isSeed = st->haveValid >= info->totalSize; + bool const isSeed = st->haveValid >= total_size; double seedRatio; bool const hasSeedRatio = tr_torrentGetSeedRatio(tor, &seedRatio); @@ -67,7 +67,7 @@ Glib::ustring getProgressString(tr_torrent const* tor, tr_info const* info, tr_s %6$s is the ratio we want to reach before we stop uploading */ _("%1$s of %2$s (%3$s%%), uploaded %4$s (Ratio: %5$s Goal: %6$s)"), tr_strlsize(haveTotal), - tr_strlsize(info->totalSize), + tr_strlsize(total_size), tr_strlpercent(st->percentComplete * 100.0), tr_strlsize(st->uploadedEver), tr_strlratio(st->ratio), @@ -83,7 +83,7 @@ Glib::ustring getProgressString(tr_torrent const* tor, tr_info const* info, tr_s %5$s is our upload-to-download ratio */ _("%1$s of %2$s (%3$s%%), uploaded %4$s (Ratio: %5$s)"), tr_strlsize(haveTotal), - tr_strlsize(info->totalSize), + tr_strlsize(total_size), tr_strlpercent(st->percentComplete * 100.0), tr_strlsize(st->uploadedEver), tr_strlratio(st->ratio)); @@ -99,7 +99,7 @@ Glib::ustring getProgressString(tr_torrent const* tor, tr_info const* info, tr_s %3$s is our upload-to-download ratio, %4$s is the ratio we want to reach before we stop uploading */ _("%1$s, uploaded %2$s (Ratio: %3$s Goal: %4$s)"), - tr_strlsize(info->totalSize), + tr_strlsize(total_size), tr_strlsize(st->uploadedEver), tr_strlratio(st->ratio), tr_strlratio(seedRatio)); @@ -111,7 +111,7 @@ Glib::ustring getProgressString(tr_torrent const* tor, tr_info const* info, tr_s %2$s is how much we've uploaded, %3$s is our upload-to-download ratio */ _("%1$s, uploaded %2$s (Ratio: %3$s)"), - tr_strlsize(info->totalSize), + tr_strlsize(total_size), tr_strlsize(st->uploadedEver), tr_strlratio(st->ratio)); } @@ -458,12 +458,12 @@ void TorrentCellRenderer::Impl::get_size_full(Gtk::Widget& widget, int& width, i auto* const tor = static_cast(torrent.get_value()); auto const* const st = tr_torrentStatCached(tor); - auto const* const inf = tr_torrentInfo(tor); + auto const total_size = tr_torrentInfo(tor)->totalSize; auto const icon = get_icon(tor, FULL_ICON_SIZE, widget); auto const name = Glib::ustring(tr_torrentName(tor)); auto const gstr_stat = getStatusString(tor, st, upload_speed_KBps.get_value(), download_speed_KBps.get_value()); - auto const gstr_prog = getProgressString(tor, inf, st); + auto const gstr_prog = getProgressString(tor, total_size, st); renderer_.get_padding(xpad, ypad); /* get the idealized cell dimensions */ @@ -677,7 +677,7 @@ void TorrentCellRenderer::Impl::render_full( auto* const tor = static_cast(torrent.get_value()); auto const* const st = tr_torrentStatCached(tor); - auto const* const inf = tr_torrentInfo(tor); + auto const total_size = tr_torrentInfo(tor)->totalSize; bool const active = st->activity != TR_STATUS_STOPPED && st->activity != TR_STATUS_DOWNLOAD_WAIT && st->activity != TR_STATUS_SEED_WAIT; auto const percentDone = get_percent_done(tor, st, &seed); @@ -685,7 +685,7 @@ void TorrentCellRenderer::Impl::render_full( auto const icon = get_icon(tor, FULL_ICON_SIZE, widget); auto const name = Glib::ustring(tr_torrentName(tor)); - auto const gstr_prog = getProgressString(tor, inf, st); + auto const gstr_prog = getProgressString(tor, total_size, st); auto const gstr_stat = getStatusString(tor, st, upload_speed_KBps.get_value(), download_speed_KBps.get_value()); renderer_.get_padding(xpad, ypad); auto const text_color = get_text_color(widget, st); diff --git a/libtransmission/announcer.cc b/libtransmission/announcer.cc index 02e0a12a3..39517e27c 100644 --- a/libtransmission/announcer.cc +++ b/libtransmission/announcer.cc @@ -1713,115 +1713,120 @@ static void onUpkeepTimer(evutil_socket_t /*fd*/, short /*what*/, void* vannounc **** ***/ -tr_tracker_stat* tr_announcerStats(tr_torrent const* torrent, int* setmeTrackerCount) +static tr_tracker_view trackerView(tr_torrent const& tor, int tier_index, tr_tier const& tier, tr_tracker const& tracker) { - TR_ASSERT(tr_isTorrent(torrent)); + auto const now = tr_time(); + auto view = tr_tracker_view{}; - time_t const now = tr_time(); + view.host = tr_quark_get_string(tracker.key); + view.announce = tr_quark_get_string(tracker.announce_url); + view.scrape = tracker.scrape_info == nullptr ? "" : tr_quark_get_string(tracker.scrape_info->scrape_url); - int out = 0; - struct tr_torrent_tiers const* const tt = torrent->tiers; + view.id = tracker.id; + view.tier = tier_index; + view.isBackup = &tracker != tier.currentTracker; + view.lastScrapeStartTime = tier.lastScrapeStartTime; + view.seederCount = tracker.seederCount; + view.leecherCount = tracker.leecherCount; + view.downloadCount = tracker.downloadCount; - /* alloc the stats */ - *setmeTrackerCount = tt->tracker_count; - tr_tracker_stat* const ret = tr_new0(tr_tracker_stat, tt->tracker_count); - - /* populate the stats */ - for (int i = 0; i < tt->tier_count; ++i) + if (view.isBackup) { - tr_tier const* const tier = &tt->tiers[i]; - - for (int j = 0; j < tier->tracker_count; ++j) + view.scrapeState = TR_TRACKER_INACTIVE; + view.announceState = TR_TRACKER_INACTIVE; + view.nextScrapeTime = 0; + view.nextAnnounceTime = 0; + } + else + { + view.hasScraped = tier.lastScrapeTime; + if (view.hasScraped != 0) { - tr_tracker const* const tracker = &tier->trackers[j]; - tr_tracker_stat* st = &ret[out++]; + view.lastScrapeTime = tier.lastScrapeTime; + view.lastScrapeSucceeded = tier.lastScrapeSucceeded; + view.lastScrapeTimedOut = tier.lastScrapeTimedOut; + tr_strlcpy(view.lastScrapeResult, tier.lastScrapeStr, sizeof(view.lastScrapeResult)); + } - st->id = tracker->id; - st->host = tr_quark_get_string(tracker->key); - st->announce = tr_quark_get_string(tracker->announce_url); - st->tier = i; - st->isBackup = tracker != tier->currentTracker; - st->lastScrapeStartTime = tier->lastScrapeStartTime; - st->scrape = tracker->scrape_info == nullptr ? "" : tr_quark_get_string(tracker->scrape_info->scrape_url); - st->seederCount = tracker->seederCount; - st->leecherCount = tracker->leecherCount; - st->downloadCount = tracker->downloadCount; + if (tier.isScraping) + { + view.scrapeState = TR_TRACKER_ACTIVE; + } + else if (tier.scrapeAt == 0) + { + view.scrapeState = TR_TRACKER_INACTIVE; + } + else if (tier.scrapeAt > now) + { + view.scrapeState = TR_TRACKER_WAITING; + view.nextScrapeTime = tier.scrapeAt; + } + else + { + view.scrapeState = TR_TRACKER_QUEUED; + } - if (st->isBackup) - { - st->scrapeState = TR_TRACKER_INACTIVE; - st->announceState = TR_TRACKER_INACTIVE; - st->nextScrapeTime = 0; - st->nextAnnounceTime = 0; - } - else - { - st->hasScraped = tier->lastScrapeTime; - if (st->hasScraped != 0) - { - st->lastScrapeTime = tier->lastScrapeTime; - st->lastScrapeSucceeded = tier->lastScrapeSucceeded; - st->lastScrapeTimedOut = tier->lastScrapeTimedOut; - tr_strlcpy(st->lastScrapeResult, tier->lastScrapeStr, sizeof(st->lastScrapeResult)); - } + view.lastAnnounceStartTime = tier.lastAnnounceStartTime; - if (tier->isScraping) - { - st->scrapeState = TR_TRACKER_ACTIVE; - } - else if (tier->scrapeAt == 0) - { - st->scrapeState = TR_TRACKER_INACTIVE; - } - else if (tier->scrapeAt > now) - { - st->scrapeState = TR_TRACKER_WAITING; - st->nextScrapeTime = tier->scrapeAt; - } - else - { - st->scrapeState = TR_TRACKER_QUEUED; - } + view.hasAnnounced = tier.lastAnnounceTime; + if (view.hasAnnounced != 0) + { + view.lastAnnounceTime = tier.lastAnnounceTime; + view.lastAnnounceSucceeded = tier.lastAnnounceSucceeded; + view.lastAnnounceTimedOut = tier.lastAnnounceTimedOut; + view.lastAnnouncePeerCount = tier.lastAnnouncePeerCount; + tr_strlcpy(view.lastAnnounceResult, tier.lastAnnounceStr, sizeof(view.lastAnnounceResult)); + } - st->lastAnnounceStartTime = tier->lastAnnounceStartTime; - - st->hasAnnounced = tier->lastAnnounceTime; - if (st->hasAnnounced != 0) - { - st->lastAnnounceTime = tier->lastAnnounceTime; - tr_strlcpy(st->lastAnnounceResult, tier->lastAnnounceStr, sizeof(st->lastAnnounceResult)); - st->lastAnnounceSucceeded = tier->lastAnnounceSucceeded; - st->lastAnnounceTimedOut = tier->lastAnnounceTimedOut; - st->lastAnnouncePeerCount = tier->lastAnnouncePeerCount; - } - - if (tier->isAnnouncing) - { - st->announceState = TR_TRACKER_ACTIVE; - } - else if (!torrent->isRunning || tier->announceAt == 0) - { - st->announceState = TR_TRACKER_INACTIVE; - } - else if (tier->announceAt > now) - { - st->announceState = TR_TRACKER_WAITING; - st->nextAnnounceTime = tier->announceAt; - } - else - { - st->announceState = TR_TRACKER_QUEUED; - } - } + if (tier.isAnnouncing) + { + view.announceState = TR_TRACKER_ACTIVE; + } + else if (!tor.isRunning || tier.announceAt == 0) + { + view.announceState = TR_TRACKER_INACTIVE; + } + else if (tier.announceAt > now) + { + view.announceState = TR_TRACKER_WAITING; + view.nextAnnounceTime = tier.announceAt; + } + else + { + view.announceState = TR_TRACKER_QUEUED; } } - return ret; + TR_ASSERT(0 <= view.tier); + TR_ASSERT(view.tier < tor.tiers->tier_count); + return view; } -void tr_announcerStatsFree(tr_tracker_stat* trackers, int /*trackerCount*/) +tr_tracker_view tr_announcerTracker(tr_torrent const* tor, size_t nth) { - tr_free(trackers); + TR_ASSERT(tr_isTorrent(tor)); + TR_ASSERT(tor->tiers != nullptr); + + // find the nth tracker + struct tr_torrent_tiers const* const tt = tor->tiers; + if (nth >= size_t(tt->tracker_count)) + { + return {}; + } + auto const& tracker = tt->trackers[nth]; + for (int i = 0; i < tt->tier_count; ++i) + { + tr_tier const& tier = tt->tiers[i]; + + for (int j = 0; j < tier.tracker_count; ++j) + { + if (&tier.trackers[j] == &tracker) + { + return trackerView(*tor, i, tier, tracker); + } + } + } + return {}; } /*** diff --git a/libtransmission/announcer.h b/libtransmission/announcer.h index 21bc197b7..6959c5f3f 100644 --- a/libtransmission/announcer.h +++ b/libtransmission/announcer.h @@ -93,9 +93,7 @@ void tr_announcerAddBytes(tr_torrent*, int up_down_or_corrupt, uint32_t byteCoun time_t tr_announcerNextManualAnnounce(tr_torrent const*); -tr_tracker_stat* tr_announcerStats(tr_torrent const* torrent, int* setmeTrackerCount); - -void tr_announcerStatsFree(tr_tracker_stat* trackers, int trackerCount); +tr_tracker_view tr_announcerTracker(tr_torrent const* torrent, size_t i); /*** **** diff --git a/libtransmission/file-piece-map.cc b/libtransmission/file-piece-map.cc index 3cb59767d..7a0e30553 100644 --- a/libtransmission/file-piece-map.cc +++ b/libtransmission/file-piece-map.cc @@ -128,6 +128,8 @@ void tr_file_priorities::set(tr_file_index_t const* files, size_t n, tr_priority tr_priority_t tr_file_priorities::filePriority(tr_file_index_t file) const { + TR_ASSERT(file < std::size(priorities_)); + return priorities_[file]; } diff --git a/libtransmission/rpcimpl.cc b/libtransmission/rpcimpl.cc index ac439d35c..7d97e5f9f 100644 --- a/libtransmission/rpcimpl.cc +++ b/libtransmission/rpcimpl.cc @@ -426,39 +426,35 @@ static void addTrackers(tr_info const* info, tr_variant* trackers) } } -static void addTrackerStats(tr_tracker_stat const* st, int n, tr_variant* list) +static void addTrackerStats(tr_tracker_view const& tracker, tr_variant* list) { - for (int i = 0; i < n; ++i) - { - tr_tracker_stat const* s = &st[i]; - tr_variant* d = tr_variantListAddDict(list, 26); - tr_variantDictAddStr(d, TR_KEY_announce, s->announce); - tr_variantDictAddInt(d, TR_KEY_announceState, s->announceState); - tr_variantDictAddInt(d, TR_KEY_downloadCount, s->downloadCount); - tr_variantDictAddBool(d, TR_KEY_hasAnnounced, s->hasAnnounced); - tr_variantDictAddBool(d, TR_KEY_hasScraped, s->hasScraped); - tr_variantDictAddStr(d, TR_KEY_host, s->host); - tr_variantDictAddInt(d, TR_KEY_id, s->id); - tr_variantDictAddBool(d, TR_KEY_isBackup, s->isBackup); - tr_variantDictAddInt(d, TR_KEY_lastAnnouncePeerCount, s->lastAnnouncePeerCount); - tr_variantDictAddStr(d, TR_KEY_lastAnnounceResult, s->lastAnnounceResult); - tr_variantDictAddInt(d, TR_KEY_lastAnnounceStartTime, s->lastAnnounceStartTime); - tr_variantDictAddBool(d, TR_KEY_lastAnnounceSucceeded, s->lastAnnounceSucceeded); - tr_variantDictAddInt(d, TR_KEY_lastAnnounceTime, s->lastAnnounceTime); - tr_variantDictAddBool(d, TR_KEY_lastAnnounceTimedOut, s->lastAnnounceTimedOut); - tr_variantDictAddStr(d, TR_KEY_lastScrapeResult, s->lastScrapeResult); - tr_variantDictAddInt(d, TR_KEY_lastScrapeStartTime, s->lastScrapeStartTime); - tr_variantDictAddBool(d, TR_KEY_lastScrapeSucceeded, s->lastScrapeSucceeded); - tr_variantDictAddInt(d, TR_KEY_lastScrapeTime, s->lastScrapeTime); - tr_variantDictAddBool(d, TR_KEY_lastScrapeTimedOut, s->lastScrapeTimedOut); - tr_variantDictAddInt(d, TR_KEY_leecherCount, s->leecherCount); - tr_variantDictAddInt(d, TR_KEY_nextAnnounceTime, s->nextAnnounceTime); - tr_variantDictAddInt(d, TR_KEY_nextScrapeTime, s->nextScrapeTime); - tr_variantDictAddStr(d, TR_KEY_scrape, s->scrape); - tr_variantDictAddInt(d, TR_KEY_scrapeState, s->scrapeState); - tr_variantDictAddInt(d, TR_KEY_seederCount, s->seederCount); - tr_variantDictAddInt(d, TR_KEY_tier, s->tier); - } + auto* const d = tr_variantListAddDict(list, 26); + tr_variantDictAddStr(d, TR_KEY_announce, tracker.announce); + tr_variantDictAddInt(d, TR_KEY_announceState, tracker.announceState); + tr_variantDictAddInt(d, TR_KEY_downloadCount, tracker.downloadCount); + tr_variantDictAddBool(d, TR_KEY_hasAnnounced, tracker.hasAnnounced); + tr_variantDictAddBool(d, TR_KEY_hasScraped, tracker.hasScraped); + tr_variantDictAddStr(d, TR_KEY_host, tracker.host); + tr_variantDictAddInt(d, TR_KEY_id, tracker.id); + tr_variantDictAddBool(d, TR_KEY_isBackup, tracker.isBackup); + tr_variantDictAddInt(d, TR_KEY_lastAnnouncePeerCount, tracker.lastAnnouncePeerCount); + tr_variantDictAddStr(d, TR_KEY_lastAnnounceResult, tracker.lastAnnounceResult); + tr_variantDictAddInt(d, TR_KEY_lastAnnounceStartTime, tracker.lastAnnounceStartTime); + tr_variantDictAddBool(d, TR_KEY_lastAnnounceSucceeded, tracker.lastAnnounceSucceeded); + tr_variantDictAddInt(d, TR_KEY_lastAnnounceTime, tracker.lastAnnounceTime); + tr_variantDictAddBool(d, TR_KEY_lastAnnounceTimedOut, tracker.lastAnnounceTimedOut); + tr_variantDictAddStr(d, TR_KEY_lastScrapeResult, tracker.lastScrapeResult); + tr_variantDictAddInt(d, TR_KEY_lastScrapeStartTime, tracker.lastScrapeStartTime); + tr_variantDictAddBool(d, TR_KEY_lastScrapeSucceeded, tracker.lastScrapeSucceeded); + tr_variantDictAddInt(d, TR_KEY_lastScrapeTime, tracker.lastScrapeTime); + tr_variantDictAddBool(d, TR_KEY_lastScrapeTimedOut, tracker.lastScrapeTimedOut); + tr_variantDictAddInt(d, TR_KEY_leecherCount, tracker.leecherCount); + tr_variantDictAddInt(d, TR_KEY_nextAnnounceTime, tracker.nextAnnounceTime); + tr_variantDictAddInt(d, TR_KEY_nextScrapeTime, tracker.nextScrapeTime); + tr_variantDictAddStr(d, TR_KEY_scrape, tracker.scrape); + tr_variantDictAddInt(d, TR_KEY_scrapeState, tracker.scrapeState); + tr_variantDictAddInt(d, TR_KEY_seederCount, tracker.seederCount); + tr_variantDictAddInt(d, TR_KEY_tier, tracker.tier); } static void addPeers(tr_torrent* tor, tr_variant* list) @@ -791,11 +787,13 @@ static void initField( case TR_KEY_trackerStats: { - auto n = int{}; - tr_tracker_stat* s = tr_torrentTrackers(tor, &n); + auto const n = tr_torrentTrackerCount(tor); tr_variantInitList(initme, n); - addTrackerStats(s, n, initme); - tr_torrentTrackersFree(s, n); + for (size_t i = 0; i < n; ++i) + { + auto const& tracker = tr_torrentTracker(tor, i); + addTrackerStats(tracker, initme); + } break; } diff --git a/libtransmission/torrent.cc b/libtransmission/torrent.cc index 73fc87357..5e80ba1a2 100644 --- a/libtransmission/torrent.cc +++ b/libtransmission/torrent.cc @@ -1253,6 +1253,16 @@ size_t tr_torrentWebseedCount(tr_torrent const* tor) return tor->webseedCount(); } +tr_tracker_view tr_torrentTracker(tr_torrent const* tor, size_t i) +{ + return tr_announcerTracker(tor, i); +} + +size_t tr_torrentTrackerCount(tr_torrent const* tor) +{ + return tor->trackerCount(); +} + /*** **** ***/ @@ -1269,18 +1279,6 @@ void tr_torrentPeersFree(tr_peer_stat* peers, int /*peerCount*/) tr_free(peers); } -tr_tracker_stat* tr_torrentTrackers(tr_torrent const* tor, int* setmeTrackerCount) -{ - TR_ASSERT(tr_isTorrent(tor)); - - return tr_announcerStats(tor, setmeTrackerCount); -} - -void tr_torrentTrackersFree(tr_tracker_stat* trackers, int trackerCount) -{ - tr_announcerStatsFree(trackers, trackerCount); -} - void tr_torrentAvailability(tr_torrent const* tor, int8_t* tab, int size) { TR_ASSERT(tr_isTorrent(tor)); @@ -1822,20 +1820,15 @@ static std::string buildTrackersString(tr_torrent const* tor) { auto buf = std::stringstream{}; - int n = 0; - tr_tracker_stat* stats = tr_torrentTrackers(tor, &n); - for (int i = 0; i < n;) + for (size_t i = 0, n = tr_torrentTrackerCount(tor); i < n; ++i) { - tr_tracker_stat const* s = &stats[i]; - - buf << s->host; + buf << tr_torrentTracker(tor, i).host; if (++i < n) { buf << ','; } } - tr_torrentTrackersFree(stats, n); return buf.str(); } diff --git a/libtransmission/torrent.h b/libtransmission/torrent.h index 39c7ce30d..bc62488ef 100644 --- a/libtransmission/torrent.h +++ b/libtransmission/torrent.h @@ -348,6 +348,13 @@ public: return info.webseeds[i]; } + /// TRACKERS + + auto trackerCount() const + { + return info.trackerCount; + } + /// CHECKSUMS bool ensurePieceIsChecked(tr_piece_index_t piece) diff --git a/libtransmission/transmission.h b/libtransmission/transmission.h index 33a52804d..ef91b255b 100644 --- a/libtransmission/transmission.h +++ b/libtransmission/transmission.h @@ -1403,103 +1403,50 @@ enum tr_tracker_state TR_TRACKER_ACTIVE = 3 }; -struct tr_tracker_stat +/* + * Unlike other _view structs, it is safe to keep a tr_tracker_view copy. + * The announce, scrape, and host strings are interned & never go out-of-scope. + */ +struct tr_tracker_view { - /* how many downloads this tracker knows of (-1 means it does not know) */ - int downloadCount; + char const* announce; // full announce URL + char const* scrape; // full scrape URL + char const* host; // human-readable tracker name. (`${host}:${port}`) - /* whether or not we've ever sent this tracker an announcement */ - bool hasAnnounced; + char lastAnnounceResult[128]; // if hasAnnounced, the human-readable result of latest announce + char lastScrapeResult[128]; // if hasScraped, the human-readable result of the latest scrape - /* whether or not we've ever scraped to this tracker */ - bool hasScraped; + time_t lastAnnounceStartTime; // if hasAnnounced, when the latest announce request was sent + time_t lastAnnounceTime; // if hasAnnounced, when the latest announce reply was received + time_t nextAnnounceTime; // if announceState == TR_TRACKER_WAITING, time of next announce - /* human-readable string identifying the tracker. - * 'host' is a slight misnomer; the current format ist `$host:$port` */ - char const* host; + time_t lastScrapeStartTime; // if hasScraped, when the latest scrape request was sent + time_t lastScrapeTime; // if hasScraped, when the latest scrape reply was received + time_t nextScrapeTime; // if scrapeState == TR_TRACKER_WAITING, time of next scrape - /* the full announce URL */ - char const* announce; + int downloadCount; // number of times this torrent's been downloaded, or -1 if unknown + int lastAnnouncePeerCount; // if hasAnnounced, the number of peers the tracker gave us + int leecherCount; // number of leechers the tracker knows of, or -1 if unknown + int seederCount; // number of seeders the tracker knows of, or -1 if unknown - /* the full scrape URL */ - char const* scrape; + int tier; // which tier this tracker is in + int id; // unique transmission-generated ID for use in libtransmission API - /* Transmission uses one tracker per tier, - * and the others are kept as backups */ - bool isBackup; + tr_tracker_state announceState; // whether we're announcing, waiting to announce, etc. + tr_tracker_state scrapeState; // whether we're scraping, waiting to scrape, etc. - /* is the tracker announcing, waiting, queued, etc */ - tr_tracker_state announceState; - - /* is the tracker scraping, waiting, queued, etc */ - tr_tracker_state scrapeState; - - /* number of peers the tracker told us about last time. - * if "lastAnnounceSucceeded" is false, this field is undefined */ - int lastAnnouncePeerCount; - - /* human-readable string with the result of the last announce. - if "hasAnnounced" is false, this field is undefined */ - char lastAnnounceResult[128]; - - /* when the last announce was sent to the tracker. - * if "hasAnnounced" is false, this field is undefined */ - time_t lastAnnounceStartTime; - - /* whether or not the last announce was a success. - if "hasAnnounced" is false, this field is undefined */ - bool lastAnnounceSucceeded; - - /* whether or not the last announce timed out. */ - bool lastAnnounceTimedOut; - - /* when the last announce was completed. - if "hasAnnounced" is false, this field is undefined */ - time_t lastAnnounceTime; - - /* human-readable string with the result of the last scrape. - * if "hasScraped" is false, this field is undefined */ - char lastScrapeResult[128]; - - /* when the last scrape was sent to the tracker. - * if "hasScraped" is false, this field is undefined */ - time_t lastScrapeStartTime; - - /* whether or not the last scrape was a success. - if "hasAnnounced" is false, this field is undefined */ - bool lastScrapeSucceeded; - - /* whether or not the last scrape timed out. */ - bool lastScrapeTimedOut; - - /* when the last scrape was completed. - if "hasScraped" is false, this field is undefined */ - time_t lastScrapeTime; - - /* number of leechers this tracker knows of (-1 means it does not know) */ - int leecherCount; - - /* when the next periodic announce message will be sent out. - if announceState isn't TR_TRACKER_WAITING, this field is undefined */ - time_t nextAnnounceTime; - - /* when the next periodic scrape message will be sent out. - if scrapeState isn't TR_TRACKER_WAITING, this field is undefined */ - time_t nextScrapeTime; - - /* number of seeders this tracker knows of (-1 means it does not know) */ - int seederCount; - - /* which tier this tracker is in */ - int tier; - - /* used to match to a tr_tracker_info */ - uint32_t id; + bool hasAnnounced; // true iff we've announced to this tracker during this session + bool hasScraped; // true iff we've scraped this tracker during this session + bool isBackup; // only one tracker per tier is used; the others are kept as backups + bool lastAnnounceSucceeded; // if hasAnnounced, whether or not the latest announce succeeded + bool lastAnnounceTimedOut; // true iff the latest announce request timed out + bool lastScrapeSucceeded; // if hasScraped, whether or not the latest scrape succeeded + bool lastScrapeTimedOut; // true iff the latest scrape request timed out }; -tr_tracker_stat* tr_torrentTrackers(tr_torrent const* torrent, int* setmeTrackerCount); +struct tr_tracker_view tr_torrentTracker(tr_torrent const* torrent, size_t i); -void tr_torrentTrackersFree(tr_tracker_stat* trackerStats, int trackerCount); +size_t tr_torrentTrackerCount(tr_torrent const* torrent); /* * This view structure is intended for short-term use. Its pointers are owned diff --git a/macosx/Torrent.mm b/macosx/Torrent.mm index cf9e28e48..4a5a5bf1b 100644 --- a/macosx/Torrent.mm +++ b/macosx/Torrent.mm @@ -20,7 +20,10 @@ * DEALINGS IN THE SOFTWARE. *****************************************************************************/ +#include + #include + #include #include #include // tr_new() @@ -736,25 +739,25 @@ bool trashDataFile(char const* filename, tr_error** error) - (NSMutableArray*)allTrackerStats { - int count; - tr_tracker_stat* stats = tr_torrentTrackers(fHandle, &count); + auto const count = tr_torrentTrackerCount(fHandle); + auto tier = std::optional{}; - NSMutableArray* trackers = [NSMutableArray arrayWithCapacity:(count > 0 ? count + (stats[count - 1].tier + 1) : 0)]; + NSMutableArray* trackers = [NSMutableArray arrayWithCapacity:count * 2]; - int prevTier = -1; - for (int i = 0; i < count; ++i) + for (size_t i = 0; i < count; ++i) { - if (stats[i].tier != prevTier) + auto const tracker = tr_torrentTracker(fHandle, i); + + if (!tier || tier != tracker.tier) { - [trackers addObject:@{ @"Tier" : @(stats[i].tier + 1), @"Name" : self.name }]; - prevTier = stats[i].tier; + tier = tracker.tier; + [trackers addObject:@{ @"Tier" : @(tracker.tier + 1), @"Name" : self.name }]; } - TrackerNode* tracker = [[TrackerNode alloc] initWithTrackerStat:&stats[i] torrent:self]; - [trackers addObject:tracker]; + auto* tracker_node = [[TrackerNode alloc] initWithTrackerView:&tracker torrent:self]; + [trackers addObject:tracker_node]; } - tr_torrentTrackersFree(stats, count); return trackers; } @@ -1548,7 +1551,7 @@ bool trashDataFile(char const* filename, tr_error** error) - (NSInteger)fileCount { - return fInfo->fileCount; + return tr_torrentFileCount(fHandle); } - (CGFloat)fileProgress:(FileListNode*)node @@ -1792,21 +1795,19 @@ bool trashDataFile(char const* filename, tr_error** error) - (NSString*)trackerSortKey { - int count; - tr_tracker_stat* stats = tr_torrentTrackers(fHandle, &count); - NSString* best = nil; - for (int i = 0; i < count; ++i) + for (size_t i = 0, n = tr_torrentTrackerCount(fHandle); i < n; ++i) { - NSString* tracker = @(stats[i].host); - if (!best || [tracker localizedCaseInsensitiveCompare:best] == NSOrderedAscending) + auto const tracker = tr_torrentTracker(fHandle, i); + + NSString* host = @(tracker.host); + if (!best || [host localizedCaseInsensitiveCompare:best] == NSOrderedAscending) { - best = tracker; + best = host; } } - tr_torrentTrackersFree(stats, count); return best; } diff --git a/macosx/TrackerNode.h b/macosx/TrackerNode.h index 38514f58f..184088616 100644 --- a/macosx/TrackerNode.h +++ b/macosx/TrackerNode.h @@ -30,7 +30,7 @@ @property(nonatomic, weak, readonly) Torrent* torrent; -- (instancetype)initWithTrackerStat:(tr_tracker_stat*)stat torrent:(Torrent*)torrent; +- (instancetype)initWithTrackerView:(tr_tracker_view const*)stat torrent:(Torrent*)torrent; - (BOOL)isEqual:(id)object; diff --git a/macosx/TrackerNode.mm b/macosx/TrackerNode.mm index 18def5dd3..61fe9b198 100644 --- a/macosx/TrackerNode.mm +++ b/macosx/TrackerNode.mm @@ -26,10 +26,10 @@ @implementation TrackerNode { - tr_tracker_stat fStat; + tr_tracker_view fStat; } -- (instancetype)initWithTrackerStat:(tr_tracker_stat*)stat torrent:(Torrent*)torrent +- (instancetype)initWithTrackerView:(tr_tracker_view const*)stat torrent:(Torrent*)torrent { if ((self = [super init])) { From eeb82b2fd32a8dc2891634f856d940a64fde0e1a Mon Sep 17 00:00:00 2001 From: Mike Gelfand Date: Thu, 9 Dec 2021 11:13:04 +0300 Subject: [PATCH 6/6] Adjust theme icons lookup logic to resemble that of GTK (Qt client) (#2288) Look for RTL and symbolic icon variants for each icon that we load. The only exception here is Transmission's own icons, where it doesn't make sense (the way I see it). --- qt/DetailsDialog.cc | 20 ++++---------- qt/DetailsDialog.h | 1 - qt/FilterBar.cc | 17 +++++++----- qt/IconCache.cc | 36 +++++++++++++++++++++--- qt/IconCache.h | 9 ++++++ qt/MainWindow.cc | 64 +++++++++++++++++++------------------------ qt/MainWindow.h | 1 - qt/TorrentDelegate.cc | 8 ++---- 8 files changed, 86 insertions(+), 70 deletions(-) diff --git a/qt/DetailsDialog.cc b/qt/DetailsDialog.cc index 7cbfc4d73..d7a4fe1a2 100644 --- a/qt/DetailsDialog.cc +++ b/qt/DetailsDialog.cc @@ -35,6 +35,7 @@ #include "ColumnResizer.h" #include "DetailsDialog.h" #include "Formatter.h" +#include "IconCache.h" #include "Prefs.h" #include "Session.h" #include "SqueezeLabel.h" @@ -198,18 +199,6 @@ private: **** ***/ -QIcon DetailsDialog::getStockIcon(QString const& freedesktop_name, int fallback) const -{ - QIcon icon = QIcon::fromTheme(freedesktop_name); - - if (icon.isNull()) - { - icon = style()->standardIcon(QStyle::StandardPixmap(fallback), nullptr, this); - } - - return icon; -} - DetailsDialog::DetailsDialog(Session& session, Prefs& prefs, TorrentModel const& model, QWidget* parent) : BaseDialog(parent) , session_(session) @@ -1449,9 +1438,10 @@ void DetailsDialog::initTrackerTab() ui_.trackersView->setModel(tracker_filter_.get()); ui_.trackersView->setItemDelegate(tracker_delegate_.get()); - ui_.addTrackerButton->setIcon(getStockIcon(QStringLiteral("list-add"), QStyle::SP_DialogOpenButton)); - ui_.editTrackerButton->setIcon(getStockIcon(QStringLiteral("document-properties"), QStyle::SP_DesktopIcon)); - ui_.removeTrackerButton->setIcon(getStockIcon(QStringLiteral("list-remove"), QStyle::SP_TrashIcon)); + auto& icons = IconCache::get(); + ui_.addTrackerButton->setIcon(icons.getThemeIcon(QStringLiteral("list-add"), QStyle::SP_DialogOpenButton)); + ui_.editTrackerButton->setIcon(icons.getThemeIcon(QStringLiteral("document-properties"), QStyle::SP_DesktopIcon)); + ui_.removeTrackerButton->setIcon(icons.getThemeIcon(QStringLiteral("list-remove"), QStyle::SP_TrashIcon)); ui_.showTrackerScrapesCheck->setChecked(prefs_.getBool(Prefs::SHOW_TRACKER_SCRAPES)); ui_.showBackupTrackersCheck->setChecked(prefs_.getBool(Prefs::SHOW_BACKUP_TRACKERS)); diff --git a/qt/DetailsDialog.h b/qt/DetailsDialog.h index 20202ce2c..856af3443 100644 --- a/qt/DetailsDialog.h +++ b/qt/DetailsDialog.h @@ -55,7 +55,6 @@ private: void initFilesTab() const; void initOptionsTab(); - QIcon getStockIcon(QString const& freedesktop_name, int fallback) const; void setEnabled(bool); private slots: diff --git a/qt/FilterBar.cc b/qt/FilterBar.cc index 9ca0c0aa9..fa517dbb6 100644 --- a/qt/FilterBar.cc +++ b/qt/FilterBar.cc @@ -22,6 +22,7 @@ #include "FilterBarComboBox.h" #include "FilterBarComboBoxDelegate.h" #include "Filters.h" +#include "IconCache.h" #include "Prefs.h" #include "Torrent.h" #include "TorrentFilter.h" @@ -53,31 +54,33 @@ FilterBarComboBox* FilterBar::createActivityCombo() model->appendRow(new QStandardItem); // separator FilterBarComboBoxDelegate::setSeparator(model, model->index(1, 0)); - row = new QStandardItem(QIcon::fromTheme(QStringLiteral("system-run")), tr("Active")); + auto& icons = IconCache::get(); + + row = new QStandardItem(icons.getThemeIcon(QStringLiteral("system-run")), tr("Active")); row->setData(FilterMode::SHOW_ACTIVE, ACTIVITY_ROLE); model->appendRow(row); - row = new QStandardItem(QIcon::fromTheme(QStringLiteral("go-down")), tr("Downloading")); + row = new QStandardItem(icons.getThemeIcon(QStringLiteral("go-down")), tr("Downloading")); row->setData(FilterMode::SHOW_DOWNLOADING, ACTIVITY_ROLE); model->appendRow(row); - row = new QStandardItem(QIcon::fromTheme(QStringLiteral("go-up")), tr("Seeding")); + row = new QStandardItem(icons.getThemeIcon(QStringLiteral("go-up")), tr("Seeding")); row->setData(FilterMode::SHOW_SEEDING, ACTIVITY_ROLE); model->appendRow(row); - row = new QStandardItem(QIcon::fromTheme(QStringLiteral("media-playback-pause")), tr("Paused")); + row = new QStandardItem(icons.getThemeIcon(QStringLiteral("media-playback-pause")), tr("Paused")); row->setData(FilterMode::SHOW_PAUSED, ACTIVITY_ROLE); model->appendRow(row); - row = new QStandardItem(QIcon::fromTheme(QStringLiteral("dialog-ok")), tr("Finished")); + row = new QStandardItem(icons.getThemeIcon(QStringLiteral("dialog-ok")), tr("Finished")); row->setData(FilterMode::SHOW_FINISHED, ACTIVITY_ROLE); model->appendRow(row); - row = new QStandardItem(QIcon::fromTheme(QStringLiteral("view-refresh")), tr("Verifying")); + row = new QStandardItem(icons.getThemeIcon(QStringLiteral("view-refresh")), tr("Verifying")); row->setData(FilterMode::SHOW_VERIFYING, ACTIVITY_ROLE); model->appendRow(row); - row = new QStandardItem(QIcon::fromTheme(QStringLiteral("process-stop")), tr("Error")); + row = new QStandardItem(icons.getThemeIcon(QStringLiteral("process-stop")), tr("Error")); row->setData(FilterMode::SHOW_ERROR, ACTIVITY_ROLE); model->appendRow(row); diff --git a/qt/IconCache.cc b/qt/IconCache.cc index 5776507f5..d37f1a5e0 100644 --- a/qt/IconCache.cc +++ b/qt/IconCache.cc @@ -13,6 +13,7 @@ #include #endif +#include #include #include #include @@ -86,11 +87,11 @@ QIcon IconCache::getMimeTypeIcon(QString const& mime_type_name, bool multifile) { QMimeDatabase mime_db; auto const type = mime_db.mimeTypeForName(mime_type_name); - icon = QIcon::fromTheme(type.iconName()); + icon = getThemeIcon(type.iconName()); if (icon.isNull()) { - icon = QIcon::fromTheme(type.genericIconName()); + icon = getThemeIcon(type.genericIconName()); } if (icon.isNull()) @@ -125,6 +126,11 @@ QIcon IconCache::getMimeTypeIcon(QString const& mime_type_name, bool multifile) return icon; } +QIcon IconCache::getThemeIcon(QString const& name, std::optional const& fallback) const +{ + return getThemeIcon(name, name + QStringLiteral("-symbolic"), fallback); +} + /*** **** ***/ @@ -197,12 +203,12 @@ QIcon IconCache::getMimeIcon(QString const& filename) const auto const type = mime_db.mimeTypeForFile(filename, QMimeDatabase::MatchExtension); if (icon.isNull()) { - icon = QIcon::fromTheme(type.iconName()); + icon = getThemeIcon(type.iconName()); } if (icon.isNull()) { - icon = QIcon::fromTheme(type.genericIconName()); + icon = getThemeIcon(type.genericIconName()); } if (icon.isNull()) @@ -215,3 +221,25 @@ QIcon IconCache::getMimeIcon(QString const& filename) const } #endif + +QIcon IconCache::getThemeIcon( + QString const& name, + QString const& fallbackName, + std::optional const& fallbackPixmap) const +{ + static auto const rtlSuffix = qApp->layoutDirection() == Qt::RightToLeft ? QStringLiteral("-rtl") : QString(); + + auto icon = QIcon::fromTheme(name + rtlSuffix); + + if (icon.isNull()) + { + icon = getThemeIcon(fallbackName + rtlSuffix); + } + + if (icon.isNull() && fallbackPixmap.has_value()) + { + icon = qApp->style()->standardIcon(*fallbackPixmap, nullptr); + } + + return icon; +} diff --git a/qt/IconCache.h b/qt/IconCache.h index ceff5525a..1f0b82798 100644 --- a/qt/IconCache.h +++ b/qt/IconCache.h @@ -12,11 +12,13 @@ #include #endif +#include #include #include #include #include +#include #include "Utils.h" // std::hash() @@ -44,6 +46,8 @@ public: QIcon guessMimeIcon(QString const& filename, QIcon fallback = {}) const; QIcon getMimeTypeIcon(QString const& mime_type, bool multifile) const; + QIcon getThemeIcon(QString const& name, std::optional const& fallback = {}) const; + protected: IconCache() = default; @@ -61,4 +65,9 @@ private: mutable std::unordered_map ext_to_icon_; QIcon getMimeIcon(QString const& filename) const; #endif + + QIcon getThemeIcon( + QString const& name, + QString const& fallbackName, + std::optional const& fallbackPixmap) const; }; diff --git a/qt/MainWindow.cc b/qt/MainWindow.cc index 7a77c04eb..0baa8b44e 100644 --- a/qt/MainWindow.cc +++ b/qt/MainWindow.cc @@ -30,6 +30,7 @@ #include "FilterBar.h" #include "Filters.h" #include "Formatter.h" +#include "IconCache.h" #include "MainWindow.h" #include "MakeDialog.h" #include "OptionsDialog.h" @@ -78,18 +79,6 @@ public: } }; -QIcon MainWindow::getStockIcon(QString const& name, int fallback) const -{ - QIcon icon = QIcon::fromTheme(name); - - if (icon.isNull() && fallback >= 0) - { - icon = style()->standardIcon(QStyle::StandardPixmap(fallback), nullptr, this); - } - - return icon; -} - QIcon MainWindow::addEmblem(QIcon base_icon, QStringList const& emblem_names) const { if (base_icon.isNull()) @@ -97,11 +86,12 @@ QIcon MainWindow::addEmblem(QIcon base_icon, QStringList const& emblem_names) co return base_icon; } + auto& icons = IconCache::get(); QIcon emblem_icon; for (QString const& emblem_name : emblem_names) { - emblem_icon = QIcon::fromTheme(emblem_name); + emblem_icon = icons.getThemeIcon(emblem_name); if (!emblem_icon.isNull()) { @@ -156,41 +146,43 @@ MainWindow::MainWindow(Session& session, Prefs& prefs, TorrentModel& model, bool ui_.listView->setStyle(lvp_style_.get()); ui_.listView->setAttribute(Qt::WA_MacShowFocusRect, false); + auto& icons = IconCache::get(); + // icons - QIcon const icon_play = getStockIcon(QStringLiteral("media-playback-start"), QStyle::SP_MediaPlay); - QIcon const icon_pause = getStockIcon(QStringLiteral("media-playback-pause"), QStyle::SP_MediaPause); - QIcon const icon_open = getStockIcon(QStringLiteral("document-open"), QStyle::SP_DialogOpenButton); + QIcon const icon_play = icons.getThemeIcon(QStringLiteral("media-playback-start"), QStyle::SP_MediaPlay); + QIcon const icon_pause = icons.getThemeIcon(QStringLiteral("media-playback-pause"), QStyle::SP_MediaPause); + QIcon const icon_open = icons.getThemeIcon(QStringLiteral("document-open"), QStyle::SP_DialogOpenButton); ui_.action_OpenFile->setIcon(icon_open); ui_.action_AddURL->setIcon( addEmblem(icon_open, QStringList() << QStringLiteral("emblem-web") << QStringLiteral("applications-internet"))); - ui_.action_New->setIcon(getStockIcon(QStringLiteral("document-new"), QStyle::SP_DesktopIcon)); - ui_.action_Properties->setIcon(getStockIcon(QStringLiteral("document-properties"), QStyle::SP_DesktopIcon)); - ui_.action_OpenFolder->setIcon(getStockIcon(QStringLiteral("folder-open"), QStyle::SP_DirOpenIcon)); + ui_.action_New->setIcon(icons.getThemeIcon(QStringLiteral("document-new"), QStyle::SP_DesktopIcon)); + ui_.action_Properties->setIcon(icons.getThemeIcon(QStringLiteral("document-properties"), QStyle::SP_DesktopIcon)); + ui_.action_OpenFolder->setIcon(icons.getThemeIcon(QStringLiteral("folder-open"), QStyle::SP_DirOpenIcon)); ui_.action_Start->setIcon(icon_play); ui_.action_StartNow->setIcon(icon_play); - ui_.action_Announce->setIcon(getStockIcon(QStringLiteral("network-transmit-receive"))); + ui_.action_Announce->setIcon(icons.getThemeIcon(QStringLiteral("network-transmit-receive"))); ui_.action_Pause->setIcon(icon_pause); - ui_.action_Remove->setIcon(getStockIcon(QStringLiteral("list-remove"), QStyle::SP_TrashIcon)); - ui_.action_Delete->setIcon(getStockIcon(QStringLiteral("edit-delete"), QStyle::SP_TrashIcon)); + ui_.action_Remove->setIcon(icons.getThemeIcon(QStringLiteral("list-remove"), QStyle::SP_TrashIcon)); + ui_.action_Delete->setIcon(icons.getThemeIcon(QStringLiteral("edit-delete"), QStyle::SP_TrashIcon)); ui_.action_StartAll->setIcon(icon_play); ui_.action_PauseAll->setIcon(icon_pause); - ui_.action_Quit->setIcon(getStockIcon(QStringLiteral("application-exit"))); - ui_.action_SelectAll->setIcon(getStockIcon(QStringLiteral("edit-select-all"))); - ui_.action_ReverseSortOrder->setIcon(getStockIcon(QStringLiteral("view-sort-ascending"), QStyle::SP_ArrowDown)); - ui_.action_Preferences->setIcon(getStockIcon(QStringLiteral("preferences-system"))); - ui_.action_Contents->setIcon(getStockIcon(QStringLiteral("help-contents"), QStyle::SP_DialogHelpButton)); - ui_.action_About->setIcon(getStockIcon(QStringLiteral("help-about"))); - ui_.action_QueueMoveTop->setIcon(getStockIcon(QStringLiteral("go-top"))); - ui_.action_QueueMoveUp->setIcon(getStockIcon(QStringLiteral("go-up"), QStyle::SP_ArrowUp)); - ui_.action_QueueMoveDown->setIcon(getStockIcon(QStringLiteral("go-down"), QStyle::SP_ArrowDown)); - ui_.action_QueueMoveBottom->setIcon(getStockIcon(QStringLiteral("go-bottom"))); + ui_.action_Quit->setIcon(icons.getThemeIcon(QStringLiteral("application-exit"))); + ui_.action_SelectAll->setIcon(icons.getThemeIcon(QStringLiteral("edit-select-all"))); + ui_.action_ReverseSortOrder->setIcon(icons.getThemeIcon(QStringLiteral("view-sort-ascending"), QStyle::SP_ArrowDown)); + ui_.action_Preferences->setIcon(icons.getThemeIcon(QStringLiteral("preferences-system"))); + ui_.action_Contents->setIcon(icons.getThemeIcon(QStringLiteral("help-contents"), QStyle::SP_DialogHelpButton)); + ui_.action_About->setIcon(icons.getThemeIcon(QStringLiteral("help-about"))); + ui_.action_QueueMoveTop->setIcon(icons.getThemeIcon(QStringLiteral("go-top"))); + ui_.action_QueueMoveUp->setIcon(icons.getThemeIcon(QStringLiteral("go-up"), QStyle::SP_ArrowUp)); + ui_.action_QueueMoveDown->setIcon(icons.getThemeIcon(QStringLiteral("go-down"), QStyle::SP_ArrowDown)); + ui_.action_QueueMoveBottom->setIcon(icons.getThemeIcon(QStringLiteral("go-bottom"))); - ui_.optionsButton->setIcon(getStockIcon(QStringLiteral("preferences-other"))); - ui_.statsModeButton->setIcon(getStockIcon(QStringLiteral("view-statistics"))); + ui_.optionsButton->setIcon(icons.getThemeIcon(QStringLiteral("preferences-other"))); + ui_.statsModeButton->setIcon(icons.getThemeIcon(QStringLiteral("view-statistics"))); - auto make_network_pixmap = [this](QString name, QSize size = { 16, 16 }) + auto make_network_pixmap = [this, &icons](QString name, QSize size = { 16, 16 }) { - return getStockIcon(name, QStyle::SP_DriveNetIcon).pixmap(size); + return icons.getThemeIcon(name, QStyle::SP_DriveNetIcon).pixmap(size); }; pixmap_network_error_ = make_network_pixmap(QStringLiteral("network-error")); pixmap_network_idle_ = make_network_pixmap(QStringLiteral("network-idle")); diff --git a/qt/MainWindow.h b/qt/MainWindow.h index 6b1b1ddef..2deca3aa9 100644 --- a/qt/MainWindow.h +++ b/qt/MainWindow.h @@ -123,7 +123,6 @@ private slots: void trayActivated(QSystemTrayIcon::ActivationReason); private: - QIcon getStockIcon(QString const&, int fallback = -1) const; QIcon addEmblem(QIcon icon, QStringList const& emblem_names) const; torrent_ids_t getSelectedTorrents(bool withMetadataOnly = false) const; diff --git a/qt/TorrentDelegate.cc b/qt/TorrentDelegate.cc index 3ef5e1aef..253e33121 100644 --- a/qt/TorrentDelegate.cc +++ b/qt/TorrentDelegate.cc @@ -17,6 +17,7 @@ #include #include "Formatter.h" +#include "IconCache.h" #include "Torrent.h" #include "TorrentDelegate.h" #include "TorrentModel.h" @@ -437,12 +438,7 @@ QIcon& TorrentDelegate::getWarningEmblem() const if (icon.isNull()) { - icon = QIcon::fromTheme(QStringLiteral("emblem-important")); - } - - if (icon.isNull()) - { - icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxWarning); + icon = IconCache::get().getThemeIcon(QStringLiteral("emblem-important"), QStyle::SP_MessageBoxWarning); } return icon;