mirror of
https://github.com/qbittorrent/qBittorrent
synced 2025-08-19 12:59:56 -07:00
commit
2f06ea2587
19 changed files with 1009 additions and 438 deletions
|
@ -290,6 +290,8 @@ BitTorrent::LoadResumeDataResult BitTorrent::BencodeResumeDataStorage::loadTorre
|
||||||
lt::add_torrent_params &p = torrentParams.ltAddTorrentParams;
|
lt::add_torrent_params &p = torrentParams.ltAddTorrentParams;
|
||||||
|
|
||||||
p = lt::read_resume_data(resumeDataRoot, ec);
|
p = lt::read_resume_data(resumeDataRoot, ec);
|
||||||
|
if (ec)
|
||||||
|
return nonstd::make_unexpected(tr("Cannot parse resume data: %1").arg(QString::fromStdString(ec.message())));
|
||||||
|
|
||||||
if (!metadata.isEmpty())
|
if (!metadata.isEmpty())
|
||||||
{
|
{
|
||||||
|
@ -320,6 +322,8 @@ BitTorrent::LoadResumeDataResult BitTorrent::BencodeResumeDataStorage::loadTorre
|
||||||
|
|
||||||
p.save_path = Profile::instance()->fromPortablePath(
|
p.save_path = Profile::instance()->fromPortablePath(
|
||||||
Path(fromLTString(p.save_path))).toString().toStdString();
|
Path(fromLTString(p.save_path))).toString().toStdString();
|
||||||
|
if (p.save_path.empty())
|
||||||
|
return nonstd::make_unexpected(tr("Corrupted resume data: %1").arg(tr("save_path is invalid")));
|
||||||
|
|
||||||
torrentParams.stopped = (p.flags & lt::torrent_flags::paused) && !(p.flags & lt::torrent_flags::auto_managed);
|
torrentParams.stopped = (p.flags & lt::torrent_flags::paused) && !(p.flags & lt::torrent_flags::auto_managed);
|
||||||
torrentParams.operatingMode = (p.flags & lt::torrent_flags::paused) || (p.flags & lt::torrent_flags::auto_managed)
|
torrentParams.operatingMode = (p.flags & lt::torrent_flags::paused) || (p.flags & lt::torrent_flags::auto_managed)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
* Copyright (C) 2021-2023 Vladimir Golovnev <glassez@yandex.ru>
|
* Copyright (C) 2021-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License
|
* modify it under the terms of the GNU General Public License
|
||||||
|
@ -217,80 +217,6 @@ namespace
|
||||||
{
|
{
|
||||||
return u"%1 %2"_s.arg(quoted(column.name), definition);
|
return u"%1 %2"_s.arg(quoted(column.name), definition);
|
||||||
}
|
}
|
||||||
|
|
||||||
LoadTorrentParams parseQueryResultRow(const QSqlQuery &query)
|
|
||||||
{
|
|
||||||
LoadTorrentParams resumeData;
|
|
||||||
resumeData.name = query.value(DB_COLUMN_NAME.name).toString();
|
|
||||||
resumeData.category = query.value(DB_COLUMN_CATEGORY.name).toString();
|
|
||||||
const QString tagsData = query.value(DB_COLUMN_TAGS.name).toString();
|
|
||||||
if (!tagsData.isEmpty())
|
|
||||||
{
|
|
||||||
const QStringList tagList = tagsData.split(u',');
|
|
||||||
resumeData.tags.insert(tagList.cbegin(), tagList.cend());
|
|
||||||
}
|
|
||||||
resumeData.hasFinishedStatus = query.value(DB_COLUMN_HAS_SEED_STATUS.name).toBool();
|
|
||||||
resumeData.firstLastPiecePriority = query.value(DB_COLUMN_HAS_OUTER_PIECES_PRIORITY.name).toBool();
|
|
||||||
resumeData.ratioLimit = query.value(DB_COLUMN_RATIO_LIMIT.name).toInt() / 1000.0;
|
|
||||||
resumeData.seedingTimeLimit = query.value(DB_COLUMN_SEEDING_TIME_LIMIT.name).toInt();
|
|
||||||
resumeData.inactiveSeedingTimeLimit = query.value(DB_COLUMN_INACTIVE_SEEDING_TIME_LIMIT.name).toInt();
|
|
||||||
resumeData.shareLimitAction = Utils::String::toEnum<ShareLimitAction>(
|
|
||||||
query.value(DB_COLUMN_SHARE_LIMIT_ACTION.name).toString(), ShareLimitAction::Default);
|
|
||||||
resumeData.contentLayout = Utils::String::toEnum<TorrentContentLayout>(
|
|
||||||
query.value(DB_COLUMN_CONTENT_LAYOUT.name).toString(), TorrentContentLayout::Original);
|
|
||||||
resumeData.operatingMode = Utils::String::toEnum<TorrentOperatingMode>(
|
|
||||||
query.value(DB_COLUMN_OPERATING_MODE.name).toString(), TorrentOperatingMode::AutoManaged);
|
|
||||||
resumeData.stopped = query.value(DB_COLUMN_STOPPED.name).toBool();
|
|
||||||
resumeData.stopCondition = Utils::String::toEnum(
|
|
||||||
query.value(DB_COLUMN_STOP_CONDITION.name).toString(), Torrent::StopCondition::None);
|
|
||||||
resumeData.sslParameters =
|
|
||||||
{
|
|
||||||
.certificate = QSslCertificate(query.value(DB_COLUMN_SSL_CERTIFICATE.name).toByteArray()),
|
|
||||||
.privateKey = Utils::SSLKey::load(query.value(DB_COLUMN_SSL_PRIVATE_KEY.name).toByteArray()),
|
|
||||||
.dhParams = query.value(DB_COLUMN_SSL_DH_PARAMS.name).toByteArray()
|
|
||||||
};
|
|
||||||
|
|
||||||
resumeData.savePath = Profile::instance()->fromPortablePath(
|
|
||||||
Path(query.value(DB_COLUMN_TARGET_SAVE_PATH.name).toString()));
|
|
||||||
resumeData.useAutoTMM = resumeData.savePath.isEmpty();
|
|
||||||
if (!resumeData.useAutoTMM)
|
|
||||||
{
|
|
||||||
resumeData.downloadPath = Profile::instance()->fromPortablePath(
|
|
||||||
Path(query.value(DB_COLUMN_DOWNLOAD_PATH.name).toString()));
|
|
||||||
}
|
|
||||||
|
|
||||||
const QByteArray bencodedResumeData = query.value(DB_COLUMN_RESUMEDATA.name).toByteArray();
|
|
||||||
const auto *pref = Preferences::instance();
|
|
||||||
const int bdecodeDepthLimit = pref->getBdecodeDepthLimit();
|
|
||||||
const int bdecodeTokenLimit = pref->getBdecodeTokenLimit();
|
|
||||||
|
|
||||||
lt::error_code ec;
|
|
||||||
const lt::bdecode_node resumeDataRoot = lt::bdecode(bencodedResumeData, ec
|
|
||||||
, nullptr, bdecodeDepthLimit, bdecodeTokenLimit);
|
|
||||||
|
|
||||||
lt::add_torrent_params &p = resumeData.ltAddTorrentParams;
|
|
||||||
|
|
||||||
p = lt::read_resume_data(resumeDataRoot, ec);
|
|
||||||
|
|
||||||
if (const QByteArray bencodedMetadata = query.value(DB_COLUMN_METADATA.name).toByteArray()
|
|
||||||
; !bencodedMetadata.isEmpty())
|
|
||||||
{
|
|
||||||
const lt::bdecode_node torentInfoRoot = lt::bdecode(bencodedMetadata, ec
|
|
||||||
, nullptr, bdecodeDepthLimit, bdecodeTokenLimit);
|
|
||||||
p.ti = std::make_shared<lt::torrent_info>(torentInfoRoot, ec);
|
|
||||||
}
|
|
||||||
|
|
||||||
p.save_path = Profile::instance()->fromPortablePath(Path(fromLTString(p.save_path)))
|
|
||||||
.toString().toStdString();
|
|
||||||
|
|
||||||
if (p.flags & lt::torrent_flags::stop_when_ready)
|
|
||||||
{
|
|
||||||
p.flags &= ~lt::torrent_flags::stop_when_ready;
|
|
||||||
resumeData.stopCondition = Torrent::StopCondition::FilesChecked;
|
|
||||||
}
|
|
||||||
|
|
||||||
return resumeData;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace BitTorrent
|
namespace BitTorrent
|
||||||
|
@ -688,6 +614,90 @@ void BitTorrent::DBResumeDataStorage::enableWALMode() const
|
||||||
throw RuntimeError(tr("WAL mode is probably unsupported due to filesystem limitations."));
|
throw RuntimeError(tr("WAL mode is probably unsupported due to filesystem limitations."));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LoadResumeDataResult DBResumeDataStorage::parseQueryResultRow(const QSqlQuery &query) const
|
||||||
|
{
|
||||||
|
LoadTorrentParams resumeData;
|
||||||
|
resumeData.name = query.value(DB_COLUMN_NAME.name).toString();
|
||||||
|
resumeData.category = query.value(DB_COLUMN_CATEGORY.name).toString();
|
||||||
|
const QString tagsData = query.value(DB_COLUMN_TAGS.name).toString();
|
||||||
|
if (!tagsData.isEmpty())
|
||||||
|
{
|
||||||
|
const QStringList tagList = tagsData.split(u',');
|
||||||
|
resumeData.tags.insert(tagList.cbegin(), tagList.cend());
|
||||||
|
}
|
||||||
|
resumeData.hasFinishedStatus = query.value(DB_COLUMN_HAS_SEED_STATUS.name).toBool();
|
||||||
|
resumeData.firstLastPiecePriority = query.value(DB_COLUMN_HAS_OUTER_PIECES_PRIORITY.name).toBool();
|
||||||
|
resumeData.ratioLimit = query.value(DB_COLUMN_RATIO_LIMIT.name).toInt() / 1000.0;
|
||||||
|
resumeData.seedingTimeLimit = query.value(DB_COLUMN_SEEDING_TIME_LIMIT.name).toInt();
|
||||||
|
resumeData.inactiveSeedingTimeLimit = query.value(DB_COLUMN_INACTIVE_SEEDING_TIME_LIMIT.name).toInt();
|
||||||
|
resumeData.shareLimitAction = Utils::String::toEnum<ShareLimitAction>(
|
||||||
|
query.value(DB_COLUMN_SHARE_LIMIT_ACTION.name).toString(), ShareLimitAction::Default);
|
||||||
|
resumeData.contentLayout = Utils::String::toEnum<TorrentContentLayout>(
|
||||||
|
query.value(DB_COLUMN_CONTENT_LAYOUT.name).toString(), TorrentContentLayout::Original);
|
||||||
|
resumeData.operatingMode = Utils::String::toEnum<TorrentOperatingMode>(
|
||||||
|
query.value(DB_COLUMN_OPERATING_MODE.name).toString(), TorrentOperatingMode::AutoManaged);
|
||||||
|
resumeData.stopped = query.value(DB_COLUMN_STOPPED.name).toBool();
|
||||||
|
resumeData.stopCondition = Utils::String::toEnum(
|
||||||
|
query.value(DB_COLUMN_STOP_CONDITION.name).toString(), Torrent::StopCondition::None);
|
||||||
|
resumeData.sslParameters =
|
||||||
|
{
|
||||||
|
.certificate = QSslCertificate(query.value(DB_COLUMN_SSL_CERTIFICATE.name).toByteArray()),
|
||||||
|
.privateKey = Utils::SSLKey::load(query.value(DB_COLUMN_SSL_PRIVATE_KEY.name).toByteArray()),
|
||||||
|
.dhParams = query.value(DB_COLUMN_SSL_DH_PARAMS.name).toByteArray()
|
||||||
|
};
|
||||||
|
|
||||||
|
resumeData.savePath = Profile::instance()->fromPortablePath(
|
||||||
|
Path(query.value(DB_COLUMN_TARGET_SAVE_PATH.name).toString()));
|
||||||
|
resumeData.useAutoTMM = resumeData.savePath.isEmpty();
|
||||||
|
if (!resumeData.useAutoTMM)
|
||||||
|
{
|
||||||
|
resumeData.downloadPath = Profile::instance()->fromPortablePath(
|
||||||
|
Path(query.value(DB_COLUMN_DOWNLOAD_PATH.name).toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
const QByteArray bencodedResumeData = query.value(DB_COLUMN_RESUMEDATA.name).toByteArray();
|
||||||
|
const auto *pref = Preferences::instance();
|
||||||
|
const int bdecodeDepthLimit = pref->getBdecodeDepthLimit();
|
||||||
|
const int bdecodeTokenLimit = pref->getBdecodeTokenLimit();
|
||||||
|
|
||||||
|
lt::error_code ec;
|
||||||
|
const lt::bdecode_node resumeDataRoot = lt::bdecode(bencodedResumeData, ec, nullptr, bdecodeDepthLimit, bdecodeTokenLimit);
|
||||||
|
if (ec)
|
||||||
|
return nonstd::make_unexpected(tr("Cannot parse resume data: %1").arg(QString::fromStdString(ec.message())));
|
||||||
|
|
||||||
|
lt::add_torrent_params &p = resumeData.ltAddTorrentParams;
|
||||||
|
|
||||||
|
p = lt::read_resume_data(resumeDataRoot, ec);
|
||||||
|
if (ec)
|
||||||
|
return nonstd::make_unexpected(tr("Cannot parse resume data: %1").arg(QString::fromStdString(ec.message())));
|
||||||
|
|
||||||
|
if (const QByteArray bencodedMetadata = query.value(DB_COLUMN_METADATA.name).toByteArray()
|
||||||
|
; !bencodedMetadata.isEmpty())
|
||||||
|
{
|
||||||
|
const lt::bdecode_node torentInfoRoot = lt::bdecode(bencodedMetadata, ec
|
||||||
|
, nullptr, bdecodeDepthLimit, bdecodeTokenLimit);
|
||||||
|
if (ec)
|
||||||
|
return nonstd::make_unexpected(tr("Cannot parse torrent info: %1").arg(QString::fromStdString(ec.message())));
|
||||||
|
|
||||||
|
p.ti = std::make_shared<lt::torrent_info>(torentInfoRoot, ec);
|
||||||
|
if (ec)
|
||||||
|
return nonstd::make_unexpected(tr("Cannot parse torrent info: %1").arg(QString::fromStdString(ec.message())));
|
||||||
|
}
|
||||||
|
|
||||||
|
p.save_path = Profile::instance()->fromPortablePath(Path(fromLTString(p.save_path)))
|
||||||
|
.toString().toStdString();
|
||||||
|
if (p.save_path.empty())
|
||||||
|
return nonstd::make_unexpected(tr("Corrupted resume data: %1").arg(tr("save_path is invalid")));
|
||||||
|
|
||||||
|
if (p.flags & lt::torrent_flags::stop_when_ready)
|
||||||
|
{
|
||||||
|
p.flags &= ~lt::torrent_flags::stop_when_ready;
|
||||||
|
resumeData.stopCondition = Torrent::StopCondition::FilesChecked;
|
||||||
|
}
|
||||||
|
|
||||||
|
return resumeData;
|
||||||
|
}
|
||||||
|
|
||||||
BitTorrent::DBResumeDataStorage::Worker::Worker(const Path &dbPath, QReadWriteLock &dbLock, QObject *parent)
|
BitTorrent::DBResumeDataStorage::Worker::Worker(const Path &dbPath, QReadWriteLock &dbLock, QObject *parent)
|
||||||
: QThread(parent)
|
: QThread(parent)
|
||||||
, m_path {dbPath}
|
, m_path {dbPath}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
* Copyright (C) 2021-2022 Vladimir Golovnev <glassez@yandex.ru>
|
* Copyright (C) 2021-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License
|
* modify it under the terms of the GNU General Public License
|
||||||
|
@ -31,9 +31,10 @@
|
||||||
#include <QReadWriteLock>
|
#include <QReadWriteLock>
|
||||||
|
|
||||||
#include "base/pathfwd.h"
|
#include "base/pathfwd.h"
|
||||||
#include "base/utils/thread.h"
|
|
||||||
#include "resumedatastorage.h"
|
#include "resumedatastorage.h"
|
||||||
|
|
||||||
|
class QSqlQuery;
|
||||||
|
|
||||||
namespace BitTorrent
|
namespace BitTorrent
|
||||||
{
|
{
|
||||||
class DBResumeDataStorage final : public ResumeDataStorage
|
class DBResumeDataStorage final : public ResumeDataStorage
|
||||||
|
@ -58,6 +59,7 @@ namespace BitTorrent
|
||||||
void createDB() const;
|
void createDB() const;
|
||||||
void updateDB(int fromVersion) const;
|
void updateDB(int fromVersion) const;
|
||||||
void enableWALMode() const;
|
void enableWALMode() const;
|
||||||
|
LoadResumeDataResult parseQueryResultRow(const QSqlQuery &query) const;
|
||||||
|
|
||||||
class Worker;
|
class Worker;
|
||||||
Worker *m_asyncWorker = nullptr;
|
Worker *m_asyncWorker = nullptr;
|
||||||
|
|
|
@ -974,24 +974,26 @@ bool SessionImpl::editCategory(const QString &name, const CategoryOptions &optio
|
||||||
if (options == currentOptions)
|
if (options == currentOptions)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
currentOptions = options;
|
|
||||||
storeCategories();
|
|
||||||
if (isDisableAutoTMMWhenCategorySavePathChanged())
|
if (isDisableAutoTMMWhenCategorySavePathChanged())
|
||||||
{
|
{
|
||||||
|
// This should be done before changing the category options
|
||||||
|
// to prevent the torrent from being moved at the new save path.
|
||||||
|
|
||||||
for (TorrentImpl *const torrent : asConst(m_torrents))
|
for (TorrentImpl *const torrent : asConst(m_torrents))
|
||||||
{
|
{
|
||||||
if (torrent->category() == name)
|
if (torrent->category() == name)
|
||||||
torrent->setAutoTMMEnabled(false);
|
torrent->setAutoTMMEnabled(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
currentOptions = options;
|
||||||
|
storeCategories();
|
||||||
|
|
||||||
for (TorrentImpl *const torrent : asConst(m_torrents))
|
for (TorrentImpl *const torrent : asConst(m_torrents))
|
||||||
{
|
{
|
||||||
if (torrent->category() == name)
|
if (torrent->category() == name)
|
||||||
torrent->handleCategoryOptionsChanged();
|
torrent->handleCategoryOptionsChanged();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
emit categoryOptionsChanged(name);
|
emit categoryOptionsChanged(name);
|
||||||
return true;
|
return true;
|
||||||
|
@ -3247,6 +3249,9 @@ void SessionImpl::setSavePath(const Path &path)
|
||||||
|
|
||||||
if (isDisableAutoTMMWhenDefaultSavePathChanged())
|
if (isDisableAutoTMMWhenDefaultSavePathChanged())
|
||||||
{
|
{
|
||||||
|
// This should be done before changing the save path
|
||||||
|
// to prevent the torrent from being moved at the new save path.
|
||||||
|
|
||||||
QSet<QString> affectedCatogories {{}}; // includes default (unnamed) category
|
QSet<QString> affectedCatogories {{}}; // includes default (unnamed) category
|
||||||
for (auto it = m_categories.cbegin(); it != m_categories.cend(); ++it)
|
for (auto it = m_categories.cbegin(); it != m_categories.cend(); ++it)
|
||||||
{
|
{
|
||||||
|
@ -3276,6 +3281,9 @@ void SessionImpl::setDownloadPath(const Path &path)
|
||||||
|
|
||||||
if (isDisableAutoTMMWhenDefaultSavePathChanged())
|
if (isDisableAutoTMMWhenDefaultSavePathChanged())
|
||||||
{
|
{
|
||||||
|
// This should be done before changing the save path
|
||||||
|
// to prevent the torrent from being moved at the new save path.
|
||||||
|
|
||||||
QSet<QString> affectedCatogories {{}}; // includes default (unnamed) category
|
QSet<QString> affectedCatogories {{}}; // includes default (unnamed) category
|
||||||
for (auto it = m_categories.cbegin(); it != m_categories.cend(); ++it)
|
for (auto it = m_categories.cbegin(); it != m_categories.cend(); ++it)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1615,18 +1615,20 @@ bool TorrentImpl::setCategory(const QString &category)
|
||||||
if (!category.isEmpty() && !m_session->categories().contains(category))
|
if (!category.isEmpty() && !m_session->categories().contains(category))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
if (m_session->isDisableAutoTMMWhenCategoryChanged())
|
||||||
|
{
|
||||||
|
// This should be done before changing the category name
|
||||||
|
// to prevent the torrent from being moved at the path of new category.
|
||||||
|
setAutoTMMEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
const QString oldCategory = m_category;
|
const QString oldCategory = m_category;
|
||||||
m_category = category;
|
m_category = category;
|
||||||
deferredRequestResumeData();
|
deferredRequestResumeData();
|
||||||
m_session->handleTorrentCategoryChanged(this, oldCategory);
|
m_session->handleTorrentCategoryChanged(this, oldCategory);
|
||||||
|
|
||||||
if (m_useAutoTMM)
|
if (m_useAutoTMM)
|
||||||
{
|
|
||||||
if (!m_session->isDisableAutoTMMWhenCategoryChanged())
|
|
||||||
adjustStorageLocation();
|
adjustStorageLocation();
|
||||||
else
|
|
||||||
setAutoTMMEnabled(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -2054,6 +2054,19 @@ void Preferences::setAddNewTorrentDialogSavePathHistoryLength(const int value)
|
||||||
setValue(u"AddNewTorrentDialog/SavePathHistoryLength"_s, clampedValue);
|
setValue(u"AddNewTorrentDialog/SavePathHistoryLength"_s, clampedValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Preferences::isAddNewTorrentDialogAttached() const
|
||||||
|
{
|
||||||
|
return value(u"AddNewTorrentDialog/Attached"_s, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Preferences::setAddNewTorrentDialogAttached(const bool attached)
|
||||||
|
{
|
||||||
|
if (attached == isAddNewTorrentDialogAttached())
|
||||||
|
return;
|
||||||
|
|
||||||
|
setValue(u"AddNewTorrentDialog/Attached"_s, attached);
|
||||||
|
}
|
||||||
|
|
||||||
void Preferences::apply()
|
void Preferences::apply()
|
||||||
{
|
{
|
||||||
if (SettingsStorage::instance()->save())
|
if (SettingsStorage::instance()->save())
|
||||||
|
|
|
@ -433,6 +433,8 @@ public:
|
||||||
void setAddNewTorrentDialogTopLevel(bool value);
|
void setAddNewTorrentDialogTopLevel(bool value);
|
||||||
int addNewTorrentDialogSavePathHistoryLength() const;
|
int addNewTorrentDialogSavePathHistoryLength() const;
|
||||||
void setAddNewTorrentDialogSavePathHistoryLength(int value);
|
void setAddNewTorrentDialogSavePathHistoryLength(int value);
|
||||||
|
bool isAddNewTorrentDialogAttached() const;
|
||||||
|
void setAddNewTorrentDialogAttached(bool attached);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void setStatusFilterState(bool checked);
|
void setStatusFilterState(bool checked);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
* Copyright (C) 2022-2024 Vladimir Golovnev <glassez@yandex.ru>
|
* Copyright (C) 2022-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||||
* Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
|
* Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
|
@ -384,7 +384,6 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::TorrentDescriptor &to
|
||||||
|
|
||||||
AddNewTorrentDialog::~AddNewTorrentDialog()
|
AddNewTorrentDialog::~AddNewTorrentDialog()
|
||||||
{
|
{
|
||||||
saveState();
|
|
||||||
delete m_ui;
|
delete m_ui;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -398,7 +397,7 @@ void AddNewTorrentDialog::loadState()
|
||||||
if (const QSize dialogSize = m_storeDialogSize; dialogSize.isValid())
|
if (const QSize dialogSize = m_storeDialogSize; dialogSize.isValid())
|
||||||
resize(dialogSize);
|
resize(dialogSize);
|
||||||
|
|
||||||
m_ui->splitter->restoreState(m_storeSplitterState);;
|
m_ui->splitter->restoreState(m_storeSplitterState);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddNewTorrentDialog::saveState()
|
void AddNewTorrentDialog::saveState()
|
||||||
|
@ -834,6 +833,12 @@ void AddNewTorrentDialog::reject()
|
||||||
QDialog::reject();
|
QDialog::reject();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AddNewTorrentDialog::done(const int result)
|
||||||
|
{
|
||||||
|
saveState();
|
||||||
|
QDialog::done(result);
|
||||||
|
}
|
||||||
|
|
||||||
void AddNewTorrentDialog::updateMetadata(const BitTorrent::TorrentInfo &metadata)
|
void AddNewTorrentDialog::updateMetadata(const BitTorrent::TorrentInfo &metadata)
|
||||||
{
|
{
|
||||||
Q_ASSERT(m_currentContext);
|
Q_ASSERT(m_currentContext);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
* Copyright (C) 2022-2024 Vladimir Golovnev <glassez@yandex.ru>
|
* Copyright (C) 2022-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||||
* Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
|
* Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
|
@ -68,6 +68,11 @@ signals:
|
||||||
void torrentAccepted(const BitTorrent::TorrentDescriptor &torrentDescriptor, const BitTorrent::AddTorrentParams &addTorrentParams);
|
void torrentAccepted(const BitTorrent::TorrentDescriptor &torrentDescriptor, const BitTorrent::AddTorrentParams &addTorrentParams);
|
||||||
void torrentRejected(const BitTorrent::TorrentDescriptor &torrentDescriptor);
|
void torrentRejected(const BitTorrent::TorrentDescriptor &torrentDescriptor);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void accept() override;
|
||||||
|
void reject() override;
|
||||||
|
void done(int result) override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void updateDiskSpaceLabel();
|
void updateDiskSpaceLabel();
|
||||||
void onSavePathChanged(const Path &newPath);
|
void onSavePathChanged(const Path &newPath);
|
||||||
|
@ -77,9 +82,6 @@ private slots:
|
||||||
void categoryChanged(int index);
|
void categoryChanged(int index);
|
||||||
void contentLayoutChanged();
|
void contentLayoutChanged();
|
||||||
|
|
||||||
void accept() override;
|
|
||||||
void reject() override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
class TorrentContentAdaptor;
|
class TorrentContentAdaptor;
|
||||||
struct Context;
|
struct Context;
|
||||||
|
|
|
@ -99,6 +99,7 @@ namespace
|
||||||
ENABLE_SPEED_WIDGET,
|
ENABLE_SPEED_WIDGET,
|
||||||
#ifndef Q_OS_MACOS
|
#ifndef Q_OS_MACOS
|
||||||
ENABLE_ICONS_IN_MENUS,
|
ENABLE_ICONS_IN_MENUS,
|
||||||
|
USE_ATTACHED_ADD_NEW_TORRENT_DIALOG,
|
||||||
#endif
|
#endif
|
||||||
// embedded tracker
|
// embedded tracker
|
||||||
TRACKER_STATUS,
|
TRACKER_STATUS,
|
||||||
|
@ -330,6 +331,7 @@ void AdvancedSettings::saveAdvancedSettings() const
|
||||||
pref->setSpeedWidgetEnabled(m_checkBoxSpeedWidgetEnabled.isChecked());
|
pref->setSpeedWidgetEnabled(m_checkBoxSpeedWidgetEnabled.isChecked());
|
||||||
#ifndef Q_OS_MACOS
|
#ifndef Q_OS_MACOS
|
||||||
pref->setIconsInMenusEnabled(m_checkBoxIconsInMenusEnabled.isChecked());
|
pref->setIconsInMenusEnabled(m_checkBoxIconsInMenusEnabled.isChecked());
|
||||||
|
pref->setAddNewTorrentDialogAttached(m_checkBoxAttachedAddNewTorrentDialog.isChecked());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Tracker
|
// Tracker
|
||||||
|
@ -856,6 +858,9 @@ void AdvancedSettings::loadAdvancedSettings()
|
||||||
// Enable icons in menus
|
// Enable icons in menus
|
||||||
m_checkBoxIconsInMenusEnabled.setChecked(pref->iconsInMenusEnabled());
|
m_checkBoxIconsInMenusEnabled.setChecked(pref->iconsInMenusEnabled());
|
||||||
addRow(ENABLE_ICONS_IN_MENUS, tr("Enable icons in menus"), &m_checkBoxIconsInMenusEnabled);
|
addRow(ENABLE_ICONS_IN_MENUS, tr("Enable icons in menus"), &m_checkBoxIconsInMenusEnabled);
|
||||||
|
|
||||||
|
m_checkBoxAttachedAddNewTorrentDialog.setChecked(pref->isAddNewTorrentDialogAttached());
|
||||||
|
addRow(USE_ATTACHED_ADD_NEW_TORRENT_DIALOG, tr("Attach \"Add new torrent\" dialog to main window"), &m_checkBoxAttachedAddNewTorrentDialog);
|
||||||
#endif
|
#endif
|
||||||
// Tracker State
|
// Tracker State
|
||||||
m_checkBoxTrackerStatus.setChecked(session->isTrackerEnabled());
|
m_checkBoxTrackerStatus.setChecked(session->isTrackerEnabled());
|
||||||
|
|
|
@ -108,6 +108,7 @@ private:
|
||||||
|
|
||||||
#ifndef Q_OS_MACOS
|
#ifndef Q_OS_MACOS
|
||||||
QCheckBox m_checkBoxIconsInMenusEnabled;
|
QCheckBox m_checkBoxIconsInMenusEnabled;
|
||||||
|
QCheckBox m_checkBoxAttachedAddNewTorrentDialog;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
|
#if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
|
||||||
|
|
|
@ -82,6 +82,15 @@ GUIAddTorrentManager::GUIAddTorrentManager(IGUIApplication *app, BitTorrent::Ses
|
||||||
connect(btSession(), &BitTorrent::Session::metadataDownloaded, this, &GUIAddTorrentManager::onMetadataDownloaded);
|
connect(btSession(), &BitTorrent::Session::metadataDownloaded, this, &GUIAddTorrentManager::onMetadataDownloaded);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GUIAddTorrentManager::~GUIAddTorrentManager()
|
||||||
|
{
|
||||||
|
for (AddNewTorrentDialog *dialog : asConst(m_dialogs))
|
||||||
|
{
|
||||||
|
dialog->disconnect(this);
|
||||||
|
dialog->reject();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool GUIAddTorrentManager::addTorrent(const QString &source, const BitTorrent::AddTorrentParams ¶ms, const AddTorrentOption option)
|
bool GUIAddTorrentManager::addTorrent(const QString &source, const BitTorrent::AddTorrentParams ¶ms, const AddTorrentOption option)
|
||||||
{
|
{
|
||||||
// `source`: .torrent file path, magnet URI or URL
|
// `source`: .torrent file path, magnet URI or URL
|
||||||
|
@ -225,11 +234,18 @@ bool GUIAddTorrentManager::processTorrent(const QString &source
|
||||||
if (!hasMetadata)
|
if (!hasMetadata)
|
||||||
btSession()->downloadMetadata(torrentDescr);
|
btSession()->downloadMetadata(torrentDescr);
|
||||||
|
|
||||||
|
#ifdef Q_OS_MACOS
|
||||||
|
const bool attached = false;
|
||||||
|
#else
|
||||||
|
const bool attached = Preferences::instance()->isAddNewTorrentDialogAttached();
|
||||||
|
#endif
|
||||||
|
|
||||||
// By not setting a parent to the "AddNewTorrentDialog", all those dialogs
|
// By not setting a parent to the "AddNewTorrentDialog", all those dialogs
|
||||||
// will be displayed on top and will not overlap with the main window.
|
// will be displayed on top and will not overlap with the main window.
|
||||||
auto *dlg = new AddNewTorrentDialog(torrentDescr, params, nullptr);
|
auto *dlg = new AddNewTorrentDialog(torrentDescr, params, (attached ? app()->mainWindow() : nullptr));
|
||||||
// Qt::Window is required to avoid showing only two dialog on top (see #12852).
|
// Qt::Window is required to avoid showing only two dialog on top (see #12852).
|
||||||
// Also improves the general convenience of adding multiple torrents.
|
// Also improves the general convenience of adding multiple torrents.
|
||||||
|
if (!attached)
|
||||||
dlg->setWindowFlags(Qt::Window);
|
dlg->setWindowFlags(Qt::Window);
|
||||||
|
|
||||||
dlg->setAttribute(Qt::WA_DeleteOnClose);
|
dlg->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
|
|
@ -61,6 +61,7 @@ class GUIAddTorrentManager : public GUIApplicationComponent<AddTorrentManager>
|
||||||
|
|
||||||
public:
|
public:
|
||||||
GUIAddTorrentManager(IGUIApplication *app, BitTorrent::Session *session, QObject *parent = nullptr);
|
GUIAddTorrentManager(IGUIApplication *app, BitTorrent::Session *session, QObject *parent = nullptr);
|
||||||
|
~GUIAddTorrentManager() override;
|
||||||
|
|
||||||
bool addTorrent(const QString &source, const BitTorrent::AddTorrentParams ¶ms = {}, AddTorrentOption option = AddTorrentOption::Default);
|
bool addTorrent(const QString &source, const BitTorrent::AddTorrentParams ¶ms = {}, AddTorrentOption option = AddTorrentOption::Default);
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -537,8 +537,6 @@ void WebApplication::sendFile(const Path &path)
|
||||||
const QDateTime lastModified = Utils::Fs::lastModified(path);
|
const QDateTime lastModified = Utils::Fs::lastModified(path);
|
||||||
|
|
||||||
// find translated file in cache
|
// find translated file in cache
|
||||||
if (!m_isAltUIUsed)
|
|
||||||
{
|
|
||||||
if (const auto it = m_translatedFiles.constFind(path);
|
if (const auto it = m_translatedFiles.constFind(path);
|
||||||
(it != m_translatedFiles.constEnd()) && (lastModified <= it->lastModified))
|
(it != m_translatedFiles.constEnd()) && (lastModified <= it->lastModified))
|
||||||
{
|
{
|
||||||
|
@ -546,7 +544,6 @@ void WebApplication::sendFile(const Path &path)
|
||||||
setHeader({Http::HEADER_CACHE_CONTROL, getCachingInterval(it->mimeType)});
|
setHeader({Http::HEADER_CACHE_CONTROL, getCachingInterval(it->mimeType)});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const auto readResult = Utils::IO::readFile(path, MAX_ALLOWED_FILESIZE);
|
const auto readResult = Utils::IO::readFile(path, MAX_ALLOWED_FILESIZE);
|
||||||
if (!readResult)
|
if (!readResult)
|
||||||
|
@ -576,7 +573,7 @@ void WebApplication::sendFile(const Path &path)
|
||||||
|
|
||||||
QByteArray data = readResult.value();
|
QByteArray data = readResult.value();
|
||||||
const QMimeType mimeType = QMimeDatabase().mimeTypeForFileNameAndData(path.data(), data);
|
const QMimeType mimeType = QMimeDatabase().mimeTypeForFileNameAndData(path.data(), data);
|
||||||
const bool isTranslatable = !m_isAltUIUsed && mimeType.inherits(u"text/plain"_s);
|
const bool isTranslatable = mimeType.inherits(u"text/plain"_s);
|
||||||
|
|
||||||
if (isTranslatable)
|
if (isTranslatable)
|
||||||
{
|
{
|
||||||
|
|
|
@ -381,8 +381,10 @@ window.addEventListener("DOMContentLoaded", () => {
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
let removed = false;
|
let removed = false;
|
||||||
for (const data of categoryMap.values())
|
for (const data of categoryMap.values()) {
|
||||||
removed ||= data.torrents.delete(hash);
|
const deleteResult = data.torrents.delete(hash);
|
||||||
|
removed ||= deleteResult;
|
||||||
|
}
|
||||||
return removed;
|
return removed;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -418,8 +420,10 @@ window.addEventListener("DOMContentLoaded", () => {
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
let removed = false;
|
let removed = false;
|
||||||
for (const torrents of tagMap.values())
|
for (const torrents of tagMap.values()) {
|
||||||
removed ||= torrents.delete(hash);
|
const deleteResult = torrents.delete(hash);
|
||||||
|
removed ||= deleteResult;
|
||||||
|
}
|
||||||
return removed;
|
return removed;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -477,6 +481,8 @@ window.addEventListener("DOMContentLoaded", () => {
|
||||||
updateFilter("checking", "QBT_TR(Checking (%1))QBT_TR[CONTEXT=StatusFilterWidget]");
|
updateFilter("checking", "QBT_TR(Checking (%1))QBT_TR[CONTEXT=StatusFilterWidget]");
|
||||||
updateFilter("moving", "QBT_TR(Moving (%1))QBT_TR[CONTEXT=StatusFilterWidget]");
|
updateFilter("moving", "QBT_TR(Moving (%1))QBT_TR[CONTEXT=StatusFilterWidget]");
|
||||||
updateFilter("errored", "QBT_TR(Errored (%1))QBT_TR[CONTEXT=StatusFilterWidget]");
|
updateFilter("errored", "QBT_TR(Errored (%1))QBT_TR[CONTEXT=StatusFilterWidget]");
|
||||||
|
if (useAutoHideZeroStatusFilters && document.getElementById(`${selectedStatus}_filter`).classList.contains("invisible"))
|
||||||
|
setStatusFilter("all");
|
||||||
};
|
};
|
||||||
|
|
||||||
const highlightSelectedStatus = () => {
|
const highlightSelectedStatus = () => {
|
||||||
|
@ -1509,6 +1515,8 @@ window.addEventListener("DOMContentLoaded", () => {
|
||||||
},
|
},
|
||||||
column: "mainColumn",
|
column: "mainColumn",
|
||||||
onResize: window.qBittorrent.Misc.createDebounceHandler(500, (e) => {
|
onResize: window.qBittorrent.Misc.createDebounceHandler(500, (e) => {
|
||||||
|
const isHidden = (parseInt(document.getElementById("propertiesPanel").style.height, 10) === 0);
|
||||||
|
if (!isHidden)
|
||||||
saveColumnSizes();
|
saveColumnSizes();
|
||||||
}),
|
}),
|
||||||
height: null
|
height: null
|
||||||
|
|
|
@ -131,7 +131,11 @@ window.qBittorrent.Download ??= (() => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
$(window).addEventListener("load", () => {
|
$(window).addEventListener("load", async () => {
|
||||||
|
// user might load this page directly (via browser magnet handler)
|
||||||
|
// so wait for crucial initialization to complete
|
||||||
|
await window.parent.qBittorrent.Client.initializeCaches();
|
||||||
|
|
||||||
getPreferences();
|
getPreferences();
|
||||||
getCategories();
|
getCategories();
|
||||||
});
|
});
|
||||||
|
|
|
@ -89,7 +89,6 @@ window.qBittorrent.DynamicTable ??= (() => {
|
||||||
this.setupCommonEvents();
|
this.setupCommonEvents();
|
||||||
this.setupHeaderEvents();
|
this.setupHeaderEvents();
|
||||||
this.setupHeaderMenu();
|
this.setupHeaderMenu();
|
||||||
this.setSortedColumnIcon(this.sortedColumn, null, (this.reverseSort === "1"));
|
|
||||||
this.setupAltRow();
|
this.setupAltRow();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -601,20 +600,22 @@ window.qBittorrent.DynamicTable ??= (() => {
|
||||||
updateTableHeaders: function() {
|
updateTableHeaders: function() {
|
||||||
this.updateHeader(this.hiddenTableHeader);
|
this.updateHeader(this.hiddenTableHeader);
|
||||||
this.updateHeader(this.fixedTableHeader);
|
this.updateHeader(this.fixedTableHeader);
|
||||||
|
this.setSortedColumnIcon(this.sortedColumn, null, (this.reverseSort === "1"));
|
||||||
},
|
},
|
||||||
|
|
||||||
updateHeader: function(header) {
|
updateHeader: function(header) {
|
||||||
const ths = this.getRowCells(header);
|
const ths = this.getRowCells(header);
|
||||||
for (let i = 0; i < ths.length; ++i) {
|
for (let i = 0; i < ths.length; ++i) {
|
||||||
const th = ths[i];
|
const th = ths[i];
|
||||||
th._this = this;
|
if (th.columnName !== this.columns[i].name) {
|
||||||
th.title = this.columns[i].caption;
|
th.title = this.columns[i].caption;
|
||||||
th.textContent = this.columns[i].caption;
|
th.textContent = this.columns[i].caption;
|
||||||
th.style.cssText = `width: ${this.columns[i].width}px; ${this.columns[i].style}`;
|
th.style.cssText = `width: ${this.columns[i].width}px; ${this.columns[i].style}`;
|
||||||
th.columnName = this.columns[i].name;
|
th.columnName = this.columns[i].name;
|
||||||
th.classList.add(`column_${th.columnName}`);
|
th.className = `column_${th.columnName}`;
|
||||||
th.classList.toggle("invisible", ((this.columns[i].visible === "0") || this.columns[i].force_hide));
|
th.classList.toggle("invisible", ((this.columns[i].visible === "0") || this.columns[i].force_hide));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
getColumnPos: function(columnName) {
|
getColumnPos: function(columnName) {
|
||||||
|
|
|
@ -138,8 +138,6 @@ window.qBittorrent.PropTrackers ??= (() => {
|
||||||
addTrackerFN();
|
addTrackerFN();
|
||||||
},
|
},
|
||||||
EditTracker: (element, ref) => {
|
EditTracker: (element, ref) => {
|
||||||
// only allow editing of one row
|
|
||||||
element.firstElementChild.click();
|
|
||||||
editTrackerFN(element);
|
editTrackerFN(element);
|
||||||
},
|
},
|
||||||
RemoveTracker: (element, ref) => {
|
RemoveTracker: (element, ref) => {
|
||||||
|
@ -162,7 +160,11 @@ window.qBittorrent.PropTrackers ??= (() => {
|
||||||
this.hideItem("CopyTrackerUrl");
|
this.hideItem("CopyTrackerUrl");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
if (selectedTrackers.length === 1)
|
||||||
this.showItem("EditTracker");
|
this.showItem("EditTracker");
|
||||||
|
else
|
||||||
|
this.hideItem("EditTracker");
|
||||||
|
|
||||||
this.showItem("RemoveTracker");
|
this.showItem("RemoveTracker");
|
||||||
this.showItem("CopyTrackerUrl");
|
this.showItem("CopyTrackerUrl");
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue