Restore BitTorrent session asynchronously

Reduce the total startup time of the application and maintain sufficient responsiveness of the UI during startup due to the following:
1. Load resume data from disk asynchronously in separate thread;
2. Split handling of loaded resume data in chunks;
3. Reduce the number of emitting signals.

PR #16840.
This commit is contained in:
Vladimir Golovnev
2022-07-04 12:48:21 +03:00
committed by GitHub
parent ec1d2cba40
commit be7cfb78de
25 changed files with 927 additions and 553 deletions

View File

@@ -32,7 +32,6 @@
#include <QIcon>
#include "base/bittorrent/session.h"
#include "base/bittorrent/torrent.h"
#include "base/global.h"
#include "uithememanager.h"
@@ -181,7 +180,7 @@ CategoryFilterModel::CategoryFilterModel(QObject *parent)
connect(session, &Session::categoryRemoved, this, &CategoryFilterModel::categoryRemoved);
connect(session, &Session::torrentCategoryChanged, this, &CategoryFilterModel::torrentCategoryChanged);
connect(session, &Session::subcategoriesSupportChanged, this, &CategoryFilterModel::subcategoriesSupportChanged);
connect(session, &Session::torrentLoaded, this, &CategoryFilterModel::torrentAdded);
connect(session, &Session::torrentsLoaded, this, &CategoryFilterModel::torrentsLoaded);
connect(session, &Session::torrentAboutToBeRemoved, this, &CategoryFilterModel::torrentAboutToBeRemoved);
populate();
@@ -333,13 +332,16 @@ void CategoryFilterModel::categoryRemoved(const QString &categoryName)
}
}
void CategoryFilterModel::torrentAdded(BitTorrent::Torrent *const torrent)
void CategoryFilterModel::torrentsLoaded(const QVector<BitTorrent::Torrent *> &torrents)
{
CategoryModelItem *item = findItem(torrent->category());
Q_ASSERT(item);
for (const BitTorrent::Torrent *torrent : torrents)
{
CategoryModelItem *item = findItem(torrent->category());
Q_ASSERT(item);
item->increaseTorrentsCount();
m_rootItem->childAt(0)->increaseTorrentsCount();
item->increaseTorrentsCount();
m_rootItem->childAt(0)->increaseTorrentsCount();
}
}
void CategoryFilterModel::torrentAboutToBeRemoved(BitTorrent::Torrent *const torrent)

View File

