33 #include <libtorrent/bdecode.hpp>
34 #include <libtorrent/bencode.hpp>
35 #include <libtorrent/entry.hpp>
36 #include <libtorrent/read_resume_data.hpp>
37 #include <libtorrent/torrent_info.hpp>
38 #include <libtorrent/write_resume_data.hpp>
43 #include <QSqlDatabase>
77 return {QLatin1String(columnName), (QLatin1Char(
':') + QLatin1String(columnName))};
99 template <
typename LTStr>
102 return QString::fromUtf8(str.data(),
static_cast<int>(str.size()));
107 const QLatin1Char quote {
'`'};
109 return (quote + name + quote);
114 return QString::fromLatin1(
"CREATE TABLE %1 (%2)").arg(
quoted(tableName), items.join(QLatin1Char(
',')));
117 std::pair<QString, QString>
joinColumns(
const QVector<Column> &columns)
119 int namesSize = columns.size();
120 int valuesSize = columns.size();
121 for (
const Column &column : columns)
123 namesSize += column.name.size() + 2;
124 valuesSize += column.placeholder.size();
128 names.reserve(namesSize);
130 values.reserve(valuesSize);
131 for (
const Column &column : columns)
133 names.append(
quoted(column.name) + QLatin1Char(
','));
134 values.append(column.placeholder + QLatin1Char(
','));
139 return std::make_pair(names, values);
145 return QString::fromLatin1(
"INSERT INTO %1 (%2) VALUES (%3)")
146 .arg(
quoted(tableName), names, values);
152 return QString::fromLatin1(
"UPDATE %1 SET (%2) = (%3)")
153 .arg(
quoted(tableName), names, values);
159 return QString::fromLatin1(
" ON CONFLICT (%1) DO UPDATE SET (%2) = (%3)")
165 return QString::fromLatin1(
"%1 %2").arg(
quoted(column.
name), QLatin1String(definition));
173 Q_DISABLE_COPY_MOVE(
Worker)
176 Worker(
const QString &dbPath,
const QString &dbConnectionName);
183 void storeQueue(
const QVector<TorrentID> &queue)
const;
193 , m_ioThread {new QThread(this)}
195 const bool needCreateDB = !QFile::exists(dbPath);
198 db.setDatabaseName(dbPath);
229 }, Qt::BlockingQueuedConnection);
237 QMetaObject::invokeMethod(m_asyncWorker, &Worker::closeDatabase);
246 const auto selectTorrentIDStatement = QString::fromLatin1(
"SELECT %1 FROM %2 ORDER BY %3;")
250 QSqlQuery query {db};
252 if (!query.exec(selectTorrentIDStatement))
255 QVector<TorrentID> registeredTorrents;
256 registeredTorrents.reserve(query.size());
260 return registeredTorrents;
265 const QString selectTorrentStatement =
266 QString(QLatin1String(
"SELECT * FROM %1 WHERE %2 = %3;"))
270 QSqlQuery query {db};
273 if (!query.prepare(selectTorrentStatement))
285 LogMsg(tr(
"Couldn't load resume data of torrent '%1'. Error: %2")
295 if (!tagsData.isEmpty())
297 const QStringList tagList = tagsData.split(QLatin1Char(
','));
298 resumeData.
tags.insert(tagList.cbegin(), tagList.cend());
304 resumeData.
contentLayout = Utils::String::toEnum<TorrentContentLayout>(
306 resumeData.
operatingMode = Utils::String::toEnum<TorrentOperatingMode>(
321 const QByteArray allData = ((bencodedMetadata.isEmpty() || bencodedResumeData.isEmpty())
323 : (bencodedResumeData.chopped(1) + bencodedMetadata.mid(1)));
326 const lt::bdecode_node root = lt::bdecode(allData, ec);
333 p = lt::read_resume_data(root, ec);
341 QMetaObject::invokeMethod(m_asyncWorker, [
this,
id, resumeData]()
343 m_asyncWorker->store(
id, resumeData);
349 QMetaObject::invokeMethod(m_asyncWorker, [
this,
id]()
351 m_asyncWorker->remove(
id);
357 QMetaObject::invokeMethod(m_asyncWorker, [
this, queue]()
359 m_asyncWorker->storeQueue(queue);
365 const auto selectDBVersionStatement = QString::fromLatin1(
"SELECT %1 FROM %2 WHERE %3 = %4;")
369 QSqlQuery query {db};
371 if (!query.prepare(selectDBVersionStatement))
383 const int dbVersion = query.value(0).toInt(&ok);
394 if (!db.transaction())
397 QSqlQuery query {db};
401 const QStringList tableMetaItems = {
407 if (!query.exec(createTableMetaQuery))
411 if (!query.prepare(insertMetaVersionQuery))
420 const QStringList tableTorrentsItems = {
439 if (!query.exec(createTableTorrentsQuery))
456 if (!db.transaction())
459 QSqlQuery query {db};
463 const auto alterTableTorrentsQuery = QString::fromLatin1(
"ALTER TABLE %1 ADD %2")
465 if (!query.exec(alterTableTorrentsQuery))
469 if (!query.prepare(updateMetaVersionQuery))
490 , m_connectionName {dbConnectionName}
496 auto db = QSqlDatabase::addDatabase(QLatin1String(
"QSQLITE"), m_connectionName);
497 db.setDatabaseName(m_path);
504 QSqlDatabase::removeDatabase(m_connectionName);
514 p.flags |= lt::torrent_flags::paused;
515 p.flags &= ~lt::torrent_flags::auto_managed;
521 if (resumeData.
operatingMode == BitTorrent::TorrentOperatingMode::AutoManaged)
523 p.flags |= lt::torrent_flags::auto_managed;
527 p.flags &= ~lt::torrent_flags::paused;
528 p.flags &= ~lt::torrent_flags::auto_managed;
532 QVector<Column> columns {
548 lt::entry data = lt::write_resume_data(p);
551 QByteArray bencodedMetadata;
554 lt::entry::dictionary_type &dataDict = data.dict();
555 lt::entry metadata {lt::entry::dictionary_t};
556 lt::entry::dictionary_type &metadataDict = metadata.dict();
557 metadataDict.insert(dataDict.extract(
"info"));
558 metadataDict.insert(dataDict.extract(
"creation date"));
559 metadataDict.insert(dataDict.extract(
"created by"));
560 metadataDict.insert(dataDict.extract(
"comment"));
564 bencodedMetadata.reserve(512 * 1024);
565 lt::bencode(std::back_inserter(bencodedMetadata), metadata);
567 catch (
const std::exception &err)
569 LogMsg(tr(
"Couldn't save torrent metadata. Error: %1.")
577 QByteArray bencodedResumeData;
578 bencodedResumeData.reserve(256 * 1024);
579 lt::bencode(std::back_inserter(bencodedResumeData), data);
583 auto db = QSqlDatabase::database(m_connectionName);
584 QSqlQuery query {db};
588 if (!query.prepare(insertTorrentStatement))
595 ? QVariant(QVariant::String) : resumeData.
tags.
join(QLatin1String(
","))));
611 if (!bencodedMetadata.isEmpty())
619 LogMsg(tr(
"Couldn't store resume data for torrent '%1'. Error: %2")
626 const auto deleteTorrentStatement = QString::fromLatin1(
"DELETE FROM %1 WHERE %2 = %3;")
629 auto db = QSqlDatabase::database(m_connectionName);
630 QSqlQuery query {db};
634 if (!query.prepare(deleteTorrentStatement))
643 LogMsg(tr(
"Couldn't delete resume data of torrent '%1'. Error: %2")
650 const auto updateQueuePosStatement = QString::fromLatin1(
"UPDATE %1 SET %2 = %3 WHERE %4 = %5;")
654 auto db = QSqlDatabase::database(m_connectionName);
658 if (!db.transaction())
661 QSqlQuery query {db};
665 if (!query.prepare(updateQueuePosStatement))
688 LogMsg(tr(
"Couldn't store torrents queue positions. Error: %1")
const QString m_connectionName
void openDatabase() const
void remove(const TorrentID &id) const
void closeDatabase() const
void storeQueue(const QVector< TorrentID > &queue) const
Worker(const QString &dbPath, const QString &dbConnectionName)
void store(const TorrentID &id, const LoadTorrentParams &resumeData) const
~DBResumeDataStorage() override
QVector< TorrentID > registeredTorrents() const override
void storeQueue(const QVector< TorrentID > &queue) const override
void remove(const TorrentID &id) const override
void store(const TorrentID &id, const LoadTorrentParams &resumeData) const override
std::optional< LoadTorrentParams > load(const TorrentID &id) const override
DBResumeDataStorage(const QString &dbPath, QObject *parent=nullptr)
int currentDBVersion() const
void updateDBFromVersion1() const
static TorrentID fromString(const QString &hashString)
QString message() const noexcept
QString join(const QString &separator) const
QString fromPortablePath(const QString &portablePath) const
QString toPortablePath(const QString &absolutePath) const
static const Profile * instance()
void LogMsg(const QString &message, const Log::MsgType &type)
QString toUniformPath(const QString &path)
QString fromEnum(const T &value)
const char DB_TABLE_META[]
const Column DB_COLUMN_OPERATING_MODE
const Column DB_COLUMN_HAS_OUTER_PIECES_PRIORITY
QString makeOnConflictUpdateStatement(const Column &constraint, const QVector< Column > &columns)
QString fromLTString(const LTStr &str)
const Column DB_COLUMN_ID
const Column DB_COLUMN_SEEDING_TIME_LIMIT
const Column DB_COLUMN_RESUMEDATA
QString makeColumnDefinition(const Column &column, const char *definition)
const char DB_TABLE_TORRENTS[]
QString quoted(const QString &name)
const Column DB_COLUMN_VALUE
QString makeInsertStatement(const QString &tableName, const QVector< Column > &columns)
const Column DB_COLUMN_RATIO_LIMIT
const Column DB_COLUMN_CONTENT_LAYOUT
const Column DB_COLUMN_STOPPED
const Column DB_COLUMN_QUEUE_POSITION
Column makeColumn(const char *columnName)
const char META_VERSION[]
const Column DB_COLUMN_TAGS
const Column DB_COLUMN_TORRENT_ID
QString makeCreateTableStatement(const QString &tableName, const QStringList &items)
const Column DB_COLUMN_NAME
const Column DB_COLUMN_TARGET_SAVE_PATH
const Column DB_COLUMN_HAS_SEED_STATUS
const char DB_CONNECTION_NAME[]
QString makeUpdateStatement(const QString &tableName, const QVector< Column > &columns)
const Column DB_COLUMN_CATEGORY
std::pair< QString, QString > joinColumns(const QVector< Column > &columns)
const Column DB_COLUMN_METADATA
const Column DB_COLUMN_DOWNLOAD_PATH
QString toString(const lt::socket_type_t socketType)
TorrentContentLayout contentLayout
bool firstLastPiecePriority
lt::add_torrent_params ltAddTorrentParams
TorrentOperatingMode operatingMode