mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2024-11-28 13:28:50 +03:00
Merge pull request #14726 from glassez/save-resume
Allow to store "resume data" in SQLite database file
This commit is contained in:
commit
6b123921a4
13 changed files with 745 additions and 25 deletions
|
@ -35,7 +35,7 @@ set_property(CACHE LibtorrentRasterbar_DIR PROPERTY TYPE PATH)
|
|||
find_package(Boost ${minBoostVersion} REQUIRED)
|
||||
find_package(OpenSSL ${minOpenSSLVersion} REQUIRED)
|
||||
find_package(ZLIB ${minZlibVersion} REQUIRED)
|
||||
find_package(Qt5 ${minQtVersion} REQUIRED COMPONENTS Core Network Xml LinguistTools)
|
||||
find_package(Qt5 ${minQtVersion} REQUIRED COMPONENTS Core Network Sql Xml LinguistTools)
|
||||
if (DBUS)
|
||||
find_package(Qt5 ${minQtVersion} REQUIRED COMPONENTS DBus)
|
||||
set_package_properties(Qt5DBus PROPERTIES
|
||||
|
|
|
@ -9,6 +9,7 @@ add_library(qbt_base STATIC
|
|||
bittorrent/cachestatus.h
|
||||
bittorrent/common.h
|
||||
bittorrent/customstorage.h
|
||||
bittorrent/dbresumedatastorage.h
|
||||
bittorrent/downloadpriority.h
|
||||
bittorrent/filesearcher.h
|
||||
bittorrent/filterparserthread.h
|
||||
|
@ -100,6 +101,7 @@ add_library(qbt_base STATIC
|
|||
bittorrent/bandwidthscheduler.cpp
|
||||
bittorrent/bencoderesumedatastorage.cpp
|
||||
bittorrent/customstorage.cpp
|
||||
bittorrent/dbresumedatastorage.cpp
|
||||
bittorrent/downloadpriority.cpp
|
||||
bittorrent/filesearcher.cpp
|
||||
bittorrent/filterparserthread.cpp
|
||||
|
@ -176,7 +178,7 @@ target_link_libraries(qbt_base
|
|||
ZLIB::ZLIB
|
||||
PUBLIC
|
||||
LibtorrentRasterbar::torrent-rasterbar
|
||||
Qt5::Core Qt5::Network Qt5::Xml
|
||||
Qt5::Core Qt5::Network Qt5::Sql Qt5::Xml
|
||||
qbt_common_cfg
|
||||
)
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ HEADERS += \
|
|||
$$PWD/bittorrent/common.h \
|
||||
$$PWD/bittorrent/customstorage.h \
|
||||
$$PWD/bittorrent/downloadpriority.h \
|
||||
$$PWD/bittorrent/dbresumedatastorage.h \
|
||||
$$PWD/bittorrent/filesearcher.h \
|
||||
$$PWD/bittorrent/filterparserthread.h \
|
||||
$$PWD/bittorrent/infohash.h \
|
||||
|
@ -100,6 +101,7 @@ SOURCES += \
|
|||
$$PWD/bittorrent/bandwidthscheduler.cpp \
|
||||
$$PWD/bittorrent/bencoderesumedatastorage.cpp \
|
||||
$$PWD/bittorrent/customstorage.cpp \
|
||||
$$PWD/bittorrent/dbresumedatastorage.cpp \
|
||||
$$PWD/bittorrent/downloadpriority.cpp \
|
||||
$$PWD/bittorrent/filesearcher.cpp \
|
||||
$$PWD/bittorrent/filterparserthread.cpp \
|
||||
|
|
|
@ -73,8 +73,6 @@ namespace BitTorrent
|
|||
|
||||
namespace
|
||||
{
|
||||
const char RESUME_FOLDER[] = "BT_backup";
|
||||
|
||||
template <typename LTStr>
|
||||
QString fromLTString(const LTStr &str)
|
||||
{
|
||||
|
@ -104,9 +102,9 @@ namespace
|
|||
}
|
||||
}
|
||||
|
||||
BitTorrent::BencodeResumeDataStorage::BencodeResumeDataStorage(QObject *parent)
|
||||
BitTorrent::BencodeResumeDataStorage::BencodeResumeDataStorage(const QString &path, QObject *parent)
|
||||
: ResumeDataStorage {parent}
|
||||
, m_resumeDataDir {Utils::Fs::expandPathAbs(specialFolderLocation(SpecialFolder::Data) + RESUME_FOLDER)}
|
||||
, m_resumeDataDir {path}
|
||||
, m_ioThread {new QThread {this}}
|
||||
, m_asyncWorker {new Worker {m_resumeDataDir}}
|
||||
{
|
||||
|
|
|
@ -46,7 +46,7 @@ namespace BitTorrent
|
|||
Q_DISABLE_COPY(BencodeResumeDataStorage)
|
||||
|
||||
public:
|
||||
explicit BencodeResumeDataStorage(QObject *parent = nullptr);
|
||||
explicit BencodeResumeDataStorage(const QString &path, QObject *parent = nullptr);
|
||||
~BencodeResumeDataStorage() override;
|
||||
|
||||
QVector<TorrentID> registeredTorrents() const override;
|
||||
|
|
577
src/base/bittorrent/dbresumedatastorage.cpp
Normal file
577
src/base/bittorrent/dbresumedatastorage.cpp
Normal file
|
@ -0,0 +1,577 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2021 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#include "dbresumedatastorage.h"
|
||||
|
||||
#include <libtorrent/bdecode.hpp>
|
||||
#include <libtorrent/bencode.hpp>
|
||||
#include <libtorrent/create_torrent.hpp>
|
||||
#include <libtorrent/entry.hpp>
|
||||
#include <libtorrent/read_resume_data.hpp>
|
||||
#include <libtorrent/write_resume_data.hpp>
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QFile>
|
||||
#include <QSet>
|
||||
#include <QSqlDatabase>
|
||||
#include <QSqlError>
|
||||
#include <QSqlQuery>
|
||||
#include <QThread>
|
||||
#include <QVector>
|
||||
|
||||
#include "base/exceptions.h"
|
||||
#include "base/global.h"
|
||||
#include "base/logger.h"
|
||||
#include "base/profile.h"
|
||||
#include "base/utils/fs.h"
|
||||
#include "base/utils/string.h"
|
||||
#include "infohash.h"
|
||||
#include "loadtorrentparams.h"
|
||||
#include "torrentinfo.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
const char DB_CONNECTION_NAME[] = "ResumeDataStorage";
|
||||
|
||||
const int DB_VERSION = 1;
|
||||
|
||||
const char DB_TABLE_META[] = "meta";
|
||||
const char DB_TABLE_TORRENTS[] = "torrents";
|
||||
|
||||
struct Column
|
||||
{
|
||||
QString name;
|
||||
QString placeholder;
|
||||
};
|
||||
|
||||
Column makeColumn(const char *columnName)
|
||||
{
|
||||
return {QLatin1String(columnName), (QLatin1Char(':') + QLatin1String(columnName))};
|
||||
}
|
||||
|
||||
const Column DB_COLUMN_ID = makeColumn("id");
|
||||
const Column DB_COLUMN_TORRENT_ID = makeColumn("torrent_id");
|
||||
const Column DB_COLUMN_QUEUE_POSITION = makeColumn("queue_position");
|
||||
const Column DB_COLUMN_NAME = makeColumn("name");
|
||||
const Column DB_COLUMN_CATEGORY = makeColumn("category");
|
||||
const Column DB_COLUMN_TAGS = makeColumn("tags");
|
||||
const Column DB_COLUMN_TARGET_SAVE_PATH = makeColumn("target_save_path");
|
||||
const Column DB_COLUMN_CONTENT_LAYOUT = makeColumn("content_layout");
|
||||
const Column DB_COLUMN_RATIO_LIMIT = makeColumn("ratio_limit");
|
||||
const Column DB_COLUMN_SEEDING_TIME_LIMIT = makeColumn("seeding_time_limit");
|
||||
const Column DB_COLUMN_HAS_OUTER_PIECES_PRIORITY = makeColumn("has_outer_pieces_priority");
|
||||
const Column DB_COLUMN_HAS_SEED_STATUS = makeColumn("has_seed_status");
|
||||
const Column DB_COLUMN_OPERATING_MODE = makeColumn("operating_mode");
|
||||
const Column DB_COLUMN_STOPPED = makeColumn("stopped");
|
||||
const Column DB_COLUMN_RESUMEDATA = makeColumn("libtorrent_resume_data");
|
||||
const Column DB_COLUMN_METADATA = makeColumn("metadata");
|
||||
const Column DB_COLUMN_VALUE = makeColumn("value");
|
||||
|
||||
template <typename LTStr>
|
||||
QString fromLTString(const LTStr &str)
|
||||
{
|
||||
return QString::fromUtf8(str.data(), static_cast<int>(str.size()));
|
||||
}
|
||||
|
||||
QString quoted(const QString &name)
|
||||
{
|
||||
const QLatin1Char quote {'`'};
|
||||
|
||||
return (quote + name + quote);
|
||||
}
|
||||
|
||||
QString makeCreateTableStatement(const QString &tableName, const QStringList &items)
|
||||
{
|
||||
return QString::fromLatin1("CREATE TABLE %1 (%2)").arg(quoted(tableName), items.join(QLatin1Char(',')));
|
||||
}
|
||||
|
||||
QString makeInsertStatement(const QString &tableName, const QVector<Column> &columns)
|
||||
{
|
||||
QStringList names;
|
||||
names.reserve(columns.size());
|
||||
QStringList values;
|
||||
values.reserve(columns.size());
|
||||
for (const Column &column : columns)
|
||||
{
|
||||
names.append(quoted(column.name));
|
||||
values.append(column.placeholder);
|
||||
}
|
||||
|
||||
const QString jointNames = names.join(QLatin1Char(','));
|
||||
const QString jointValues = values.join(QLatin1Char(','));
|
||||
|
||||
return QString::fromLatin1("INSERT INTO %1 (%2) VALUES (%3)")
|
||||
.arg(quoted(tableName), jointNames, jointValues);
|
||||
}
|
||||
|
||||
QString makeOnConflictUpdateStatement(const Column &constraint, const QVector<Column> &columns)
|
||||
{
|
||||
QStringList names;
|
||||
names.reserve(columns.size());
|
||||
QStringList values;
|
||||
values.reserve(columns.size());
|
||||
for (const Column &column : columns)
|
||||
{
|
||||
names.append(quoted(column.name));
|
||||
values.append(column.placeholder);
|
||||
}
|
||||
|
||||
const QString jointNames = names.join(QLatin1Char(','));
|
||||
const QString jointValues = values.join(QLatin1Char(','));
|
||||
|
||||
return QString::fromLatin1(" ON CONFLICT (%1) DO UPDATE SET (%2) = (%3)")
|
||||
.arg(quoted(constraint.name), jointNames, jointValues);
|
||||
}
|
||||
|
||||
QString makeColumnDefinition(const Column &column, const char *definition)
|
||||
{
|
||||
return QString::fromLatin1("%1 %2").arg(quoted(column.name), QLatin1String(definition));
|
||||
}
|
||||
}
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
class DBResumeDataStorage::Worker final : public QObject
|
||||
{
|
||||
Q_DISABLE_COPY(Worker)
|
||||
|
||||
public:
|
||||
Worker(const QString &dbPath, const QString &dbConnectionName);
|
||||
|
||||
void openDatabase() const;
|
||||
void closeDatabase() const;
|
||||
|
||||
void store(const TorrentID &id, const LoadTorrentParams &resumeData) const;
|
||||
void remove(const TorrentID &id) const;
|
||||
void storeQueue(const QVector<TorrentID> &queue) const;
|
||||
|
||||
private:
|
||||
const QString m_path;
|
||||
const QString m_connectionName;
|
||||
};
|
||||
}
|
||||
|
||||
BitTorrent::DBResumeDataStorage::DBResumeDataStorage(const QString &dbPath, QObject *parent)
|
||||
: ResumeDataStorage {parent}
|
||||
, m_ioThread {new QThread(this)}
|
||||
{
|
||||
const bool needCreateDB = !QFile::exists(dbPath);
|
||||
|
||||
auto db = QSqlDatabase::addDatabase(QLatin1String("QSQLITE"), DB_CONNECTION_NAME);
|
||||
db.setDatabaseName(dbPath);
|
||||
if (!db.open())
|
||||
throw RuntimeError(db.lastError().text());
|
||||
|
||||
if (needCreateDB)
|
||||
createDB();
|
||||
|
||||
m_asyncWorker = new Worker(dbPath, QLatin1String("ResumeDataStorageWorker"));
|
||||
m_asyncWorker->moveToThread(m_ioThread);
|
||||
connect(m_ioThread, &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;
|
||||
}
|
||||
|
||||
BitTorrent::DBResumeDataStorage::~DBResumeDataStorage()
|
||||
{
|
||||
QMetaObject::invokeMethod(m_asyncWorker, &Worker::closeDatabase);
|
||||
QSqlDatabase::removeDatabase(DB_CONNECTION_NAME);
|
||||
|
||||
m_ioThread->quit();
|
||||
m_ioThread->wait();
|
||||
}
|
||||
|
||||
QVector<BitTorrent::TorrentID> BitTorrent::DBResumeDataStorage::registeredTorrents() const
|
||||
{
|
||||
const auto selectTorrentIDStatement = QString::fromLatin1("SELECT %1 FROM %2 ORDER BY %3;")
|
||||
.arg(quoted(DB_COLUMN_TORRENT_ID.name), quoted(DB_TABLE_TORRENTS), quoted(DB_COLUMN_QUEUE_POSITION.name));
|
||||
|
||||
auto db = QSqlDatabase::database(DB_CONNECTION_NAME);
|
||||
QSqlQuery query {db};
|
||||
|
||||
if (!query.exec(selectTorrentIDStatement))
|
||||
throw RuntimeError(query.lastError().text());
|
||||
|
||||
QVector<TorrentID> registeredTorrents;
|
||||
registeredTorrents.reserve(query.size());
|
||||
while (query.next())
|
||||
registeredTorrents.append(BitTorrent::TorrentID::fromString(query.value(0).toString()));
|
||||
|
||||
return registeredTorrents;
|
||||
}
|
||||
|
||||
std::optional<BitTorrent::LoadTorrentParams> BitTorrent::DBResumeDataStorage::load(const TorrentID &id) const
|
||||
{
|
||||
const QString selectTorrentStatement =
|
||||
QString(QLatin1String("SELECT * FROM %1 WHERE %2 = %3;"))
|
||||
.arg(quoted(DB_TABLE_TORRENTS), quoted(DB_COLUMN_TORRENT_ID.name), DB_COLUMN_TORRENT_ID.placeholder);
|
||||
|
||||
auto db = QSqlDatabase::database(DB_CONNECTION_NAME);
|
||||
QSqlQuery query {db};
|
||||
try
|
||||
{
|
||||
if (!query.prepare(selectTorrentStatement))
|
||||
throw RuntimeError(query.lastError().text());
|
||||
|
||||
query.bindValue(DB_COLUMN_TORRENT_ID.placeholder, id.toString());
|
||||
if (!query.exec())
|
||||
throw RuntimeError(query.lastError().text());
|
||||
|
||||
if (!query.next())
|
||||
throw RuntimeError(tr("Not found."));
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
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(QLatin1Char(','));
|
||||
resumeData.tags.insert(tagList.cbegin(), tagList.cend());
|
||||
}
|
||||
resumeData.savePath = Profile::instance()->fromPortablePath(
|
||||
Utils::Fs::toUniformPath(query.value(DB_COLUMN_TARGET_SAVE_PATH.name).toString()));
|
||||
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();
|
||||
|
||||
const QByteArray bencodedResumeData = query.value(DB_COLUMN_RESUMEDATA.name).toByteArray();
|
||||
|
||||
lt::error_code ec;
|
||||
const lt::bdecode_node root = lt::bdecode(bencodedResumeData, ec);
|
||||
|
||||
lt::add_torrent_params &p = resumeData.ltAddTorrentParams;
|
||||
|
||||
p = lt::read_resume_data(root, ec);
|
||||
p.save_path = Profile::instance()->fromPortablePath(fromLTString(p.save_path)).toStdString();
|
||||
|
||||
const QByteArray bencodedMetadata = query.value(DB_COLUMN_METADATA.name).toByteArray();
|
||||
auto metadata = TorrentInfo::load(bencodedMetadata);
|
||||
if (metadata.isValid())
|
||||
p.ti = metadata.nativeInfo();
|
||||
|
||||
return resumeData;
|
||||
}
|
||||
|
||||
void BitTorrent::DBResumeDataStorage::store(const TorrentID &id, const LoadTorrentParams &resumeData) const
|
||||
{
|
||||
QMetaObject::invokeMethod(m_asyncWorker, [this, 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);
|
||||
});
|
||||
}
|
||||
|
||||
void BitTorrent::DBResumeDataStorage::storeQueue(const QVector<TorrentID> &queue) const
|
||||
{
|
||||
QMetaObject::invokeMethod(m_asyncWorker, [this, queue]()
|
||||
{
|
||||
m_asyncWorker->storeQueue(queue);
|
||||
});
|
||||
}
|
||||
|
||||
void BitTorrent::DBResumeDataStorage::createDB() const
|
||||
{
|
||||
auto db = QSqlDatabase::database(DB_CONNECTION_NAME);
|
||||
|
||||
if (!db.transaction())
|
||||
throw RuntimeError(db.lastError().text());
|
||||
|
||||
QSqlQuery query {db};
|
||||
|
||||
try
|
||||
{
|
||||
const QStringList tableMetaItems = {
|
||||
makeColumnDefinition(DB_COLUMN_ID, "INTEGER PRIMARY KEY"),
|
||||
makeColumnDefinition(DB_COLUMN_NAME, "TEXT NOT NULL UNIQUE"),
|
||||
makeColumnDefinition(DB_COLUMN_VALUE, "BLOB")
|
||||
};
|
||||
const QString createTableMetaQuery = makeCreateTableStatement(DB_TABLE_META, tableMetaItems);
|
||||
if (!query.exec(createTableMetaQuery))
|
||||
throw RuntimeError(query.lastError().text());
|
||||
|
||||
const QString insertMetaVersionQuery = makeInsertStatement(DB_TABLE_META, {DB_COLUMN_NAME, DB_COLUMN_VALUE});
|
||||
if (!query.prepare(insertMetaVersionQuery))
|
||||
throw RuntimeError(query.lastError().text());
|
||||
|
||||
query.bindValue(DB_COLUMN_NAME.placeholder, QString::fromLatin1("version"));
|
||||
query.bindValue(DB_COLUMN_VALUE.placeholder, DB_VERSION);
|
||||
|
||||
if (!query.exec())
|
||||
throw RuntimeError(query.lastError().text());
|
||||
|
||||
const QStringList tableTorrentsItems = {
|
||||
makeColumnDefinition(DB_COLUMN_ID, "INTEGER PRIMARY KEY"),
|
||||
makeColumnDefinition(DB_COLUMN_TORRENT_ID, "BLOB NOT NULL UNIQUE"),
|
||||
makeColumnDefinition(DB_COLUMN_QUEUE_POSITION, "INTEGER NOT NULL DEFAULT -1"),
|
||||
makeColumnDefinition(DB_COLUMN_NAME, "TEXT"),
|
||||
makeColumnDefinition(DB_COLUMN_CATEGORY, "TEXT"),
|
||||
makeColumnDefinition(DB_COLUMN_TAGS, "TEXT"),
|
||||
makeColumnDefinition(DB_COLUMN_TARGET_SAVE_PATH, "TEXT"),
|
||||
makeColumnDefinition(DB_COLUMN_CONTENT_LAYOUT, "TEXT NOT NULL"),
|
||||
makeColumnDefinition(DB_COLUMN_RATIO_LIMIT, "INTEGER NOT NULL"),
|
||||
makeColumnDefinition(DB_COLUMN_SEEDING_TIME_LIMIT, "INTEGER NOT NULL"),
|
||||
makeColumnDefinition(DB_COLUMN_HAS_OUTER_PIECES_PRIORITY, "INTEGER NOT NULL"),
|
||||
makeColumnDefinition(DB_COLUMN_HAS_SEED_STATUS, "INTEGER NOT NULL"),
|
||||
makeColumnDefinition(DB_COLUMN_OPERATING_MODE, "TEXT NOT NULL"),
|
||||
makeColumnDefinition(DB_COLUMN_STOPPED, "INTEGER NOT NULL"),
|
||||
makeColumnDefinition(DB_COLUMN_RESUMEDATA, "BLOB NOT NULL"),
|
||||
makeColumnDefinition(DB_COLUMN_METADATA, "BLOB")
|
||||
};
|
||||
const QString createTableTorrentsQuery = makeCreateTableStatement(DB_TABLE_TORRENTS, tableTorrentsItems);
|
||||
if (!query.exec(createTableTorrentsQuery))
|
||||
throw RuntimeError(query.lastError().text());
|
||||
|
||||
if (!db.commit())
|
||||
throw RuntimeError(db.lastError().text());
|
||||
}
|
||||
catch (const RuntimeError &err)
|
||||
{
|
||||
db.rollback();
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
BitTorrent::DBResumeDataStorage::Worker::Worker(const QString &dbPath, const QString &dbConnectionName)
|
||||
: m_path {dbPath}
|
||||
, m_connectionName {dbConnectionName}
|
||||
{
|
||||
}
|
||||
|
||||
void BitTorrent::DBResumeDataStorage::Worker::openDatabase() const
|
||||
{
|
||||
auto db = QSqlDatabase::addDatabase(QLatin1String("QSQLITE"), m_connectionName);
|
||||
db.setDatabaseName(m_path);
|
||||
if (!db.open())
|
||||
throw RuntimeError(db.lastError().text());
|
||||
}
|
||||
|
||||
void BitTorrent::DBResumeDataStorage::Worker::closeDatabase() const
|
||||
{
|
||||
QSqlDatabase::removeDatabase(m_connectionName);
|
||||
}
|
||||
|
||||
void BitTorrent::DBResumeDataStorage::Worker::store(const TorrentID &id, const LoadTorrentParams &resumeData) const
|
||||
{
|
||||
// We need to adjust native libtorrent resume data
|
||||
lt::add_torrent_params p = resumeData.ltAddTorrentParams;
|
||||
p.save_path = Profile::instance()->toPortablePath(QString::fromStdString(p.save_path)).toStdString();
|
||||
if (resumeData.stopped)
|
||||
{
|
||||
p.flags |= lt::torrent_flags::paused;
|
||||
p.flags &= ~lt::torrent_flags::auto_managed;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
p.flags |= lt::torrent_flags::auto_managed;
|
||||
}
|
||||
else
|
||||
{
|
||||
p.flags &= ~lt::torrent_flags::paused;
|
||||
p.flags &= ~lt::torrent_flags::auto_managed;
|
||||
}
|
||||
}
|
||||
|
||||
QVector<Column> 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_RESUMEDATA
|
||||
};
|
||||
|
||||
// metadata is stored in separate column
|
||||
QByteArray bencodedMetadata;
|
||||
bencodedMetadata.reserve(512 * 1024);
|
||||
const std::shared_ptr<lt::torrent_info> torrentInfo = std::move(p.ti);
|
||||
if (torrentInfo)
|
||||
{
|
||||
const lt::create_torrent torrentCreator = lt::create_torrent(*torrentInfo);
|
||||
const lt::entry metadata = torrentCreator.generate();
|
||||
lt::bencode(std::back_inserter(bencodedMetadata), metadata);
|
||||
|
||||
columns.append(DB_COLUMN_METADATA);
|
||||
}
|
||||
|
||||
QByteArray bencodedResumeData;
|
||||
bencodedResumeData.reserve(256 * 1024);
|
||||
lt::bencode(std::back_inserter(bencodedResumeData), lt::write_resume_data(p));
|
||||
|
||||
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());
|
||||
|
||||
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(QLatin1String(","))));
|
||||
query.bindValue(DB_COLUMN_TARGET_SAVE_PATH.placeholder, Profile::instance()->toPortablePath(resumeData.savePath));
|
||||
query.bindValue(DB_COLUMN_CONTENT_LAYOUT.placeholder, Utils::String::fromEnum(resumeData.contentLayout));
|
||||
query.bindValue(DB_COLUMN_RATIO_LIMIT.placeholder, static_cast<int>(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.hasSeedStatus);
|
||||
query.bindValue(DB_COLUMN_OPERATING_MODE.placeholder, Utils::String::fromEnum(resumeData.operatingMode));
|
||||
query.bindValue(DB_COLUMN_STOPPED.placeholder, resumeData.stopped);
|
||||
query.bindValue(DB_COLUMN_RESUMEDATA.placeholder, bencodedResumeData);
|
||||
if (torrentInfo)
|
||||
query.bindValue(DB_COLUMN_METADATA.placeholder, bencodedMetadata);
|
||||
|
||||
if (!query.exec())
|
||||
throw RuntimeError(query.lastError().text());
|
||||
}
|
||||
catch (const RuntimeError &err)
|
||||
{
|
||||
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 = QString::fromLatin1("DELETE FROM %1 WHERE %2 = %3;")
|
||||
.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
|
||||
{
|
||||
if (!query.prepare(deleteTorrentStatement))
|
||||
throw RuntimeError(query.lastError().text());
|
||||
|
||||
query.bindValue(DB_COLUMN_TORRENT_ID.placeholder, id.toString());
|
||||
if (!query.exec())
|
||||
throw RuntimeError(query.lastError().text());
|
||||
}
|
||||
catch (const RuntimeError &err)
|
||||
{
|
||||
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<TorrentID> &queue) const
|
||||
{
|
||||
const auto updateQueuePosStatement = QString::fromLatin1("UPDATE %1 SET %2 = %3 WHERE %4 = %5;")
|
||||
.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
|
||||
{
|
||||
if (!db.transaction())
|
||||
throw RuntimeError(db.lastError().text());
|
||||
|
||||
QSqlQuery query {db};
|
||||
|
||||
try
|
||||
{
|
||||
if (!query.prepare(updateQueuePosStatement))
|
||||
throw RuntimeError(query.lastError().text());
|
||||
|
||||
int pos = 0;
|
||||
for (const TorrentID &torrentID : 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 &err)
|
||||
{
|
||||
db.rollback();
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
catch (const RuntimeError &err)
|
||||
{
|
||||
LogMsg(tr("Couldn't store torrents queue positions. Error: %1")
|
||||
.arg(err.message()), Log::CRITICAL);
|
||||
}
|
||||
}
|
60
src/base/bittorrent/dbresumedatastorage.h
Normal file
60
src/base/bittorrent/dbresumedatastorage.h
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2021 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "resumedatastorage.h"
|
||||
|
||||
class QThread;
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
class DBResumeDataStorage final : public ResumeDataStorage
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(DBResumeDataStorage)
|
||||
|
||||
public:
|
||||
explicit DBResumeDataStorage(const QString &dbPath, QObject *parent = nullptr);
|
||||
~DBResumeDataStorage() override;
|
||||
|
||||
QVector<TorrentID> registeredTorrents() const override;
|
||||
std::optional<LoadTorrentParams> load(const TorrentID &id) const override;
|
||||
void store(const TorrentID &id, const LoadTorrentParams &resumeData) const override;
|
||||
void remove(const TorrentID &id) const override;
|
||||
void storeQueue(const QVector<TorrentID> &queue) const override;
|
||||
|
||||
private:
|
||||
void createDB() const;
|
||||
|
||||
QThread *m_ioThread = nullptr;
|
||||
|
||||
class Worker;
|
||||
Worker *m_asyncWorker = nullptr;
|
||||
};
|
||||
}
|
|
@ -88,6 +88,7 @@
|
|||
#include "bencoderesumedatastorage.h"
|
||||
#include "common.h"
|
||||
#include "customstorage.h"
|
||||
#include "dbresumedatastorage.h"
|
||||
#include "filesearcher.h"
|
||||
#include "filterparserthread.h"
|
||||
#include "loadtorrentparams.h"
|
||||
|
@ -436,6 +437,7 @@ Session::Session(QObject *parent)
|
|||
return tmp;
|
||||
}
|
||||
)
|
||||
, m_resumeDataStorageType(BITTORRENT_SESSION_KEY("ResumeDataStorageType"), ResumeDataStorageType::Legacy)
|
||||
#if defined(Q_OS_WIN)
|
||||
, m_OSMemoryPriority(BITTORRENT_KEY("OSMemoryPriority"), OSMemoryPriority::BelowNormal)
|
||||
#endif
|
||||
|
@ -451,8 +453,6 @@ Session::Session(QObject *parent)
|
|||
if (port() < 0)
|
||||
m_port = Utils::Random::rand(1024, 65535);
|
||||
|
||||
initResumeDataStorage();
|
||||
|
||||
m_recentErroredTorrentsTimer->setSingleShot(true);
|
||||
m_recentErroredTorrentsTimer->setInterval(1000);
|
||||
connect(m_recentErroredTorrentsTimer, &QTimer::timeout
|
||||
|
@ -2938,6 +2938,16 @@ void Session::setBannedIPs(const QStringList &newList)
|
|||
configureDeferred();
|
||||
}
|
||||
|
||||
ResumeDataStorageType Session::resumeDataStorageType() const
|
||||
{
|
||||
return m_resumeDataStorageType;
|
||||
}
|
||||
|
||||
void Session::setResumeDataStorageType(const ResumeDataStorageType type)
|
||||
{
|
||||
m_resumeDataStorageType = type;
|
||||
}
|
||||
|
||||
QStringList Session::bannedIPs() const
|
||||
{
|
||||
return m_bannedIPs;
|
||||
|
@ -4035,11 +4045,6 @@ bool Session::hasPerTorrentSeedingTimeLimit() const
|
|||
});
|
||||
}
|
||||
|
||||
void Session::initResumeDataStorage()
|
||||
{
|
||||
m_resumeDataStorage = new BencodeResumeDataStorage(this);
|
||||
}
|
||||
|
||||
void Session::configureDeferred()
|
||||
{
|
||||
if (m_deferredConfigureScheduled)
|
||||
|
@ -4118,18 +4123,56 @@ const CacheStatus &Session::cacheStatus() const
|
|||
return m_cacheStatus;
|
||||
}
|
||||
|
||||
// Will resume torrents in backup directory
|
||||
void Session::startUpTorrents()
|
||||
{
|
||||
qDebug("Initializing torrents resume data storage...");
|
||||
|
||||
const QString dbPath = Utils::Fs::expandPathAbs(
|
||||
specialFolderLocation(SpecialFolder::Data) + QLatin1String("torrents.db"));
|
||||
const bool dbStorageExists = QFile::exists(dbPath);
|
||||
|
||||
ResumeDataStorage *startupStorage = nullptr;
|
||||
if (resumeDataStorageType() == ResumeDataStorageType::SQLite)
|
||||
{
|
||||
m_resumeDataStorage = new DBResumeDataStorage(dbPath, this);
|
||||
|
||||
if (!dbStorageExists)
|
||||
{
|
||||
const QString dataPath = Utils::Fs::expandPathAbs(
|
||||
specialFolderLocation(SpecialFolder::Data) + QLatin1String("BT_backup"));
|
||||
startupStorage = new BencodeResumeDataStorage(dataPath, this);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const QString dataPath = Utils::Fs::expandPathAbs(
|
||||
specialFolderLocation(SpecialFolder::Data) + QLatin1String("BT_backup"));
|
||||
m_resumeDataStorage = new BencodeResumeDataStorage(dataPath, this);
|
||||
|
||||
if (dbStorageExists)
|
||||
startupStorage = new DBResumeDataStorage(dbPath, this);
|
||||
}
|
||||
|
||||
if (!startupStorage)
|
||||
startupStorage = m_resumeDataStorage;
|
||||
|
||||
qDebug("Starting up torrents...");
|
||||
|
||||
const QVector<TorrentID> torrents = m_resumeDataStorage->registeredTorrents();
|
||||
const QVector<TorrentID> torrents = startupStorage->registeredTorrents();
|
||||
int resumedTorrentsCount = 0;
|
||||
QVector<TorrentID> queue;
|
||||
for (const TorrentID &torrentID : torrents)
|
||||
{
|
||||
const std::optional<LoadTorrentParams> resumeData = m_resumeDataStorage->load(torrentID);
|
||||
const std::optional<LoadTorrentParams> resumeData = startupStorage->load(torrentID);
|
||||
if (resumeData)
|
||||
{
|
||||
if (m_resumeDataStorage != startupStorage)
|
||||
{
|
||||
m_resumeDataStorage->store(torrentID, *resumeData);
|
||||
if (isQueueingSystemEnabled() && !resumeData->hasSeedStatus)
|
||||
queue.append(torrentID);
|
||||
}
|
||||
|
||||
qDebug() << "Starting up torrent" << torrentID.toString() << "...";
|
||||
if (!loadTorrent(*resumeData))
|
||||
LogMsg(tr("Unable to resume torrent '%1'.", "e.g: Unable to resume torrent 'hash'.")
|
||||
|
@ -4146,6 +4189,16 @@ void Session::startUpTorrents()
|
|||
.arg(torrentID.toString()), Log::CRITICAL);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_resumeDataStorage != startupStorage)
|
||||
{
|
||||
delete startupStorage;
|
||||
if (resumeDataStorageType() == ResumeDataStorageType::Legacy)
|
||||
Utils::Fs::forceRemove(dbPath);
|
||||
|
||||
if (isQueueingSystemEnabled())
|
||||
m_resumeDataStorage->storeQueue(queue);
|
||||
}
|
||||
}
|
||||
|
||||
quint64 Session::getAlltimeDL() const
|
||||
|
|
|
@ -142,6 +142,13 @@ namespace BitTorrent
|
|||
};
|
||||
Q_ENUM_NS(SeedChokingAlgorithm)
|
||||
|
||||
enum class ResumeDataStorageType
|
||||
{
|
||||
Legacy,
|
||||
SQLite
|
||||
};
|
||||
Q_ENUM_NS(ResumeDataStorageType)
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
enum class OSMemoryPriority : int
|
||||
{
|
||||
|
@ -429,6 +436,8 @@ namespace BitTorrent
|
|||
void setTrackerFilteringEnabled(bool enabled);
|
||||
QStringList bannedIPs() const;
|
||||
void setBannedIPs(const QStringList &newList);
|
||||
ResumeDataStorageType resumeDataStorageType() const;
|
||||
void setResumeDataStorageType(ResumeDataStorageType type);
|
||||
#if defined(Q_OS_WIN)
|
||||
OSMemoryPriority getOSMemoryPriority() const;
|
||||
void setOSMemoryPriority(OSMemoryPriority priority);
|
||||
|
@ -570,8 +579,6 @@ namespace BitTorrent
|
|||
bool hasPerTorrentRatioLimit() const;
|
||||
bool hasPerTorrentSeedingTimeLimit() const;
|
||||
|
||||
void initResumeDataStorage();
|
||||
|
||||
// Session configuration
|
||||
Q_INVOKABLE void configure();
|
||||
void configureComponents();
|
||||
|
@ -738,6 +745,7 @@ namespace BitTorrent
|
|||
CachedSettingValue<int> m_peerTurnoverCutoff;
|
||||
CachedSettingValue<int> m_peerTurnoverInterval;
|
||||
CachedSettingValue<QStringList> m_bannedIPs;
|
||||
CachedSettingValue<ResumeDataStorageType> m_resumeDataStorageType;
|
||||
#if defined(Q_OS_WIN)
|
||||
CachedSettingValue<OSMemoryPriority> m_OSMemoryPriority;
|
||||
#endif
|
||||
|
|
|
@ -50,11 +50,21 @@ namespace BitTorrent
|
|||
struct PeerAddress;
|
||||
struct TrackerEntry;
|
||||
|
||||
enum class TorrentOperatingMode
|
||||
// Using `Q_ENUM_NS()` without a wrapper namespace in our case is not advised
|
||||
// since `Q_NAMESPACE` cannot be used when the same namespace resides at different files.
|
||||
// https://www.kdab.com/new-qt-5-8-meta-object-support-namespaces/#comment-143779
|
||||
inline namespace TorrentOperatingModeNS
|
||||
{
|
||||
AutoManaged = 0,
|
||||
Forced = 1
|
||||
};
|
||||
Q_NAMESPACE
|
||||
|
||||
enum class TorrentOperatingMode
|
||||
{
|
||||
AutoManaged = 0,
|
||||
Forced = 1
|
||||
};
|
||||
|
||||
Q_ENUM_NS(TorrentOperatingMode)
|
||||
}
|
||||
|
||||
enum class TorrentState
|
||||
{
|
||||
|
|
|
@ -61,6 +61,7 @@ namespace
|
|||
{
|
||||
// qBittorrent section
|
||||
QBITTORRENT_HEADER,
|
||||
RESUME_DATA_STORAGE,
|
||||
#if defined(Q_OS_WIN)
|
||||
OS_MEMORY_PRIORITY,
|
||||
#endif
|
||||
|
@ -166,6 +167,10 @@ void AdvancedSettings::saveAdvancedSettings()
|
|||
Preferences *const pref = Preferences::instance();
|
||||
BitTorrent::Session *const session = BitTorrent::Session::instance();
|
||||
|
||||
session->setResumeDataStorageType((m_comboBoxResumeDataStorage.currentIndex() == 0)
|
||||
? BitTorrent::ResumeDataStorageType::Legacy
|
||||
: BitTorrent::ResumeDataStorageType::SQLite);
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
BitTorrent::OSMemoryPriority prio = BitTorrent::OSMemoryPriority::Normal;
|
||||
switch (m_comboBoxOSMemoryPriority.currentIndex())
|
||||
|
@ -392,6 +397,10 @@ void AdvancedSettings::loadAdvancedSettings()
|
|||
addRow(LIBTORRENT_HEADER, QString::fromLatin1("<b>%1</b>").arg(tr("libtorrent Section")), labelLibtorrentLink);
|
||||
static_cast<QLabel *>(cellWidget(LIBTORRENT_HEADER, PROPERTY))->setAlignment(Qt::AlignCenter | Qt::AlignVCenter);
|
||||
|
||||
m_comboBoxResumeDataStorage.addItems({tr("Fastresume files"), tr("SQLite database (experimental)")});
|
||||
m_comboBoxResumeDataStorage.setCurrentIndex((session->resumeDataStorageType() == BitTorrent::ResumeDataStorageType::Legacy) ? 0 : 1);
|
||||
addRow(RESUME_DATA_STORAGE, tr("Resume data storage type (requires restart)"), &m_comboBoxResumeDataStorage);
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
m_comboBoxOSMemoryPriority.addItems({tr("Normal"), tr("Below normal"), tr("Medium"), tr("Low"), tr("Very low")});
|
||||
int OSMemoryPriorityIndex = 0;
|
||||
|
|
|
@ -70,7 +70,8 @@ private:
|
|||
m_checkBoxConfirmTorrentRecheck, m_checkBoxConfirmRemoveAllTags, m_checkBoxAnnounceAllTrackers, m_checkBoxAnnounceAllTiers,
|
||||
m_checkBoxMultiConnectionsPerIp, m_checkBoxValidateHTTPSTrackerCertificate, m_checkBoxBlockPeersOnPrivilegedPorts, m_checkBoxPieceExtentAffinity,
|
||||
m_checkBoxSuggestMode, m_checkBoxSpeedWidgetEnabled, m_checkBoxIDNSupport;
|
||||
QComboBox m_comboBoxInterface, m_comboBoxInterfaceAddress, m_comboBoxUtpMixedMode, m_comboBoxChokingAlgorithm, m_comboBoxSeedChokingAlgorithm;
|
||||
QComboBox m_comboBoxInterface, m_comboBoxInterfaceAddress, m_comboBoxUtpMixedMode, m_comboBoxChokingAlgorithm,
|
||||
m_comboBoxSeedChokingAlgorithm, m_comboBoxResumeDataStorage;
|
||||
QLineEdit m_lineEditAnnounceIP;
|
||||
|
||||
#if (LIBTORRENT_VERSION_NUM < 20000)
|
||||
|
|
|
@ -7,7 +7,7 @@ win32: include(../winconf.pri)
|
|||
macx: include(../macxconf.pri)
|
||||
unix:!macx: include(../unixconf.pri)
|
||||
|
||||
QT += network xml
|
||||
QT += network sql xml
|
||||
|
||||
macx|*-clang*: QMAKE_CXXFLAGS_WARN_ON += -Wno-range-loop-analysis
|
||||
|
||||
|
|
Loading…
Reference in a new issue