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:
parent
4c79758dbc
commit
d857a5821a
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
127
qt/MainWindow.cc
127
qt/MainWindow.cc
|
@ -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.
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
|
|
@ -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()
|
||||
|
|
29
qt/Session.h
29
qt/Session.h
|
@ -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);
|
||||
|
|
282
qt/Torrent.cc
282
qt/Torrent.cc
|
@ -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
|
||||
|
|
66
qt/Torrent.h
66
qt/Torrent.h
|
@ -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[];
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue