Merge pull request #16587 from jagannatharjun/tracker-filter

Optimize torrent filters in GUI
This commit is contained in:
Chocobo1 2022-03-12 12:48:49 +08:00 committed by GitHub
commit 85b0a40a0e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 217 additions and 88 deletions

View file

@ -2090,6 +2090,11 @@ QVector<Torrent *> Session::torrents() const
return result;
}
qsizetype Session::torrentsCount() const
{
return m_torrents.size();
}
bool Session::addTorrent(const QString &source, const AddTorrentParams &params)
{
// `source`: .torrent file path/url or magnet uri

View file

@ -463,6 +463,7 @@ namespace BitTorrent
void startUpTorrents();
Torrent *findTorrent(const TorrentID &id) const;
QVector<Torrent *> torrents() const;
qsizetype torrentsCount() const;
bool hasActiveTorrents() const;
bool hasUnfinishedTorrents() const;
bool hasRunningSeed() const;

View file

@ -217,6 +217,7 @@ namespace BitTorrent
virtual bool hasMissingFiles() const = 0;
virtual bool hasError() const = 0;
virtual int queuePosition() const = 0;
virtual QVector<QString> trackerURLs() const = 0;
virtual QVector<TrackerEntry> trackers() const = 0;
virtual QVector<QUrl> urlSeeds() const = 0;
virtual QString error() const = 0;

View file

@ -517,6 +517,22 @@ void TorrentImpl::setAutoManaged(const bool enable)
m_nativeHandle.unset_flags(lt::torrent_flags::auto_managed);
}
QVector<QString> TorrentImpl::trackerURLs() const
{
const std::vector<lt::announce_entry> nativeTrackers = m_nativeHandle.trackers();
QVector<QString> urls;
urls.reserve(static_cast<decltype(urls)::size_type>(nativeTrackers.size()));
for (const lt::announce_entry &tracker : nativeTrackers)
{
const QString trackerURL = QString::fromStdString(tracker.url);
urls.push_back(trackerURL);
}
return urls;
}
QVector<TrackerEntry> TorrentImpl::trackers() const
{
const std::vector<lt::announce_entry> nativeTrackers = m_nativeHandle.trackers();

View file

@ -153,6 +153,7 @@ namespace BitTorrent
bool hasMissingFiles() const override;
bool hasError() const override;
int queuePosition() const override;
QVector<QString> trackerURLs() const override;
QVector<TrackerEntry> trackers() const override;
QVector<QUrl> urlSeeds() const override;
QString error() const override;

View file

@ -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<Digest32 *>(this)->m_dataPtr->hashString = QString::fromLatin1(raw.toHex());
}
return m_dataPtr->hashString;
}

View file

@ -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<BitTorrent::Torrent *> 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();
}
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));
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);
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<BitTorrent::Torrent *> 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)
@ -342,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
{
@ -364,7 +442,7 @@ void TrackerFiltersList::addItem(const QString &tracker, const BitTorrent::Torre
}
if (!trackerItem) return;
QSet<BitTorrent::TorrentID> &torrentIDs {m_trackers[host]};
QSet<BitTorrent::TorrentID> &torrentIDs {m_trackers[host].torrents};
torrentIDs.insert(id);
if (host == NULL_HOST)
@ -378,7 +456,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;
}
@ -401,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<BitTorrent::TorrentID> torrentIDs = m_trackers.value(host);
QSet<BitTorrent::TorrentID> torrentIDs = m_trackers.value(host).torrents;
if (torrentIDs.empty())
return;
torrentIDs.remove(id);
int row = 0;
QListWidgetItem *trackerItem = nullptr;
if (!host.isEmpty())
@ -441,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);
@ -459,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)
@ -630,12 +705,12 @@ void TrackerFiltersList::applyFilter(const int row)
void TrackerFiltersList::handleNewTorrent(BitTorrent::Torrent *const torrent)
{
const BitTorrent::TorrentID torrentID {torrent->id()};
const QVector<BitTorrent::TrackerEntry> trackers {torrent->trackers()};
for (const BitTorrent::TrackerEntry &tracker : trackers)
addItem(tracker.url, torrentID);
const QVector<QString> 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 +719,12 @@ void TrackerFiltersList::handleNewTorrent(BitTorrent::Torrent *const torrent)
void TrackerFiltersList::torrentAboutToBeDeleted(BitTorrent::Torrent *const torrent)
{
const BitTorrent::TorrentID torrentID {torrent->id()};
const QVector<BitTorrent::TrackerEntry> trackers {torrent->trackers()};
for (const BitTorrent::TrackerEntry &tracker : trackers)
removeItem(tracker.url, torrentID);
const QVector<QString> 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));
@ -681,13 +756,13 @@ QSet<BitTorrent::TorrentID> 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;
}
}

View file

@ -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<BitTorrent::Torrent *> 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<const BitTorrent::Torrent *, TorrentFilter::Type> 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
@ -123,7 +142,13 @@ private:
QSet<BitTorrent::TorrentID> getTorrentIDs(int row) const;
void downloadFavicon(const QString &url);
QHash<QString, QSet<BitTorrent::TorrentID>> m_trackers; // <tracker host, torrent IDs>
struct TrackerData
{
QSet<BitTorrent::TorrentID> torrents;
QListWidgetItem *item = nullptr;
};
QHash<QString, TrackerData> m_trackers;
QHash<BitTorrent::TorrentID, QSet<QString>> m_errors; // <torrent ID, tracker hosts>
QHash<BitTorrent::TorrentID, QSet<QString>> m_warnings; // <torrent ID, tracker hosts>
PathList m_iconPaths;