refactor: only update filterbar comboboxes when necessary (#1335)

* refactor: Torrent::update() returns a delta bitset

Setting up for followup PRs where, instead of doing expensive work every
time there is a change, we can be more fine-grained and do the work only
if the relevant Torrent properties changed.

* chore: make uncrustify happy

* refactor: update filterbar counts more selectively

Only rebuild the activity and tracker combobox models when the model's
size changes or when the relevant Torrent properties change.

Previously, rebuild would happen on any Torrent property change even if
the properties were unrelated to activity or trackers.

* chore: remove redundant "private:" key
This commit is contained in:
Charles Kerr 2020-06-23 18:54:08 -05:00 committed by GitHub
parent f37253a3ab
commit 33a421e97f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 127 additions and 88 deletions

View File

@ -6,6 +6,7 @@
*
*/
#include <cstdint> // uint64_t
#include <map>
#include <unordered_map>
@ -95,6 +96,12 @@ QString getCountString(int n)
return QStringLiteral("%L1").arg(n);
}
Torrent::fields_t constexpr TrackerFields = {
(uint64_t(1) << Torrent::TRACKER_STATS)
};
auto constexpr ActivityFields = FilterMode::TorrentFields;
} // namespace
void FilterBar::refreshTrackers()
@ -229,14 +236,14 @@ FilterBar::FilterBar(Prefs& prefs, TorrentModel const& torrents, TorrentFilter c
connect(&prefs_, SIGNAL(changed(int)), this, SLOT(refreshPref(int)));
connect(activity_combo_, SIGNAL(currentIndexChanged(int)), this, SLOT(onActivityIndexChanged(int)));
connect(tracker_combo_, SIGNAL(currentIndexChanged(int)), this, SLOT(onTrackerIndexChanged(int)));
connect(&torrents_, SIGNAL(modelReset()), this, SLOT(recountSoon()));
connect(&torrents_, SIGNAL(rowsInserted(QModelIndex, int, int)), this, SLOT(recountSoon()));
connect(&torrents_, SIGNAL(rowsRemoved(QModelIndex, int, int)), this, SLOT(recountSoon()));
connect(&torrents_, &TorrentModel::modelReset, this, &FilterBar::recountAllSoon);
connect(&torrents_, &TorrentModel::rowsInserted, this, &FilterBar::recountAllSoon);
connect(&torrents_, &TorrentModel::rowsRemoved, this, &FilterBar::recountAllSoon);
connect(&torrents_, &TorrentModel::torrentsChanged, this, &FilterBar::onTorrentsChanged);
connect(recount_timer_, SIGNAL(timeout()), this, SLOT(recount()));
connect(&qApp->faviconCache(), &FaviconCache::pixmapReady, this, &FilterBar::recountTrackersSoon);
recountSoon();
refreshTrackers();
recountAllSoon();
is_bootstrapping_ = false;
// initialize our state
@ -299,10 +306,17 @@ void FilterBar::refreshPref(int key)
void FilterBar::onTorrentsChanged(torrent_ids_t const& ids, Torrent::fields_t const& changed_fields)
{
Q_UNUSED(ids);
Q_UNUSED(changed_fields);
Q_UNUSED(ids)
recountSoon();
if ((changed_fields & TrackerFields).any())
{
recountTrackersSoon();
}
if ((changed_fields & ActivityFields).any())
{
recountActivitySoon();
}
}
void FilterBar::onTextChanged(QString const& str)
@ -352,8 +366,10 @@ void FilterBar::onActivityIndexChanged(int i)
****
***/
void FilterBar::recountSoon()
void FilterBar::recountSoon(Pending const& pending)
{
pending_ |= pending;
if (!recount_timer_->isActive())
{
recount_timer_->setSingleShot(true);
@ -365,16 +381,25 @@ void FilterBar::recount()
{
QAbstractItemModel* model = activity_combo_->model();
auto const torrents_per_mode = filter_.countTorrentsPerMode();
decltype(pending_) pending = {};
std::swap(pending_, pending);
for (int row = 0, n = model->rowCount(); row < n; ++row)
if (pending[ACTIVITY])
{
QModelIndex index = model->index(row, 0);
int const mode = index.data(ACTIVITY_ROLE).toInt();
int const count = torrents_per_mode[mode];
model->setData(index, count, FilterBarComboBox::CountRole);
model->setData(index, getCountString(count), FilterBarComboBox::CountStringRole);
auto const torrents_per_mode = filter_.countTorrentsPerMode();
for (int row = 0, n = model->rowCount(); row < n; ++row)
{
auto const index = model->index(row, 0);
auto const mode = index.data(ACTIVITY_ROLE).toInt();
auto const count = torrents_per_mode[mode];
model->setData(index, count, FilterBarComboBox::CountRole);
model->setData(index, getCountString(count), FilterBarComboBox::CountStringRole);
}
}
refreshTrackers();
if (pending[TRACKERS])
{
refreshTrackers();
}
}

View File

@ -8,8 +8,10 @@
#pragma once
#include <unordered_map>
#include <bitset>
#include <map>
#include <QString>
#include <QWidget>
#include "Torrent.h"
@ -40,16 +42,16 @@ private:
FilterBarComboBox* createActivityCombo();
void refreshTrackers();
private slots:
void recountSoon();
void recount();
void refreshPref(int key);
void onActivityIndexChanged(int index);
void onTrackerIndexChanged(int index);
void onTextChanged(QString const&);
void onTorrentsChanged(torrent_ids_t const&, Torrent::fields_t const& fields);
enum
{
ACTIVITY,
TRACKERS,
NUM_FLAGS
};
using Pending = std::bitset<NUM_FLAGS>;
private:
Prefs& prefs_;
TorrentModel const& torrents_;
TorrentFilter const& filter_;
@ -61,5 +63,19 @@ private:
QStandardItemModel* tracker_model_ = {};
QTimer* recount_timer_ = {};
QLineEdit* line_edit_ = {};
Pending pending_ = {};
bool is_bootstrapping_ = {};
private slots:
void recount();
void recountSoon(Pending const& fields);
void recountActivitySoon() { recountSoon(Pending().set(ACTIVITY)); }
void recountTrackersSoon() { recountSoon(Pending().set(TRACKERS)); }
void recountAllSoon() { recountSoon(Pending().set(ACTIVITY).set(TRACKERS)); }
void refreshPref(int key);
void onActivityIndexChanged(int index);
void onTextChanged(QString const&);
void onTorrentsChanged(torrent_ids_t const&, Torrent::fields_t const& fields);
void onTrackerIndexChanged(int index);
};

View File

@ -6,6 +6,8 @@
*
*/
#include <cstdint> // uint64_t
#include "Filters.h"
std::array<QString, FilterMode::NUM_MODES> const FilterMode::Names =
@ -33,6 +35,41 @@ int FilterMode::modeFromName(QString const& name)
return FilterMode().mode(); // use the default value
}
// NB: if you change this function, update TorrentFields too
bool FilterMode::test(Torrent const& tor, int mode)
{
switch (mode)
{
case SHOW_ACTIVE:
return tor.peersWeAreUploadingTo() > 0 || tor.peersWeAreDownloadingFrom() > 0 || tor.isVerifying();
case SHOW_DOWNLOADING:
return tor.isDownloading() || tor.isWaitingToDownload();
case SHOW_ERROR:
return tor.hasError();
case SHOW_FINISHED:
return tor.isFinished();
case SHOW_PAUSED:
return tor.isPaused();
case SHOW_SEEDING:
return tor.isSeeding() || tor.isWaitingToSeed();
case SHOW_VERIFYING:
return tor.isVerifying() || tor.isWaitingToVerify();
default: // SHOW_ALL
return true;
}
}
/***
****
***/
std::array<QString, SortMode::NUM_MODES> const SortMode::Names =
{
QStringLiteral("sort-by-activity"),

View File

@ -14,6 +14,8 @@
#include <QString>
#include <QVariant>
#include "Torrent.h"
class FilterMode
{
public:
@ -58,6 +60,19 @@ public:
return Names[mode];
}
/* The Torrent properties that can affect this filter.
When one of these changes, it's time to refilter. */
static Torrent::fields_t constexpr TorrentFields = {
(uint64_t(1) << Torrent::ERROR) |
(uint64_t(1) << Torrent::IS_FINISHED) |
(uint64_t(1) << Torrent::PEERS_GETTING_FROM_US) |
(uint64_t(1) << Torrent::PEERS_SENDING_TO_US) |
(uint64_t(1) << Torrent::STATUS)
};
static bool test(Torrent const& tor, int mode);
bool test(Torrent const& tor) const { return test(tor, mode()); }
private:
int mode_;

View File

@ -231,79 +231,28 @@ bool TorrentFilter::lessThan(QModelIndex const& left, QModelIndex const& right)
****
***/
bool TorrentFilter::trackerFilterAcceptsTorrent(Torrent const* tor, QString const& tracker) const
{
return tracker.isEmpty() || tor->hasTrackerSubstring(tracker);
}
bool TorrentFilter::activityFilterAcceptsTorrent(Torrent const* tor, FilterMode const& m) const
{
bool accepts;
switch (m.mode())
{
case FilterMode::SHOW_ACTIVE:
accepts = tor->peersWeAreUploadingTo() > 0 || tor->peersWeAreDownloadingFrom() > 0 || tor->isVerifying();
break;
case FilterMode::SHOW_DOWNLOADING:
accepts = tor->isDownloading() || tor->isWaitingToDownload();
break;
case FilterMode::SHOW_SEEDING:
accepts = tor->isSeeding() || tor->isWaitingToSeed();
break;
case FilterMode::SHOW_PAUSED:
accepts = tor->isPaused();
break;
case FilterMode::SHOW_FINISHED:
accepts = tor->isFinished();
break;
case FilterMode::SHOW_VERIFYING:
accepts = tor->isVerifying() || tor->isWaitingToVerify();
break;
case FilterMode::SHOW_ERROR:
accepts = tor->hasError();
break;
default: // FilterMode::SHOW_ALL
accepts = true;
break;
}
return accepts;
}
bool TorrentFilter::filterAcceptsRow(int source_row, QModelIndex const& source_parent) const
{
QModelIndex child_index = sourceModel()->index(source_row, 0, source_parent);
auto const* tor = child_index.model()->data(child_index, TorrentModel::TorrentRole).value<Torrent const*>();
auto const& tor = *child_index.model()->data(child_index, TorrentModel::TorrentRole).value<Torrent const*>();
bool accepts = true;
if (accepts)
{
auto const m = prefs_.get<FilterMode>(Prefs::FILTER_MODE);
accepts = activityFilterAcceptsTorrent(tor, m);
accepts = m.test(tor);
}
if (accepts)
{
QString const trackers = prefs_.getString(Prefs::FILTER_TRACKERS);
accepts = trackerFilterAcceptsTorrent(tor, trackers);
auto const name = prefs_.getString(Prefs::FILTER_TRACKERS);
accepts = name.isEmpty() || tor.hasTrackerSubstring(name);
}
if (accepts)
{
QString const text = prefs_.getString(Prefs::FILTER_TEXT);
if (!text.isEmpty())
{
accepts = tor->name().contains(text, Qt::CaseInsensitive);
}
auto const text = prefs_.getString(Prefs::FILTER_TEXT);
accepts = text.isEmpty() || tor.name().contains(text, Qt::CaseInsensitive);
}
return accepts;
@ -317,7 +266,7 @@ std::array<int, FilterMode::NUM_MODES> TorrentFilter::countTorrentsPerMode() con
{
for (int mode = 0; mode < FilterMode::NUM_MODES; ++mode)
{
if (activityFilterAcceptsTorrent(tor, mode))
if (FilterMode::test(*tor, mode))
{
++torrent_counts[mode];
}

View File

@ -47,9 +47,6 @@ private slots:
void refilter();
private:
bool activityFilterAcceptsTorrent(Torrent const* tor, FilterMode const& mode) const;
bool trackerFilterAcceptsTorrent(Torrent const* tor, QString const& tracker) const;
QTimer refilter_timer_;
Prefs const& prefs_;
};