From cd9ed1706d56805d5e1e903da8a34bd20b6ce0a5 Mon Sep 17 00:00:00 2001 From: Prince Gupta Date: Sat, 5 Mar 2022 12:10:10 +0530 Subject: [PATCH 1/5] Improve torrent event handling in TrackerFiltersList --- src/base/bittorrent/torrent.h | 1 + src/base/bittorrent/torrentimpl.cpp | 16 ++++++++++++++++ src/base/bittorrent/torrentimpl.h | 1 + src/gui/transferlistfilterswidget.cpp | 16 ++++++++-------- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/base/bittorrent/torrent.h b/src/base/bittorrent/torrent.h index aaa548d70..526255b88 100644 --- a/src/base/bittorrent/torrent.h +++ b/src/base/bittorrent/torrent.h @@ -217,6 +217,7 @@ namespace BitTorrent virtual bool hasMissingFiles() const = 0; virtual bool hasError() const = 0; virtual int queuePosition() const = 0; + virtual QVector trackerURLs() const = 0; virtual QVector trackers() const = 0; virtual QVector urlSeeds() const = 0; virtual QString error() const = 0; diff --git a/src/base/bittorrent/torrentimpl.cpp b/src/base/bittorrent/torrentimpl.cpp index 030c87f21..f0330d7f1 100644 --- a/src/base/bittorrent/torrentimpl.cpp +++ b/src/base/bittorrent/torrentimpl.cpp @@ -517,6 +517,22 @@ void TorrentImpl::setAutoManaged(const bool enable) m_nativeHandle.unset_flags(lt::torrent_flags::auto_managed); } +QVector TorrentImpl::trackerURLs() const +{ + const std::vector nativeTrackers = m_nativeHandle.trackers(); + + QVector urls; + urls.reserve(static_cast(nativeTrackers.size())); + + for (const lt::announce_entry &tracker : nativeTrackers) + { + const QString trackerURL = QString::fromStdString(tracker.url); + urls.push_back(trackerURL); + } + + return urls; +} + QVector TorrentImpl::trackers() const { const std::vector nativeTrackers = m_nativeHandle.trackers(); diff --git a/src/base/bittorrent/torrentimpl.h b/src/base/bittorrent/torrentimpl.h index a83e144e3..0fee0d8e1 100644 --- a/src/base/bittorrent/torrentimpl.h +++ b/src/base/bittorrent/torrentimpl.h @@ -153,6 +153,7 @@ namespace BitTorrent bool hasMissingFiles() const override; bool hasError() const override; int queuePosition() const override; + QVector trackerURLs() const override; QVector trackers() const override; QVector urlSeeds() const override; QString error() const override; diff --git a/src/gui/transferlistfilterswidget.cpp b/src/gui/transferlistfilterswidget.cpp index 05268a20e..9f154c51e 100644 --- a/src/gui/transferlistfilterswidget.cpp +++ b/src/gui/transferlistfilterswidget.cpp @@ -630,12 +630,12 @@ void TrackerFiltersList::applyFilter(const int row) void TrackerFiltersList::handleNewTorrent(BitTorrent::Torrent *const torrent) { const BitTorrent::TorrentID torrentID {torrent->id()}; - const QVector trackers {torrent->trackers()}; - for (const BitTorrent::TrackerEntry &tracker : trackers) - addItem(tracker.url, torrentID); + const QVector trackerURLs {torrent->trackerURLs()}; + for (const QString &trackerURL : trackerURLs) + addItem(trackerURL, torrentID); // Check for trackerless torrent - if (trackers.isEmpty()) + if (trackerURLs.isEmpty()) addItem(NULL_HOST, torrentID); item(ALL_ROW)->setText(tr("All (%1)", "this is for the tracker filter").arg(++m_totalTorrents)); @@ -644,12 +644,12 @@ void TrackerFiltersList::handleNewTorrent(BitTorrent::Torrent *const torrent) void TrackerFiltersList::torrentAboutToBeDeleted(BitTorrent::Torrent *const torrent) { const BitTorrent::TorrentID torrentID {torrent->id()}; - const QVector trackers {torrent->trackers()}; - for (const BitTorrent::TrackerEntry &tracker : trackers) - removeItem(tracker.url, torrentID); + const QVector trackerURLs {torrent->trackerURLs()}; + for (const QString &trackerURL : trackerURLs) + removeItem(trackerURL, torrentID); // Check for trackerless torrent - if (trackers.isEmpty()) + if (trackerURLs.isEmpty()) removeItem(NULL_HOST, torrentID); item(ALL_ROW)->setText(tr("All (%1)", "this is for the tracker filter").arg(--m_totalTorrents)); From 25be00931afc440bb6b25697eda3372e6fb64997 Mon Sep 17 00:00:00 2001 From: Prince Gupta Date: Sat, 5 Mar 2022 13:02:57 +0530 Subject: [PATCH 2/5] Optimize tracker insertion in TrackerFiltersList --- src/gui/transferlistfilterswidget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/transferlistfilterswidget.cpp b/src/gui/transferlistfilterswidget.cpp index 9f154c51e..8d313a39d 100644 --- a/src/gui/transferlistfilterswidget.cpp +++ b/src/gui/transferlistfilterswidget.cpp @@ -378,7 +378,7 @@ void TrackerFiltersList::addItem(const QString &tracker, const BitTorrent::Torre trackerItem->setText(QString::fromLatin1("%1 (%2)").arg(host, QString::number(torrentIDs.size()))); if (exists) { - if (currentRow() == rowFromTracker(host)) + if (trackerFromRow(currentRow()) == host) applyFilter(currentRow()); return; } From 30319e51e55ed7740b7bdc4b75563f441f9ccb71 Mon Sep 17 00:00:00 2001 From: Prince Gupta Date: Sat, 5 Mar 2022 14:04:27 +0530 Subject: [PATCH 3/5] Improve Status Filter replaces quadratic update operation with linear --- src/base/bittorrent/session.cpp | 5 + src/base/bittorrent/session.h | 1 + src/gui/transferlistfilterswidget.cpp | 193 ++++++++++++++++++-------- src/gui/transferlistfilterswidget.h | 21 ++- 4 files changed, 161 insertions(+), 59 deletions(-) diff --git a/src/base/bittorrent/session.cpp b/src/base/bittorrent/session.cpp index 2ec5dbf4c..28be659de 100644 --- a/src/base/bittorrent/session.cpp +++ b/src/base/bittorrent/session.cpp @@ -2085,6 +2085,11 @@ QVector Session::torrents() const return result; } +qsizetype Session::torrentsCount() const +{ + return m_torrents.size(); +} + bool Session::addTorrent(const QString &source, const AddTorrentParams ¶ms) { // `source`: .torrent file path/url or magnet uri diff --git a/src/base/bittorrent/session.h b/src/base/bittorrent/session.h index b478f1a31..376868a86 100644 --- a/src/base/bittorrent/session.h +++ b/src/base/bittorrent/session.h @@ -459,6 +459,7 @@ namespace BitTorrent void startUpTorrents(); Torrent *findTorrent(const TorrentID &id) const; QVector torrents() const; + qsizetype torrentsCount() const; bool hasActiveTorrents() const; bool hasUnfinishedTorrents() const; bool hasRunningSeed() const; diff --git a/src/gui/transferlistfilterswidget.cpp b/src/gui/transferlistfilterswidget.cpp index 8d313a39d..86bc01a38 100644 --- a/src/gui/transferlistfilterswidget.cpp +++ b/src/gui/transferlistfilterswidget.cpp @@ -169,13 +169,6 @@ void BaseFilterWidget::toggleFilter(bool checked) StatusFilterWidget::StatusFilterWidget(QWidget *parent, TransferListWidget *transferList) : BaseFilterWidget(parent, transferList) { - connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentLoaded - , this, &StatusFilterWidget::updateTorrentNumbers); - connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentsUpdated - , this, &StatusFilterWidget::updateTorrentNumbers); - connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentAboutToBeRemoved - , this, &StatusFilterWidget::updateTorrentNumbers); - // Add status filters auto *all = new QListWidgetItem(this); all->setData(Qt::DisplayRole, tr("All (0)", "this is for the status filter")); @@ -220,6 +213,11 @@ StatusFilterWidget::StatusFilterWidget(QWidget *parent, TransferListWidget *tran const Preferences *const pref = Preferences::instance(); setCurrentRow(pref->getTransSelFilter(), QItemSelectionModel::SelectCurrent); toggleFilter(pref->getStatusFilterState()); + + populate(); + + connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentsUpdated + , this, &StatusFilterWidget::handleTorrentsUpdated); } StatusFilterWidget::~StatusFilterWidget() @@ -227,63 +225,87 @@ StatusFilterWidget::~StatusFilterWidget() Preferences::instance()->setTransSelFilter(currentRow()); } -void StatusFilterWidget::updateTorrentNumbers() +void StatusFilterWidget::populate() { - int nbDownloading = 0; - int nbSeeding = 0; - int nbCompleted = 0; - int nbResumed = 0; - int nbPaused = 0; - int nbActive = 0; - int nbInactive = 0; - int nbStalled = 0; - int nbStalledUploading = 0; - int nbStalledDownloading = 0; - int nbChecking = 0; - int nbErrored = 0; + m_torrentsStatus.clear(); const QVector torrents = BitTorrent::Session::instance()->torrents(); for (const BitTorrent::Torrent *torrent : torrents) { - if (torrent->isDownloading()) - ++nbDownloading; - if (torrent->isUploading()) - ++nbSeeding; - if (torrent->isCompleted()) - ++nbCompleted; - if (torrent->isResumed()) - ++nbResumed; - if (torrent->isPaused()) - ++nbPaused; - if (torrent->isActive()) - ++nbActive; - if (torrent->isInactive()) - ++nbInactive; - if (torrent->state() == BitTorrent::TorrentState::StalledUploading) - ++nbStalledUploading; - if (torrent->state() == BitTorrent::TorrentState::StalledDownloading) - ++nbStalledDownloading; - if (torrent->isChecking()) - ++nbChecking; - if (torrent->isErrored()) - ++nbErrored; + updateTorrentStatus(torrent); } - nbStalled = nbStalledUploading + nbStalledDownloading; + updateTexts(); +} + +void StatusFilterWidget::updateTorrentStatus(const BitTorrent::Torrent *torrent) +{ + const auto update = [this, torrent](const TorrentFilter::Type status, const bool insert, int &count) + { + const bool contains = m_torrentsStatus.contains(torrent, status); + if (insert && !contains) + { + ++count; + m_torrentsStatus.insert(torrent, status); + } + else if (!insert && contains) + { + --count; + m_torrentsStatus.remove(torrent, status); + } + }; + + update(TorrentFilter::Downloading, torrent->isDownloading(), m_nbDownloading); + + update(TorrentFilter::Seeding, torrent->isUploading(), m_nbSeeding); + + update(TorrentFilter::Completed, torrent->isCompleted(), m_nbCompleted); + + update(TorrentFilter::Resumed, torrent->isResumed(), m_nbResumed); + + update(TorrentFilter::Paused, torrent->isPaused(), m_nbPaused); + + update(TorrentFilter::Active, torrent->isActive(), m_nbActive); + + update(TorrentFilter::Inactive, torrent->isInactive(), m_nbInactive); - item(TorrentFilter::All)->setData(Qt::DisplayRole, tr("All (%1)").arg(torrents.count())); - item(TorrentFilter::Downloading)->setData(Qt::DisplayRole, tr("Downloading (%1)").arg(nbDownloading)); - item(TorrentFilter::Seeding)->setData(Qt::DisplayRole, tr("Seeding (%1)").arg(nbSeeding)); - item(TorrentFilter::Completed)->setData(Qt::DisplayRole, tr("Completed (%1)").arg(nbCompleted)); - item(TorrentFilter::Resumed)->setData(Qt::DisplayRole, tr("Resumed (%1)").arg(nbResumed)); - item(TorrentFilter::Paused)->setData(Qt::DisplayRole, tr("Paused (%1)").arg(nbPaused)); - item(TorrentFilter::Active)->setData(Qt::DisplayRole, tr("Active (%1)").arg(nbActive)); - item(TorrentFilter::Inactive)->setData(Qt::DisplayRole, tr("Inactive (%1)").arg(nbInactive)); - item(TorrentFilter::Stalled)->setData(Qt::DisplayRole, tr("Stalled (%1)").arg(nbStalled)); - item(TorrentFilter::StalledUploading)->setData(Qt::DisplayRole, tr("Stalled Uploading (%1)").arg(nbStalledUploading)); - item(TorrentFilter::StalledDownloading)->setData(Qt::DisplayRole, tr("Stalled Downloading (%1)").arg(nbStalledDownloading)); - item(TorrentFilter::Checking)->setData(Qt::DisplayRole, tr("Checking (%1)").arg(nbChecking)); - item(TorrentFilter::Errored)->setData(Qt::DisplayRole, tr("Errored (%1)").arg(nbErrored)); + const bool isStalledUploading = (torrent->state() == BitTorrent::TorrentState::StalledUploading); + update(TorrentFilter::StalledUploading, isStalledUploading, m_nbStalledUploading); + + const bool isStalledDownloading = (torrent->state() == BitTorrent::TorrentState::StalledDownloading); + update(TorrentFilter::StalledDownloading, isStalledDownloading, m_nbStalledDownloading); + + update(TorrentFilter::Checking, torrent->isChecking(), m_nbChecking); + + update(TorrentFilter::Errored, torrent->isErrored(), m_nbErrored); + + m_nbStalled = m_nbStalledUploading + m_nbStalledDownloading; +} + +void StatusFilterWidget::updateTexts() +{ + const qsizetype torrentsCount = BitTorrent::Session::instance()->torrentsCount(); + item(TorrentFilter::All)->setData(Qt::DisplayRole, tr("All (%1)").arg(torrentsCount)); + item(TorrentFilter::Downloading)->setData(Qt::DisplayRole, tr("Downloading (%1)").arg(m_nbDownloading)); + item(TorrentFilter::Seeding)->setData(Qt::DisplayRole, tr("Seeding (%1)").arg(m_nbSeeding)); + item(TorrentFilter::Completed)->setData(Qt::DisplayRole, tr("Completed (%1)").arg(m_nbCompleted)); + item(TorrentFilter::Resumed)->setData(Qt::DisplayRole, tr("Resumed (%1)").arg(m_nbResumed)); + item(TorrentFilter::Paused)->setData(Qt::DisplayRole, tr("Paused (%1)").arg(m_nbPaused)); + item(TorrentFilter::Active)->setData(Qt::DisplayRole, tr("Active (%1)").arg(m_nbActive)); + item(TorrentFilter::Inactive)->setData(Qt::DisplayRole, tr("Inactive (%1)").arg(m_nbInactive)); + item(TorrentFilter::Stalled)->setData(Qt::DisplayRole, tr("Stalled (%1)").arg(m_nbStalled)); + item(TorrentFilter::StalledUploading)->setData(Qt::DisplayRole, tr("Stalled Uploading (%1)").arg(m_nbStalledUploading)); + item(TorrentFilter::StalledDownloading)->setData(Qt::DisplayRole, tr("Stalled Downloading (%1)").arg(m_nbStalledDownloading)); + item(TorrentFilter::Checking)->setData(Qt::DisplayRole, tr("Checking (%1)").arg(m_nbChecking)); + item(TorrentFilter::Errored)->setData(Qt::DisplayRole, tr("Errored (%1)").arg(m_nbErrored)); +} + +void StatusFilterWidget::handleTorrentsUpdated(const QVector torrents) +{ + for (const BitTorrent::Torrent *torrent : torrents) + updateTorrentStatus(torrent); + + updateTexts(); } void StatusFilterWidget::showMenu() @@ -306,9 +328,64 @@ void StatusFilterWidget::applyFilter(int row) transferList->applyStatusFilter(row); } -void StatusFilterWidget::handleNewTorrent(BitTorrent::Torrent *const) {} +void StatusFilterWidget::handleNewTorrent(BitTorrent::Torrent *const torrent) +{ + updateTorrentStatus(torrent); + updateTexts(); +} -void StatusFilterWidget::torrentAboutToBeDeleted(BitTorrent::Torrent *const) {} +void StatusFilterWidget::torrentAboutToBeDeleted(BitTorrent::Torrent *const torrent) +{ + for (const TorrentFilter::Type status : m_torrentsStatus.values(torrent)) + { + switch (status) + { + case TorrentFilter::Downloading: + --m_nbDownloading; + break; + case TorrentFilter::Seeding: + --m_nbSeeding; + break; + case TorrentFilter::Completed: + --m_nbCompleted; + break; + case TorrentFilter::Resumed: + --m_nbResumed; + break; + case TorrentFilter::Paused: + --m_nbPaused; + break; + case TorrentFilter::Active: + --m_nbActive; + break; + case TorrentFilter::Inactive: + --m_nbInactive; + break; + case TorrentFilter::StalledUploading: + --m_nbStalledUploading; + break; + case TorrentFilter::StalledDownloading: + --m_nbStalledDownloading; + break; + case TorrentFilter::Checking: + --m_nbChecking; + break; + case TorrentFilter::Errored: + --m_nbErrored; + break; + + default: + Q_ASSERT(false); + break; + } + } + + m_nbStalled = m_nbStalledUploading + m_nbStalledDownloading; + + m_torrentsStatus.remove(torrent); + + updateTexts(); +} TrackerFiltersList::TrackerFiltersList(QWidget *parent, TransferListWidget *transferList, const bool downloadFavicon) : BaseFilterWidget(parent, transferList) diff --git a/src/gui/transferlistfilterswidget.h b/src/gui/transferlistfilterswidget.h index c60f6a4ef..68c55422d 100644 --- a/src/gui/transferlistfilterswidget.h +++ b/src/gui/transferlistfilterswidget.h @@ -35,6 +35,7 @@ #include "base/bittorrent/infohash.h" #include "base/bittorrent/session.h" #include "base/bittorrent/trackerentry.h" +#include "base/torrentfilter.h" #include "base/path.h" class QCheckBox; @@ -81,7 +82,7 @@ public: ~StatusFilterWidget() override; private slots: - void updateTorrentNumbers(); + void handleTorrentsUpdated(const QVector torrents); private: // These 4 methods are virtual slots in the base class. @@ -90,6 +91,24 @@ private: void applyFilter(int row) override; void handleNewTorrent(BitTorrent::Torrent *const) override; void torrentAboutToBeDeleted(BitTorrent::Torrent *const) override; + + void populate(); + void updateTorrentStatus(const BitTorrent::Torrent *torrent); + void updateTexts(); + + QMultiHash m_torrentsStatus; + int m_nbDownloading = 0; + int m_nbSeeding = 0; + int m_nbCompleted = 0; + int m_nbResumed = 0; + int m_nbPaused = 0; + int m_nbActive = 0; + int m_nbInactive = 0; + int m_nbStalled = 0; + int m_nbStalledUploading = 0; + int m_nbStalledDownloading = 0; + int m_nbChecking = 0; + int m_nbErrored = 0; }; class TrackerFiltersList final : public BaseFilterWidget From da12daffee4f778c65458f4cfba6710a6ce74c3c Mon Sep 17 00:00:00 2001 From: Prince Gupta Date: Sat, 5 Mar 2022 16:08:10 +0530 Subject: [PATCH 4/5] Cache tracker item in TrackerFilterList --- src/gui/transferlistfilterswidget.cpp | 32 +++++++++++++-------------- src/gui/transferlistfilterswidget.h | 8 ++++++- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/gui/transferlistfilterswidget.cpp b/src/gui/transferlistfilterswidget.cpp index 86bc01a38..da0ab15ef 100644 --- a/src/gui/transferlistfilterswidget.cpp +++ b/src/gui/transferlistfilterswidget.cpp @@ -419,17 +419,18 @@ TrackerFiltersList::~TrackerFiltersList() void TrackerFiltersList::addItem(const QString &tracker, const BitTorrent::TorrentID &id) { const QString host {getHost(tracker)}; - const bool exists {m_trackers.contains(host)}; + const auto existingDataItr = m_trackers.find(host); + const bool exists {existingDataItr != m_trackers.end()}; QListWidgetItem *trackerItem {nullptr}; if (exists) { - if (m_trackers.value(host).contains(id)) + if (existingDataItr->torrents.contains(id)) return; - trackerItem = item((host == NULL_HOST) - ? TRACKERLESS_ROW - : rowFromTracker(host)); + trackerItem = (host == NULL_HOST) + ? item(TRACKERLESS_ROW) + : existingDataItr->item; } else { @@ -441,7 +442,7 @@ void TrackerFiltersList::addItem(const QString &tracker, const BitTorrent::Torre } if (!trackerItem) return; - QSet &torrentIDs {m_trackers[host]}; + QSet &torrentIDs {m_trackers[host].torrents}; torrentIDs.insert(id); if (host == NULL_HOST) @@ -478,13 +479,12 @@ void TrackerFiltersList::addItem(const QString &tracker, const BitTorrent::Torre void TrackerFiltersList::removeItem(const QString &trackerURL, const BitTorrent::TorrentID &id) { const QString host = getHost(trackerURL); - QSet torrentIDs = m_trackers.value(host); + QSet torrentIDs = m_trackers.value(host).torrents; if (torrentIDs.empty()) return; torrentIDs.remove(id); - int row = 0; QListWidgetItem *trackerItem = nullptr; if (!host.isEmpty()) @@ -518,12 +518,11 @@ void TrackerFiltersList::removeItem(const QString &trackerURL, const BitTorrent: } } - row = rowFromTracker(host); - trackerItem = item(row); + trackerItem = m_trackers.value(host).item; if (torrentIDs.empty()) { - if (currentRow() == row) + if (currentItem() == trackerItem) setCurrentRow(0, QItemSelectionModel::SelectCurrent); delete trackerItem; m_trackers.remove(host); @@ -536,15 +535,14 @@ void TrackerFiltersList::removeItem(const QString &trackerURL, const BitTorrent: } else { - row = 1; trackerItem = item(TRACKERLESS_ROW); trackerItem->setText(tr("Trackerless (%1)").arg(torrentIDs.size())); } - m_trackers.insert(host, torrentIDs); + m_trackers.insert(host, {torrentIDs, trackerItem}); - if (currentRow() == row) - applyFilter(row); + if (currentItem() == trackerItem) + applyFilter(currentRow()); } void TrackerFiltersList::changeTrackerless(const bool trackerless, const BitTorrent::TorrentID &id) @@ -758,13 +756,13 @@ QSet TrackerFiltersList::getTorrentIDs(const int row) con switch (row) { case TRACKERLESS_ROW: - return m_trackers.value(NULL_HOST); + return m_trackers.value(NULL_HOST).torrents; case ERROR_ROW: return {m_errors.keyBegin(), m_errors.keyEnd()}; case WARNING_ROW: return {m_warnings.keyBegin(), m_warnings.keyEnd()}; default: - return m_trackers.value(trackerFromRow(row)); + return m_trackers.value(trackerFromRow(row)).torrents; } } diff --git a/src/gui/transferlistfilterswidget.h b/src/gui/transferlistfilterswidget.h index 68c55422d..6f53071ad 100644 --- a/src/gui/transferlistfilterswidget.h +++ b/src/gui/transferlistfilterswidget.h @@ -142,7 +142,13 @@ private: QSet getTorrentIDs(int row) const; void downloadFavicon(const QString &url); - QHash> m_trackers; // + struct TrackerData + { + QSet torrents; + QListWidgetItem *item = nullptr; + }; + + QHash m_trackers; QHash> m_errors; // QHash> m_warnings; // PathList m_iconPaths; From ad6e2b4b9439744b4939cf2b449e0c5fd2253e6c Mon Sep 17 00:00:00 2001 From: Prince Gupta Date: Sun, 6 Mar 2022 18:43:43 +0530 Subject: [PATCH 5/5] Create hash string of Digest on demand most of the time hash string is not needed and InfoHash is often used as temporaries for torrent searching in handling of torrent alerts. This improves the creation time of Infohash --- src/base/digest32.h | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/base/digest32.h b/src/base/digest32.h index f24b448f3..bf007f9d3 100644 --- a/src/base/digest32.h +++ b/src/base/digest32.h @@ -50,8 +50,7 @@ public: { m_dataPtr->valid = true; m_dataPtr->nativeDigest = nativeDigest; - const QByteArray raw = QByteArray::fromRawData(nativeDigest.data(), length()); - m_dataPtr->hashString = QString::fromLatin1(raw.toHex()); + m_dataPtr->hashString.clear(); // hashString is created on demand } static constexpr int length() @@ -91,6 +90,12 @@ public: QString toString() const { + if (m_dataPtr->hashString.isEmpty()) + { + const QByteArray raw = QByteArray::fromRawData(m_dataPtr->nativeDigest.data(), length()); + const_cast(this)->m_dataPtr->hashString = QString::fromLatin1(raw.toHex()); + } + return m_dataPtr->hashString; }