mirror of
https://github.com/qbittorrent/qBittorrent
synced 2025-08-14 02:27:09 -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;
|
||||
|
||||
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)
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 ¶ms, 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;
|
||||
|
|
|
@ -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 ¶ms = {}, AddTorrentOption option = AddTorrentOption::Default);
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue