Backport changes to v5.1.x branch

PR #22490.
This commit is contained in:
Vladimir Golovnev 2025-04-17 20:54:03 +03:00 committed by GitHub
commit 2f06ea2587
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 1009 additions and 438 deletions

View file

@ -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)

View file

@ -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}

View file

@ -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;

View file

@ -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)
{ {

View file

@ -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;

View file

@ -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())

View file

@ -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);

View file

@ -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);

View file

@ -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;

View file

@ -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());

View file

@ -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)

View file

@ -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 &params, const AddTorrentOption option) bool GUIAddTorrentManager::addTorrent(const QString &source, const BitTorrent::AddTorrentParams &params, 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);

View file

@ -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 &params = {}, AddTorrentOption option = AddTorrentOption::Default); bool addTorrent(const QString &source, const BitTorrent::AddTorrentParams &params = {}, AddTorrentOption option = AddTorrentOption::Default);

File diff suppressed because it is too large Load diff

View file

@ -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)
{ {

View file

@ -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

View file

@ -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();
}); });

View file

@ -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) {

View file

@ -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");
} }