mirror of
https://github.com/d47081/qBittorrent.git
synced 2025-01-11 15:27:54 +00: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
@ -35,7 +35,7 @@ set_property(CACHE LibtorrentRasterbar_DIR PROPERTY TYPE PATH)
|
|||||||
find_package(Boost ${minBoostVersion} REQUIRED)
|
find_package(Boost ${minBoostVersion} REQUIRED)
|
||||||
find_package(OpenSSL ${minOpenSSLVersion} REQUIRED)
|
find_package(OpenSSL ${minOpenSSLVersion} REQUIRED)
|
||||||
find_package(ZLIB ${minZlibVersion} 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)
|
if (DBUS)
|
||||||
find_package(Qt5 ${minQtVersion} REQUIRED COMPONENTS DBus)
|
find_package(Qt5 ${minQtVersion} REQUIRED COMPONENTS DBus)
|
||||||
set_package_properties(Qt5DBus PROPERTIES
|
set_package_properties(Qt5DBus PROPERTIES
|
||||||
|
@ -9,6 +9,7 @@ add_library(qbt_base STATIC
|
|||||||
bittorrent/cachestatus.h
|
bittorrent/cachestatus.h
|
||||||
bittorrent/common.h
|
bittorrent/common.h
|
||||||
bittorrent/customstorage.h
|
bittorrent/customstorage.h
|
||||||
|
bittorrent/dbresumedatastorage.h
|
||||||
bittorrent/downloadpriority.h
|
bittorrent/downloadpriority.h
|
||||||
bittorrent/filesearcher.h
|
bittorrent/filesearcher.h
|
||||||
bittorrent/filterparserthread.h
|
bittorrent/filterparserthread.h
|
||||||
@ -100,6 +101,7 @@ add_library(qbt_base STATIC
|
|||||||
bittorrent/bandwidthscheduler.cpp
|
bittorrent/bandwidthscheduler.cpp
|
||||||
bittorrent/bencoderesumedatastorage.cpp
|
bittorrent/bencoderesumedatastorage.cpp
|
||||||
bittorrent/customstorage.cpp
|
bittorrent/customstorage.cpp
|
||||||
|
bittorrent/dbresumedatastorage.cpp
|
||||||
bittorrent/downloadpriority.cpp
|
bittorrent/downloadpriority.cpp
|
||||||
bittorrent/filesearcher.cpp
|
bittorrent/filesearcher.cpp
|
||||||
bittorrent/filterparserthread.cpp
|
bittorrent/filterparserthread.cpp
|
||||||
@ -176,7 +178,7 @@ target_link_libraries(qbt_base
|
|||||||
ZLIB::ZLIB
|
ZLIB::ZLIB
|
||||||
PUBLIC
|
PUBLIC
|
||||||
LibtorrentRasterbar::torrent-rasterbar
|
LibtorrentRasterbar::torrent-rasterbar
|
||||||
Qt5::Core Qt5::Network Qt5::Xml
|
Qt5::Core Qt5::Network Qt5::Sql Qt5::Xml
|
||||||
qbt_common_cfg
|
qbt_common_cfg
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ HEADERS += \
|
|||||||
$$PWD/bittorrent/common.h \
|
$$PWD/bittorrent/common.h \
|
||||||
$$PWD/bittorrent/customstorage.h \
|
$$PWD/bittorrent/customstorage.h \
|
||||||
$$PWD/bittorrent/downloadpriority.h \
|
$$PWD/bittorrent/downloadpriority.h \
|
||||||
|
$$PWD/bittorrent/dbresumedatastorage.h \
|
||||||
$$PWD/bittorrent/filesearcher.h \
|
$$PWD/bittorrent/filesearcher.h \
|
||||||
$$PWD/bittorrent/filterparserthread.h \
|
$$PWD/bittorrent/filterparserthread.h \
|
||||||
$$PWD/bittorrent/infohash.h \
|
$$PWD/bittorrent/infohash.h \
|
||||||
@ -100,6 +101,7 @@ SOURCES += \
|
|||||||
$$PWD/bittorrent/bandwidthscheduler.cpp \
|
$$PWD/bittorrent/bandwidthscheduler.cpp \
|
||||||
$$PWD/bittorrent/bencoderesumedatastorage.cpp \
|
$$PWD/bittorrent/bencoderesumedatastorage.cpp \
|
||||||
$$PWD/bittorrent/customstorage.cpp \
|
$$PWD/bittorrent/customstorage.cpp \
|
||||||
|
$$PWD/bittorrent/dbresumedatastorage.cpp \
|
||||||
$$PWD/bittorrent/downloadpriority.cpp \
|
$$PWD/bittorrent/downloadpriority.cpp \
|
||||||
$$PWD/bittorrent/filesearcher.cpp \
|
$$PWD/bittorrent/filesearcher.cpp \
|
||||||
$$PWD/bittorrent/filterparserthread.cpp \
|
$$PWD/bittorrent/filterparserthread.cpp \
|
||||||
|
@ -73,8 +73,6 @@ namespace BitTorrent
|
|||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
const char RESUME_FOLDER[] = "BT_backup";
|
|
||||||
|
|
||||||
template <typename LTStr>
|
template <typename LTStr>
|
||||||
QString fromLTString(const LTStr &str)
|
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}
|
: ResumeDataStorage {parent}
|
||||||
, m_resumeDataDir {Utils::Fs::expandPathAbs(specialFolderLocation(SpecialFolder::Data) + RESUME_FOLDER)}
|
, m_resumeDataDir {path}
|
||||||
, m_ioThread {new QThread {this}}
|
, m_ioThread {new QThread {this}}
|
||||||
, m_asyncWorker {new Worker {m_resumeDataDir}}
|
, m_asyncWorker {new Worker {m_resumeDataDir}}
|
||||||
{
|
{
|
||||||
|
@ -46,7 +46,7 @@ namespace BitTorrent
|
|||||||
Q_DISABLE_COPY(BencodeResumeDataStorage)
|
Q_DISABLE_COPY(BencodeResumeDataStorage)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit BencodeResumeDataStorage(QObject *parent = nullptr);
|
explicit BencodeResumeDataStorage(const QString &path, QObject *parent = nullptr);
|
||||||
~BencodeResumeDataStorage() override;
|
~BencodeResumeDataStorage() override;
|
||||||
|
|
||||||
QVector<TorrentID> registeredTorrents() const 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 "bencoderesumedatastorage.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "customstorage.h"
|
#include "customstorage.h"
|
||||||
|
#include "dbresumedatastorage.h"
|
||||||
#include "filesearcher.h"
|
#include "filesearcher.h"
|
||||||
#include "filterparserthread.h"
|
#include "filterparserthread.h"
|
||||||
#include "loadtorrentparams.h"
|
#include "loadtorrentparams.h"
|
||||||
@ -436,6 +437,7 @@ Session::Session(QObject *parent)
|
|||||||
return tmp;
|
return tmp;
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
, m_resumeDataStorageType(BITTORRENT_SESSION_KEY("ResumeDataStorageType"), ResumeDataStorageType::Legacy)
|
||||||
#if defined(Q_OS_WIN)
|
#if defined(Q_OS_WIN)
|
||||||
, m_OSMemoryPriority(BITTORRENT_KEY("OSMemoryPriority"), OSMemoryPriority::BelowNormal)
|
, m_OSMemoryPriority(BITTORRENT_KEY("OSMemoryPriority"), OSMemoryPriority::BelowNormal)
|
||||||
#endif
|
#endif
|
||||||
@ -451,8 +453,6 @@ Session::Session(QObject *parent)
|
|||||||
if (port() < 0)
|
if (port() < 0)
|
||||||
m_port = Utils::Random::rand(1024, 65535);
|
m_port = Utils::Random::rand(1024, 65535);
|
||||||
|
|
||||||
initResumeDataStorage();
|
|
||||||
|
|
||||||
m_recentErroredTorrentsTimer->setSingleShot(true);
|
m_recentErroredTorrentsTimer->setSingleShot(true);
|
||||||
m_recentErroredTorrentsTimer->setInterval(1000);
|
m_recentErroredTorrentsTimer->setInterval(1000);
|
||||||
connect(m_recentErroredTorrentsTimer, &QTimer::timeout
|
connect(m_recentErroredTorrentsTimer, &QTimer::timeout
|
||||||
@ -2938,6 +2938,16 @@ void Session::setBannedIPs(const QStringList &newList)
|
|||||||
configureDeferred();
|
configureDeferred();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ResumeDataStorageType Session::resumeDataStorageType() const
|
||||||
|
{
|
||||||
|
return m_resumeDataStorageType;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Session::setResumeDataStorageType(const ResumeDataStorageType type)
|
||||||
|
{
|
||||||
|
m_resumeDataStorageType = type;
|
||||||
|
}
|
||||||
|
|
||||||
QStringList Session::bannedIPs() const
|
QStringList Session::bannedIPs() const
|
||||||
{
|
{
|
||||||
return m_bannedIPs;
|
return m_bannedIPs;
|
||||||
@ -4035,11 +4045,6 @@ bool Session::hasPerTorrentSeedingTimeLimit() const
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void Session::initResumeDataStorage()
|
|
||||||
{
|
|
||||||
m_resumeDataStorage = new BencodeResumeDataStorage(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Session::configureDeferred()
|
void Session::configureDeferred()
|
||||||
{
|
{
|
||||||
if (m_deferredConfigureScheduled)
|
if (m_deferredConfigureScheduled)
|
||||||
@ -4118,18 +4123,56 @@ const CacheStatus &Session::cacheStatus() const
|
|||||||
return m_cacheStatus;
|
return m_cacheStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Will resume torrents in backup directory
|
|
||||||
void Session::startUpTorrents()
|
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...");
|
qDebug("Starting up torrents...");
|
||||||
|
|
||||||
const QVector<TorrentID> torrents = m_resumeDataStorage->registeredTorrents();
|
const QVector<TorrentID> torrents = startupStorage->registeredTorrents();
|
||||||
int resumedTorrentsCount = 0;
|
int resumedTorrentsCount = 0;
|
||||||
|
QVector<TorrentID> queue;
|
||||||
for (const TorrentID &torrentID : torrents)
|
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 (resumeData)
|
||||||
{
|
{
|
||||||
|
if (m_resumeDataStorage != startupStorage)
|
||||||
|
{
|
||||||
|
m_resumeDataStorage->store(torrentID, *resumeData);
|
||||||
|
if (isQueueingSystemEnabled() && !resumeData->hasSeedStatus)
|
||||||
|
queue.append(torrentID);
|
||||||
|
}
|
||||||
|
|
||||||
qDebug() << "Starting up torrent" << torrentID.toString() << "...";
|
qDebug() << "Starting up torrent" << torrentID.toString() << "...";
|
||||||
if (!loadTorrent(*resumeData))
|
if (!loadTorrent(*resumeData))
|
||||||
LogMsg(tr("Unable to resume torrent '%1'.", "e.g: Unable to resume torrent 'hash'.")
|
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);
|
.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
|
quint64 Session::getAlltimeDL() const
|
||||||
|
@ -142,6 +142,13 @@ namespace BitTorrent
|
|||||||
};
|
};
|
||||||
Q_ENUM_NS(SeedChokingAlgorithm)
|
Q_ENUM_NS(SeedChokingAlgorithm)
|
||||||
|
|
||||||
|
enum class ResumeDataStorageType
|
||||||
|
{
|
||||||
|
Legacy,
|
||||||
|
SQLite
|
||||||
|
};
|
||||||
|
Q_ENUM_NS(ResumeDataStorageType)
|
||||||
|
|
||||||
#if defined(Q_OS_WIN)
|
#if defined(Q_OS_WIN)
|
||||||
enum class OSMemoryPriority : int
|
enum class OSMemoryPriority : int
|
||||||
{
|
{
|
||||||
@ -429,6 +436,8 @@ namespace BitTorrent
|
|||||||
void setTrackerFilteringEnabled(bool enabled);
|
void setTrackerFilteringEnabled(bool enabled);
|
||||||
QStringList bannedIPs() const;
|
QStringList bannedIPs() const;
|
||||||
void setBannedIPs(const QStringList &newList);
|
void setBannedIPs(const QStringList &newList);
|
||||||
|
ResumeDataStorageType resumeDataStorageType() const;
|
||||||
|
void setResumeDataStorageType(ResumeDataStorageType type);
|
||||||
#if defined(Q_OS_WIN)
|
#if defined(Q_OS_WIN)
|
||||||
OSMemoryPriority getOSMemoryPriority() const;
|
OSMemoryPriority getOSMemoryPriority() const;
|
||||||
void setOSMemoryPriority(OSMemoryPriority priority);
|
void setOSMemoryPriority(OSMemoryPriority priority);
|
||||||
@ -570,8 +579,6 @@ namespace BitTorrent
|
|||||||
bool hasPerTorrentRatioLimit() const;
|
bool hasPerTorrentRatioLimit() const;
|
||||||
bool hasPerTorrentSeedingTimeLimit() const;
|
bool hasPerTorrentSeedingTimeLimit() const;
|
||||||
|
|
||||||
void initResumeDataStorage();
|
|
||||||
|
|
||||||
// Session configuration
|
// Session configuration
|
||||||
Q_INVOKABLE void configure();
|
Q_INVOKABLE void configure();
|
||||||
void configureComponents();
|
void configureComponents();
|
||||||
@ -738,6 +745,7 @@ namespace BitTorrent
|
|||||||
CachedSettingValue<int> m_peerTurnoverCutoff;
|
CachedSettingValue<int> m_peerTurnoverCutoff;
|
||||||
CachedSettingValue<int> m_peerTurnoverInterval;
|
CachedSettingValue<int> m_peerTurnoverInterval;
|
||||||
CachedSettingValue<QStringList> m_bannedIPs;
|
CachedSettingValue<QStringList> m_bannedIPs;
|
||||||
|
CachedSettingValue<ResumeDataStorageType> m_resumeDataStorageType;
|
||||||
#if defined(Q_OS_WIN)
|
#if defined(Q_OS_WIN)
|
||||||
CachedSettingValue<OSMemoryPriority> m_OSMemoryPriority;
|
CachedSettingValue<OSMemoryPriority> m_OSMemoryPriority;
|
||||||
#endif
|
#endif
|
||||||
|
@ -50,11 +50,21 @@ namespace BitTorrent
|
|||||||
struct PeerAddress;
|
struct PeerAddress;
|
||||||
struct TrackerEntry;
|
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,
|
Q_NAMESPACE
|
||||||
Forced = 1
|
|
||||||
};
|
enum class TorrentOperatingMode
|
||||||
|
{
|
||||||
|
AutoManaged = 0,
|
||||||
|
Forced = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
Q_ENUM_NS(TorrentOperatingMode)
|
||||||
|
}
|
||||||
|
|
||||||
enum class TorrentState
|
enum class TorrentState
|
||||||
{
|
{
|
||||||
|
@ -61,6 +61,7 @@ namespace
|
|||||||
{
|
{
|
||||||
// qBittorrent section
|
// qBittorrent section
|
||||||
QBITTORRENT_HEADER,
|
QBITTORRENT_HEADER,
|
||||||
|
RESUME_DATA_STORAGE,
|
||||||
#if defined(Q_OS_WIN)
|
#if defined(Q_OS_WIN)
|
||||||
OS_MEMORY_PRIORITY,
|
OS_MEMORY_PRIORITY,
|
||||||
#endif
|
#endif
|
||||||
@ -166,6 +167,10 @@ void AdvancedSettings::saveAdvancedSettings()
|
|||||||
Preferences *const pref = Preferences::instance();
|
Preferences *const pref = Preferences::instance();
|
||||||
BitTorrent::Session *const session = BitTorrent::Session::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)
|
#if defined(Q_OS_WIN)
|
||||||
BitTorrent::OSMemoryPriority prio = BitTorrent::OSMemoryPriority::Normal;
|
BitTorrent::OSMemoryPriority prio = BitTorrent::OSMemoryPriority::Normal;
|
||||||
switch (m_comboBoxOSMemoryPriority.currentIndex())
|
switch (m_comboBoxOSMemoryPriority.currentIndex())
|
||||||
@ -392,6 +397,10 @@ void AdvancedSettings::loadAdvancedSettings()
|
|||||||
addRow(LIBTORRENT_HEADER, QString::fromLatin1("<b>%1</b>").arg(tr("libtorrent Section")), labelLibtorrentLink);
|
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);
|
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)
|
#if defined(Q_OS_WIN)
|
||||||
m_comboBoxOSMemoryPriority.addItems({tr("Normal"), tr("Below normal"), tr("Medium"), tr("Low"), tr("Very low")});
|
m_comboBoxOSMemoryPriority.addItems({tr("Normal"), tr("Below normal"), tr("Medium"), tr("Low"), tr("Very low")});
|
||||||
int OSMemoryPriorityIndex = 0;
|
int OSMemoryPriorityIndex = 0;
|
||||||
|
@ -70,7 +70,8 @@ private:
|
|||||||
m_checkBoxConfirmTorrentRecheck, m_checkBoxConfirmRemoveAllTags, m_checkBoxAnnounceAllTrackers, m_checkBoxAnnounceAllTiers,
|
m_checkBoxConfirmTorrentRecheck, m_checkBoxConfirmRemoveAllTags, m_checkBoxAnnounceAllTrackers, m_checkBoxAnnounceAllTiers,
|
||||||
m_checkBoxMultiConnectionsPerIp, m_checkBoxValidateHTTPSTrackerCertificate, m_checkBoxBlockPeersOnPrivilegedPorts, m_checkBoxPieceExtentAffinity,
|
m_checkBoxMultiConnectionsPerIp, m_checkBoxValidateHTTPSTrackerCertificate, m_checkBoxBlockPeersOnPrivilegedPorts, m_checkBoxPieceExtentAffinity,
|
||||||
m_checkBoxSuggestMode, m_checkBoxSpeedWidgetEnabled, m_checkBoxIDNSupport;
|
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;
|
QLineEdit m_lineEditAnnounceIP;
|
||||||
|
|
||||||
#if (LIBTORRENT_VERSION_NUM < 20000)
|
#if (LIBTORRENT_VERSION_NUM < 20000)
|
||||||
|
@ -7,7 +7,7 @@ win32: include(../winconf.pri)
|
|||||||
macx: include(../macxconf.pri)
|
macx: include(../macxconf.pri)
|
||||||
unix:!macx: include(../unixconf.pri)
|
unix:!macx: include(../unixconf.pri)
|
||||||
|
|
||||||
QT += network xml
|
QT += network sql xml
|
||||||
|
|
||||||
macx|*-clang*: QMAKE_CXXFLAGS_WARN_ON += -Wno-range-loop-analysis
|
macx|*-clang*: QMAKE_CXXFLAGS_WARN_ON += -Wno-range-loop-analysis
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user