@@ -28,17 +28,15 @@
#pragma once
#include <QtContainerFwd>
#include <QAbstractItemModel>
#include "base/bittorrent/torrent.h"
class QModelIndex;
class CategoryModelItem;
namespace BitTorrent
{
class Torrent;
}
class CategoryFilterModel final : public QAbstractItemModel
{
Q_OBJECT
@@ -64,7 +62,7 @@ public:
private slots:
void categoryAdded(const QString &categoryName);
void categoryRemoved(const QString &categoryName);
void torrentAdded(BitTorrent::Torrent *const torrent);
void torrentsLoaded(const QVector<BitTorrent::Torrent *> &torrents);
void torrentAboutToBeRemoved(BitTorrent::Torrent *const torrent);
void torrentCategoryChanged(BitTorrent::Torrent *const torrent, const QString &oldCategory);
void subcategoriesSupportChanged();

View File

@@ -1205,12 +1205,12 @@ void MainWindow::keyPressEvent(QKeyEvent *event)
{
if (event->matches(QKeySequence::Paste))
{
const QMimeData *mimeData {QGuiApplication::clipboard()->mimeData()};
const QMimeData *mimeData = QGuiApplication::clipboard()->mimeData();
if (mimeData->hasText())
{
const bool useTorrentAdditionDialog {AddNewTorrentDialog::isEnabled()};
const QStringList lines {mimeData->text().split(u'\n', Qt::SkipEmptyParts)};
const bool useTorrentAdditionDialog = AddNewTorrentDialog::isEnabled();
const QStringList lines = mimeData->text().split(u'\n', Qt::SkipEmptyParts);
for (QString line : lines)
{
@@ -1438,6 +1438,7 @@ void MainWindow::dragEnterEvent(QDragEnterEvent *event)
{
for (const QString &mime : asConst(event->mimeData()->formats()))
qDebug("mimeData: %s", mime.toLocal8Bit().data());
if (event->mimeData()->hasFormat(u"text/plain"_qs) || event->mimeData()->hasFormat(u"text/uri-list"_qs))
event->acceptProposedAction();
}

View File

@@ -208,7 +208,7 @@ void SearchJobWidget::cancelSearch()
void SearchJobWidget::downloadTorrents(const AddTorrentOption option)
{
const QModelIndexList rows {m_ui->resultsBrowser->selectionModel()->selectedRows()};
const QModelIndexList rows = m_ui->resultsBrowser->selectionModel()->selectedRows();
for (const QModelIndex &rowIndex : rows)
downloadTorrent(rowIndex, option);
}
@@ -390,10 +390,10 @@ void SearchJobWidget::contextMenuEvent(QContextMenuEvent *event)
auto *menu = new QMenu(this);
menu->setAttribute(Qt::WA_DeleteOnClose);
menu->addAction(UIThemeManager::instance()->getIcon(u"kt-set-max-download-speed"_qs), tr("Open download window")
, this, [this]() { downloadTorrents(AddTorrentOption::ShowDialog); });
menu->addAction(UIThemeManager::instance()->getIcon(u"downloading"_qs), tr("Download")
, this, [this]() { downloadTorrents(AddTorrentOption::SkipDialog); });
menu->addAction(UIThemeManager::instance()->getIcon(u"kt-set-max-download-speed"_qs)
, tr("Open download window"), this, [this]() { downloadTorrents(AddTorrentOption::ShowDialog); });
menu->addAction(UIThemeManager::instance()->getIcon(u"downloading"_qs)
, tr("Download"), this, [this]() { downloadTorrents(AddTorrentOption::SkipDialog); });
menu->addSeparator();
menu->addAction(UIThemeManager::instance()->getIcon(u"application-x-mswinurl"_qs), tr("Open description page")
, this, &SearchJobWidget::openTorrentPages);

View File

@@ -33,7 +33,6 @@
#include <QVector>
#include "base/bittorrent/session.h"
#include "base/bittorrent/torrent.h"
#include "base/global.h"
#include "uithememanager.h"
@@ -99,7 +98,7 @@ TagFilterModel::TagFilterModel(QObject *parent)
connect(session, &Session::tagRemoved, this, &TagFilterModel::tagRemoved);
connect(session, &Session::torrentTagAdded, this, &TagFilterModel::torrentTagAdded);
connect(session, &Session::torrentTagRemoved, this, &TagFilterModel::torrentTagRemoved);
connect(session, &Session::torrentLoaded, this, &TagFilterModel::torrentAdded);
connect(session, &Session::torrentsLoaded, this, &TagFilterModel::torrentsLoaded);
connect(session, &Session::torrentAboutToBeRemoved, this, &TagFilterModel::torrentAboutToBeRemoved);
populate();
}
@@ -230,16 +229,19 @@ void TagFilterModel::torrentTagRemoved(BitTorrent::Torrent *const torrent, const
emit dataChanged(i, i);
}
void TagFilterModel::torrentAdded(BitTorrent::Torrent *const torrent)
void TagFilterModel::torrentsLoaded(const QVector<BitTorrent::Torrent *> &torrents)
{
allTagsItem()->increaseTorrentsCount();
for (const BitTorrent::Torrent *torrent : torrents)
{
allTagsItem()->increaseTorrentsCount();
const QVector<TagModelItem *> items = findItems(torrent->tags());
if (items.isEmpty())
untaggedItem()->increaseTorrentsCount();
const QVector<TagModelItem *> items = findItems(torrent->tags());
if (items.isEmpty())
untaggedItem()->increaseTorrentsCount();
for (TagModelItem *item : items)
item->increaseTorrentsCount();
for (TagModelItem *item : items)
item->increaseTorrentsCount();
}
}
void TagFilterModel::torrentAboutToBeRemoved(BitTorrent::Torrent *const torrent)

View File

@@ -28,20 +28,16 @@
#pragma once
#include <QAbstractListModel>
#include <QtContainerFwd>
#include <QAbstractItemModel>
#include "base/bittorrent/torrent.h"
#include "base/tagset.h"
class QModelIndex;
class TagModelItem;
namespace BitTorrent
{
class Torrent;
}
class TagFilterModel final : public QAbstractListModel
{
Q_OBJECT
@@ -67,7 +63,7 @@ private slots:
void tagRemoved(const QString &tag);
void torrentTagAdded(BitTorrent::Torrent *const torrent, const QString &tag);
void torrentTagRemoved(BitTorrent::Torrent *const, const QString &tag);
void torrentAdded(BitTorrent::Torrent *const torrent);
void torrentsLoaded(const QVector<BitTorrent::Torrent *> &torrents);
void torrentAboutToBeRemoved(BitTorrent::Torrent *const torrent);
private:

View File

@@ -134,8 +134,8 @@ BaseFilterWidget::BaseFilterWidget(QWidget *parent, TransferListWidget *transfer
connect(this, &BaseFilterWidget::customContextMenuRequested, this, &BaseFilterWidget::showMenu);
connect(this, &BaseFilterWidget::currentRowChanged, this, &BaseFilterWidget::applyFilter);
connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentLoaded
, this, &BaseFilterWidget::handleNewTorrent);
connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentsLoaded
, this, &BaseFilterWidget::handleTorrentsLoaded);
connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentAboutToBeRemoved
, this, &BaseFilterWidget::torrentAboutToBeDeleted);
}
@@ -318,9 +318,11 @@ void StatusFilterWidget::applyFilter(int row)
transferList->applyStatusFilter(row);
}
void StatusFilterWidget::handleNewTorrent(BitTorrent::Torrent *const torrent)
void StatusFilterWidget::handleTorrentsLoaded(const QVector<BitTorrent::Torrent *> &torrents)
{
updateTorrentStatus(torrent);
for (const BitTorrent::Torrent *torrent : torrents)
updateTorrentStatus(torrent);
updateTexts();
}
@@ -376,6 +378,8 @@ TrackerFiltersList::TrackerFiltersList(QWidget *parent, TransferListWidget *tran
m_trackers[NULL_HOST] = {{}, noTracker};
handleTorrentsLoaded(BitTorrent::Session::instance()->torrents());
setCurrentRow(0, QItemSelectionModel::SelectCurrent);
toggleFilter(Preferences::instance()->getTrackerFilterState());
}
@@ -390,7 +394,7 @@ void TrackerFiltersList::addTrackers(const BitTorrent::Torrent *torrent, const Q
{
const BitTorrent::TorrentID torrentID = torrent->id();
for (const BitTorrent::TrackerEntry &tracker : trackers)
addItem(tracker.url, torrentID);
addItems(tracker.url, {torrentID});
}
void TrackerFiltersList::removeTrackers(const BitTorrent::Torrent *torrent, const QStringList &trackers)
@@ -431,12 +435,12 @@ void TrackerFiltersList::refreshTrackers(const BitTorrent::Torrent *torrent)
const bool isTrackerless = trackerEntries.isEmpty();
if (isTrackerless)
{
addItem(NULL_HOST, torrentID);
addItems(NULL_HOST, {torrentID});
}
else
{
for (const BitTorrent::TrackerEntry &trackerEntry : trackerEntries)
addItem(trackerEntry.url, torrentID);
addItems(trackerEntry.url, {torrentID});
}
updateGeometry();
@@ -445,23 +449,20 @@ void TrackerFiltersList::refreshTrackers(const BitTorrent::Torrent *torrent)
void TrackerFiltersList::changeTrackerless(const BitTorrent::Torrent *torrent, const bool trackerless)
{
if (trackerless)
addItem(NULL_HOST, torrent->id());
addItems(NULL_HOST, {torrent->id()});
else
removeItem(NULL_HOST, torrent->id());
}
void TrackerFiltersList::addItem(const QString &tracker, const BitTorrent::TorrentID &id)
void TrackerFiltersList::addItems(const QString &trackerURL, const QVector<BitTorrent::TorrentID> &torrents)
{
const QString host = getHost(tracker);
const QString host = getHost(trackerURL);
auto trackersIt = m_trackers.find(host);
const bool exists = (trackersIt != m_trackers.end());
QListWidgetItem *trackerItem = nullptr;
if (exists)
{
if (trackersIt->torrents.contains(id))
return;
trackerItem = trackersIt->item;
}
else
@@ -469,17 +470,18 @@ void TrackerFiltersList::addItem(const QString &tracker, const BitTorrent::Torre
trackerItem = new QListWidgetItem();
trackerItem->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"trackers"_qs));
TrackerData trackerData {{}, trackerItem};
const TrackerData trackerData {{}, trackerItem};
trackersIt = m_trackers.insert(host, trackerData);
const QString scheme = getScheme(tracker);
const QString scheme = getScheme(trackerURL);
downloadFavicon(u"%1://%2/favicon.ico"_qs.arg((scheme.startsWith(u"http") ? scheme : u"http"_qs), host));
}
Q_ASSERT(trackerItem);
QSet<BitTorrent::TorrentID> &torrentIDs = trackersIt->torrents;
torrentIDs.insert(id);
for (const BitTorrent::TorrentID &torrentID : torrents)
torrentIDs.insert(torrentID);
trackerItem->setText(u"%1 (%2)"_qs.arg(((host == NULL_HOST) ? tr("Trackerless") : host), QString::number(torrentIDs.size())));
if (exists)
@@ -724,18 +726,30 @@ void TrackerFiltersList::applyFilter(const int row)
transferList->applyTrackerFilter(getTorrentIDs(row));
}
void TrackerFiltersList::handleNewTorrent(BitTorrent::Torrent *const torrent)
void TrackerFiltersList::handleTorrentsLoaded(const QVector<BitTorrent::Torrent *> &torrents)
{
const BitTorrent::TorrentID torrentID = torrent->id();
const QVector<BitTorrent::TrackerEntry> trackers = torrent->trackers();
for (const BitTorrent::TrackerEntry &tracker : trackers)
addItem(tracker.url, torrentID);
QHash<QString, QVector<BitTorrent::TorrentID>> torrentsPerTracker;
for (const BitTorrent::Torrent *torrent : torrents)
{
const BitTorrent::TorrentID torrentID = torrent->id();
const QVector<BitTorrent::TrackerEntry> trackers = torrent->trackers();
for (const BitTorrent::TrackerEntry &tracker : trackers)
torrentsPerTracker[tracker.url].append(torrentID);
// Check for trackerless torrent
if (trackers.isEmpty())
addItem(NULL_HOST, torrentID);
// Check for trackerless torrent
if (trackers.isEmpty())
torrentsPerTracker[NULL_HOST].append(torrentID);
}
item(ALL_ROW)->setText(tr("All (%1)", "this is for the tracker filter").arg(++m_totalTorrents));
for (auto it = torrentsPerTracker.cbegin(); it != torrentsPerTracker.cend(); ++it)
{
const QString &trackerURL = it.key();
const QVector<BitTorrent::TorrentID> &torrents = it.value();
addItems(trackerURL, torrents);
}
m_totalTorrents += torrents.count();
item(ALL_ROW)->setText(tr("All (%1)", "this is for the tracker filter").arg(m_totalTorrents));
}
void TrackerFiltersList::torrentAboutToBeDeleted(BitTorrent::Torrent *const torrent)

