refractor: simplify torrent model signal emissions (#1044)

refractor: simplify torrent model signal emissions

The Torrent->Application signal connections make up for about 5% of the
app's memory use. Move this to the TorrentModel so that there are only a
handful of signal connections.

Moving signals to TorrentModel means batch change signals can be emitted
instead of per-change-per-torrent emissions and can be handled that way.
This commit is contained in:
Charles Kerr 2019-11-09 08:44:40 -06:00 committed by GitHub
parent 4c79758dbc
commit d857a5821a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 715 additions and 679 deletions

View File

@ -293,47 +293,36 @@ Application::Application(int& argc, char** argv) :
myWindow = new MainWindow(*mySession, *myPrefs, *myModel, minimized);
myWatchDir = new WatchDir(*myModel);
// when the session gets torrent info, update the model
connect(mySession, SIGNAL(torrentsUpdated(tr_variant*, bool)), myModel, SLOT(updateTorrents(tr_variant*, bool)));
connect(mySession, SIGNAL(torrentsUpdated(tr_variant*, bool)), myWindow, SLOT(refreshActionSensitivity()));
connect(mySession, SIGNAL(torrentsRemoved(tr_variant*)), myModel, SLOT(removeTorrents(tr_variant*)));
// when the session source gets changed, request a full refresh
connect(mySession, SIGNAL(sourceChanged()), this, SLOT(onSessionSourceChanged()));
// when the model sees a torrent for the first time, ask the session for full info on it
connect(myModel, SIGNAL(torrentsAdded(QSet<int>)), mySession, SLOT(initTorrents(QSet<int>)));
connect(myModel, SIGNAL(torrentsAdded(QSet<int>)), this, SLOT(onTorrentsAdded(QSet<int>)));
mySession->initTorrents();
mySession->refreshSessionStats();
// when torrents are added to the watch directory, tell the session
connect(myWatchDir, SIGNAL(torrentFileAdded(QString)), this, SLOT(addTorrent(QString)));
connect(myModel, &TorrentModel::torrentsAdded, this, &Application::onTorrentsAdded);
connect(myModel, &TorrentModel::torrentsChanged, myWindow, &MainWindow::refreshActionSensitivity);
connect(myModel, &TorrentModel::torrentsCompleted, this, &Application::onTorrentsCompleted);
connect(myModel, &TorrentModel::torrentsNeedInfo, this, &Application::onTorrentsNeedInfo);
connect(myPrefs, &Prefs::changed, this, &Application::refreshPref);
connect(mySession, &Session::sourceChanged, this, &Application::onSessionSourceChanged);
connect(mySession, &Session::torrentsRemoved, myModel, &TorrentModel::removeTorrents);
connect(mySession, &Session::torrentsUpdated, myModel, &TorrentModel::updateTorrents);
connect(myWatchDir, &WatchDir::torrentFileAdded, this, &Application::addTorrent);
// init from preferences
QList<int> initKeys;
initKeys << Prefs::DIR_WATCH;
for (int const key : initKeys)
for (auto const key : { Prefs::DIR_WATCH })
{
refreshPref(key);
}
connect(myPrefs, SIGNAL(changed(int)), this, SLOT(refreshPref(int const)));
QTimer* timer = &myModelTimer;
connect(timer, SIGNAL(timeout()), this, SLOT(refreshTorrents()));
connect(timer, &QTimer::timeout, this, &Application::refreshTorrents);
timer->setSingleShot(false);
timer->setInterval(MODEL_REFRESH_INTERVAL_MSEC);
timer->start();
timer = &myStatsTimer;
connect(timer, SIGNAL(timeout()), mySession, SLOT(refreshSessionStats()));
connect(timer, &QTimer::timeout, mySession, &Session::refreshSessionStats);
timer->setSingleShot(false);
timer->setInterval(STATS_REFRESH_INTERVAL_MSEC);
timer->start();
timer = &mySessionTimer;
connect(timer, SIGNAL(timeout()), mySession, SLOT(refreshSessionInfo()));
connect(timer, &QTimer::timeout, mySession, &Session::refreshSessionInfo);
timer->setSingleShot(false);
timer->setInterval(SESSION_REFRESH_INTERVAL_MSEC);
timer->start();
@ -410,78 +399,52 @@ void Application::quitLater()
QTimer::singleShot(0, this, SLOT(quit()));
}
/* these functions are for popping up desktop notifications */
void Application::onTorrentsAdded(QSet<int> const& torrents)
QStringList Application::getNames(QSet<int> const& ids) const
{
if (!myPrefs->getBool(Prefs::SHOW_NOTIFICATION_ON_ADD))
QStringList names;
for (auto const& id : ids)
{
return;
names.push_back(myModel->getTorrentFromId(id)->name());
}
for (int const id : torrents)
names.sort();
return names;
}
void Application::onTorrentsAdded(QSet<int> const& ids)
{
if (myPrefs->getBool(Prefs::SHOW_NOTIFICATION_ON_ADD))
{
Torrent* tor = myModel->getTorrentFromId(id);
if (tor->name().isEmpty()) // wait until the torrent's INFO fields are loaded
{
connect(tor, SIGNAL(torrentChanged(int)), this, SLOT(onNewTorrentChanged(int)));
}
else
{
onNewTorrentChanged(id);
if (!tor->isSeed())
{
connect(tor, SIGNAL(torrentCompleted(int)), this, SLOT(onTorrentCompleted(int)));
}
}
auto const title = tr("Torrent(s) Added", nullptr, ids.size());
auto const body = getNames(ids).join(QStringLiteral("\n"));
notifyApp(title, body);
}
}
void Application::onTorrentCompleted(int id)
void Application::onTorrentsCompleted(QSet<int> const& ids)
{
Torrent* tor = myModel->getTorrentFromId(id);
if (tor != nullptr)
if (myPrefs->getBool(Prefs::SHOW_NOTIFICATION_ON_COMPLETE))
{
if (myPrefs->getBool(Prefs::SHOW_NOTIFICATION_ON_COMPLETE))
{
notifyApp(tr("Torrent Completed"), tor->name());
}
auto const title = tr("Torrent Completed", nullptr, ids.size());
auto const body = getNames(ids).join(QStringLiteral("\n"));
notifyApp(title, body);
}
if (myPrefs->getBool(Prefs::COMPLETE_SOUND_ENABLED))
{
if (myPrefs->getBool(Prefs::COMPLETE_SOUND_ENABLED))
{
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
beep();
beep();
#else
QProcess::execute(myPrefs->getString(Prefs::COMPLETE_SOUND_COMMAND));
QProcess::execute(myPrefs->getString(Prefs::COMPLETE_SOUND_COMMAND));
#endif
}
disconnect(tor, SIGNAL(torrentCompleted(int)), this, SLOT(onTorrentCompleted(int)));
}
}
void Application::onNewTorrentChanged(int id)
void Application::onTorrentsNeedInfo(QSet<int> const& ids)
{
Torrent* tor = myModel->getTorrentFromId(id);
if (tor != nullptr && !tor->name().isEmpty())
if (!ids.isEmpty())
{
int const age_secs = int(std::difftime(time(nullptr), tor->dateAdded()));
if (age_secs < 30)
{
notifyApp(tr("Torrent Added"), tor->name());
}
disconnect(tor, SIGNAL(torrentChanged(int)), this, SLOT(onNewTorrentChanged(int)));
if (!tor->isSeed())
{
connect(tor, SIGNAL(torrentCompleted(int)), this, SLOT(onTorrentCompleted(int)));
}
mySession->initTorrents(ids);
}
}
@ -592,25 +555,20 @@ void Application::refreshTorrents()
****
***/
void Application::addTorrent(QString const& key)
{
AddData const addme(key);
if (addme.type != addme.NONE)
{
addTorrent(addme);
}
}
void Application::addTorrent(AddData const& addme)
{
if (addme.type == addme.NONE)
{
return;
}
if (!myPrefs->getBool(Prefs::OPTIONS_PROMPT))
{
mySession->addTorrent(addme);
}
else
{
OptionsDialog* o = new OptionsDialog(*mySession, *myPrefs, addme, myWindow);
auto o = new OptionsDialog(*mySession, *myPrefs, addme, myWindow);
o->show();
}

View File

@ -36,22 +36,22 @@ public:
FaviconCache& faviconCache();
public slots:
void addTorrent(QString const&);
void addTorrent(AddData const&);
private:
void maybeUpdateBlocklist();
void loadTranslations();
void quitLater();
private slots:
void consentGiven(int result);
void onSessionSourceChanged();
void refreshPref(int key);
void refreshTorrents();
void onTorrentsAdded(QSet<int> const& torrents);
void onTorrentCompleted(int);
void onNewTorrentChanged(int);
void onTorrentsCompleted(QSet<int> const& torrents);
void onTorrentsNeedInfo(QSet<int> const& torrents);
private:
void maybeUpdateBlocklist();
void loadTranslations();
void quitLater();
QStringList getNames(QSet<int> const& ids) const;
private:
Prefs* myPrefs;

View File

@ -233,8 +233,9 @@ DetailsDialog::DetailsDialog(Session& session, Prefs& prefs, TorrentModel const&
refreshPref(key);
}
connect(&myTimer, SIGNAL(timeout()), this, SLOT(onTimer()));
connect(&myPrefs, SIGNAL(changed(int)), this, SLOT(refreshPref(int)));
connect(&myModel, &TorrentModel::torrentsChanged, this, &DetailsDialog::onTorrentsChanged);
connect(&myPrefs, &Prefs::changed, this, &DetailsDialog::refreshPref);
connect(&myTimer, &QTimer::timeout, this, &DetailsDialog::onTimer);
onTimer();
myTimer.setSingleShot(false);
@ -250,45 +251,17 @@ DetailsDialog::~DetailsDialog()
void DetailsDialog::setIds(QSet<int> const& ids)
{
if (ids == myIds)
if (ids != myIds)
{
return;
setEnabled(false);
ui.filesView->clear();
myIds = ids;
mySession.refreshDetailInfo(myIds);
myChangedTorrents = true;
myTrackerModel->refresh(myModel, myIds);
onTimer();
}
myChangedTorrents = true;
// stop listening to the old torrents
for (int const id : myIds)
{
Torrent const* tor = myModel.getTorrentFromId(id);
if (tor != nullptr)
{
disconnect(tor, SIGNAL(torrentChanged(int)), this, SLOT(onTorrentChanged()));
}
}
ui.filesView->clear();
myIds = ids;
myTrackerModel->refresh(myModel, myIds);
// listen to the new torrents
for (int const id : myIds)
{
Torrent const* tor = myModel.getTorrentFromId(id);
if (tor != nullptr)
{
connect(tor, SIGNAL(torrentChanged(int)), this, SLOT(onTorrentChanged()));
}
}
for (int i = 0; i < ui.tabs->count(); ++i)
{
ui.tabs->widget(i)->setEnabled(false);
}
onTimer();
}
void DetailsDialog::refreshPref(int key)
@ -332,30 +305,15 @@ void DetailsDialog::getNewData()
{
if (!myIds.empty())
{
QSet<int> infos;
for (int const id : myIds)
{
Torrent const* tor = myModel.getTorrentFromId(id);
if (tor->isMagnet())
{
infos.insert(tor->id());
}
}
if (!infos.isEmpty())
{
mySession.initTorrents(infos);
}
mySession.refreshExtraStats(myIds);
}
}
void DetailsDialog::onTorrentChanged()
void DetailsDialog::onTorrentsChanged(QSet<int> const& ids)
{
if (!myHavePendingRefresh)
auto const interesting = ids & myIds;
if (!interesting.isEmpty() && !myHavePendingRefresh)
{
myHavePendingRefresh = true;
QTimer::singleShot(100, this, SLOT(refresh()));
@ -1199,10 +1157,14 @@ void DetailsDialog::refresh()
myChangedTorrents = false;
myHavePendingRefresh = false;
setEnabled(true);
}
void DetailsDialog::setEnabled(bool enabled)
{
for (int i = 0; i < ui.tabs->count(); ++i)
{
ui.tabs->widget(i)->setEnabled(true);
ui.tabs->widget(i)->setEnabled(enabled);
}
}
@ -1441,18 +1403,20 @@ void DetailsDialog::initOptionsTab()
cr->addLayout(ui.peerConnectionsSectionLayout);
cr->update();
connect(ui.sessionLimitCheck, SIGNAL(clicked(bool)), SLOT(onHonorsSessionLimitsToggled(bool)));
connect(ui.singleDownCheck, SIGNAL(clicked(bool)), SLOT(onDownloadLimitedToggled(bool)));
connect(ui.singleDownSpin, SIGNAL(editingFinished()), SLOT(onSpinBoxEditingFinished()));
connect(ui.singleUpCheck, SIGNAL(clicked(bool)), SLOT(onUploadLimitedToggled(bool)));
connect(ui.singleUpSpin, SIGNAL(editingFinished()), SLOT(onSpinBoxEditingFinished()));
connect(ui.bandwidthPriorityCombo, SIGNAL(currentIndexChanged(int)), SLOT(onBandwidthPriorityChanged(int)));
connect(ui.ratioCombo, SIGNAL(currentIndexChanged(int)), SLOT(onRatioModeChanged(int)));
connect(ui.ratioSpin, SIGNAL(editingFinished()), SLOT(onSpinBoxEditingFinished()));
connect(ui.idleCombo, SIGNAL(currentIndexChanged(int)), SLOT(onIdleModeChanged(int)));
connect(ui.idleSpin, SIGNAL(editingFinished()), SLOT(onSpinBoxEditingFinished()));
connect(ui.idleSpin, SIGNAL(valueChanged(int)), SLOT(onIdleLimitChanged()));
connect(ui.peerLimitSpin, SIGNAL(editingFinished()), SLOT(onSpinBoxEditingFinished()));
void (QComboBox::* comboIndexChanged)(int) = &QComboBox::currentIndexChanged;
void (QSpinBox::* spinValueChanged)(int) = &QSpinBox::valueChanged;
connect(ui.bandwidthPriorityCombo, comboIndexChanged, this, &DetailsDialog::onBandwidthPriorityChanged);
connect(ui.idleCombo, comboIndexChanged, this, &DetailsDialog::onIdleModeChanged);
connect(ui.idleSpin, &QSpinBox::editingFinished, this, &DetailsDialog::onSpinBoxEditingFinished);
connect(ui.idleSpin, spinValueChanged, this, &DetailsDialog::onIdleLimitChanged);
connect(ui.peerLimitSpin, &QSpinBox::editingFinished, this, &DetailsDialog::onSpinBoxEditingFinished);
connect(ui.ratioCombo, comboIndexChanged, this, &DetailsDialog::onRatioModeChanged);
connect(ui.ratioSpin, &QSpinBox::editingFinished, this, &DetailsDialog::onSpinBoxEditingFinished);
connect(ui.sessionLimitCheck, &QCheckBox::clicked, this, &DetailsDialog::onHonorsSessionLimitsToggled);
connect(ui.singleDownCheck, &QCheckBox::clicked, this, &DetailsDialog::onDownloadLimitedToggled);
connect(ui.singleDownSpin, &QSpinBox::editingFinished, this, &DetailsDialog::onSpinBoxEditingFinished);
connect(ui.singleUpCheck, &QCheckBox::clicked, this, &DetailsDialog::onUploadLimitedToggled);
connect(ui.singleUpSpin, &QSpinBox::editingFinished, this, &DetailsDialog::onSpinBoxEditingFinished);
}
/***
@ -1476,13 +1440,14 @@ void DetailsDialog::initTrackerTab()
ui.showTrackerScrapesCheck->setChecked(myPrefs.getBool(Prefs::SHOW_TRACKER_SCRAPES));
ui.showBackupTrackersCheck->setChecked(myPrefs.getBool(Prefs::SHOW_BACKUP_TRACKERS));
connect(ui.trackersView->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
SLOT(onTrackerSelectionChanged()));
connect(ui.addTrackerButton, SIGNAL(clicked()), SLOT(onAddTrackerClicked()));
connect(ui.editTrackerButton, SIGNAL(clicked()), SLOT(onEditTrackerClicked()));
connect(ui.removeTrackerButton, SIGNAL(clicked()), SLOT(onRemoveTrackerClicked()));
connect(ui.showTrackerScrapesCheck, SIGNAL(clicked(bool)), SLOT(onShowTrackerScrapesToggled(bool)));
connect(ui.showBackupTrackersCheck, SIGNAL(clicked(bool)), SLOT(onShowBackupTrackersToggled(bool)));
connect(ui.addTrackerButton, &QAbstractButton::clicked, this, &DetailsDialog::onAddTrackerClicked);
connect(ui.editTrackerButton, &QAbstractButton::clicked, this, &DetailsDialog::onEditTrackerClicked);
connect(ui.removeTrackerButton, &QAbstractButton::clicked, this, &DetailsDialog::onRemoveTrackerClicked);
connect(ui.showBackupTrackersCheck, &QAbstractButton::clicked, this, &DetailsDialog::onShowBackupTrackersToggled);
connect(ui.showTrackerScrapesCheck, &QAbstractButton::clicked, this, &DetailsDialog::onShowTrackerScrapesToggled);
connect(
ui.trackersView->selectionModel(), &QItemSelectionModel::selectionChanged, this,
&DetailsDialog::onTrackerSelectionChanged);
onTrackerSelectionChanged();
}
@ -1493,10 +1458,7 @@ void DetailsDialog::initTrackerTab()
void DetailsDialog::initPeersTab()
{
QStringList headers;
headers << QString() << tr("Up") << tr("Down") << tr("%") << tr("Status") << tr("Address") << tr("Client");
ui.peersView->setHeaderLabels(headers);
ui.peersView->setHeaderLabels({ QString(), tr("Up"), tr("Down"), tr("%"), tr("Status"), tr("Address"), tr("Client") });
ui.peersView->sortByColumn(COL_ADDRESS, Qt::AscendingOrder);
ui.peersView->setColumnWidth(COL_LOCK, 20);
@ -1513,10 +1475,10 @@ void DetailsDialog::initPeersTab()
void DetailsDialog::initFilesTab()
{
connect(ui.filesView, SIGNAL(priorityChanged(QSet<int>, int)), SLOT(onFilePriorityChanged(QSet<int>, int)));
connect(ui.filesView, SIGNAL(wantedChanged(QSet<int>, bool)), SLOT(onFileWantedChanged(QSet<int>, bool)));
connect(ui.filesView, SIGNAL(pathEdited(QString, QString)), SLOT(onPathEdited(QString, QString)));
connect(ui.filesView, SIGNAL(openRequested(QString)), SLOT(onOpenRequested(QString)));
connect(ui.filesView, &FileTreeView::openRequested, this, &DetailsDialog::onOpenRequested);
connect(ui.filesView, &FileTreeView::pathEdited, this, &DetailsDialog::onPathEdited);
connect(ui.filesView, &FileTreeView::priorityChanged, this, &DetailsDialog::onFilePriorityChanged);
connect(ui.filesView, &FileTreeView::wantedChanged, this, &DetailsDialog::onFileWantedChanged);
}
void DetailsDialog::onFilePriorityChanged(QSet<int> const& indices, int priority)

View File

@ -53,12 +53,13 @@ private:
void getNewData();
QIcon getStockIcon(QString const& freedesktop_name, int fallback);
void setEnabled(bool);
private slots:
void refresh();
void refreshPref(int key);
void onTorrentChanged();
void onTorrentsChanged(QSet<int> const& ids);
void onTimer();
// Tracker tab

View File

@ -152,8 +152,7 @@ MainWindow::MainWindow(Session& session, Prefs& prefs, TorrentModel& model, bool
myLastReadTime(0),
myNetworkTimer(this),
myNetworkError(false),
myRefreshTrayIconTimer(this),
myRefreshActionSensitivityTimer(this)
myRefreshTimer(this)
{
setAcceptDrops(true);
@ -238,28 +237,25 @@ MainWindow::MainWindow(Session& session, Prefs& prefs, TorrentModel& model, bool
connect(ui.action_SetLocation, SIGNAL(triggered()), this, SLOT(setLocation()));
connect(ui.action_Properties, SIGNAL(triggered()), this, SLOT(openProperties()));
connect(ui.action_SessionDialog, SIGNAL(triggered()), this, SLOT(openSession()));
connect(ui.listView, SIGNAL(activated(QModelIndex)), ui.action_Properties, SLOT(trigger()));
// signals
connect(ui.action_SelectAll, SIGNAL(triggered()), ui.listView, SLOT(selectAll()));
connect(ui.action_DeselectAll, SIGNAL(triggered()), ui.listView, SLOT(clearSelection()));
connect(&myFilterModel, SIGNAL(rowsInserted(QModelIndex, int, int)), this, SLOT(refreshActionSensitivitySoon()));
connect(&myFilterModel, SIGNAL(rowsRemoved(QModelIndex, int, int)), this, SLOT(refreshActionSensitivitySoon()));
connect(ui.action_Quit, SIGNAL(triggered()), qApp, SLOT(quit()));
auto refreshActionSensitivitySoon = [this]() { refreshSoon(REFRESH_ACTION_SENSITIVITY); };
connect(&myFilterModel, &TorrentFilter::rowsInserted, refreshActionSensitivitySoon);
connect(&myFilterModel, &TorrentFilter::rowsRemoved, refreshActionSensitivitySoon);
// torrent view
myFilterModel.setSourceModel(&myModel);
connect(&myModel, SIGNAL(modelReset()), this, SLOT(onModelReset()));
connect(&myModel, SIGNAL(rowsRemoved(QModelIndex, int, int)), this, SLOT(onModelReset()));
connect(&myModel, SIGNAL(rowsInserted(QModelIndex, int, int)), this, SLOT(onModelReset()));
connect(&myModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(refreshTrayIconSoon()));
auto refreshSoonAdapter = [this]() { refreshSoon(); };
connect(&myModel, &TorrentModel::modelReset, refreshSoonAdapter);
connect(&myModel, &TorrentModel::rowsRemoved, refreshSoonAdapter);
connect(&myModel, &TorrentModel::rowsInserted, refreshSoonAdapter);
connect(&myModel, &TorrentModel::dataChanged, refreshSoonAdapter);
ui.listView->setModel(&myFilterModel);
connect(ui.listView->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), this,
SLOT(refreshActionSensitivitySoon()));
connect(ui.listView->selectionModel(), &QItemSelectionModel::selectionChanged, refreshActionSensitivitySoon);
QPair<QAction*, int> const sortModes[] =
{
@ -316,10 +312,11 @@ MainWindow::MainWindow(Session& session, Prefs& prefs, TorrentModel& model, bool
initStatusBar();
ui.verticalLayout->insertWidget(0, myFilterBar = new FilterBar(myPrefs, myModel, myFilterModel));
connect(&myModel, SIGNAL(rowsInserted(QModelIndex, int, int)), SLOT(refreshTorrentViewHeader()));
connect(&myModel, SIGNAL(rowsRemoved(QModelIndex, int, int)), SLOT(refreshTorrentViewHeader()));
connect(&myFilterModel, SIGNAL(rowsInserted(QModelIndex, int, int)), SLOT(refreshTorrentViewHeader()));
connect(&myFilterModel, SIGNAL(rowsRemoved(QModelIndex, int, int)), SLOT(refreshTorrentViewHeader()));
auto refreshHeaderSoon = [this]() { refreshSoon(REFRESH_TORRENT_VIEW_HEADER); };
connect(&myModel, &TorrentModel::rowsInserted, refreshHeaderSoon);
connect(&myModel, &TorrentModel::rowsRemoved, refreshHeaderSoon);
connect(&myFilterModel, &TorrentFilter::rowsInserted, refreshHeaderSoon);
connect(&myFilterModel, &TorrentFilter::rowsRemoved, refreshHeaderSoon);
connect(ui.listView, SIGNAL(headerDoubleClicked()), myFilterBar, SLOT(clear()));
QList<int> initKeys;
@ -347,41 +344,23 @@ MainWindow::MainWindow(Session& session, Prefs& prefs, TorrentModel& model, bool
}
else
{
connect(&myNetworkTimer, SIGNAL(timeout()), this, SLOT(onNetworkTimer()));
connect(&myNetworkTimer, &QTimer::timeout, this, &MainWindow::onNetworkTimer);
myNetworkTimer.start(1000);
}
connect(&myRefreshTrayIconTimer, SIGNAL(timeout()), this, SLOT(refreshTrayIcon()));
connect(&myRefreshActionSensitivityTimer, SIGNAL(timeout()), this, SLOT(refreshActionSensitivity()));
refreshActionSensitivitySoon();
refreshTrayIconSoon();
refreshStatusBar();
refreshTitle();
refreshTorrentViewHeader();
connect(&myRefreshTimer, &QTimer::timeout, this, &MainWindow::onRefreshTimer);
refreshSoon();
}
MainWindow::~MainWindow()
{
}
/****
*****
****/
void MainWindow::onSessionSourceChanged()
{
myModel.clear();
}
void MainWindow::onModelReset()
{
refreshTitle();
refreshActionSensitivitySoon();
refreshStatusBar();
refreshTrayIconSoon();
}
/****
*****
****/
@ -702,6 +681,52 @@ void MainWindow::openHelp()
arg(MINOR_VERSION / 10)));
}
/****
*****
****/
void MainWindow::refreshSoon(int fields)
{
myRefreshFields |= fields;
if (!myRefreshTimer.isActive())
{
myRefreshTimer.setSingleShot(true);
myRefreshTimer.start(100);
}
}
void MainWindow::onRefreshTimer()
{
int fields = 0;
std::swap(fields, myRefreshFields);
if (fields & REFRESH_TITLE)
{
refreshTitle();
}
if (fields & REFRESH_STATUS_BAR)
{
refreshStatusBar();
}
if (fields & REFRESH_TRAY_ICON)
{
refreshTrayIcon();
}
if (fields & REFRESH_TORRENT_VIEW_HEADER)
{
refreshTorrentViewHeader();
}
if (fields & REFRESH_ACTION_SENSITIVITY)
{
refreshActionSensitivity();
}
}
void MainWindow::refreshTitle()
{
QString title(QLatin1String("Transmission"));
@ -717,15 +742,6 @@ void MainWindow::refreshTitle()
setWindowTitle(title);
}
void MainWindow::refreshTrayIconSoon()
{
if (!myRefreshTrayIconTimer.isActive())
{
myRefreshTrayIconTimer.setSingleShot(true);
myRefreshTrayIconTimer.start(100);
}
}
void MainWindow::refreshTrayIcon()
{
Speed upSpeed;
@ -814,15 +830,6 @@ void MainWindow::refreshTorrentViewHeader()
}
}
void MainWindow::refreshActionSensitivitySoon()
{
if (!myRefreshActionSensitivityTimer.isActive())
{
myRefreshActionSensitivityTimer.setSingleShot(true);
myRefreshActionSensitivityTimer.start(100);
}
}
void MainWindow::refreshActionSensitivity()
{
int paused(0);
@ -1179,7 +1186,7 @@ void MainWindow::refreshPref(int key)
ui.action_TrayIcon->setChecked(b);
myTrayIcon.setVisible(b);
qApp->setQuitOnLastWindowClosed(!b);
refreshTrayIconSoon();
refreshSoon(REFRESH_TRAY_ICON);
break;
case Prefs::COMPACT_VIEW:
@ -1523,7 +1530,7 @@ void MainWindow::onNetworkResponse(QNetworkReply::NetworkError code, QString con
myNetworkError = haveError;
myErrorMessage = message;
refreshTrayIconSoon();
refreshSoon(REFRESH_TRAY_ICON);
updateNetworkIcon();
// Refresh our model if we've just gotten a clean connection to the session.

View File

@ -78,7 +78,6 @@ public slots:
void setStatusbarVisible(bool);
void setCompactView(bool);
void refreshActionSensitivity();
void refreshActionSensitivitySoon();
void wrongAuthentication();
void openSession();
@ -108,41 +107,36 @@ private:
virtual void showEvent(QShowEvent* event);
private slots:
void openPreferences();
void refreshTitle();
void refreshStatusBar();
void refreshTrayIcon();
void refreshTrayIconSoon();
void refreshTorrentViewHeader();
void openTorrent();
void openURL();
void newTorrent();
void trayActivated(QSystemTrayIcon::ActivationReason);
void refreshPref(int key);
void addTorrents(QStringList const& filenames);
void removeTorrents(bool const deleteFiles);
void openStats();
void openDonate();
void openAbout();
void openHelp();
void openFolder();
void copyMagnetLinkToClipboard();
void setLocation();
void openProperties();
void toggleSpeedMode();
void dataReadProgress();
void dataSendProgress();
void newTorrent();
void onNetworkResponse(QNetworkReply::NetworkError code, QString const& message);
void toggleWindows(bool doShow);
void onRefreshTimer();
void onSessionSourceChanged();
void onSetPrefs();
void onSetPrefs(bool);
void onSessionSourceChanged();
void onModelReset();
void setSortAscendingPref(bool);
void onStatsModeChanged(QAction* action);
void onSortModeChanged(QAction* action);
void onStatsModeChanged(QAction* action);
void openAbout();
void openDonate();
void openFolder();
void openHelp();
void openPreferences();
void openProperties();
void openStats();
void openTorrent();
void openURL();
void refreshPref(int key);
void refreshSoon(int fields = ~0);
void refreshStatusBar();
void removeTorrents(bool const deleteFiles);
void setLocation();
void setSortAscendingPref(bool);
void toggleSpeedMode();
void toggleWindows(bool doShow);
void trayActivated(QSystemTrayIcon::ActivationReason);
private:
Session& mySession;
@ -171,8 +165,6 @@ private:
time_t myLastReadTime;
QTimer myNetworkTimer;
bool myNetworkError;
QTimer myRefreshTrayIconTimer;
QTimer myRefreshActionSensitivityTimer;
QAction* myDlimitOffAction;
QAction* myDlimitOnAction;
QAction* myUlimitOffAction;
@ -183,4 +175,18 @@ private:
QWidget* myFilterBar;
QAction* myAltSpeedAction;
QString myErrorMessage;
enum
{
REFRESH_TITLE = (1 << 0),
REFRESH_STATUS_BAR = (1 << 1),
REFRESH_TRAY_ICON = (1 << 2),
REFRESH_TORRENT_VIEW_HEADER = (1 << 3),
REFRESH_ACTION_SENSITIVITY = (1 << 4)
};
int myRefreshFields = 0;
QTimer myRefreshTimer;
void refreshTitle();
void refreshTrayIcon();
void refreshTorrentViewHeader();
};

View File

@ -43,21 +43,6 @@ namespace
typedef Torrent::KeyList KeyList;
KeyList const& getInfoKeys()
{
return Torrent::getInfoKeys();
}
KeyList const& getStatKeys()
{
return Torrent::getStatKeys();
}
KeyList const& getExtraStatKeys()
{
return Torrent::getExtraStatKeys();
}
void addList(tr_variant* list, KeyList const& keys)
{
tr_variantListReserve(list, keys.size());
@ -598,9 +583,14 @@ void Session::refreshTorrents(QSet<int> const& ids, KeyList const& keys)
q->run();
}
void Session::refreshDetailInfo(QSet<int> const& ids)
{
refreshTorrents(ids, Torrent::detailInfoKeys);
}
void Session::refreshExtraStats(QSet<int> const& ids)
{
refreshTorrents(ids, getStatKeys() + getExtraStatKeys());
refreshTorrents(ids, Torrent::mainStatKeys + Torrent::detailStatKeys);
}
void Session::sendTorrentRequest(char const* request, QSet<int> const& ids)
@ -618,7 +608,7 @@ void Session::sendTorrentRequest(char const* request, QSet<int> const& ids)
q->add([this, ids]()
{
refreshTorrents(ids, getStatKeys());
refreshTorrents(ids, Torrent::mainStatKeys);
});
q->run();
@ -661,17 +651,17 @@ void Session::queueMoveBottom(QSet<int> const& ids)
void Session::refreshActiveTorrents()
{
refreshTorrents(recentlyActiveIds, getStatKeys());
refreshTorrents(recentlyActiveIds, Torrent::mainStatKeys);
}
void Session::refreshAllTorrents()
{
refreshTorrents(allIds, getStatKeys());
refreshTorrents(allIds, Torrent::mainStatKeys);
}
void Session::initTorrents(QSet<int> const& ids)
{
refreshTorrents(ids, getStatKeys() + getInfoKeys());
refreshTorrents(ids, Torrent::allMainKeys);
}
void Session::refreshSessionStats()

View File

@ -86,31 +86,30 @@ public:
void torrentSetLocation(QSet<int> const& ids, QString const& path, bool doMove);
void torrentRenamePath(QSet<int> const& ids, QString const& oldpath, QString const& newname);
void addTorrent(AddData const& addme, tr_variant* top, bool trashOriginal);
public slots:
void initTorrents(QSet<int> const& ids = QSet<int>());
void pauseTorrents(QSet<int> const& torrentIds = QSet<int>());
void startTorrents(QSet<int> const& torrentIds = QSet<int>());
void startTorrentsNow(QSet<int> const& torrentIds = QSet<int>());
void queueMoveTop(QSet<int> const& torrentIds = QSet<int>());
void queueMoveUp(QSet<int> const& torrentIds = QSet<int>());
void queueMoveDown(QSet<int> const& torrentIds = QSet<int>());
void queueMoveBottom(QSet<int> const& torrentIds = QSet<int>());
void refreshSessionInfo();
void refreshSessionStats();
void refreshDetailInfo(QSet<int> const& torrentIds);
void refreshActiveTorrents();
void refreshAllTorrents();
void initTorrents(QSet<int> const& ids = QSet<int>());
void addNewlyCreatedTorrent(QString const& filename, QString const& localPath);
void addTorrent(AddData const& addme);
void removeTorrents(QSet<int> const& torrentIds, bool deleteFiles = false);
void verifyTorrents(QSet<int> const& torrentIds);
void reannounceTorrents(QSet<int> const& torrentIds);
void launchWebInterface();
void updatePref(int key);
/** request a refresh for statistics, including the ones only used by the properties dialog, for a specific torrent */
void refreshExtraStats(QSet<int> const& ids);
public slots:
void addTorrent(AddData const& addme);
void launchWebInterface();
void queueMoveBottom(QSet<int> const& torrentIds = QSet<int>());
void queueMoveDown(QSet<int> const& torrentIds = QSet<int>());
void queueMoveTop(QSet<int> const& torrentIds = QSet<int>());
void queueMoveUp(QSet<int> const& torrentIds = QSet<int>());
void refreshSessionInfo();
void refreshSessionStats();
void removeTorrents(QSet<int> const& torrentIds, bool deleteFiles = false);
void updatePref(int key);
signals:
void sourceChanged();
void portTested(bool isOpen);

View File

@ -24,8 +24,8 @@
#include "Utils.h"
Torrent::Torrent(Prefs const& prefs, int id) :
myPrefs(prefs),
magnetTorrent(false)
myId(id),
myPrefs(prefs)
{
#ifndef NDEBUG
@ -36,125 +36,155 @@ Torrent::Torrent(Prefs const& prefs, int id) :
#endif
setInt(ID, id);
setIcon(MIME_ICON, Utils::getFileIcon());
}
Torrent::~Torrent()
{
}
/***
****
***/
Torrent::Property Torrent::myProperties[] =
{
{ ID, TR_KEY_id, QVariant::Int, INFO },
{ UPLOAD_SPEED, TR_KEY_rateUpload, QVariant::ULongLong, STAT } /* Bps */,
{ DOWNLOAD_SPEED, TR_KEY_rateDownload, QVariant::ULongLong, STAT }, /* Bps */
{ DOWNLOAD_DIR, TR_KEY_downloadDir, QVariant::String, STAT },
{ ACTIVITY, TR_KEY_status, QVariant::Int, STAT },
{ NAME, TR_KEY_name, QVariant::String, INFO },
{ ERROR, TR_KEY_error, QVariant::Int, STAT },
{ ERROR_STRING, TR_KEY_errorString, QVariant::String, STAT },
{ SIZE_WHEN_DONE, TR_KEY_sizeWhenDone, QVariant::ULongLong, STAT },
{ LEFT_UNTIL_DONE, TR_KEY_leftUntilDone, QVariant::ULongLong, STAT },
{ HAVE_UNCHECKED, TR_KEY_haveUnchecked, QVariant::ULongLong, STAT },
{ HAVE_VERIFIED, TR_KEY_haveValid, QVariant::ULongLong, STAT },
{ DESIRED_AVAILABLE, TR_KEY_desiredAvailable, QVariant::ULongLong, STAT },
{ TOTAL_SIZE, TR_KEY_totalSize, QVariant::ULongLong, INFO },
{ PIECE_SIZE, TR_KEY_pieceSize, QVariant::ULongLong, INFO },
{ PIECE_COUNT, TR_KEY_pieceCount, QVariant::Int, INFO },
{ PEERS_GETTING_FROM_US, TR_KEY_peersGettingFromUs, QVariant::Int, STAT },
{ PEERS_SENDING_TO_US, TR_KEY_peersSendingToUs, QVariant::Int, STAT },
{ WEBSEEDS_SENDING_TO_US, TR_KEY_webseedsSendingToUs, QVariant::Int, STAT_EXTRA },
{ PERCENT_DONE, TR_KEY_percentDone, QVariant::Double, STAT },
{ METADATA_PERCENT_DONE, TR_KEY_metadataPercentComplete, QVariant::Double, STAT },
{ PERCENT_VERIFIED, TR_KEY_recheckProgress, QVariant::Double, STAT },
{ DATE_ACTIVITY, TR_KEY_activityDate, QVariant::DateTime, STAT_EXTRA },
{ DATE_ADDED, TR_KEY_addedDate, QVariant::DateTime, INFO },
{ DATE_STARTED, TR_KEY_startDate, QVariant::DateTime, STAT_EXTRA },
{ DATE_CREATED, TR_KEY_dateCreated, QVariant::DateTime, INFO },
{ PEERS_CONNECTED, TR_KEY_peersConnected, QVariant::Int, STAT },
{ ETA, TR_KEY_eta, QVariant::Int, STAT },
{ RATIO, TR_KEY_uploadRatio, QVariant::Double, STAT },
{ DOWNLOADED_EVER, TR_KEY_downloadedEver, QVariant::ULongLong, STAT },
{ UPLOADED_EVER, TR_KEY_uploadedEver, QVariant::ULongLong, STAT },
{ FAILED_EVER, TR_KEY_corruptEver, QVariant::ULongLong, STAT_EXTRA },
{ TRACKERS, TR_KEY_trackers, QVariant::StringList, STAT },
{ HOSTS, TR_KEY_NONE, QVariant::StringList, DERIVED },
{ TRACKERSTATS, TR_KEY_trackerStats, CustomVariantType::TrackerStatsList, STAT_EXTRA },
{ MIME_ICON, TR_KEY_NONE, QVariant::Icon, DERIVED },
{ SEED_RATIO_LIMIT, TR_KEY_seedRatioLimit, QVariant::Double, STAT },
{ SEED_RATIO_MODE, TR_KEY_seedRatioMode, QVariant::Int, STAT },
{ SEED_IDLE_LIMIT, TR_KEY_seedIdleLimit, QVariant::Int, STAT_EXTRA },
{ SEED_IDLE_MODE, TR_KEY_seedIdleMode, QVariant::Int, STAT_EXTRA },
{ DOWN_LIMIT, TR_KEY_downloadLimit, QVariant::Int, STAT_EXTRA }, /* KB/s */
{ DOWN_LIMITED, TR_KEY_downloadLimited, QVariant::Bool, STAT_EXTRA },
{ UP_LIMIT, TR_KEY_uploadLimit, QVariant::Int, STAT_EXTRA }, /* KB/s */
{ UP_LIMITED, TR_KEY_uploadLimited, QVariant::Bool, STAT_EXTRA },
{ HONORS_SESSION_LIMITS, TR_KEY_honorsSessionLimits, QVariant::Bool, STAT_EXTRA },
{ PEER_LIMIT, TR_KEY_peer_limit, QVariant::Int, STAT_EXTRA },
{ HASH_STRING, TR_KEY_hashString, QVariant::String, INFO },
{ IS_FINISHED, TR_KEY_isFinished, QVariant::Bool, STAT },
{ IS_PRIVATE, TR_KEY_isPrivate, QVariant::Bool, INFO },
{ IS_STALLED, TR_KEY_isStalled, QVariant::Bool, STAT },
{ COMMENT, TR_KEY_comment, QVariant::String, INFO },
{ CREATOR, TR_KEY_creator, QVariant::String, INFO },
{ MANUAL_ANNOUNCE_TIME, TR_KEY_manualAnnounceTime, QVariant::DateTime, STAT_EXTRA },
{ PEERS, TR_KEY_peers, CustomVariantType::PeerList, STAT_EXTRA },
{ BANDWIDTH_PRIORITY, TR_KEY_bandwidthPriority, QVariant::Int, STAT_EXTRA },
{ QUEUE_POSITION, TR_KEY_queuePosition, QVariant::Int, STAT },
{ UPLOAD_SPEED, TR_KEY_rateUpload, QVariant::ULongLong } /* Bps */,
{ DOWNLOAD_SPEED, TR_KEY_rateDownload, QVariant::ULongLong }, /* Bps */
{ DOWNLOAD_DIR, TR_KEY_downloadDir, QVariant::String },
{ ACTIVITY, TR_KEY_status, QVariant::Int },
{ NAME, TR_KEY_name, QVariant::String },
{ ERROR, TR_KEY_error, QVariant::Int },
{ ERROR_STRING, TR_KEY_errorString, QVariant::String },
{ SIZE_WHEN_DONE, TR_KEY_sizeWhenDone, QVariant::ULongLong },
{ LEFT_UNTIL_DONE, TR_KEY_leftUntilDone, QVariant::ULongLong },
{ HAVE_UNCHECKED, TR_KEY_haveUnchecked, QVariant::ULongLong },
{ HAVE_VERIFIED, TR_KEY_haveValid, QVariant::ULongLong },
{ DESIRED_AVAILABLE, TR_KEY_desiredAvailable, QVariant::ULongLong },
{ TOTAL_SIZE, TR_KEY_totalSize, QVariant::ULongLong },
{ PIECE_SIZE, TR_KEY_pieceSize, QVariant::ULongLong },
{ PIECE_COUNT, TR_KEY_pieceCount, QVariant::Int },
{ PEERS_GETTING_FROM_US, TR_KEY_peersGettingFromUs, QVariant::Int },
{ PEERS_SENDING_TO_US, TR_KEY_peersSendingToUs, QVariant::Int },
{ WEBSEEDS_SENDING_TO_US, TR_KEY_webseedsSendingToUs, QVariant::Int },
{ PERCENT_DONE, TR_KEY_percentDone, QVariant::Double },
{ METADATA_PERCENT_DONE, TR_KEY_metadataPercentComplete, QVariant::Double },
{ PERCENT_VERIFIED, TR_KEY_recheckProgress, QVariant::Double },
{ DATE_ACTIVITY, TR_KEY_activityDate, QVariant::DateTime },
{ DATE_ADDED, TR_KEY_addedDate, QVariant::DateTime },
{ DATE_STARTED, TR_KEY_startDate, QVariant::DateTime },
{ DATE_CREATED, TR_KEY_dateCreated, QVariant::DateTime },
{ PEERS_CONNECTED, TR_KEY_peersConnected, QVariant::Int },
{ ETA, TR_KEY_eta, QVariant::Int },
{ DOWNLOADED_EVER, TR_KEY_downloadedEver, QVariant::ULongLong },
{ UPLOADED_EVER, TR_KEY_uploadedEver, QVariant::ULongLong },
{ FAILED_EVER, TR_KEY_corruptEver, QVariant::ULongLong },
{ TRACKERS, TR_KEY_trackers, QVariant::StringList },
{ HOSTS, TR_KEY_NONE, QVariant::StringList },
{ TRACKERSTATS, TR_KEY_trackerStats, CustomVariantType::TrackerStatsList },
{ MIME_ICON, TR_KEY_NONE, QVariant::Icon },
{ SEED_RATIO_LIMIT, TR_KEY_seedRatioLimit, QVariant::Double },
{ SEED_RATIO_MODE, TR_KEY_seedRatioMode, QVariant::Int },
{ SEED_IDLE_LIMIT, TR_KEY_seedIdleLimit, QVariant::Int },
{ SEED_IDLE_MODE, TR_KEY_seedIdleMode, QVariant::Int },
{ DOWN_LIMIT, TR_KEY_downloadLimit, QVariant::Int }, /* KB/s */
{ DOWN_LIMITED, TR_KEY_downloadLimited, QVariant::Bool },
{ UP_LIMIT, TR_KEY_uploadLimit, QVariant::Int }, /* KB/s */
{ UP_LIMITED, TR_KEY_uploadLimited, QVariant::Bool },
{ HONORS_SESSION_LIMITS, TR_KEY_honorsSessionLimits, QVariant::Bool },
{ PEER_LIMIT, TR_KEY_peer_limit, QVariant::Int },
{ HASH_STRING, TR_KEY_hashString, QVariant::String },
{ IS_FINISHED, TR_KEY_isFinished, QVariant::Bool },
{ IS_PRIVATE, TR_KEY_isPrivate, QVariant::Bool },
{ IS_STALLED, TR_KEY_isStalled, QVariant::Bool },
{ COMMENT, TR_KEY_comment, QVariant::String },
{ CREATOR, TR_KEY_creator, QVariant::String },
{ MANUAL_ANNOUNCE_TIME, TR_KEY_manualAnnounceTime, QVariant::DateTime },
{ PEERS, TR_KEY_peers, CustomVariantType::PeerList },
{ BANDWIDTH_PRIORITY, TR_KEY_bandwidthPriority, QVariant::Int },
{ QUEUE_POSITION, TR_KEY_queuePosition, QVariant::Int },
};
Torrent::KeyList Torrent::buildKeyList(Group group)
{
KeyList keys;
/***
****
***/
if (keys.empty())
{
for (int i = 0; i < PROPERTY_COUNT; ++i)
{
if (myProperties[i].id == ID || myProperties[i].group == group)
{
keys << myProperties[i].key;
}
}
}
// unchanging fields needed by the main window
Torrent::KeyList const Torrent::mainInfoKeys{
TR_KEY_addedDate,
TR_KEY_downloadDir,
TR_KEY_hashString,
TR_KEY_id, // must be in every req
TR_KEY_name,
TR_KEY_totalSize,
TR_KEY_trackers,
};
return keys;
}
// changing fields needed by the main window
Torrent::KeyList const Torrent::mainStatKeys{
TR_KEY_downloadedEver,
TR_KEY_error,
TR_KEY_errorString,
TR_KEY_eta,
TR_KEY_haveUnchecked,
TR_KEY_haveValid,
TR_KEY_id, // must be in every req
TR_KEY_isFinished,
TR_KEY_leftUntilDone,
TR_KEY_manualAnnounceTime,
TR_KEY_metadataPercentComplete,
TR_KEY_peersConnected,
TR_KEY_peersGettingFromUs,
TR_KEY_peersSendingToUs,
TR_KEY_percentDone,
TR_KEY_queuePosition,
TR_KEY_rateDownload,
TR_KEY_rateUpload,
TR_KEY_recheckProgress,
TR_KEY_seedRatioLimit,
TR_KEY_seedRatioMode,
TR_KEY_sizeWhenDone,
TR_KEY_status,
TR_KEY_uploadedEver,
TR_KEY_webseedsSendingToUs
};
Torrent::KeyList const& Torrent::getInfoKeys()
{
static KeyList keys;
Torrent::KeyList const Torrent::allMainKeys = Torrent::mainInfoKeys + Torrent::mainStatKeys;
if (keys.isEmpty())
{
keys << buildKeyList(INFO) << TR_KEY_files;
}
// unchanging fields needed by the details dialog
Torrent::KeyList const Torrent::detailInfoKeys{
TR_KEY_comment,
TR_KEY_creator,
TR_KEY_dateCreated,
TR_KEY_files,
TR_KEY_id, // must be in every req
TR_KEY_isPrivate,
TR_KEY_pieceCount,
TR_KEY_pieceSize,
TR_KEY_trackers
};
return keys;
}
// changing fields needed by the details dialog
Torrent::KeyList const Torrent::detailStatKeys{
TR_KEY_activityDate,
TR_KEY_bandwidthPriority,
TR_KEY_corruptEver,
TR_KEY_desiredAvailable,
TR_KEY_downloadedEver,
TR_KEY_downloadLimit,
TR_KEY_downloadLimited,
TR_KEY_fileStats,
TR_KEY_honorsSessionLimits,
TR_KEY_id, // must be in every req
TR_KEY_peer_limit,
TR_KEY_peers,
TR_KEY_seedIdleLimit,
TR_KEY_seedIdleMode,
TR_KEY_startDate,
TR_KEY_trackerStats,
TR_KEY_uploadLimit,
TR_KEY_uploadLimited
};
Torrent::KeyList const& Torrent::getStatKeys()
{
static KeyList keys(buildKeyList(STAT));
return keys;
}
Torrent::KeyList const& Torrent::getExtraStatKeys()
{
static KeyList keys;
if (keys.isEmpty())
{
keys << buildKeyList(STAT_EXTRA) << TR_KEY_fileStats;
}
return keys;
}
/***
****
***/
bool Torrent::setInt(int i, int value)
{
@ -239,16 +269,19 @@ bool Torrent::setSize(int i, qulonglong value)
return changed;
}
bool Torrent::setString(int i, char const* value)
bool Torrent::setString(int i, char const* value, size_t len)
{
bool changed = false;
assert(0 <= i && i < PROPERTY_COUNT);
assert(myProperties[i].type == QVariant::String);
if (myValues[i].isNull() || myValues[i].toString() != QString::fromUtf8(value))
auto& oldval = myValues[i];
auto const newval = QString::fromUtf8(value, len);
if (oldval != newval)
{
myValues[i].setValue(QString::fromUtf8(value));
oldval = newval;
changed = true;
}
@ -268,6 +301,7 @@ int Torrent::getInt(int i) const
{
assert(0 <= i && i < PROPERTY_COUNT);
assert(myProperties[i].type == QVariant::Int);
// assert(!myValues[i].isNull());
return myValues[i].toInt();
}
@ -276,6 +310,7 @@ time_t Torrent::getTime(int i) const
{
assert(0 <= i && i < PROPERTY_COUNT);
assert(myProperties[i].type == QVariant::DateTime);
// assert((i == DATE_ADDED) || !myValues[i].isNull());
return time_t(myValues[i].toLongLong());
}
@ -284,6 +319,7 @@ bool Torrent::getBool(int i) const
{
assert(0 <= i && i < PROPERTY_COUNT);
assert(myProperties[i].type == QVariant::Bool);
// assert(!myValues[i].isNull());
return myValues[i].toBool();
}
@ -292,6 +328,7 @@ qulonglong Torrent::getSize(int i) const
{
assert(0 <= i && i < PROPERTY_COUNT);
assert(myProperties[i].type == QVariant::ULongLong);
// assert(!myValues[i].isNull());
return myValues[i].toULongLong();
}
@ -300,6 +337,7 @@ double Torrent::getDouble(int i) const
{
assert(0 <= i && i < PROPERTY_COUNT);
assert(myProperties[i].type == QVariant::Double);
// assert(!myValues[i].isNull());
return myValues[i].toDouble();
}
@ -308,6 +346,7 @@ QString Torrent::getString(int i) const
{
assert(0 <= i && i < PROPERTY_COUNT);
assert(myProperties[i].type == QVariant::String);
// assert((i == HASH_STRING) || !myValues[i].isNull());
return myValues[i].toString();
}
@ -316,6 +355,7 @@ QIcon Torrent::getIcon(int i) const
{
assert(0 <= i && i < PROPERTY_COUNT);
assert(myProperties[i].type == QVariant::Icon);
// assert(!myValues[i].isNull());
return myValues[i].value<QIcon>();
}
@ -501,22 +541,11 @@ void Torrent::updateMimeIcon()
****
***/
void Torrent::notifyComplete() const
{
// if someone wants to implement notification, here's the hook.
}
/***
****
***/
void Torrent::update(tr_variant* d)
bool Torrent::update(tr_variant* d)
{
static bool lookup_initialized = false;
static int key_to_property_index[TR_N_KEYS];
bool changed = false;
bool const was_seed = isSeed();
uint64_t const old_verified_size = haveVerified();
if (!lookup_initialized)
{
@ -577,10 +606,11 @@ void Torrent::update(tr_variant* d)
case QVariant::String:
{
char const* val;
size_t len;
if (tr_variantGetStr(child, &val, nullptr))
if (tr_variantGetStr(child, &val, &len))
{
bool const field_changed = setString(property_index, val);
bool const field_changed = setString(property_index, val, len);
changed |= field_changed;
if (field_changed && key == TR_KEY_name)
@ -993,15 +1023,7 @@ void Torrent::update(tr_variant* d)
changed = true;
}
if (changed)
{
emit torrentChanged(id());
}
if (!was_seed && isSeed() && old_verified_size > 0)
{
emit torrentCompleted(id());
}
return changed;
}
QString Torrent::activityString() const

View File

@ -128,7 +128,6 @@ class Torrent : public QObject
public:
enum
{
ID,
UPLOAD_SPEED,
DOWNLOAD_SPEED,
DOWNLOAD_DIR,
@ -156,7 +155,6 @@ public:
DATE_CREATED,
PEERS_CONNECTED,
ETA,
RATIO,
DOWNLOADED_EVER,
UPLOADED_EVER,
FAILED_EVER,
@ -188,11 +186,10 @@ public:
PROPERTY_COUNT
};
typedef QList<tr_quark> KeyList;
public:
Torrent(Prefs const&, int id);
virtual ~Torrent();
virtual ~Torrent() = default;
;
int getBandwidthPriority() const
{
@ -201,7 +198,7 @@ public:
int id() const
{
return getInt(ID);
return myId;
}
QString name() const
@ -209,6 +206,11 @@ public:
return getString(NAME);
}
bool hasName() const
{
return !myValues[NAME].isNull();
}
QString creator() const
{
return getString(CREATOR);
@ -298,11 +300,6 @@ public:
return getDouble(METADATA_PERCENT_DONE) >= 1.0;
}
bool isMagnet() const
{
return magnetTorrent;
}
int pieceCount() const
{
return getInt(PIECE_COUNT);
@ -310,7 +307,10 @@ public:
double ratio() const
{
return getDouble(RATIO);
auto const u = uploadedEver();
auto const d = downloadedEver();
auto const t = totalSize();
return double(u) / (d ? d : t);
}
double percentComplete() const
@ -320,7 +320,9 @@ public:
double percentDone() const
{
return getDouble(PERCENT_DONE);
auto const l = leftUntilDone();
auto const s = sizeWhenDone();
return s ? double(s - l) / s : 0.0;
}
double metadataPercentDone() const
@ -578,43 +580,26 @@ public:
return isWaitingToDownload() || isWaitingToSeed();
}
void notifyComplete() const;
void update(tr_variant* dict);
void setMagnet(bool magnet)
{
magnetTorrent = magnet;
}
bool update(tr_variant* dict);
QIcon getMimeTypeIcon() const
{
return getIcon(MIME_ICON);
}
static KeyList const& getInfoKeys();
static KeyList const& getStatKeys();
static KeyList const& getExtraStatKeys();
signals:
void torrentChanged(int id);
void torrentCompleted(int id);
using KeyList = QSet<tr_quark>;
static KeyList const allMainKeys;
static KeyList const detailInfoKeys;
static KeyList const detailStatKeys;
static KeyList const mainInfoKeys;
static KeyList const mainStatKeys;
private:
enum Group
{
INFO, // info fields that only need to be loaded once
STAT, // commonly-used stats that should be refreshed often
STAT_EXTRA, // rarely used; only refresh if details dialog is open
DERIVED // doesn't come from RPC
};
struct Property
{
int id;
tr_quark key;
int type;
int group;
};
private:
@ -630,20 +615,17 @@ private:
bool setBool(int key, bool value);
bool setIcon(int key, QIcon const&);
bool setDouble(int key, double);
bool setString(int key, char const*);
bool setString(int key, char const*, size_t len);
bool setSize(int key, qulonglong);
bool setTime(int key, time_t);
char const* getMimeTypeString() const;
void updateMimeIcon();
static KeyList buildKeyList(Group group);
private:
int const myId;
Prefs const& myPrefs;
QVariant myValues[PROPERTY_COUNT];
bool magnetTorrent;
FileList myFiles;
static Property myProperties[];

View File

@ -178,7 +178,7 @@ bool TorrentFilter::lessThan(QModelIndex const& left, QModelIndex const& right)
case SortMode::SORT_BY_PROGRESS:
if (val == 0)
{
val = -compare(a->isMagnet(), b->isMagnet());
val = compare(a->metadataPercentDone(), b->metadataPercentDone());
}
if (val == 0)

View File

@ -6,7 +6,9 @@
*
*/
#include <algorithm>
#include <iostream>
#include <utility>
#include <libtransmission/transmission.h>
#include <libtransmission/variant.h>
@ -16,6 +18,10 @@
#include "TorrentDelegate.h"
#include "TorrentModel.h"
/***
****
***/
namespace
{
@ -37,15 +43,40 @@ struct TorrentIdLessThan
}
};
template<typename Iter>
QSet<int> getIds(Iter it, Iter end)
{
QSet<int> ids;
for ( ; it != end; ++it)
{
ids.insert((*it)->id());
}
return ids;
}
} // namespace
/***
****
***/
TorrentModel::TorrentModel(Prefs const& prefs) :
myPrefs(prefs)
{
}
TorrentModel::~TorrentModel()
{
clear();
}
void TorrentModel::clear()
{
beginResetModel();
qDeleteAll(myTorrents);
myTorrents.clear();
endResetModel();
}
@ -91,199 +122,296 @@ QVariant TorrentModel::data(QModelIndex const& index, int role) const
****
***/
void TorrentModel::addTorrent(Torrent* t)
{
torrents_t::iterator const torrentIt = qLowerBound(myTorrents.begin(), myTorrents.end(), t, TorrentIdLessThan());
int const row = torrentIt == myTorrents.end() ? myTorrents.size() : torrentIt - myTorrents.begin();
beginInsertRows(QModelIndex(), row, row);
myTorrents.insert(torrentIt, t);
endInsertRows();
}
void TorrentModel::addTorrents(torrents_t&& torrents, QSet<int>& addIds)
{
if (myTorrents.isEmpty())
{
qSort(torrents.begin(), torrents.end(), TorrentIdLessThan());
beginInsertRows(QModelIndex(), 0, torrents.size() - 1);
myTorrents.swap(torrents);
endInsertRows();
addIds += getIds();
}
else
{
for (Torrent* const tor : torrents)
{
addTorrent(tor);
addIds.insert(tor->id());
}
}
}
TorrentModel::TorrentModel(Prefs const& prefs) :
myPrefs(prefs)
{
}
TorrentModel::~TorrentModel()
{
clear();
}
/***
****
***/
Torrent* TorrentModel::getTorrentFromId(int id)
{
torrents_t::const_iterator const torrentIt = qBinaryFind(myTorrents.begin(), myTorrents.end(), id, TorrentIdLessThan());
return torrentIt == myTorrents.end() ? nullptr : *torrentIt;
}
Torrent const* TorrentModel::getTorrentFromId(int id) const
{
torrents_t::const_iterator const torrentIt = qBinaryFind(myTorrents.begin(), myTorrents.end(), id, TorrentIdLessThan());
return torrentIt == myTorrents.end() ? nullptr : *torrentIt;
}
/***
****
***/
void TorrentModel::onTorrentChanged(int torrentId)
{
torrents_t::iterator const torrentIt = qBinaryFind(myTorrents.begin(), myTorrents.end(), torrentId, TorrentIdLessThan());
if (torrentIt == myTorrents.end())
{
return;
}
int const row = torrentIt - myTorrents.begin();
QModelIndex const qmi(index(row, 0));
emit dataChanged(qmi, qmi);
}
void TorrentModel::removeTorrents(tr_variant* torrents)
void TorrentModel::removeTorrents(tr_variant* list)
{
int i = 0;
tr_variant* child;
QSet<Torrent*> torrents;
while ((child = tr_variantListChild(torrents, i++)) != nullptr)
while ((child = tr_variantListChild(list, i++)) != nullptr)
{
int64_t intVal;
int64_t id;
Torrent* torrent = nullptr;
if (tr_variantGetInt(child, &intVal))
if (tr_variantGetInt(child, &id))
{
removeTorrent(intVal);
torrent = getTorrentFromId(id);
}
if (torrent != nullptr)
{
torrents.insert(torrent);
}
}
if (!torrents.isEmpty())
{
rowsRemove(torrents);
}
}
void TorrentModel::updateTorrents(tr_variant* torrents, bool isCompleteList)
{
torrents_t newTorrents;
QSet<int> oldIds;
QSet<int> addIds;
QSet<int> newIds;
auto const old = QSet<Torrent*>::fromList(myTorrents.toList());
auto added = QSet<int>{};
auto changed = QSet<int>{};
auto completed = QSet<int>{};
auto instantiated = torrents_t{};
auto needinfo = QSet<int>{};
auto processed = QSet<Torrent*>{};
auto const now = time(nullptr);
auto const recently_added = [now](auto const& tor)
{
static auto constexpr max_age = 60;
auto const date = tor->dateAdded();
return (date != 0) && (difftime(now, date) < max_age);
};
size_t i = 0;
tr_variant* child;
while ((child = tr_variantListChild(torrents, i++)) != nullptr)
{
int64_t id;
if (!tr_variantDictFindInt(child, TR_KEY_id, &id))
{
continue;
}
Torrent* tor = getTorrentFromId(id);
std::optional<uint64_t> leftUntilDone;
if (tor == nullptr)
{
tor = new Torrent(myPrefs, id);
instantiated.push_back(tor);
}
else
{
leftUntilDone = tor->leftUntilDone();
}
if (tor->update(child))
{
changed.insert(id);
}
if (!tor->hasName() && !old.contains(tor))
{
needinfo.insert(id);
}
if (recently_added(tor) && tor->hasName() && !myAlreadyAdded.contains(id))
{
added.insert(id);
myAlreadyAdded.insert(id);
}
if (leftUntilDone && (*leftUntilDone > 0) && (tor->leftUntilDone() == 0) && (tor->downloadedEver() > 0))
{
completed.insert(id);
}
processed.insert(tor);
}
// model upkeep
if (!instantiated.isEmpty())
{
rowsAdd(instantiated);
}
if (!changed.isEmpty())
{
rowsEmitChanged(changed);
}
// emit signals
if (!added.isEmpty())
{
emit torrentsAdded(added);
}
if (!needinfo.isEmpty())
{
emit torrentsNeedInfo(needinfo);
}
if (!changed.isEmpty())
{
emit torrentsChanged(changed);
}
if (!completed.isEmpty())
{
emit torrentsCompleted(completed);
}
// model upkeep
if (isCompleteList)
{
oldIds = getIds();
auto const removed = old - processed;
if (!removed.isEmpty())
{
rowsRemove(removed);
}
}
}
/***
****
***/
std::optional<int> TorrentModel::getRow(int id) const
{
std::optional<int> row;
auto const it = std::equal_range(myTorrents.begin(), myTorrents.end(), id, TorrentIdLessThan());
if (it.first != it.second)
{
row = std::distance(myTorrents.begin(), it.first);
assert(myTorrents[*row]->id() == id);
}
if (tr_variantIsList(torrents))
return row;
}
std::optional<int> TorrentModel::getRow(Torrent const* tor) const
{
return getRow(tor->id());
}
Torrent* TorrentModel::getTorrentFromId(int id)
{
auto const row = getRow(id);
return row ? myTorrents[*row] : nullptr;
}
Torrent const* TorrentModel::getTorrentFromId(int id) const
{
auto const row = getRow(id);
return row ? myTorrents[*row] : nullptr;
}
/***
****
***/
std::vector<TorrentModel::span_t> TorrentModel::getSpans(QSet<int> const& ids) const
{
// ids -> rows
std::vector<int> rows;
rows.reserve(ids.size());
for (auto const& id : ids)
{
size_t i(0);
tr_variant* child;
while ((child = tr_variantListChild(torrents, i++)) != nullptr)
auto const row = getRow(id);
if (row)
{
int64_t id;
rows.push_back(*row);
}
}
if (tr_variantDictFindInt(child, TR_KEY_id, &id))
std::sort(rows.begin(), rows.end());
// rows -> spans
std::vector<span_t> spans;
spans.reserve(rows.size());
span_t span;
bool in_span = false;
for (auto const& row : rows)
{
if (in_span)
{
if (span.second + 1 == row)
{
if (isCompleteList)
{
newIds.insert(id);
}
Torrent* tor = getTorrentFromId(id);
if (tor == nullptr)
{
tor = new Torrent(myPrefs, id);
tor->update(child);
if (!tor->hasMetadata())
{
tor->setMagnet(true);
}
newTorrents.append(tor);
connect(tor, SIGNAL(torrentChanged(int)), this, SLOT(onTorrentChanged(int)));
}
else
{
tor->update(child);
if (tor->isMagnet() && tor->hasMetadata())
{
addIds.insert(tor->id());
tor->setMagnet(false);
}
}
span.second = row;
}
else
{
spans.push_back(span);
in_span = false;
}
}
}
if (!newTorrents.isEmpty())
{
addTorrents(std::move(newTorrents), addIds);
}
if (!addIds.isEmpty())
{
emit torrentsAdded(addIds);
}
if (isCompleteList)
{
QSet<int> removedIds(oldIds);
removedIds -= newIds;
for (int const id : removedIds)
if (!in_span)
{
removeTorrent(id);
span.first = span.second = row;
in_span = true;
}
}
if (in_span)
{
spans.push_back(span);
}
return spans;
}
/***
****
***/
void TorrentModel::rowsEmitChanged(QSet<int> const& ids)
{
for (auto const& span : getSpans(ids))
{
emit dataChanged(index(span.first), index(span.second));
}
}
void TorrentModel::rowsAdd(torrents_t const& torrents)
{
auto const compare = TorrentIdLessThan();
if (myTorrents.isEmpty())
{
beginInsertRows(QModelIndex(), 0, torrents.size() - 1);
myTorrents = torrents;
std::sort(myTorrents.begin(), myTorrents.end(), TorrentIdLessThan());
endInsertRows();
}
else
{
for (auto const& tor : torrents)
{
auto const it = std::lower_bound(myTorrents.begin(), myTorrents.end(), tor, compare);
auto const row = std::distance(myTorrents.begin(), it);
beginInsertRows(QModelIndex(), row, row);
myTorrents.insert(it, tor);
endInsertRows();
}
}
}
void TorrentModel::removeTorrent(int id)
void TorrentModel::rowsRemove(QSet<Torrent*> const& torrents)
{
torrents_t::iterator const torrentIt = qBinaryFind(myTorrents.begin(), myTorrents.end(), id, TorrentIdLessThan());
if (torrentIt == myTorrents.end())
// must walk in reverse to avoid invalidating row numbers
auto const& spans = getSpans(getIds(torrents.begin(), torrents.end()));
for (auto it = spans.rbegin(), end = spans.rend(); it != end; ++it)
{
return;
auto const& span = *it;
beginRemoveRows(QModelIndex(), span.first, span.second);
auto const n = span.second + 1 - span.first;
myTorrents.remove(span.first, n);
endRemoveRows();
}
Torrent* const tor = *torrentIt;
int const row = torrentIt - myTorrents.begin();
beginRemoveRows(QModelIndex(), row, row);
myTorrents.remove(row);
endRemoveRows();
delete tor;
qDeleteAll(torrents);
}
/***
****
***/
void TorrentModel::getTransferSpeed(Speed& uploadSpeed, size_t& uploadPeerCount, Speed& downloadSpeed,
size_t& downloadPeerCount)
size_t& downloadPeerCount) const
{
Speed upSpeed;
Speed downSpeed;
@ -305,29 +433,8 @@ void TorrentModel::getTransferSpeed(Speed& uploadSpeed, size_t& uploadPeerCount,
downloadPeerCount = downCount;
}
QSet<int> TorrentModel::getIds() const
{
QSet<int> ids;
ids.reserve(myTorrents.size());
for (Torrent const* const tor : myTorrents)
{
ids.insert(tor->id());
}
return ids;
}
bool TorrentModel::hasTorrent(QString const& hashString) const
{
for (Torrent const* const tor : myTorrents)
{
if (tor->hashString() == hashString)
{
return true;
}
}
return false;
auto test = [hashString](auto const& tor) { return tor->hashString() == hashString; };
return std::any_of(myTorrents.cbegin(), myTorrents.cend(), test);
}

View File

@ -8,6 +8,8 @@
#pragma once
#include <optional>
#include <QAbstractListModel>
#include <QSet>
#include <QVector>
@ -31,43 +33,43 @@ public:
TorrentRole = Qt::UserRole
};
public:
TorrentModel(Prefs const& prefs);
explicit TorrentModel(Prefs const& prefs);
virtual ~TorrentModel();
void clear();
bool hasTorrent(QString const& hashString) const;
Torrent* getTorrentFromId(int id);
Torrent const* getTorrentFromId(int id) const;
void getTransferSpeed(Speed& uploadSpeed, size_t& uploadPeerCount, Speed& downloadSpeed, size_t& downloadPeerCount);
void getTransferSpeed(Speed& uploadSpeed, size_t& uploadPeerCount, Speed& downloadSpeed, size_t& downloadPeerCount) const;
// QAbstractItemModel
virtual int rowCount(QModelIndex const& parent = QModelIndex()) const;
virtual QVariant data(QModelIndex const& index, int role = Qt::DisplayRole) const;
int rowCount(QModelIndex const& parent = QModelIndex()) const override;
QVariant data(QModelIndex const& index, int role = Qt::DisplayRole) const override;
public slots:
void updateTorrents(tr_variant* torrentList, bool isCompleteList);
void removeTorrents(tr_variant* torrentList);
void removeTorrent(int id);
signals:
void torrentsAdded(QSet<int>);
void torrentsChanged(QSet<int>);
void torrentsCompleted(QSet<int>);
void torrentsNeedInfo(QSet<int>);
private:
typedef QVector<Torrent*> torrents_t;
using torrents_t = QVector<Torrent*>;
void rowsAdd(torrents_t const& torrents);
void rowsRemove(QSet<Torrent*> const& torrents);
void rowsEmitChanged(QSet<int> const& ids);
private:
void addTorrent(Torrent*);
void addTorrents(torrents_t&& torrents, QSet<int>& addIds);
QSet<int> getIds() const;
std::optional<int> getRow(int id) const;
std::optional<int> getRow(Torrent const* tor) const;
using span_t = std::pair<int, int>;
std::vector<span_t> getSpans(QSet<int> const& ids) const;
private slots:
void onTorrentChanged(int propertyId);
private:
Prefs const& myPrefs;
torrents_t myTorrents;
QSet<int> myAlreadyAdded;
};