mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2025-12-22 16:37:21 -06:00
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:
committed by
GitHub
parent
ec1d2cba40
commit
be7cfb78de
@@ -175,7 +175,7 @@ namespace BitTorrent
|
||||
Q_DISABLE_COPY_MOVE(Worker)
|
||||
|
||||
public:
|
||||
Worker(const Path &dbPath, const QString &dbConnectionName);
|
||||
Worker(const Path &dbPath, const QString &dbConnectionName, QReadWriteLock &dbLock);
|
||||
|
||||
void openDatabase() const;
|
||||
void closeDatabase() const;
|
||||
@@ -187,11 +187,64 @@ namespace BitTorrent
|
||||
private:
|
||||
const Path m_path;
|
||||
const QString m_connectionName;
|
||||
QReadWriteLock &m_dbLock;
|
||||
};
|
||||
|
||||
namespace
|
||||
{
|
||||
LoadTorrentParams parseQueryResultRow(const QSqlQuery &query)
|
||||
{
|
||||
LoadTorrentParams resumeData;
|
||||
resumeData.restored = true;
|
||||
resumeData.name = query.value(DB_COLUMN_NAME.name).toString();
|
||||
resumeData.category = query.value(DB_COLUMN_CATEGORY.name).toString();
|
||||
const QString tagsData = query.value(DB_COLUMN_TAGS.name).toString();
|
||||
if (!tagsData.isEmpty())
|
||||
{
|
||||
const QStringList tagList = tagsData.split(u',');
|
||||
resumeData.tags.insert(tagList.cbegin(), tagList.cend());
|
||||
}
|
||||
resumeData.hasSeedStatus = query.value(DB_COLUMN_HAS_SEED_STATUS.name).toBool();
|
||||
resumeData.firstLastPiecePriority = query.value(DB_COLUMN_HAS_OUTER_PIECES_PRIORITY.name).toBool();
|
||||
resumeData.ratioLimit = query.value(DB_COLUMN_RATIO_LIMIT.name).toInt() / 1000.0;
|
||||
resumeData.seedingTimeLimit = query.value(DB_COLUMN_SEEDING_TIME_LIMIT.name).toInt();
|
||||
resumeData.contentLayout = Utils::String::toEnum<TorrentContentLayout>(
|
||||
query.value(DB_COLUMN_CONTENT_LAYOUT.name).toString(), TorrentContentLayout::Original);
|
||||
resumeData.operatingMode = Utils::String::toEnum<TorrentOperatingMode>(
|
||||
query.value(DB_COLUMN_OPERATING_MODE.name).toString(), TorrentOperatingMode::AutoManaged);
|
||||
resumeData.stopped = query.value(DB_COLUMN_STOPPED.name).toBool();
|
||||
|
||||
resumeData.savePath = Profile::instance()->fromPortablePath(
|
||||
Path(query.value(DB_COLUMN_TARGET_SAVE_PATH.name).toString()));
|
||||
resumeData.useAutoTMM = resumeData.savePath.isEmpty();
|
||||
if (!resumeData.useAutoTMM)
|
||||
{
|
||||
resumeData.downloadPath = Profile::instance()->fromPortablePath(
|
||||
Path(query.value(DB_COLUMN_DOWNLOAD_PATH.name).toString()));
|
||||
}
|
||||
|
||||
const QByteArray bencodedResumeData = query.value(DB_COLUMN_RESUMEDATA.name).toByteArray();
|
||||
const QByteArray bencodedMetadata = query.value(DB_COLUMN_METADATA.name).toByteArray();
|
||||
const QByteArray allData = ((bencodedMetadata.isEmpty() || bencodedResumeData.isEmpty())
|
||||
? bencodedResumeData
|
||||
: (bencodedResumeData.chopped(1) + bencodedMetadata.mid(1)));
|
||||
|
||||
lt::error_code ec;
|
||||
const lt::bdecode_node root = lt::bdecode(allData, ec);
|
||||
|
||||
lt::add_torrent_params &p = resumeData.ltAddTorrentParams;
|
||||
|
||||
p = lt::read_resume_data(root, ec);
|
||||
p.save_path = Profile::instance()->fromPortablePath(Path(fromLTString(p.save_path)))
|
||||
.toString().toStdString();
|
||||
|
||||
return resumeData;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BitTorrent::DBResumeDataStorage::DBResumeDataStorage(const Path &dbPath, QObject *parent)
|
||||
: ResumeDataStorage {parent}
|
||||
: ResumeDataStorage(dbPath, parent)
|
||||
, m_ioThread {new QThread(this)}
|
||||
{
|
||||
const bool needCreateDB = !dbPath.exists();
|
||||
@@ -212,7 +265,7 @@ BitTorrent::DBResumeDataStorage::DBResumeDataStorage(const Path &dbPath, QObject
|
||||
updateDBFromVersion1();
|
||||
}
|
||||
|
||||
m_asyncWorker = new Worker(dbPath, u"ResumeDataStorageWorker"_qs);
|
||||
m_asyncWorker = new Worker(dbPath, u"ResumeDataStorageWorker"_qs, m_dbLock);
|
||||
m_asyncWorker->moveToThread(m_ioThread);
|
||||
connect(m_ioThread, &QThread::finished, m_asyncWorker, &QObject::deleteLater);
|
||||
m_ioThread->start();
|
||||
@@ -262,7 +315,7 @@ QVector<BitTorrent::TorrentID> BitTorrent::DBResumeDataStorage::registeredTorren
|
||||
return registeredTorrents;
|
||||
}
|
||||
|
||||
std::optional<BitTorrent::LoadTorrentParams> BitTorrent::DBResumeDataStorage::load(const TorrentID &id) const
|
||||
BitTorrent::LoadResumeDataResult BitTorrent::DBResumeDataStorage::load(const TorrentID &id) const
|
||||
{
|
||||
const QString selectTorrentStatement = u"SELECT * FROM %1 WHERE %2 = %3;"_qs
|
||||
.arg(quoted(DB_TABLE_TORRENTS), quoted(DB_COLUMN_TORRENT_ID.name), DB_COLUMN_TORRENT_ID.placeholder);
|
||||
@@ -283,56 +336,11 @@ std::optional<BitTorrent::LoadTorrentParams> BitTorrent::DBResumeDataStorage::lo
|
||||
}
|
||||
catch (const RuntimeError &err)
|
||||
{
|
||||
LogMsg(tr("Couldn't load resume data of torrent '%1'. Error: %2")
|
||||
.arg(id.toString(), err.message()), Log::CRITICAL);
|
||||
return std::nullopt;
|
||||
return nonstd::make_unexpected(tr("Couldn't load resume data of torrent '%1'. Error: %2")
|
||||
.arg(id.toString(), err.message()));
|
||||
}
|
||||
|
||||
LoadTorrentParams resumeData;
|
||||
resumeData.restored = true;
|
||||
resumeData.name = query.value(DB_COLUMN_NAME.name).toString();
|
||||
resumeData.category = query.value(DB_COLUMN_CATEGORY.name).toString();
|
||||
const QString tagsData = query.value(DB_COLUMN_TAGS.name).toString();
|
||||
if (!tagsData.isEmpty())
|
||||
{
|
||||
const QStringList tagList = tagsData.split(u',');
|
||||
resumeData.tags.insert(tagList.cbegin(), tagList.cend());
|
||||
}
|
||||
resumeData.hasSeedStatus = query.value(DB_COLUMN_HAS_SEED_STATUS.name).toBool();
|
||||
resumeData.firstLastPiecePriority = query.value(DB_COLUMN_HAS_OUTER_PIECES_PRIORITY.name).toBool();
|
||||
resumeData.ratioLimit = query.value(DB_COLUMN_RATIO_LIMIT.name).toInt() / 1000.0;
|
||||
resumeData.seedingTimeLimit = query.value(DB_COLUMN_SEEDING_TIME_LIMIT.name).toInt();
|
||||
resumeData.contentLayout = Utils::String::toEnum<TorrentContentLayout>(
|
||||
query.value(DB_COLUMN_CONTENT_LAYOUT.name).toString(), TorrentContentLayout::Original);
|
||||
resumeData.operatingMode = Utils::String::toEnum<TorrentOperatingMode>(
|
||||
query.value(DB_COLUMN_OPERATING_MODE.name).toString(), TorrentOperatingMode::AutoManaged);
|
||||
resumeData.stopped = query.value(DB_COLUMN_STOPPED.name).toBool();
|
||||
|
||||
resumeData.savePath = Profile::instance()->fromPortablePath(
|
||||
Path(query.value(DB_COLUMN_TARGET_SAVE_PATH.name).toString()));
|
||||
resumeData.useAutoTMM = resumeData.savePath.isEmpty();
|
||||
if (!resumeData.useAutoTMM)
|
||||
{
|
||||
resumeData.downloadPath = Profile::instance()->fromPortablePath(
|
||||
Path(query.value(DB_COLUMN_DOWNLOAD_PATH.name).toString()));
|
||||
}
|
||||
|
||||
const QByteArray bencodedResumeData = query.value(DB_COLUMN_RESUMEDATA.name).toByteArray();
|
||||
const QByteArray bencodedMetadata = query.value(DB_COLUMN_METADATA.name).toByteArray();
|
||||
const QByteArray allData = ((bencodedMetadata.isEmpty() || bencodedResumeData.isEmpty())
|
||||
? bencodedResumeData
|
||||
: (bencodedResumeData.chopped(1) + bencodedMetadata.mid(1)));
|
||||
|
||||
lt::error_code ec;
|
||||
const lt::bdecode_node root = lt::bdecode(allData, ec);
|
||||
|
||||
lt::add_torrent_params &p = resumeData.ltAddTorrentParams;
|
||||
|
||||
p = lt::read_resume_data(root, ec);
|
||||
p.save_path = Profile::instance()->fromPortablePath(Path(fromLTString(p.save_path)))
|
||||
.toString().toStdString();
|
||||
|
||||
return resumeData;
|
||||
return parseQueryResultRow(query);
|
||||
}
|
||||
|
||||
void BitTorrent::DBResumeDataStorage::store(const TorrentID &id, const LoadTorrentParams &resumeData) const
|
||||
@@ -359,6 +367,49 @@ void BitTorrent::DBResumeDataStorage::storeQueue(const QVector<TorrentID> &queue
|
||||
});
|
||||
}
|
||||
|
||||
void BitTorrent::DBResumeDataStorage::doLoadAll() const
|
||||
{
|
||||
const QString connectionName = u"ResumeDataStorageLoadAll"_qs;
|
||||
|
||||
{
|
||||
auto db = QSqlDatabase::addDatabase(u"QSQLITE"_qs, connectionName);
|
||||
db.setDatabaseName(path().data());
|
||||
if (!db.open())
|
||||
throw RuntimeError(db.lastError().text());
|
||||
|
||||
QSqlQuery query {db};
|
||||
|
||||
const auto selectTorrentIDStatement = u"SELECT %1 FROM %2 ORDER BY %3;"_qs
|
||||
.arg(quoted(DB_COLUMN_TORRENT_ID.name), quoted(DB_TABLE_TORRENTS), quoted(DB_COLUMN_QUEUE_POSITION.name));
|
||||
|
||||
const QReadLocker locker {&m_dbLock};
|
||||
|
||||
if (!query.exec(selectTorrentIDStatement))
|
||||
throw RuntimeError(query.lastError().text());
|
||||
|
||||
QVector<TorrentID> registeredTorrents;
|
||||
registeredTorrents.reserve(query.size());
|
||||
while (query.next())
|
||||
registeredTorrents.append(TorrentID::fromString(query.value(0).toString()));
|
||||
|
||||
emit const_cast<DBResumeDataStorage *>(this)->loadStarted(registeredTorrents);
|
||||
|
||||
const auto selectStatement = u"SELECT * FROM %1 ORDER BY %2;"_qs.arg(quoted(DB_TABLE_TORRENTS), quoted(DB_COLUMN_QUEUE_POSITION.name));
|
||||
if (!query.exec(selectStatement))
|
||||
throw RuntimeError(query.lastError().text());
|
||||
|
||||
while (query.next())
|
||||
{
|
||||
const auto torrentID = TorrentID::fromString(query.value(DB_COLUMN_TORRENT_ID.name).toString());
|
||||
onResumeDataLoaded(torrentID, parseQueryResultRow(query));
|
||||
}
|
||||
}
|
||||
|
||||
emit const_cast<DBResumeDataStorage *>(this)->loadFinished();
|
||||
|
||||
QSqlDatabase::removeDatabase(connectionName);
|
||||
}
|
||||
|
||||
int BitTorrent::DBResumeDataStorage::currentDBVersion() const
|
||||
{
|
||||
const auto selectDBVersionStatement = u"SELECT %1 FROM %2 WHERE %3 = %4;"_qs
|
||||
@@ -372,6 +423,8 @@ int BitTorrent::DBResumeDataStorage::currentDBVersion() const
|
||||
|
||||
query.bindValue(DB_COLUMN_NAME.placeholder, META_VERSION);
|
||||
|
||||
const QReadLocker locker {&m_dbLock};
|
||||
|
||||
if (!query.exec())
|
||||
throw RuntimeError(query.lastError().text());
|
||||
|
||||
@@ -390,6 +443,8 @@ void BitTorrent::DBResumeDataStorage::createDB() const
|
||||
{
|
||||
auto db = QSqlDatabase::database(DB_CONNECTION_NAME);
|
||||
|
||||
const QWriteLocker locker {&m_dbLock};
|
||||
|
||||
if (!db.transaction())
|
||||
throw RuntimeError(db.lastError().text());
|
||||
|
||||
@@ -453,6 +508,8 @@ void BitTorrent::DBResumeDataStorage::updateDBFromVersion1() const
|
||||
{
|
||||
auto db = QSqlDatabase::database(DB_CONNECTION_NAME);
|
||||
|
||||
const QWriteLocker locker {&m_dbLock};
|
||||
|
||||
if (!db.transaction())
|
||||
throw RuntimeError(db.lastError().text());
|
||||
|
||||
@@ -485,9 +542,10 @@ void BitTorrent::DBResumeDataStorage::updateDBFromVersion1() const
|
||||
}
|
||||
}
|
||||
|
||||
BitTorrent::DBResumeDataStorage::Worker::Worker(const Path &dbPath, const QString &dbConnectionName)
|
||||
BitTorrent::DBResumeDataStorage::Worker::Worker(const Path &dbPath, const QString &dbConnectionName, QReadWriteLock &dbLock)
|
||||
: m_path {dbPath}
|
||||
, m_connectionName {dbConnectionName}
|
||||
, m_dbLock {dbLock}
|
||||
{
|
||||
}
|
||||
|
||||
@@ -612,6 +670,7 @@ void BitTorrent::DBResumeDataStorage::Worker::store(const TorrentID &id, const L
|
||||
if (!bencodedMetadata.isEmpty())
|
||||
query.bindValue(DB_COLUMN_METADATA.placeholder, bencodedMetadata);
|
||||
|
||||
const QWriteLocker locker {&m_dbLock};
|
||||
if (!query.exec())
|
||||
throw RuntimeError(query.lastError().text());
|
||||
}
|
||||
@@ -636,6 +695,8 @@ void BitTorrent::DBResumeDataStorage::Worker::remove(const TorrentID &id) const
|
||||
throw RuntimeError(query.lastError().text());
|
||||
|
||||
query.bindValue(DB_COLUMN_TORRENT_ID.placeholder, id.toString());
|
||||
|
||||
const QWriteLocker locker {&m_dbLock};
|
||||
if (!query.exec())
|
||||
throw RuntimeError(query.lastError().text());
|
||||
}
|
||||
@@ -656,6 +717,8 @@ void BitTorrent::DBResumeDataStorage::Worker::storeQueue(const QVector<TorrentID
|
||||
|
||||
try
|
||||
{
|
||||
const QWriteLocker locker {&m_dbLock};
|
||||
|
||||
if (!db.transaction())
|
||||
throw RuntimeError(db.lastError().text());
|
||||
|
||||
|
||||
Reference in New Issue
Block a user