View File

@@ -71,7 +71,7 @@ protected:
private slots:
virtual void showMenu() = 0;
virtual void applyFilter(int row) = 0;
virtual void handleNewTorrent(BitTorrent::Torrent *const) = 0;
virtual void handleTorrentsLoaded(const QVector<BitTorrent::Torrent *> &torrents) = 0;
virtual void torrentAboutToBeDeleted(BitTorrent::Torrent *const) = 0;
};
@@ -92,7 +92,7 @@ private:
// No need to redeclare them here as slots.
void showMenu() override;
void applyFilter(int row) override;
void handleNewTorrent(BitTorrent::Torrent *const) override;
void handleTorrentsLoaded(const QVector<BitTorrent::Torrent *> &torrents) override;
void torrentAboutToBeDeleted(BitTorrent::Torrent *const) override;
void populate();
@@ -139,10 +139,10 @@ private:
// No need to redeclare them here as slots.
void showMenu() override;
void applyFilter(int row) override;
void handleNewTorrent(BitTorrent::Torrent *const torrent) override;
void handleTorrentsLoaded(const QVector<BitTorrent::Torrent *> &torrents) override;
void torrentAboutToBeDeleted(BitTorrent::Torrent *const torrent) override;
void addItem(const QString &tracker, const BitTorrent::TorrentID &id);
void addItems(const QString &trackerURL, const QVector<BitTorrent::TorrentID> &torrents);
void removeItem(const QString &trackerURL, const BitTorrent::TorrentID &id);
QString trackerFromRow(int row) const;
int rowFromTracker(const QString &tracker) const;

