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;
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())
{
@ -320,6 +322,8 @@ BitTorrent::LoadResumeDataResult BitTorrent::BencodeResumeDataStorage::loadTorre
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")));
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)

View file

@ -1,6 +1,6 @@
/*
* 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
* 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);
}
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
@ -688,6 +614,90 @@ void BitTorrent::DBResumeDataStorage::enableWALMode() const
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)
: QThread(parent)
, m_path {dbPath}

View file

@ -1,6 +1,6 @@
/*
* 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
* modify it under the terms of the GNU General Public License
@ -31,9 +31,10 @@
#include <QReadWriteLock>
#include "base/pathfwd.h"
#include "base/utils/thread.h"
#include "resumedatastorage.h"
class QSqlQuery;
namespace BitTorrent
{
class DBResumeDataStorage final : public ResumeDataStorage
@ -58,6 +59,7 @@ namespace BitTorrent
void createDB() const;
void updateDB(int fromVersion) const;
void enableWALMode() const;
LoadResumeDataResult parseQueryResultRow(const QSqlQuery &query) const;
class Worker;
Worker *m_asyncWorker = nullptr;

View file

@ -974,23 +974,25 @@ bool SessionImpl::editCategory(const QString &name, const CategoryOptions &optio
if (options == currentOptions)
return false;
currentOptions = options;
storeCategories();
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))
{
if (torrent->category() == name)
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)
torrent->handleCategoryOptionsChanged();
}
if (torrent->category() == name)
torrent->handleCategoryOptionsChanged();
}
emit categoryOptionsChanged(name);
@ -3247,6 +3249,9 @@ void SessionImpl::setSavePath(const Path &path)
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
for (auto it = m_categories.cbegin(); it != m_categories.cend(); ++it)
{
@ -3276,6 +3281,9 @@ void SessionImpl::setDownloadPath(const Path &path)
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
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))
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;
m_category = category;
deferredRequestResumeData();
m_session->handleTorrentCategoryChanged(this, oldCategory);
if (m_useAutoTMM)
{
if (!m_session->isDisableAutoTMMWhenCategoryChanged())
adjustStorageLocation();
else
setAutoTMMEnabled(false);
}
adjustStorageLocation();
}
return true;

View file

@ -2054,6 +2054,19 @@ void Preferences::setAddNewTorrentDialogSavePathHistoryLength(const int value)
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()
{
if (SettingsStorage::instance()->save())

View file

@ -433,6 +433,8 @@ public:
void setAddNewTorrentDialogTopLevel(bool value);
int addNewTorrentDialogSavePathHistoryLength() const;
void setAddNewTorrentDialogSavePathHistoryLength(int value);
bool isAddNewTorrentDialogAttached() const;
void setAddNewTorrentDialogAttached(bool attached);
public slots:
void setStatusFilterState(bool checked);

View file

@ -1,6 +1,6 @@
/*
* 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>
*
* This program is free software; you can redistribute it and/or
@ -384,7 +384,6 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::TorrentDescriptor &to
AddNewTorrentDialog::~AddNewTorrentDialog()
{
saveState();
delete m_ui;
}
@ -398,7 +397,7 @@ void AddNewTorrentDialog::loadState()
if (const QSize dialogSize = m_storeDialogSize; dialogSize.isValid())
resize(dialogSize);
m_ui->splitter->restoreState(m_storeSplitterState);;
m_ui->splitter->restoreState(m_storeSplitterState);
}
void AddNewTorrentDialog::saveState()
@ -834,6 +833,12 @@ void AddNewTorrentDialog::reject()
QDialog::reject();
}
void AddNewTorrentDialog::done(const int result)
{
saveState();
QDialog::done(result);
}
void AddNewTorrentDialog::updateMetadata(const BitTorrent::TorrentInfo &metadata)
{
Q_ASSERT(m_currentContext);

View file

@ -1,6 +1,6 @@
/*
* 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>
*
* 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 torrentRejected(const BitTorrent::TorrentDescriptor &torrentDescriptor);
public slots:
void accept() override;
void reject() override;
void done(int result) override;
private slots:
void updateDiskSpaceLabel();
void onSavePathChanged(const Path &newPath);
@ -77,9 +82,6 @@ private slots:
void categoryChanged(int index);
void contentLayoutChanged();
void accept() override;
void reject() override;
private:
class TorrentContentAdaptor;
struct Context;

View file

@ -99,6 +99,7 @@ namespace
ENABLE_SPEED_WIDGET,
#ifndef Q_OS_MACOS
ENABLE_ICONS_IN_MENUS,
USE_ATTACHED_ADD_NEW_TORRENT_DIALOG,
#endif
// embedded tracker
TRACKER_STATUS,
@ -330,6 +331,7 @@ void AdvancedSettings::saveAdvancedSettings() const
pref->setSpeedWidgetEnabled(m_checkBoxSpeedWidgetEnabled.isChecked());
#ifndef Q_OS_MACOS
pref->setIconsInMenusEnabled(m_checkBoxIconsInMenusEnabled.isChecked());
pref->setAddNewTorrentDialogAttached(m_checkBoxAttachedAddNewTorrentDialog.isChecked());
#endif
// Tracker
@ -856,6 +858,9 @@ void AdvancedSettings::loadAdvancedSettings()
// Enable icons in menus
m_checkBoxIconsInMenusEnabled.setChecked(pref->iconsInMenusEnabled());
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
// Tracker State
m_checkBoxTrackerStatus.setChecked(session->isTrackerEnabled());

View file

@ -108,6 +108,7 @@ private:
#ifndef Q_OS_MACOS
QCheckBox m_checkBoxIconsInMenusEnabled;
QCheckBox m_checkBoxAttachedAddNewTorrentDialog;
#endif
#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);
}
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)
{
// `source`: .torrent file path, magnet URI or URL
@ -225,12 +234,19 @@ bool GUIAddTorrentManager::processTorrent(const QString &source
if (!hasMetadata)
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
// 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).
// Also improves the general convenience of adding multiple torrents.
dlg->setWindowFlags(Qt::Window);
if (!attached)
dlg->setWindowFlags(Qt::Window);
dlg->setAttribute(Qt::WA_DeleteOnClose);
m_dialogs[infoHash] = dlg;

View file

@ -61,6 +61,7 @@ class GUIAddTorrentManager : public GUIApplicationComponent<AddTorrentManager>
public:
GUIAddTorrentManager(IGUIApplication *app, BitTorrent::Session *session, QObject *parent = nullptr);
~GUIAddTorrentManager() override;
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,15 +537,12 @@ void WebApplication::sendFile(const Path &path)
const QDateTime lastModified = Utils::Fs::lastModified(path);
// find translated file in cache
if (!m_isAltUIUsed)
if (const auto it = m_translatedFiles.constFind(path);
(it != m_translatedFiles.constEnd()) && (lastModified <= it->lastModified))
{
if (const auto it = m_translatedFiles.constFind(path);
(it != m_translatedFiles.constEnd()) && (lastModified <= it->lastModified))
{
print(it->data, it->mimeType);
setHeader({Http::HEADER_CACHE_CONTROL, getCachingInterval(it->mimeType)});
return;
}
print(it->data, it->mimeType);
setHeader({Http::HEADER_CACHE_CONTROL, getCachingInterval(it->mimeType)});
return;
}
const auto readResult = Utils::IO::readFile(path, MAX_ALLOWED_FILESIZE);
@ -576,7 +573,7 @@ void WebApplication::sendFile(const Path &path)
QByteArray data = readResult.value();
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)
{

View file

@ -381,8 +381,10 @@ window.addEventListener("DOMContentLoaded", () => {
return false;
let removed = false;
for (const data of categoryMap.values())
removed ||= data.torrents.delete(hash);
for (const data of categoryMap.values()) {
const deleteResult = data.torrents.delete(hash);
removed ||= deleteResult;
}
return removed;
};
@ -418,8 +420,10 @@ window.addEventListener("DOMContentLoaded", () => {
return false;
let removed = false;
for (const torrents of tagMap.values())
removed ||= torrents.delete(hash);
for (const torrents of tagMap.values()) {
const deleteResult = torrents.delete(hash);
removed ||= deleteResult;
}
return removed;
};
@ -477,6 +481,8 @@ window.addEventListener("DOMContentLoaded", () => {
updateFilter("checking", "QBT_TR(Checking (%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]");
if (useAutoHideZeroStatusFilters && document.getElementById(`${selectedStatus}_filter`).classList.contains("invisible"))
setStatusFilter("all");
};
const highlightSelectedStatus = () => {
@ -1509,7 +1515,9 @@ window.addEventListener("DOMContentLoaded", () => {
},
column: "mainColumn",
onResize: window.qBittorrent.Misc.createDebounceHandler(500, (e) => {
saveColumnSizes();
const isHidden = (parseInt(document.getElementById("propertiesPanel").style.height, 10) === 0);
if (!isHidden)
saveColumnSizes();
}),
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();
getCategories();
});

View file

@ -89,7 +89,6 @@ window.qBittorrent.DynamicTable ??= (() => {
this.setupCommonEvents();
this.setupHeaderEvents();
this.setupHeaderMenu();
this.setSortedColumnIcon(this.sortedColumn, null, (this.reverseSort === "1"));
this.setupAltRow();
},
@ -601,19 +600,21 @@ window.qBittorrent.DynamicTable ??= (() => {
updateTableHeaders: function() {
this.updateHeader(this.hiddenTableHeader);
this.updateHeader(this.fixedTableHeader);
this.setSortedColumnIcon(this.sortedColumn, null, (this.reverseSort === "1"));
},
updateHeader: function(header) {
const ths = this.getRowCells(header);
for (let i = 0; i < ths.length; ++i) {
const th = ths[i];
th._this = this;
th.title = this.columns[i].caption;
th.textContent = this.columns[i].caption;
th.style.cssText = `width: ${this.columns[i].width}px; ${this.columns[i].style}`;
th.columnName = this.columns[i].name;
th.classList.add(`column_${th.columnName}`);
th.classList.toggle("invisible", ((this.columns[i].visible === "0") || this.columns[i].force_hide));
if (th.columnName !== this.columns[i].name) {
th.title = this.columns[i].caption;
th.textContent = this.columns[i].caption;
th.style.cssText = `width: ${this.columns[i].width}px; ${this.columns[i].style}`;
th.columnName = this.columns[i].name;
th.className = `column_${th.columnName}`;
th.classList.toggle("invisible", ((this.columns[i].visible === "0") || this.columns[i].force_hide));
}
}
},

View file

@ -138,8 +138,6 @@ window.qBittorrent.PropTrackers ??= (() => {
addTrackerFN();
},
EditTracker: (element, ref) => {
// only allow editing of one row
element.firstElementChild.click();
editTrackerFN(element);
},
RemoveTracker: (element, ref) => {
@ -162,7 +160,11 @@ window.qBittorrent.PropTrackers ??= (() => {
this.hideItem("CopyTrackerUrl");
}
else {
this.showItem("EditTracker");
if (selectedTrackers.length === 1)
this.showItem("EditTracker");
else
this.hideItem("EditTracker");
this.showItem("RemoveTracker");
this.showItem("CopyTrackerUrl");
}