diff --git a/src/base/bittorrent/dbresumedatastorage.cpp b/src/base/bittorrent/dbresumedatastorage.cpp index b678704c8..fc4d94dc5 100644 --- a/src/base/bittorrent/dbresumedatastorage.cpp +++ b/src/base/bittorrent/dbresumedatastorage.cpp @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2021-2022 Vladimir Golovnev + * Copyright (C) 2021-2023 Vladimir Golovnev * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -28,6 +28,8 @@ #include "dbresumedatastorage.h" +#include +#include #include #include @@ -38,7 +40,9 @@ #include #include +#include #include +#include #include #include #include @@ -46,6 +50,7 @@ #include #include #include +#include #include "base/exceptions.h" #include "base/global.h" @@ -68,6 +73,46 @@ namespace const QString META_VERSION = u"version"_qs; + using namespace BitTorrent; + + class Job + { + public: + virtual ~Job() = default; + virtual void perform(QSqlDatabase db) = 0; + }; + + class StoreJob final : public Job + { + public: + StoreJob(const TorrentID &torrentID, const LoadTorrentParams &resumeData); + void perform(QSqlDatabase db) override; + + private: + const TorrentID m_torrentID; + const LoadTorrentParams m_resumeData; + }; + + class RemoveJob final : public Job + { + public: + explicit RemoveJob(const TorrentID &torrentID); + void perform(QSqlDatabase db) override; + + private: + const TorrentID m_torrentID; + }; + + class StoreQueueJob final : public Job + { + public: + explicit StoreQueueJob(const QVector &queue); + void perform(QSqlDatabase db) override; + + private: + const QVector m_queue; + }; + struct Column { QString name; @@ -167,91 +212,95 @@ namespace { return u"%1 %2"_qs.arg(quoted(column.name), QString::fromLatin1(definition)); } -} -namespace BitTorrent -{ - class DBResumeDataStorage::Worker final : public QObject + LoadTorrentParams parseQueryResultRow(const QSqlQuery &query) { - Q_DISABLE_COPY_MOVE(Worker) + LoadTorrentParams resumeData; + 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.hasFinishedStatus = 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( + query.value(DB_COLUMN_CONTENT_LAYOUT.name).toString(), TorrentContentLayout::Original); + resumeData.operatingMode = Utils::String::toEnum( + query.value(DB_COLUMN_OPERATING_MODE.name).toString(), TorrentOperatingMode::AutoManaged); + resumeData.stopped = query.value(DB_COLUMN_STOPPED.name).toBool(); + resumeData.stopCondition = Utils::String::toEnum( + query.value(DB_COLUMN_STOP_CONDITION.name).toString(), Torrent::StopCondition::None); + + 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())); + } - public: - Worker(const Path &dbPath, const QString &dbConnectionName, QReadWriteLock &dbLock); + const QByteArray bencodedResumeData = query.value(DB_COLUMN_RESUMEDATA.name).toByteArray(); - void openDatabase() const; - void closeDatabase() const; + lt::error_code ec; + const lt::bdecode_node resumeDataRoot = lt::bdecode(bencodedResumeData, ec); - void store(const TorrentID &id, const LoadTorrentParams &resumeData) const; - void remove(const TorrentID &id) const; - void storeQueue(const QVector &queue) const; + lt::add_torrent_params &p = resumeData.ltAddTorrentParams; - private: - const Path m_path; - const QString m_connectionName; - QReadWriteLock &m_dbLock; - }; + p = lt::read_resume_data(resumeDataRoot, ec); - namespace - { - LoadTorrentParams parseQueryResultRow(const QSqlQuery &query) + if (const QByteArray bencodedMetadata = query.value(DB_COLUMN_METADATA.name).toByteArray() + ; !bencodedMetadata.isEmpty()) { - LoadTorrentParams resumeData; - 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.hasFinishedStatus = 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( - query.value(DB_COLUMN_CONTENT_LAYOUT.name).toString(), TorrentContentLayout::Original); - resumeData.operatingMode = Utils::String::toEnum( - query.value(DB_COLUMN_OPERATING_MODE.name).toString(), TorrentOperatingMode::AutoManaged); - resumeData.stopped = query.value(DB_COLUMN_STOPPED.name).toBool(); - resumeData.stopCondition = Utils::String::toEnum( - query.value(DB_COLUMN_STOP_CONDITION.name).toString(), Torrent::StopCondition::None); - - 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 lt::bdecode_node torentInfoRoot = lt::bdecode(bencodedMetadata, ec); + p.ti = std::make_shared(torentInfoRoot, ec); + } + + p.save_path = Profile::instance()->fromPortablePath(Path(fromLTString(p.save_path))) + .toString().toStdString(); + + if (p.flags & lt::torrent_flags::stop_when_ready) + { + p.flags &= ~lt::torrent_flags::stop_when_ready; + resumeData.stopCondition = Torrent::StopCondition::FilesChecked; + } - const QByteArray bencodedResumeData = query.value(DB_COLUMN_RESUMEDATA.name).toByteArray(); + return resumeData; + } +} - lt::error_code ec; - const lt::bdecode_node resumeDataRoot = lt::bdecode(bencodedResumeData, ec); +namespace BitTorrent +{ + class DBResumeDataStorage::Worker final : public QThread + { + Q_DISABLE_COPY_MOVE(Worker) - lt::add_torrent_params &p = resumeData.ltAddTorrentParams; + public: + Worker(const Path &dbPath, QReadWriteLock &dbLock); - p = lt::read_resume_data(resumeDataRoot, ec); + void run() override; + void requestInterruption(); - if (const QByteArray bencodedMetadata = query.value(DB_COLUMN_METADATA.name).toByteArray(); !bencodedMetadata.isEmpty()) - { - const lt::bdecode_node torentInfoRoot = lt::bdecode(bencodedMetadata, ec); - p.ti = std::make_shared(torentInfoRoot, ec); - } + void store(const TorrentID &id, const LoadTorrentParams &resumeData); + void remove(const TorrentID &id); + void storeQueue(const QVector &queue); - p.save_path = Profile::instance()->fromPortablePath(Path(fromLTString(p.save_path))) - .toString().toStdString(); + private: + void addJob(std::unique_ptr job); - if (p.flags & lt::torrent_flags::stop_when_ready) - { - p.flags &= ~lt::torrent_flags::stop_when_ready; - resumeData.stopCondition = Torrent::StopCondition::FilesChecked; - } + const QString m_connectionName = u"ResumeDataStorageWorker"_qs; + const Path m_path; + QReadWriteLock &m_dbLock; - return resumeData; - } - } + std::queue> m_jobs; + QMutex m_jobsMutex; + QWaitCondition m_waitCondition; + }; } BitTorrent::DBResumeDataStorage::DBResumeDataStorage(const Path &dbPath, QObject *parent) @@ -276,31 +325,14 @@ BitTorrent::DBResumeDataStorage::DBResumeDataStorage(const Path &dbPath, QObject updateDB(dbVersion); } - m_asyncWorker = new Worker(dbPath, u"ResumeDataStorageWorker"_qs, m_dbLock); - m_asyncWorker->moveToThread(m_ioThread.get()); - connect(m_ioThread.get(), &QThread::finished, m_asyncWorker, &QObject::deleteLater); - m_ioThread->start(); - - RuntimeError *errPtr = nullptr; - QMetaObject::invokeMethod(m_asyncWorker, [this, &errPtr]() - { - try - { - m_asyncWorker->openDatabase(); - } - catch (const RuntimeError &err) - { - errPtr = new RuntimeError(err); - } - }, Qt::BlockingQueuedConnection); - - if (errPtr) - throw *errPtr; + m_asyncWorker = new Worker(dbPath, m_dbLock); + m_asyncWorker->start(); } BitTorrent::DBResumeDataStorage::~DBResumeDataStorage() { - QMetaObject::invokeMethod(m_asyncWorker, &Worker::closeDatabase); + m_asyncWorker->requestInterruption(); + m_asyncWorker->wait(); QSqlDatabase::removeDatabase(DB_CONNECTION_NAME); } @@ -353,26 +385,17 @@ BitTorrent::LoadResumeDataResult BitTorrent::DBResumeDataStorage::load(const Tor void BitTorrent::DBResumeDataStorage::store(const TorrentID &id, const LoadTorrentParams &resumeData) const { - QMetaObject::invokeMethod(m_asyncWorker, [this, id, resumeData]() - { - m_asyncWorker->store(id, resumeData); - }); + m_asyncWorker->store(id, resumeData); } void BitTorrent::DBResumeDataStorage::remove(const BitTorrent::TorrentID &id) const { - QMetaObject::invokeMethod(m_asyncWorker, [this, id]() - { - m_asyncWorker->remove(id); - }); + m_asyncWorker->remove(id); } void BitTorrent::DBResumeDataStorage::storeQueue(const QVector &queue) const { - QMetaObject::invokeMethod(m_asyncWorker, [this, queue]() - { - m_asyncWorker->storeQueue(queue); - }); + m_asyncWorker->storeQueue(queue); } void BitTorrent::DBResumeDataStorage::doLoadAll() const @@ -614,216 +637,288 @@ void BitTorrent::DBResumeDataStorage::enableWALMode() const throw RuntimeError(tr("WAL mode is probably unsupported due to filesystem limitations.")); } -BitTorrent::DBResumeDataStorage::Worker::Worker(const Path &dbPath, const QString &dbConnectionName, QReadWriteLock &dbLock) +BitTorrent::DBResumeDataStorage::Worker::Worker(const Path &dbPath, QReadWriteLock &dbLock) : m_path {dbPath} - , m_connectionName {dbConnectionName} , m_dbLock {dbLock} { } -void BitTorrent::DBResumeDataStorage::Worker::openDatabase() const +void BitTorrent::DBResumeDataStorage::Worker::run() { - auto db = QSqlDatabase::addDatabase(u"QSQLITE"_qs, m_connectionName); - db.setDatabaseName(m_path.data()); - if (!db.open()) - throw RuntimeError(db.lastError().text()); + { + auto db = QSqlDatabase::addDatabase(u"QSQLITE"_qs, m_connectionName); + db.setDatabaseName(m_path.data()); + if (!db.open()) + throw RuntimeError(db.lastError().text()); + + int64_t transactedJobsCount = 0; + while (true) + { + m_jobsMutex.lock(); + if (m_jobs.empty()) + { + if (transactedJobsCount > 0) + { + db.commit(); + m_dbLock.unlock(); + + qDebug() << "Resume data changes are commited. Transacted jobs:" << transactedJobsCount; + transactedJobsCount = 0; + } + + if (isInterruptionRequested()) + { + m_jobsMutex.unlock(); + break; + } + + m_waitCondition.wait(&m_jobsMutex); + if (isInterruptionRequested()) + { + m_jobsMutex.unlock(); + break; + } + + m_dbLock.lockForWrite(); + if (!db.transaction()) + { + LogMsg(tr("Couldn't begin transaction. Error: %1").arg(db.lastError().text()), Log::WARNING); + m_dbLock.unlock(); + break; + } + } + std::unique_ptr job = std::move(m_jobs.front()); + m_jobs.pop(); + m_jobsMutex.unlock(); + + job->perform(db); + ++transactedJobsCount; + } + + db.close(); + } + + QSqlDatabase::removeDatabase(m_connectionName); } -void BitTorrent::DBResumeDataStorage::Worker::closeDatabase() const +void DBResumeDataStorage::Worker::requestInterruption() { - QSqlDatabase::removeDatabase(m_connectionName); + m_waitCondition.wakeAll(); + QThread::requestInterruption(); +} + +void BitTorrent::DBResumeDataStorage::Worker::store(const TorrentID &id, const LoadTorrentParams &resumeData) +{ + addJob(std::make_unique(id, resumeData)); +} + +void BitTorrent::DBResumeDataStorage::Worker::remove(const TorrentID &id) +{ + addJob(std::make_unique(id)); +} + +void BitTorrent::DBResumeDataStorage::Worker::storeQueue(const QVector &queue) +{ + addJob(std::make_unique(queue)); +} + +void BitTorrent::DBResumeDataStorage::Worker::addJob(std::unique_ptr job) +{ + m_jobsMutex.lock(); + m_jobs.push(std::move(job)); + m_jobsMutex.unlock(); + + m_waitCondition.wakeAll(); } -void BitTorrent::DBResumeDataStorage::Worker::store(const TorrentID &id, const LoadTorrentParams &resumeData) const +namespace { - // We need to adjust native libtorrent resume data - lt::add_torrent_params p = resumeData.ltAddTorrentParams; - p.save_path = Profile::instance()->toPortablePath(Path(p.save_path)) - .toString().toStdString(); - if (resumeData.stopped) - { - p.flags |= lt::torrent_flags::paused; - p.flags &= ~lt::torrent_flags::auto_managed; + using namespace BitTorrent; + + StoreJob::StoreJob(const TorrentID &torrentID, const LoadTorrentParams &resumeData) + : m_torrentID {torrentID} + , m_resumeData {resumeData} + { } - else + + void StoreJob::perform(QSqlDatabase db) { - // Torrent can be actually "running" but temporarily "paused" to perform some - // service jobs behind the scenes so we need to restore it as "running" - if (resumeData.operatingMode == BitTorrent::TorrentOperatingMode::AutoManaged) + // We need to adjust native libtorrent resume data + lt::add_torrent_params p = m_resumeData.ltAddTorrentParams; + p.save_path = Profile::instance()->toPortablePath(Path(p.save_path)) + .toString().toStdString(); + if (m_resumeData.stopped) { - p.flags |= lt::torrent_flags::auto_managed; + p.flags |= lt::torrent_flags::paused; + p.flags &= ~lt::torrent_flags::auto_managed; } else { - p.flags &= ~lt::torrent_flags::paused; - p.flags &= ~lt::torrent_flags::auto_managed; + // Torrent can be actually "running" but temporarily "paused" to perform some + // service jobs behind the scenes so we need to restore it as "running" + if (m_resumeData.operatingMode == BitTorrent::TorrentOperatingMode::AutoManaged) + { + p.flags |= lt::torrent_flags::auto_managed; + } + else + { + p.flags &= ~lt::torrent_flags::paused; + p.flags &= ~lt::torrent_flags::auto_managed; + } } - } - QVector columns { - DB_COLUMN_TORRENT_ID, - DB_COLUMN_NAME, - DB_COLUMN_CATEGORY, - DB_COLUMN_TAGS, - DB_COLUMN_TARGET_SAVE_PATH, - DB_COLUMN_CONTENT_LAYOUT, - DB_COLUMN_RATIO_LIMIT, - DB_COLUMN_SEEDING_TIME_LIMIT, - DB_COLUMN_HAS_OUTER_PIECES_PRIORITY, - DB_COLUMN_HAS_SEED_STATUS, - DB_COLUMN_OPERATING_MODE, - DB_COLUMN_STOPPED, - DB_COLUMN_STOP_CONDITION, - DB_COLUMN_RESUMEDATA - }; - - lt::entry data = lt::write_resume_data(p); + QVector columns { + DB_COLUMN_TORRENT_ID, + DB_COLUMN_NAME, + DB_COLUMN_CATEGORY, + DB_COLUMN_TAGS, + DB_COLUMN_TARGET_SAVE_PATH, + DB_COLUMN_CONTENT_LAYOUT, + DB_COLUMN_RATIO_LIMIT, + DB_COLUMN_SEEDING_TIME_LIMIT, + DB_COLUMN_HAS_OUTER_PIECES_PRIORITY, + DB_COLUMN_HAS_SEED_STATUS, + DB_COLUMN_OPERATING_MODE, + DB_COLUMN_STOPPED, + DB_COLUMN_STOP_CONDITION, + DB_COLUMN_RESUMEDATA + }; - // metadata is stored in separate column - QByteArray bencodedMetadata; - if (p.ti) - { - lt::entry::dictionary_type &dataDict = data.dict(); - lt::entry metadata {lt::entry::dictionary_t}; - lt::entry::dictionary_type &metadataDict = metadata.dict(); - metadataDict.insert(dataDict.extract("info")); - metadataDict.insert(dataDict.extract("creation date")); - metadataDict.insert(dataDict.extract("created by")); - metadataDict.insert(dataDict.extract("comment")); + lt::entry data = lt::write_resume_data(p); - try - { - bencodedMetadata.reserve(512 * 1024); - lt::bencode(std::back_inserter(bencodedMetadata), metadata); - } - catch (const std::exception &err) + // metadata is stored in separate column + QByteArray bencodedMetadata; + if (p.ti) { - LogMsg(tr("Couldn't save torrent metadata. Error: %1.") - .arg(QString::fromLocal8Bit(err.what())), Log::CRITICAL); - return; + lt::entry::dictionary_type &dataDict = data.dict(); + lt::entry metadata {lt::entry::dictionary_t}; + lt::entry::dictionary_type &metadataDict = metadata.dict(); + metadataDict.insert(dataDict.extract("info")); + metadataDict.insert(dataDict.extract("creation date")); + metadataDict.insert(dataDict.extract("created by")); + metadataDict.insert(dataDict.extract("comment")); + + try + { + bencodedMetadata.reserve(512 * 1024); + lt::bencode(std::back_inserter(bencodedMetadata), metadata); + } + catch (const std::exception &err) + { + LogMsg(ResumeDataStorage::tr("Couldn't save torrent metadata. Error: %1.") + .arg(QString::fromLocal8Bit(err.what())), Log::CRITICAL); + return; + } + + columns.append(DB_COLUMN_METADATA); } - columns.append(DB_COLUMN_METADATA); - } + QByteArray bencodedResumeData; + bencodedResumeData.reserve(256 * 1024); + lt::bencode(std::back_inserter(bencodedResumeData), data); - QByteArray bencodedResumeData; - bencodedResumeData.reserve(256 * 1024); - lt::bencode(std::back_inserter(bencodedResumeData), data); + const QString insertTorrentStatement = makeInsertStatement(DB_TABLE_TORRENTS, columns) + + makeOnConflictUpdateStatement(DB_COLUMN_TORRENT_ID, columns); + QSqlQuery query {db}; - const QString insertTorrentStatement = makeInsertStatement(DB_TABLE_TORRENTS, columns) - + makeOnConflictUpdateStatement(DB_COLUMN_TORRENT_ID, columns); - auto db = QSqlDatabase::database(m_connectionName); - QSqlQuery query {db}; + try + { + if (!query.prepare(insertTorrentStatement)) + throw RuntimeError(query.lastError().text()); - try - { - if (!query.prepare(insertTorrentStatement)) - throw RuntimeError(query.lastError().text()); + query.bindValue(DB_COLUMN_TORRENT_ID.placeholder, m_torrentID.toString()); + query.bindValue(DB_COLUMN_NAME.placeholder, m_resumeData.name); + query.bindValue(DB_COLUMN_CATEGORY.placeholder, m_resumeData.category); + query.bindValue(DB_COLUMN_TAGS.placeholder, (m_resumeData.tags.isEmpty() + ? QVariant(QVariant::String) : m_resumeData.tags.join(u","_qs))); + query.bindValue(DB_COLUMN_CONTENT_LAYOUT.placeholder, Utils::String::fromEnum(m_resumeData.contentLayout)); + query.bindValue(DB_COLUMN_RATIO_LIMIT.placeholder, static_cast(m_resumeData.ratioLimit * 1000)); + query.bindValue(DB_COLUMN_SEEDING_TIME_LIMIT.placeholder, m_resumeData.seedingTimeLimit); + query.bindValue(DB_COLUMN_HAS_OUTER_PIECES_PRIORITY.placeholder, m_resumeData.firstLastPiecePriority); + query.bindValue(DB_COLUMN_HAS_SEED_STATUS.placeholder, m_resumeData.hasFinishedStatus); + query.bindValue(DB_COLUMN_OPERATING_MODE.placeholder, Utils::String::fromEnum(m_resumeData.operatingMode)); + query.bindValue(DB_COLUMN_STOPPED.placeholder, m_resumeData.stopped); + query.bindValue(DB_COLUMN_STOP_CONDITION.placeholder, Utils::String::fromEnum(m_resumeData.stopCondition)); + + if (!m_resumeData.useAutoTMM) + { + query.bindValue(DB_COLUMN_TARGET_SAVE_PATH.placeholder, Profile::instance()->toPortablePath(m_resumeData.savePath).data()); + query.bindValue(DB_COLUMN_DOWNLOAD_PATH.placeholder, Profile::instance()->toPortablePath(m_resumeData.downloadPath).data()); + } - query.bindValue(DB_COLUMN_TORRENT_ID.placeholder, id.toString()); - query.bindValue(DB_COLUMN_NAME.placeholder, resumeData.name); - query.bindValue(DB_COLUMN_CATEGORY.placeholder, resumeData.category); - query.bindValue(DB_COLUMN_TAGS.placeholder, (resumeData.tags.isEmpty() - ? QVariant(QVariant::String) : resumeData.tags.join(u","_qs))); - query.bindValue(DB_COLUMN_CONTENT_LAYOUT.placeholder, Utils::String::fromEnum(resumeData.contentLayout)); - query.bindValue(DB_COLUMN_RATIO_LIMIT.placeholder, static_cast(resumeData.ratioLimit * 1000)); - query.bindValue(DB_COLUMN_SEEDING_TIME_LIMIT.placeholder, resumeData.seedingTimeLimit); - query.bindValue(DB_COLUMN_HAS_OUTER_PIECES_PRIORITY.placeholder, resumeData.firstLastPiecePriority); - query.bindValue(DB_COLUMN_HAS_SEED_STATUS.placeholder, resumeData.hasFinishedStatus); - query.bindValue(DB_COLUMN_OPERATING_MODE.placeholder, Utils::String::fromEnum(resumeData.operatingMode)); - query.bindValue(DB_COLUMN_STOPPED.placeholder, resumeData.stopped); - query.bindValue(DB_COLUMN_STOP_CONDITION.placeholder, Utils::String::fromEnum(resumeData.stopCondition)); + query.bindValue(DB_COLUMN_RESUMEDATA.placeholder, bencodedResumeData); + if (!bencodedMetadata.isEmpty()) + query.bindValue(DB_COLUMN_METADATA.placeholder, bencodedMetadata); - if (!resumeData.useAutoTMM) + if (!query.exec()) + throw RuntimeError(query.lastError().text()); + } + catch (const RuntimeError &err) { - query.bindValue(DB_COLUMN_TARGET_SAVE_PATH.placeholder, Profile::instance()->toPortablePath(resumeData.savePath).data()); - query.bindValue(DB_COLUMN_DOWNLOAD_PATH.placeholder, Profile::instance()->toPortablePath(resumeData.downloadPath).data()); + LogMsg(ResumeDataStorage::tr("Couldn't store resume data for torrent '%1'. Error: %2") + .arg(m_torrentID.toString(), err.message()), Log::CRITICAL); } - - query.bindValue(DB_COLUMN_RESUMEDATA.placeholder, bencodedResumeData); - if (!bencodedMetadata.isEmpty()) - query.bindValue(DB_COLUMN_METADATA.placeholder, bencodedMetadata); - - const QWriteLocker locker {&m_dbLock}; - if (!query.exec()) - throw RuntimeError(query.lastError().text()); } - catch (const RuntimeError &err) + + RemoveJob::RemoveJob(const TorrentID &torrentID) + : m_torrentID {torrentID} { - LogMsg(tr("Couldn't store resume data for torrent '%1'. Error: %2") - .arg(id.toString(), err.message()), Log::CRITICAL); } -} - -void BitTorrent::DBResumeDataStorage::Worker::remove(const TorrentID &id) const -{ - const auto deleteTorrentStatement = u"DELETE FROM %1 WHERE %2 = %3;"_qs - .arg(quoted(DB_TABLE_TORRENTS), quoted(DB_COLUMN_TORRENT_ID.name), DB_COLUMN_TORRENT_ID.placeholder); - - auto db = QSqlDatabase::database(m_connectionName); - QSqlQuery query {db}; - try + void RemoveJob::perform(QSqlDatabase db) { - if (!query.prepare(deleteTorrentStatement)) - throw RuntimeError(query.lastError().text()); + const auto deleteTorrentStatement = u"DELETE FROM %1 WHERE %2 = %3;"_qs + .arg(quoted(DB_TABLE_TORRENTS), quoted(DB_COLUMN_TORRENT_ID.name), DB_COLUMN_TORRENT_ID.placeholder); - query.bindValue(DB_COLUMN_TORRENT_ID.placeholder, id.toString()); + QSqlQuery query {db}; + try + { + if (!query.prepare(deleteTorrentStatement)) + throw RuntimeError(query.lastError().text()); - const QWriteLocker locker {&m_dbLock}; - if (!query.exec()) - throw RuntimeError(query.lastError().text()); + query.bindValue(DB_COLUMN_TORRENT_ID.placeholder, m_torrentID.toString()); + + if (!query.exec()) + throw RuntimeError(query.lastError().text()); + } + catch (const RuntimeError &err) + { + LogMsg(ResumeDataStorage::tr("Couldn't delete resume data of torrent '%1'. Error: %2") + .arg(m_torrentID.toString(), err.message()), Log::CRITICAL); + } } - catch (const RuntimeError &err) + + StoreQueueJob::StoreQueueJob(const QVector &queue) + : m_queue {queue} { - LogMsg(tr("Couldn't delete resume data of torrent '%1'. Error: %2") - .arg(id.toString(), err.message()), Log::CRITICAL); } -} - -void BitTorrent::DBResumeDataStorage::Worker::storeQueue(const QVector &queue) const -{ - const auto updateQueuePosStatement = u"UPDATE %1 SET %2 = %3 WHERE %4 = %5;"_qs - .arg(quoted(DB_TABLE_TORRENTS), quoted(DB_COLUMN_QUEUE_POSITION.name), DB_COLUMN_QUEUE_POSITION.placeholder - , quoted(DB_COLUMN_TORRENT_ID.name), DB_COLUMN_TORRENT_ID.placeholder); - - auto db = QSqlDatabase::database(m_connectionName); - try + void StoreQueueJob::perform(QSqlDatabase db) { - const QWriteLocker locker {&m_dbLock}; - - if (!db.transaction()) - throw RuntimeError(db.lastError().text()); - - QSqlQuery query {db}; + const auto updateQueuePosStatement = u"UPDATE %1 SET %2 = %3 WHERE %4 = %5;"_qs + .arg(quoted(DB_TABLE_TORRENTS), quoted(DB_COLUMN_QUEUE_POSITION.name), DB_COLUMN_QUEUE_POSITION.placeholder + , quoted(DB_COLUMN_TORRENT_ID.name), DB_COLUMN_TORRENT_ID.placeholder); try { + QSqlQuery query {db}; + if (!query.prepare(updateQueuePosStatement)) throw RuntimeError(query.lastError().text()); int pos = 0; - for (const TorrentID &torrentID : queue) + for (const TorrentID &torrentID : m_queue) { query.bindValue(DB_COLUMN_TORRENT_ID.placeholder, torrentID.toString()); query.bindValue(DB_COLUMN_QUEUE_POSITION.placeholder, pos++); if (!query.exec()) throw RuntimeError(query.lastError().text()); } - - if (!db.commit()) - throw RuntimeError(db.lastError().text()); } - catch (const RuntimeError &) + catch (const RuntimeError &err) { - db.rollback(); - throw; + LogMsg(ResumeDataStorage::tr("Couldn't store torrents queue positions. Error: %1") + .arg(err.message()), Log::CRITICAL); } } - catch (const RuntimeError &err) - { - LogMsg(tr("Couldn't store torrents queue positions. Error: %1") - .arg(err.message()), Log::CRITICAL); - } }