View File

@@ -166,11 +166,10 @@ TransferListModel::TransferListModel(QObject *parent)
// Load the torrents
using namespace BitTorrent;
for (Torrent *const torrent : asConst(Session::instance()->torrents()))
addTorrent(torrent);
addTorrents(Session::instance()->torrents());
// Listen for torrent changes
connect(Session::instance(), &Session::torrentLoaded, this, &TransferListModel::addTorrent);
connect(Session::instance(), &Session::torrentsLoaded, this, &TransferListModel::addTorrents);
connect(Session::instance(), &Session::torrentAboutToBeRemoved, this, &TransferListModel::handleTorrentAboutToBeRemoved);
connect(Session::instance(), &Session::torrentsUpdated, this, &TransferListModel::handleTorrentsUpdated);
@@ -599,15 +598,19 @@ bool TransferListModel::setData(const QModelIndex &index, const QVariant &value,
return true;
}
void TransferListModel::addTorrent(BitTorrent::Torrent *const torrent)
void TransferListModel::addTorrents(const QVector<BitTorrent::Torrent *> &torrents)
{
Q_ASSERT(!m_torrentMap.contains(torrent));
int row = m_torrentList.size();
beginInsertRows({}, row, (row + torrents.size()));
const int row = m_torrentList.size();
for (BitTorrent::Torrent *torrent : torrents)
{
Q_ASSERT(!m_torrentMap.contains(torrent));
m_torrentList.append(torrent);
m_torrentMap[torrent] = row++;
}
beginInsertRows({}, row, row);
m_torrentList << torrent;
m_torrentMap[torrent] = row;
endInsertRows();
}

View File

@@ -103,7 +103,7 @@ public:
BitTorrent::Torrent *torrentHandle(const QModelIndex &index) const;
private slots:
void addTorrent(BitTorrent::Torrent *const torrent);
void addTorrents(const QVector<BitTorrent::Torrent *> &torrents);
void handleTorrentAboutToBeRemoved(BitTorrent::Torrent *const torrent);
void handleTorrentStatusUpdated(BitTorrent::Torrent *const torrent);
void handleTorrentsUpdated(const QVector<BitTorrent::Torrent *> &torrents);