mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2024-10-22 02:36:15 +03:00
commit
267d504ec0
56 changed files with 717 additions and 397 deletions
9
.github/workflows/ci_windows.yaml
vendored
9
.github/workflows/ci_windows.yaml
vendored
|
@ -90,34 +90,35 @@ jobs:
|
|||
--recurse-submodules `
|
||||
https://github.com/arvidn/libtorrent.git
|
||||
cd libtorrent
|
||||
$env:CXXFLAGS+=" /guard:cf"
|
||||
$env:LDFLAGS+=" /guard:cf"
|
||||
cmake `
|
||||
-B build `
|
||||
-G "Ninja" `
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo `
|
||||
-DCMAKE_CXX_FLAGS=/guard:cf `
|
||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON `
|
||||
-DCMAKE_INSTALL_PREFIX="${{ env.libtorrent_path }}" `
|
||||
-DCMAKE_TOOLCHAIN_FILE="${{ env.RUNVCPKG_VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake" `
|
||||
-DBOOST_ROOT="${{ env.boost_path }}" `
|
||||
-DBUILD_SHARED_LIBS=OFF `
|
||||
-Ddeprecated-functions=OFF `
|
||||
-Dstatic_runtime=ON `
|
||||
-Dstatic_runtime=OFF `
|
||||
-DVCPKG_TARGET_TRIPLET=x64-windows-static-release
|
||||
cmake --build build
|
||||
cmake --install build
|
||||
|
||||
- name: Build qBittorrent
|
||||
run: |
|
||||
$env:CXXFLAGS+=" /WX"
|
||||
cmake `
|
||||
-B build `
|
||||
-G "Ninja" `
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo `
|
||||
-DCMAKE_CXX_FLAGS="/WX" `
|
||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON `
|
||||
-DCMAKE_TOOLCHAIN_FILE="${{ env.RUNVCPKG_VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake" `
|
||||
-DBOOST_ROOT="${{ env.boost_path }}" `
|
||||
-DLibtorrentRasterbar_DIR="${{ env.libtorrent_path }}/lib/cmake/LibtorrentRasterbar" `
|
||||
-DMSVC_RUNTIME_DYNAMIC=OFF `
|
||||
-DMSVC_RUNTIME_DYNAMIC=ON `
|
||||
-DQT6=ON `
|
||||
-DTESTING=ON `
|
||||
-DVCPKG_TARGET_TRIPLET=x64-windows-static-release `
|
||||
|
|
|
@ -96,7 +96,6 @@
|
|||
#include "gui/mainwindow.h"
|
||||
#include "gui/shutdownconfirmdialog.h"
|
||||
#include "gui/uithememanager.h"
|
||||
#include "gui/utils.h"
|
||||
#include "gui/windowstate.h"
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
|
@ -106,6 +105,9 @@
|
|||
|
||||
#ifndef DISABLE_WEBUI
|
||||
#include "webui/webui.h"
|
||||
#ifdef DISABLE_GUI
|
||||
#include "base/utils/password.h"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace
|
||||
|
@ -310,8 +312,8 @@ Application::Application(int &argc, char **argv)
|
|||
if (isFileLoggerEnabled())
|
||||
m_fileLogger = new FileLogger(fileLoggerPath(), isFileLoggerBackup(), fileLoggerMaxSize(), isFileLoggerDeleteOld(), fileLoggerAge(), static_cast<FileLogger::FileLogAgeType>(fileLoggerAgeType()));
|
||||
|
||||
if (m_commandLineArgs.webUiPort > 0) // it will be -1 when user did not set any value
|
||||
Preferences::instance()->setWebUiPort(m_commandLineArgs.webUiPort);
|
||||
if (m_commandLineArgs.webUIPort > 0) // it will be -1 when user did not set any value
|
||||
Preferences::instance()->setWebUIPort(m_commandLineArgs.webUIPort);
|
||||
|
||||
if (m_commandLineArgs.torrentingPort > 0) // it will be -1 when user did not set any value
|
||||
{
|
||||
|
@ -375,7 +377,7 @@ void Application::setMemoryWorkingSetLimit(const int size)
|
|||
return;
|
||||
|
||||
m_storeMemoryWorkingSetLimit = size;
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
#if defined(QBT_USES_LIBTORRENT2) && !defined(Q_OS_MACOS)
|
||||
applyMemoryWorkingSetLimit();
|
||||
#endif
|
||||
}
|
||||
|
@ -773,7 +775,7 @@ int Application::exec()
|
|||
printf("%s\n", qUtf8Printable(loadingStr));
|
||||
#endif
|
||||
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
#if defined(QBT_USES_LIBTORRENT2) && !defined(Q_OS_MACOS)
|
||||
applyMemoryWorkingSetLimit();
|
||||
#endif
|
||||
|
||||
|
@ -899,25 +901,28 @@ int Application::exec()
|
|||
#endif // DISABLE_GUI
|
||||
|
||||
#ifndef DISABLE_WEBUI
|
||||
#ifndef DISABLE_GUI
|
||||
m_webui = new WebUI(this);
|
||||
#ifdef DISABLE_GUI
|
||||
#else
|
||||
const Preferences *pref = Preferences::instance();
|
||||
const QString tempPassword = pref->getWebUIPassword().isEmpty()
|
||||
? Utils::Password::generate() : QString();
|
||||
m_webui = new WebUI(this, (!tempPassword.isEmpty() ? Utils::Password::PBKDF2::generate(tempPassword) : QByteArray()));
|
||||
if (m_webui->isErrored())
|
||||
QCoreApplication::exit(EXIT_FAILURE);
|
||||
connect(m_webui, &WebUI::fatalError, this, []() { QCoreApplication::exit(EXIT_FAILURE); });
|
||||
|
||||
const Preferences *pref = Preferences::instance();
|
||||
|
||||
const auto scheme = pref->isWebUiHttpsEnabled() ? u"https"_s : u"http"_s;
|
||||
const auto url = u"%1://localhost:%2\n"_s.arg(scheme, QString::number(pref->getWebUiPort()));
|
||||
const auto scheme = pref->isWebUIHttpsEnabled() ? u"https"_s : u"http"_s;
|
||||
const auto url = u"%1://localhost:%2\n"_s.arg(scheme, QString::number(pref->getWebUIPort()));
|
||||
const QString mesg = u"\n******** %1 ********\n"_s.arg(tr("Information"))
|
||||
+ tr("To control qBittorrent, access the WebUI at: %1").arg(url);
|
||||
printf("%s\n", qUtf8Printable(mesg));
|
||||
|
||||
if (pref->getWebUIPassword() == QByteArrayLiteral("ARQ77eY1NUZaQsuDHbIMCA==:0WMRkYTUWVT9wVvdDtHAjU9b3b7uB8NR1Gur2hmQCvCDpm39Q+PsJRJPaCU51dEiz+dTzh8qbPsL8WkFljQYFQ=="))
|
||||
if (!tempPassword.isEmpty())
|
||||
{
|
||||
const QString warning = tr("The Web UI administrator username is: %1").arg(pref->getWebUiUsername()) + u'\n'
|
||||
+ tr("The Web UI administrator password has not been changed from the default: %1").arg(u"adminadmin"_s) + u'\n'
|
||||
+ tr("This is a security risk, please change your password in program preferences.") + u'\n';
|
||||
const QString warning = tr("The WebUI administrator username is: %1").arg(pref->getWebUIUsername()) + u'\n'
|
||||
+ tr("The WebUI administrator password was not set. A temporary password is provided for this session: %1").arg(tempPassword) + u'\n'
|
||||
+ tr("You should set your own password in program preferences.") + u'\n';
|
||||
printf("%s", qUtf8Printable(warning));
|
||||
}
|
||||
#endif // DISABLE_GUI
|
||||
|
@ -1080,7 +1085,7 @@ void Application::shutdownCleanup(QSessionManager &manager)
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
#if defined(QBT_USES_LIBTORRENT2) && !defined(Q_OS_MACOS)
|
||||
void Application::applyMemoryWorkingSetLimit() const
|
||||
{
|
||||
const size_t MiB = 1024 * 1024;
|
||||
|
@ -1300,3 +1305,10 @@ void Application::cleanup()
|
|||
Utils::Misc::shutdownComputer(m_shutdownAct);
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef DISABLE_WEBUI
|
||||
WebUI *Application::webUI() const
|
||||
{
|
||||
return m_webui;
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -149,12 +149,16 @@ private slots:
|
|||
#endif
|
||||
|
||||
private:
|
||||
#ifndef DISABLE_WEBUI
|
||||
WebUI *webUI() const override;
|
||||
#endif
|
||||
|
||||
void initializeTranslation();
|
||||
void processParams(const QBtCommandLineParameters ¶ms);
|
||||
void runExternalProgram(const QString &programTemplate, const BitTorrent::Torrent *torrent) const;
|
||||
void sendNotificationEmail(const BitTorrent::Torrent *torrent);
|
||||
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
#if defined(QBT_USES_LIBTORRENT2) && !defined(Q_OS_MACOS)
|
||||
void applyMemoryWorkingSetLimit() const;
|
||||
#endif
|
||||
|
||||
|
|
|
@ -349,7 +349,7 @@ QBtCommandLineParameters::QBtCommandLineParameters(const QProcessEnvironment &en
|
|||
#elif !defined(Q_OS_WIN)
|
||||
, shouldDaemonize(DAEMON_OPTION.value(env))
|
||||
#endif
|
||||
, webUiPort(WEBUI_PORT_OPTION.value(env, -1))
|
||||
, webUIPort(WEBUI_PORT_OPTION.value(env, -1))
|
||||
, torrentingPort(TORRENTING_PORT_OPTION.value(env, -1))
|
||||
, skipDialog(SKIP_DIALOG_OPTION.value(env))
|
||||
, profileDir(PROFILE_OPTION.value(env))
|
||||
|
@ -387,8 +387,8 @@ QBtCommandLineParameters parseCommandLine(const QStringList &args)
|
|||
#endif
|
||||
else if (arg == WEBUI_PORT_OPTION)
|
||||
{
|
||||
result.webUiPort = WEBUI_PORT_OPTION.value(arg);
|
||||
if ((result.webUiPort < 1) || (result.webUiPort > 65535))
|
||||
result.webUIPort = WEBUI_PORT_OPTION.value(arg);
|
||||
if ((result.webUIPort < 1) || (result.webUIPort > 65535))
|
||||
throw CommandLineParameterError(QCoreApplication::translate("CMD Options", "%1 must specify a valid port (1 to 65535).")
|
||||
.arg(u"--webui-port"_s));
|
||||
}
|
||||
|
@ -509,7 +509,7 @@ QString makeUsage(const QString &prgName)
|
|||
#endif
|
||||
+ SHOW_HELP_OPTION.usage() + wrapText(QCoreApplication::translate("CMD Options", "Display this help message and exit")) + u'\n'
|
||||
+ WEBUI_PORT_OPTION.usage(QCoreApplication::translate("CMD Options", "port"))
|
||||
+ wrapText(QCoreApplication::translate("CMD Options", "Change the Web UI port"))
|
||||
+ wrapText(QCoreApplication::translate("CMD Options", "Change the WebUI port"))
|
||||
+ u'\n'
|
||||
+ TORRENTING_PORT_OPTION.usage(QCoreApplication::translate("CMD Options", "port"))
|
||||
+ wrapText(QCoreApplication::translate("CMD Options", "Change the torrenting port"))
|
||||
|
|
|
@ -53,7 +53,7 @@ struct QBtCommandLineParameters
|
|||
#elif !defined(Q_OS_WIN)
|
||||
bool shouldDaemonize = false;
|
||||
#endif
|
||||
int webUiPort = -1;
|
||||
int webUIPort = -1;
|
||||
int torrentingPort = -1;
|
||||
std::optional<bool> skipDialog;
|
||||
Path profileDir;
|
||||
|
|
|
@ -2435,6 +2435,11 @@ bool SessionImpl::cancelDownloadMetadata(const TorrentID &id)
|
|||
return false;
|
||||
|
||||
const lt::torrent_handle nativeHandle = downloadedMetadataIter.value();
|
||||
m_downloadedMetadata.erase(downloadedMetadataIter);
|
||||
|
||||
if (!nativeHandle.is_valid())
|
||||
return true;
|
||||
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
const InfoHash infoHash {nativeHandle.info_hashes()};
|
||||
if (infoHash.isHybrid())
|
||||
|
@ -2445,7 +2450,7 @@ bool SessionImpl::cancelDownloadMetadata(const TorrentID &id)
|
|||
m_downloadedMetadata.remove((altID == downloadedMetadataIter.key()) ? id : altID);
|
||||
}
|
||||
#endif
|
||||
m_downloadedMetadata.erase(downloadedMetadataIter);
|
||||
|
||||
m_nativeSession->remove_torrent(nativeHandle, lt::session::delete_files);
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -1811,6 +1811,7 @@ void TorrentImpl::moveStorage(const Path &newPath, const MoveStorageContext cont
|
|||
{
|
||||
if (!hasMetadata())
|
||||
{
|
||||
m_savePath = newPath;
|
||||
m_session->handleTorrentSavePathChanged(this);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
class QString;
|
||||
|
||||
class Path;
|
||||
class WebUI;
|
||||
struct QBtCommandLineParameters;
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
|
@ -83,4 +84,8 @@ public:
|
|||
virtual MemoryPriority processMemoryPriority() const = 0;
|
||||
virtual void setProcessMemoryPriority(MemoryPriority priority) = 0;
|
||||
#endif
|
||||
|
||||
#ifndef DISABLE_WEBUI
|
||||
virtual WebUI *webUI() const = 0;
|
||||
#endif
|
||||
};
|
||||
|
|
|
@ -628,7 +628,7 @@ void Preferences::setSearchEnabled(const bool enabled)
|
|||
setValue(u"Preferences/Search/SearchEnabled"_s, enabled);
|
||||
}
|
||||
|
||||
bool Preferences::isWebUiEnabled() const
|
||||
bool Preferences::isWebUIEnabled() const
|
||||
{
|
||||
#ifdef DISABLE_GUI
|
||||
const bool defaultValue = true;
|
||||
|
@ -638,41 +638,41 @@ bool Preferences::isWebUiEnabled() const
|
|||
return value(u"Preferences/WebUI/Enabled"_s, defaultValue);
|
||||
}
|
||||
|
||||
void Preferences::setWebUiEnabled(const bool enabled)
|
||||
void Preferences::setWebUIEnabled(const bool enabled)
|
||||
{
|
||||
if (enabled == isWebUiEnabled())
|
||||
if (enabled == isWebUIEnabled())
|
||||
return;
|
||||
|
||||
setValue(u"Preferences/WebUI/Enabled"_s, enabled);
|
||||
}
|
||||
|
||||
bool Preferences::isWebUiLocalAuthEnabled() const
|
||||
bool Preferences::isWebUILocalAuthEnabled() const
|
||||
{
|
||||
return value(u"Preferences/WebUI/LocalHostAuth"_s, true);
|
||||
}
|
||||
|
||||
void Preferences::setWebUiLocalAuthEnabled(const bool enabled)
|
||||
void Preferences::setWebUILocalAuthEnabled(const bool enabled)
|
||||
{
|
||||
if (enabled == isWebUiLocalAuthEnabled())
|
||||
if (enabled == isWebUILocalAuthEnabled())
|
||||
return;
|
||||
|
||||
setValue(u"Preferences/WebUI/LocalHostAuth"_s, enabled);
|
||||
}
|
||||
|
||||
bool Preferences::isWebUiAuthSubnetWhitelistEnabled() const
|
||||
bool Preferences::isWebUIAuthSubnetWhitelistEnabled() const
|
||||
{
|
||||
return value(u"Preferences/WebUI/AuthSubnetWhitelistEnabled"_s, false);
|
||||
}
|
||||
|
||||
void Preferences::setWebUiAuthSubnetWhitelistEnabled(const bool enabled)
|
||||
void Preferences::setWebUIAuthSubnetWhitelistEnabled(const bool enabled)
|
||||
{
|
||||
if (enabled == isWebUiAuthSubnetWhitelistEnabled())
|
||||
if (enabled == isWebUIAuthSubnetWhitelistEnabled())
|
||||
return;
|
||||
|
||||
setValue(u"Preferences/WebUI/AuthSubnetWhitelistEnabled"_s, enabled);
|
||||
}
|
||||
|
||||
QVector<Utils::Net::Subnet> Preferences::getWebUiAuthSubnetWhitelist() const
|
||||
QVector<Utils::Net::Subnet> Preferences::getWebUIAuthSubnetWhitelist() const
|
||||
{
|
||||
const auto subnets = value<QStringList>(u"Preferences/WebUI/AuthSubnetWhitelist"_s);
|
||||
|
||||
|
@ -689,7 +689,7 @@ QVector<Utils::Net::Subnet> Preferences::getWebUiAuthSubnetWhitelist() const
|
|||
return ret;
|
||||
}
|
||||
|
||||
void Preferences::setWebUiAuthSubnetWhitelist(QStringList subnets)
|
||||
void Preferences::setWebUIAuthSubnetWhitelist(QStringList subnets)
|
||||
{
|
||||
Algorithm::removeIf(subnets, [](const QString &subnet)
|
||||
{
|
||||
|
@ -712,27 +712,27 @@ void Preferences::setServerDomains(const QString &str)
|
|||
setValue(u"Preferences/WebUI/ServerDomains"_s, str);
|
||||
}
|
||||
|
||||
QString Preferences::getWebUiAddress() const
|
||||
QString Preferences::getWebUIAddress() const
|
||||
{
|
||||
return value<QString>(u"Preferences/WebUI/Address"_s, u"*"_s).trimmed();
|
||||
}
|
||||
|
||||
void Preferences::setWebUiAddress(const QString &addr)
|
||||
void Preferences::setWebUIAddress(const QString &addr)
|
||||
{
|
||||
if (addr == getWebUiAddress())
|
||||
if (addr == getWebUIAddress())
|
||||
return;
|
||||
|
||||
setValue(u"Preferences/WebUI/Address"_s, addr.trimmed());
|
||||
}
|
||||
|
||||
quint16 Preferences::getWebUiPort() const
|
||||
quint16 Preferences::getWebUIPort() const
|
||||
{
|
||||
return value<quint16>(u"Preferences/WebUI/Port"_s, 8080);
|
||||
}
|
||||
|
||||
void Preferences::setWebUiPort(const quint16 port)
|
||||
void Preferences::setWebUIPort(const quint16 port)
|
||||
{
|
||||
if (port == getWebUiPort())
|
||||
if (port == getWebUIPort())
|
||||
return;
|
||||
|
||||
// cast to `int` type so it will show human readable unit in configuration file
|
||||
|
@ -752,14 +752,14 @@ void Preferences::setUPnPForWebUIPort(const bool enabled)
|
|||
setValue(u"Preferences/WebUI/UseUPnP"_s, enabled);
|
||||
}
|
||||
|
||||
QString Preferences::getWebUiUsername() const
|
||||
QString Preferences::getWebUIUsername() const
|
||||
{
|
||||
return value<QString>(u"Preferences/WebUI/Username"_s, u"admin"_s);
|
||||
}
|
||||
|
||||
void Preferences::setWebUiUsername(const QString &username)
|
||||
void Preferences::setWebUIUsername(const QString &username)
|
||||
{
|
||||
if (username == getWebUiUsername())
|
||||
if (username == getWebUIUsername())
|
||||
return;
|
||||
|
||||
setValue(u"Preferences/WebUI/Username"_s, username);
|
||||
|
@ -767,9 +767,7 @@ void Preferences::setWebUiUsername(const QString &username)
|
|||
|
||||
QByteArray Preferences::getWebUIPassword() const
|
||||
{
|
||||
// default: adminadmin
|
||||
const auto defaultValue = QByteArrayLiteral("ARQ77eY1NUZaQsuDHbIMCA==:0WMRkYTUWVT9wVvdDtHAjU9b3b7uB8NR1Gur2hmQCvCDpm39Q+PsJRJPaCU51dEiz+dTzh8qbPsL8WkFljQYFQ==");
|
||||
return value(u"Preferences/WebUI/Password_PBKDF2"_s, defaultValue);
|
||||
return value<QByteArray>(u"Preferences/WebUI/Password_PBKDF2"_s);
|
||||
}
|
||||
|
||||
void Preferences::setWebUIPassword(const QByteArray &password)
|
||||
|
@ -832,40 +830,40 @@ void Preferences::setWebAPISessionCookieName(const QString &cookieName)
|
|||
setValue(u"WebAPI/SessionCookieName"_s, cookieName);
|
||||
}
|
||||
|
||||
bool Preferences::isWebUiClickjackingProtectionEnabled() const
|
||||
bool Preferences::isWebUIClickjackingProtectionEnabled() const
|
||||
{
|
||||
return value(u"Preferences/WebUI/ClickjackingProtection"_s, true);
|
||||
}
|
||||
|
||||
void Preferences::setWebUiClickjackingProtectionEnabled(const bool enabled)
|
||||
void Preferences::setWebUIClickjackingProtectionEnabled(const bool enabled)
|
||||
{
|
||||
if (enabled == isWebUiClickjackingProtectionEnabled())
|
||||
if (enabled == isWebUIClickjackingProtectionEnabled())
|
||||
return;
|
||||
|
||||
setValue(u"Preferences/WebUI/ClickjackingProtection"_s, enabled);
|
||||
}
|
||||
|
||||
bool Preferences::isWebUiCSRFProtectionEnabled() const
|
||||
bool Preferences::isWebUICSRFProtectionEnabled() const
|
||||
{
|
||||
return value(u"Preferences/WebUI/CSRFProtection"_s, true);
|
||||
}
|
||||
|
||||
void Preferences::setWebUiCSRFProtectionEnabled(const bool enabled)
|
||||
void Preferences::setWebUICSRFProtectionEnabled(const bool enabled)
|
||||
{
|
||||
if (enabled == isWebUiCSRFProtectionEnabled())
|
||||
if (enabled == isWebUICSRFProtectionEnabled())
|
||||
return;
|
||||
|
||||
setValue(u"Preferences/WebUI/CSRFProtection"_s, enabled);
|
||||
}
|
||||
|
||||
bool Preferences::isWebUiSecureCookieEnabled() const
|
||||
bool Preferences::isWebUISecureCookieEnabled() const
|
||||
{
|
||||
return value(u"Preferences/WebUI/SecureCookie"_s, true);
|
||||
}
|
||||
|
||||
void Preferences::setWebUiSecureCookieEnabled(const bool enabled)
|
||||
void Preferences::setWebUISecureCookieEnabled(const bool enabled)
|
||||
{
|
||||
if (enabled == isWebUiSecureCookieEnabled())
|
||||
if (enabled == isWebUISecureCookieEnabled())
|
||||
return;
|
||||
|
||||
setValue(u"Preferences/WebUI/SecureCookie"_s, enabled);
|
||||
|
@ -884,14 +882,14 @@ void Preferences::setWebUIHostHeaderValidationEnabled(const bool enabled)
|
|||
setValue(u"Preferences/WebUI/HostHeaderValidation"_s, enabled);
|
||||
}
|
||||
|
||||
bool Preferences::isWebUiHttpsEnabled() const
|
||||
bool Preferences::isWebUIHttpsEnabled() const
|
||||
{
|
||||
return value(u"Preferences/WebUI/HTTPS/Enabled"_s, false);
|
||||
}
|
||||
|
||||
void Preferences::setWebUiHttpsEnabled(const bool enabled)
|
||||
void Preferences::setWebUIHttpsEnabled(const bool enabled)
|
||||
{
|
||||
if (enabled == isWebUiHttpsEnabled())
|
||||
if (enabled == isWebUIHttpsEnabled())
|
||||
return;
|
||||
|
||||
setValue(u"Preferences/WebUI/HTTPS/Enabled"_s, enabled);
|
||||
|
@ -923,27 +921,27 @@ void Preferences::setWebUIHttpsKeyPath(const Path &path)
|
|||
setValue(u"Preferences/WebUI/HTTPS/KeyPath"_s, path);
|
||||
}
|
||||
|
||||
bool Preferences::isAltWebUiEnabled() const
|
||||
bool Preferences::isAltWebUIEnabled() const
|
||||
{
|
||||
return value(u"Preferences/WebUI/AlternativeUIEnabled"_s, false);
|
||||
}
|
||||
|
||||
void Preferences::setAltWebUiEnabled(const bool enabled)
|
||||
void Preferences::setAltWebUIEnabled(const bool enabled)
|
||||
{
|
||||
if (enabled == isAltWebUiEnabled())
|
||||
if (enabled == isAltWebUIEnabled())
|
||||
return;
|
||||
|
||||
setValue(u"Preferences/WebUI/AlternativeUIEnabled"_s, enabled);
|
||||
}
|
||||
|
||||
Path Preferences::getWebUiRootFolder() const
|
||||
Path Preferences::getWebUIRootFolder() const
|
||||
{
|
||||
return value<Path>(u"Preferences/WebUI/RootFolder"_s);
|
||||
}
|
||||
|
||||
void Preferences::setWebUiRootFolder(const Path &path)
|
||||
void Preferences::setWebUIRootFolder(const Path &path)
|
||||
{
|
||||
if (path == getWebUiRootFolder())
|
||||
if (path == getWebUIRootFolder())
|
||||
return;
|
||||
|
||||
setValue(u"Preferences/WebUI/RootFolder"_s, path);
|
||||
|
|
|
@ -169,26 +169,26 @@ public:
|
|||
void setSearchEnabled(bool enabled);
|
||||
|
||||
// HTTP Server
|
||||
bool isWebUiEnabled() const;
|
||||
void setWebUiEnabled(bool enabled);
|
||||
bool isWebUIEnabled() const;
|
||||
void setWebUIEnabled(bool enabled);
|
||||
QString getServerDomains() const;
|
||||
void setServerDomains(const QString &str);
|
||||
QString getWebUiAddress() const;
|
||||
void setWebUiAddress(const QString &addr);
|
||||
quint16 getWebUiPort() const;
|
||||
void setWebUiPort(quint16 port);
|
||||
QString getWebUIAddress() const;
|
||||
void setWebUIAddress(const QString &addr);
|
||||
quint16 getWebUIPort() const;
|
||||
void setWebUIPort(quint16 port);
|
||||
bool useUPnPForWebUIPort() const;
|
||||
void setUPnPForWebUIPort(bool enabled);
|
||||
|
||||
// Authentication
|
||||
bool isWebUiLocalAuthEnabled() const;
|
||||
void setWebUiLocalAuthEnabled(bool enabled);
|
||||
bool isWebUiAuthSubnetWhitelistEnabled() const;
|
||||
void setWebUiAuthSubnetWhitelistEnabled(bool enabled);
|
||||
QVector<Utils::Net::Subnet> getWebUiAuthSubnetWhitelist() const;
|
||||
void setWebUiAuthSubnetWhitelist(QStringList subnets);
|
||||
QString getWebUiUsername() const;
|
||||
void setWebUiUsername(const QString &username);
|
||||
bool isWebUILocalAuthEnabled() const;
|
||||
void setWebUILocalAuthEnabled(bool enabled);
|
||||
bool isWebUIAuthSubnetWhitelistEnabled() const;
|
||||
void setWebUIAuthSubnetWhitelistEnabled(bool enabled);
|
||||
QVector<Utils::Net::Subnet> getWebUIAuthSubnetWhitelist() const;
|
||||
void setWebUIAuthSubnetWhitelist(QStringList subnets);
|
||||
QString getWebUIUsername() const;
|
||||
void setWebUIUsername(const QString &username);
|
||||
QByteArray getWebUIPassword() const;
|
||||
void setWebUIPassword(const QByteArray &password);
|
||||
int getWebUIMaxAuthFailCount() const;
|
||||
|
@ -201,26 +201,26 @@ public:
|
|||
void setWebAPISessionCookieName(const QString &cookieName);
|
||||
|
||||
// WebUI security
|
||||
bool isWebUiClickjackingProtectionEnabled() const;
|
||||
void setWebUiClickjackingProtectionEnabled(bool enabled);
|
||||
bool isWebUiCSRFProtectionEnabled() const;
|
||||
void setWebUiCSRFProtectionEnabled(bool enabled);
|
||||
bool isWebUiSecureCookieEnabled () const;
|
||||
void setWebUiSecureCookieEnabled(bool enabled);
|
||||
bool isWebUIClickjackingProtectionEnabled() const;
|
||||
void setWebUIClickjackingProtectionEnabled(bool enabled);
|
||||
bool isWebUICSRFProtectionEnabled() const;
|
||||
void setWebUICSRFProtectionEnabled(bool enabled);
|
||||
bool isWebUISecureCookieEnabled () const;
|
||||
void setWebUISecureCookieEnabled(bool enabled);
|
||||
bool isWebUIHostHeaderValidationEnabled() const;
|
||||
void setWebUIHostHeaderValidationEnabled(bool enabled);
|
||||
|
||||
// HTTPS
|
||||
bool isWebUiHttpsEnabled() const;
|
||||
void setWebUiHttpsEnabled(bool enabled);
|
||||
bool isWebUIHttpsEnabled() const;
|
||||
void setWebUIHttpsEnabled(bool enabled);
|
||||
Path getWebUIHttpsCertificatePath() const;
|
||||
void setWebUIHttpsCertificatePath(const Path &path);
|
||||
Path getWebUIHttpsKeyPath() const;
|
||||
void setWebUIHttpsKeyPath(const Path &path);
|
||||
bool isAltWebUiEnabled() const;
|
||||
void setAltWebUiEnabled(bool enabled);
|
||||
Path getWebUiRootFolder() const;
|
||||
void setWebUiRootFolder(const Path &path);
|
||||
bool isAltWebUIEnabled() const;
|
||||
void setAltWebUIEnabled(bool enabled);
|
||||
Path getWebUIRootFolder() const;
|
||||
void setWebUIRootFolder(const Path &path);
|
||||
|
||||
// WebUI custom HTTP headers
|
||||
bool isWebUICustomHTTPHeadersEnabled() const;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2018 Mike Tzou (Chocobo1)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
|
@ -36,6 +37,7 @@
|
|||
#include <QString>
|
||||
#include <QVector>
|
||||
|
||||
#include "base/global.h"
|
||||
#include "bytearray.h"
|
||||
#include "random.h"
|
||||
|
||||
|
@ -65,6 +67,21 @@ bool Utils::Password::slowEquals(const QByteArray &a, const QByteArray &b)
|
|||
return (diff == 0);
|
||||
}
|
||||
|
||||
QString Utils::Password::generate()
|
||||
{
|
||||
const QString alphanum = u"23456789ABCDEFGHIJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz"_s;
|
||||
const int passwordLength = 9;
|
||||
QString pass;
|
||||
pass.reserve(passwordLength);
|
||||
while (pass.length() < passwordLength)
|
||||
{
|
||||
const auto num = Utils::Random::rand(0, (alphanum.size() - 1));
|
||||
pass.append(alphanum[num]);
|
||||
}
|
||||
|
||||
return pass;
|
||||
}
|
||||
|
||||
QByteArray Utils::Password::PBKDF2::generate(const QString &password)
|
||||
{
|
||||
return generate(password.toUtf8());
|
||||
|
@ -72,9 +89,8 @@ QByteArray Utils::Password::PBKDF2::generate(const QString &password)
|
|||
|
||||
QByteArray Utils::Password::PBKDF2::generate(const QByteArray &password)
|
||||
{
|
||||
const std::array<uint32_t, 4> salt
|
||||
{{Random::rand(), Random::rand()
|
||||
, Random::rand(), Random::rand()}};
|
||||
const std::array<uint32_t, 4> salt {
|
||||
{Random::rand(), Random::rand(), Random::rand(), Random::rand()}};
|
||||
|
||||
std::array<unsigned char, 64> outBuf {};
|
||||
const int hmacResult = PKCS5_PBKDF2_HMAC(password.constData(), password.size()
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2018 Mike Tzou (Chocobo1)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
|
@ -37,6 +38,8 @@ namespace Utils::Password
|
|||
// Taken from https://crackstation.net/hashing-security.htm
|
||||
bool slowEquals(const QByteArray &a, const QByteArray &b);
|
||||
|
||||
QString generate();
|
||||
|
||||
namespace PBKDF2
|
||||
{
|
||||
QByteArray generate(const QString &password);
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include "addnewtorrentdialog.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
|
||||
#include <QAction>
|
||||
#include <QDateTime>
|
||||
|
@ -141,10 +142,11 @@ class AddNewTorrentDialog::TorrentContentAdaptor final
|
|||
{
|
||||
public:
|
||||
TorrentContentAdaptor(BitTorrent::TorrentInfo &torrentInfo, PathList &filePaths
|
||||
, QVector<BitTorrent::DownloadPriority> &filePriorities)
|
||||
, QVector<BitTorrent::DownloadPriority> &filePriorities, std::function<void ()> onFilePrioritiesChanged)
|
||||
: m_torrentInfo {torrentInfo}
|
||||
, m_filePaths {filePaths}
|
||||
, m_filePriorities {filePriorities}
|
||||
, m_onFilePrioritiesChanged {std::move(onFilePrioritiesChanged)}
|
||||
{
|
||||
Q_ASSERT(filePaths.isEmpty() || (filePaths.size() == torrentInfo.filesCount()));
|
||||
|
||||
|
@ -255,6 +257,8 @@ public:
|
|||
{
|
||||
Q_ASSERT(priorities.size() == filesCount());
|
||||
m_filePriorities = priorities;
|
||||
if (m_onFilePrioritiesChanged)
|
||||
m_onFilePrioritiesChanged();
|
||||
}
|
||||
|
||||
Path actualStorageLocation() const override
|
||||
|
@ -275,6 +279,7 @@ private:
|
|||
BitTorrent::TorrentInfo &m_torrentInfo;
|
||||
PathList &m_filePaths;
|
||||
QVector<BitTorrent::DownloadPriority> &m_filePriorities;
|
||||
std::function<void ()> m_onFilePrioritiesChanged;
|
||||
Path m_originalRootFolder;
|
||||
BitTorrent::TorrentContentLayout m_currentContentLayout;
|
||||
};
|
||||
|
@ -767,7 +772,7 @@ void AddNewTorrentDialog::contentLayoutChanged()
|
|||
|
||||
const auto contentLayout = static_cast<BitTorrent::TorrentContentLayout>(m_ui->contentLayoutComboBox->currentIndex());
|
||||
m_contentAdaptor->applyContentLayout(contentLayout);
|
||||
m_ui->contentTreeView->setContentHandler(m_contentAdaptor); // to cause reloading
|
||||
m_ui->contentTreeView->setContentHandler(m_contentAdaptor.get()); // to cause reloading
|
||||
}
|
||||
|
||||
void AddNewTorrentDialog::saveTorrentFile()
|
||||
|
@ -992,7 +997,8 @@ void AddNewTorrentDialog::setupTreeview()
|
|||
if (m_torrentParams.filePaths.isEmpty())
|
||||
m_torrentParams.filePaths = m_torrentInfo.filePaths();
|
||||
|
||||
m_contentAdaptor = new TorrentContentAdaptor(m_torrentInfo, m_torrentParams.filePaths, m_torrentParams.filePriorities);
|
||||
m_contentAdaptor = std::make_unique<TorrentContentAdaptor>(m_torrentInfo, m_torrentParams.filePaths
|
||||
, m_torrentParams.filePriorities, [this] { updateDiskSpaceLabel(); });
|
||||
|
||||
const auto contentLayout = static_cast<BitTorrent::TorrentContentLayout>(m_ui->contentLayoutComboBox->currentIndex());
|
||||
m_contentAdaptor->applyContentLayout(contentLayout);
|
||||
|
@ -1013,7 +1019,7 @@ void AddNewTorrentDialog::setupTreeview()
|
|||
m_contentAdaptor->prioritizeFiles(priorities);
|
||||
}
|
||||
|
||||
m_ui->contentTreeView->setContentHandler(m_contentAdaptor);
|
||||
m_ui->contentTreeView->setContentHandler(m_contentAdaptor.get());
|
||||
|
||||
m_filterLine->blockSignals(false);
|
||||
|
||||
|
|
|
@ -116,7 +116,7 @@ private:
|
|||
void showEvent(QShowEvent *event) override;
|
||||
|
||||
Ui::AddNewTorrentDialog *m_ui = nullptr;
|
||||
TorrentContentAdaptor *m_contentAdaptor = nullptr;
|
||||
std::unique_ptr<TorrentContentAdaptor> m_contentAdaptor;
|
||||
BitTorrent::MagnetUri m_magnetURI;
|
||||
BitTorrent::TorrentInfo m_torrentInfo;
|
||||
int m_savePathIndex = -1;
|
||||
|
|
|
@ -63,7 +63,7 @@ namespace
|
|||
// qBittorrent section
|
||||
QBITTORRENT_HEADER,
|
||||
RESUME_DATA_STORAGE,
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
#if defined(QBT_USES_LIBTORRENT2) && !defined(Q_OS_MACOS)
|
||||
MEMORY_WORKING_SET_LIMIT,
|
||||
#endif
|
||||
#if defined(Q_OS_WIN)
|
||||
|
@ -195,7 +195,7 @@ void AdvancedSettings::saveAdvancedSettings() const
|
|||
BitTorrent::Session *const session = BitTorrent::Session::instance();
|
||||
|
||||
session->setResumeDataStorageType(m_comboBoxResumeDataStorage.currentData().value<BitTorrent::ResumeDataStorageType>());
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
#if defined(QBT_USES_LIBTORRENT2) && !defined(Q_OS_MACOS)
|
||||
// Physical memory (RAM) usage limit
|
||||
app()->setMemoryWorkingSetLimit(m_spinBoxMemoryWorkingSetLimit.value());
|
||||
#endif
|
||||
|
@ -451,7 +451,7 @@ void AdvancedSettings::loadAdvancedSettings()
|
|||
m_comboBoxResumeDataStorage.setCurrentIndex(m_comboBoxResumeDataStorage.findData(QVariant::fromValue(session->resumeDataStorageType())));
|
||||
addRow(RESUME_DATA_STORAGE, tr("Resume data storage type (requires restart)"), &m_comboBoxResumeDataStorage);
|
||||
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
#if defined(QBT_USES_LIBTORRENT2) && !defined(Q_OS_MACOS)
|
||||
// Physical memory (RAM) usage limit
|
||||
m_spinBoxMemoryWorkingSetLimit.setMinimum(1);
|
||||
m_spinBoxMemoryWorkingSetLimit.setMaximum(std::numeric_limits<int>::max());
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
|
||||
#include <libtorrent/config.hpp>
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QLineEdit>
|
||||
|
@ -88,7 +89,11 @@ private:
|
|||
QCheckBox m_checkBoxCoalesceRW;
|
||||
#else
|
||||
QComboBox m_comboBoxDiskIOType;
|
||||
QSpinBox m_spinBoxMemoryWorkingSetLimit, m_spinBoxHashingThreads;
|
||||
QSpinBox m_spinBoxHashingThreads;
|
||||
#endif
|
||||
|
||||
#if defined(QBT_USES_LIBTORRENT2) && !defined(Q_OS_MACOS)
|
||||
QSpinBox m_spinBoxMemoryWorkingSetLimit;
|
||||
#endif
|
||||
|
||||
#if defined(QBT_USES_LIBTORRENT2) && TORRENT_USE_I2P
|
||||
|
|
|
@ -50,7 +50,7 @@ IPSubnetWhitelistOptionsDialog::IPSubnetWhitelistOptionsDialog(QWidget *parent)
|
|||
connect(m_ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
|
||||
QStringList authSubnetWhitelistStringList;
|
||||
for (const Utils::Net::Subnet &subnet : asConst(Preferences::instance()->getWebUiAuthSubnetWhitelist()))
|
||||
for (const Utils::Net::Subnet &subnet : asConst(Preferences::instance()->getWebUIAuthSubnetWhitelist()))
|
||||
authSubnetWhitelistStringList << Utils::Net::subnetToString(subnet);
|
||||
m_model = new QStringListModel(authSubnetWhitelistStringList, this);
|
||||
|
||||
|
@ -81,7 +81,7 @@ void IPSubnetWhitelistOptionsDialog::on_buttonBox_accepted()
|
|||
// Operate on the m_sortFilter to grab the strings in sorted order
|
||||
for (int i = 0; i < m_sortFilter->rowCount(); ++i)
|
||||
subnets.append(m_sortFilter->index(i, 0).data().toString());
|
||||
Preferences::instance()->setWebUiAuthSubnetWhitelist(subnets);
|
||||
Preferences::instance()->setWebUIAuthSubnetWhitelist(subnets);
|
||||
QDialog::accept();
|
||||
}
|
||||
else
|
||||
|
|
|
@ -1182,7 +1182,7 @@ void MainWindow::closeEvent(QCloseEvent *e)
|
|||
if (!isVisible())
|
||||
show();
|
||||
QMessageBox confirmBox(QMessageBox::Question, tr("Exiting qBittorrent"),
|
||||
// Split it because the last sentence is used in the Web UI
|
||||
// Split it because the last sentence is used in the WebUI
|
||||
tr("Some files are currently transferring.") + u'\n' + tr("Are you sure you want to quit qBittorrent?"),
|
||||
QMessageBox::NoButton, this);
|
||||
QPushButton *noBtn = confirmBox.addButton(tr("&No"), QMessageBox::NoRole);
|
||||
|
|
|
@ -69,6 +69,7 @@
|
|||
#include "utils.h"
|
||||
#include "watchedfolderoptionsdialog.h"
|
||||
#include "watchedfoldersmodel.h"
|
||||
#include "webui/webui.h"
|
||||
|
||||
#ifndef DISABLE_WEBUI
|
||||
#include "base/net/dnsupdater.h"
|
||||
|
@ -80,6 +81,9 @@
|
|||
|
||||
#define SETTINGS_KEY(name) u"OptionsDialog/" name
|
||||
|
||||
const int WEBUI_MIN_USERNAME_LENGTH = 3;
|
||||
const int WEBUI_MIN_PASSWORD_LENGTH = 6;
|
||||
|
||||
namespace
|
||||
{
|
||||
QStringList translatedWeekdayNames()
|
||||
|
@ -106,6 +110,16 @@ namespace
|
|||
}
|
||||
};
|
||||
|
||||
bool isValidWebUIUsername(const QString &username)
|
||||
{
|
||||
return (username.length() >= WEBUI_MIN_USERNAME_LENGTH);
|
||||
}
|
||||
|
||||
bool isValidWebUIPassword(const QString &password)
|
||||
{
|
||||
return (password.length() >= WEBUI_MIN_PASSWORD_LENGTH);
|
||||
}
|
||||
|
||||
// Shortcuts for frequently used signals that have more than one overload. They would require
|
||||
// type casts and that is why we declare required member pointer here instead.
|
||||
void (QComboBox::*qComboBoxCurrentIndexChanged)(int) = &QComboBox::currentIndexChanged;
|
||||
|
@ -175,7 +189,11 @@ OptionsDialog::OptionsDialog(IGUIApplication *app, QWidget *parent)
|
|||
|
||||
// setup apply button
|
||||
m_applyButton->setEnabled(false);
|
||||
connect(m_applyButton, &QPushButton::clicked, this, &OptionsDialog::applySettings);
|
||||
connect(m_applyButton, &QPushButton::clicked, this, [this]
|
||||
{
|
||||
if (applySettings())
|
||||
m_applyButton->setEnabled(false);
|
||||
});
|
||||
|
||||
// disable mouse wheel event on widgets to avoid misselection
|
||||
auto *wheelEventEater = new WheelEventEater(this);
|
||||
|
@ -1211,28 +1229,33 @@ void OptionsDialog::loadWebUITabOptions()
|
|||
m_ui->textWebUIRootFolder->setMode(FileSystemPathEdit::Mode::DirectoryOpen);
|
||||
m_ui->textWebUIRootFolder->setDialogCaption(tr("Choose Alternative UI files location"));
|
||||
|
||||
m_ui->checkWebUi->setChecked(pref->isWebUiEnabled());
|
||||
m_ui->textWebUiAddress->setText(pref->getWebUiAddress());
|
||||
m_ui->spinWebUiPort->setValue(pref->getWebUiPort());
|
||||
if (app()->webUI()->isErrored())
|
||||
m_ui->labelWebUIError->setText(tr("WebUI configuration failed. Reason: %1").arg(app()->webUI()->errorMessage()));
|
||||
else
|
||||
m_ui->labelWebUIError->hide();
|
||||
|
||||
m_ui->checkWebUI->setChecked(pref->isWebUIEnabled());
|
||||
m_ui->textWebUIAddress->setText(pref->getWebUIAddress());
|
||||
m_ui->spinWebUIPort->setValue(pref->getWebUIPort());
|
||||
m_ui->checkWebUIUPnP->setChecked(pref->useUPnPForWebUIPort());
|
||||
m_ui->checkWebUiHttps->setChecked(pref->isWebUiHttpsEnabled());
|
||||
m_ui->checkWebUIHttps->setChecked(pref->isWebUIHttpsEnabled());
|
||||
webUIHttpsCertChanged(pref->getWebUIHttpsCertificatePath());
|
||||
webUIHttpsKeyChanged(pref->getWebUIHttpsKeyPath());
|
||||
m_ui->textWebUiUsername->setText(pref->getWebUiUsername());
|
||||
m_ui->checkBypassLocalAuth->setChecked(!pref->isWebUiLocalAuthEnabled());
|
||||
m_ui->checkBypassAuthSubnetWhitelist->setChecked(pref->isWebUiAuthSubnetWhitelistEnabled());
|
||||
m_ui->textWebUIUsername->setText(pref->getWebUIUsername());
|
||||
m_ui->checkBypassLocalAuth->setChecked(!pref->isWebUILocalAuthEnabled());
|
||||
m_ui->checkBypassAuthSubnetWhitelist->setChecked(pref->isWebUIAuthSubnetWhitelistEnabled());
|
||||
m_ui->IPSubnetWhitelistButton->setEnabled(m_ui->checkBypassAuthSubnetWhitelist->isChecked());
|
||||
m_ui->spinBanCounter->setValue(pref->getWebUIMaxAuthFailCount());
|
||||
m_ui->spinBanDuration->setValue(pref->getWebUIBanDuration().count());
|
||||
m_ui->spinSessionTimeout->setValue(pref->getWebUISessionTimeout());
|
||||
// Alternative UI
|
||||
m_ui->groupAltWebUI->setChecked(pref->isAltWebUiEnabled());
|
||||
m_ui->textWebUIRootFolder->setSelectedPath(pref->getWebUiRootFolder());
|
||||
m_ui->groupAltWebUI->setChecked(pref->isAltWebUIEnabled());
|
||||
m_ui->textWebUIRootFolder->setSelectedPath(pref->getWebUIRootFolder());
|
||||
// Security
|
||||
m_ui->checkClickjacking->setChecked(pref->isWebUiClickjackingProtectionEnabled());
|
||||
m_ui->checkCSRFProtection->setChecked(pref->isWebUiCSRFProtectionEnabled());
|
||||
m_ui->checkSecureCookie->setEnabled(pref->isWebUiHttpsEnabled());
|
||||
m_ui->checkSecureCookie->setChecked(pref->isWebUiSecureCookieEnabled());
|
||||
m_ui->checkClickjacking->setChecked(pref->isWebUIClickjackingProtectionEnabled());
|
||||
m_ui->checkCSRFProtection->setChecked(pref->isWebUICSRFProtectionEnabled());
|
||||
m_ui->checkSecureCookie->setEnabled(pref->isWebUIHttpsEnabled());
|
||||
m_ui->checkSecureCookie->setChecked(pref->isWebUISecureCookieEnabled());
|
||||
m_ui->groupHostHeaderValidation->setChecked(pref->isWebUIHostHeaderValidationEnabled());
|
||||
m_ui->textServerDomains->setText(pref->getServerDomains());
|
||||
// Custom HTTP headers
|
||||
|
@ -1248,18 +1271,18 @@ void OptionsDialog::loadWebUITabOptions()
|
|||
m_ui->DNSUsernameTxt->setText(pref->getDynDNSUsername());
|
||||
m_ui->DNSPasswordTxt->setText(pref->getDynDNSPassword());
|
||||
|
||||
connect(m_ui->checkWebUi, &QGroupBox::toggled, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->textWebUiAddress, &QLineEdit::textChanged, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->spinWebUiPort, qSpinBoxValueChanged, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->checkWebUI, &QGroupBox::toggled, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->textWebUIAddress, &QLineEdit::textChanged, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->spinWebUIPort, qSpinBoxValueChanged, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->checkWebUIUPnP, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->checkWebUiHttps, &QGroupBox::toggled, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->checkWebUIHttps, &QGroupBox::toggled, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->textWebUIHttpsCert, &FileSystemPathLineEdit::selectedPathChanged, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->textWebUIHttpsCert, &FileSystemPathLineEdit::selectedPathChanged, this, &OptionsDialog::webUIHttpsCertChanged);
|
||||
connect(m_ui->textWebUIHttpsKey, &FileSystemPathLineEdit::selectedPathChanged, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->textWebUIHttpsKey, &FileSystemPathLineEdit::selectedPathChanged, this, &OptionsDialog::webUIHttpsKeyChanged);
|
||||
|
||||
connect(m_ui->textWebUiUsername, &QLineEdit::textChanged, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->textWebUiPassword, &QLineEdit::textChanged, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->textWebUIUsername, &QLineEdit::textChanged, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->textWebUIPassword, &QLineEdit::textChanged, this, &ThisType::enableApplyButton);
|
||||
|
||||
connect(m_ui->checkBypassLocalAuth, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->checkBypassAuthSubnetWhitelist, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
|
||||
|
@ -1273,7 +1296,7 @@ void OptionsDialog::loadWebUITabOptions()
|
|||
|
||||
connect(m_ui->checkClickjacking, &QCheckBox::toggled, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->checkCSRFProtection, &QCheckBox::toggled, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->checkWebUiHttps, &QGroupBox::toggled, m_ui->checkSecureCookie, &QWidget::setEnabled);
|
||||
connect(m_ui->checkWebUIHttps, &QGroupBox::toggled, m_ui->checkSecureCookie, &QWidget::setEnabled);
|
||||
connect(m_ui->checkSecureCookie, &QCheckBox::toggled, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->groupHostHeaderValidation, &QGroupBox::toggled, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->textServerDomains, &QLineEdit::textChanged, this, &ThisType::enableApplyButton);
|
||||
|
@ -1295,29 +1318,32 @@ void OptionsDialog::saveWebUITabOptions() const
|
|||
{
|
||||
auto *pref = Preferences::instance();
|
||||
|
||||
pref->setWebUiEnabled(isWebUiEnabled());
|
||||
pref->setWebUiAddress(m_ui->textWebUiAddress->text());
|
||||
pref->setWebUiPort(m_ui->spinWebUiPort->value());
|
||||
const bool webUIEnabled = isWebUIEnabled();
|
||||
|
||||
pref->setWebUIEnabled(webUIEnabled);
|
||||
pref->setWebUIAddress(m_ui->textWebUIAddress->text());
|
||||
pref->setWebUIPort(m_ui->spinWebUIPort->value());
|
||||
pref->setUPnPForWebUIPort(m_ui->checkWebUIUPnP->isChecked());
|
||||
pref->setWebUiHttpsEnabled(m_ui->checkWebUiHttps->isChecked());
|
||||
pref->setWebUIHttpsEnabled(m_ui->checkWebUIHttps->isChecked());
|
||||
pref->setWebUIHttpsCertificatePath(m_ui->textWebUIHttpsCert->selectedPath());
|
||||
pref->setWebUIHttpsKeyPath(m_ui->textWebUIHttpsKey->selectedPath());
|
||||
pref->setWebUIMaxAuthFailCount(m_ui->spinBanCounter->value());
|
||||
pref->setWebUIBanDuration(std::chrono::seconds {m_ui->spinBanDuration->value()});
|
||||
pref->setWebUISessionTimeout(m_ui->spinSessionTimeout->value());
|
||||
// Authentication
|
||||
pref->setWebUiUsername(webUiUsername());
|
||||
if (!webUiPassword().isEmpty())
|
||||
pref->setWebUIPassword(Utils::Password::PBKDF2::generate(webUiPassword()));
|
||||
pref->setWebUiLocalAuthEnabled(!m_ui->checkBypassLocalAuth->isChecked());
|
||||
pref->setWebUiAuthSubnetWhitelistEnabled(m_ui->checkBypassAuthSubnetWhitelist->isChecked());
|
||||
if (const QString username = webUIUsername(); isValidWebUIUsername(username))
|
||||
pref->setWebUIUsername(username);
|
||||
if (const QString password = webUIPassword(); isValidWebUIPassword(password))
|
||||
pref->setWebUIPassword(Utils::Password::PBKDF2::generate(password));
|
||||
pref->setWebUILocalAuthEnabled(!m_ui->checkBypassLocalAuth->isChecked());
|
||||
pref->setWebUIAuthSubnetWhitelistEnabled(m_ui->checkBypassAuthSubnetWhitelist->isChecked());
|
||||
// Alternative UI
|
||||
pref->setAltWebUiEnabled(m_ui->groupAltWebUI->isChecked());
|
||||
pref->setWebUiRootFolder(m_ui->textWebUIRootFolder->selectedPath());
|
||||
pref->setAltWebUIEnabled(m_ui->groupAltWebUI->isChecked());
|
||||
pref->setWebUIRootFolder(m_ui->textWebUIRootFolder->selectedPath());
|
||||
// Security
|
||||
pref->setWebUiClickjackingProtectionEnabled(m_ui->checkClickjacking->isChecked());
|
||||
pref->setWebUiCSRFProtectionEnabled(m_ui->checkCSRFProtection->isChecked());
|
||||
pref->setWebUiSecureCookieEnabled(m_ui->checkSecureCookie->isChecked());
|
||||
pref->setWebUIClickjackingProtectionEnabled(m_ui->checkClickjacking->isChecked());
|
||||
pref->setWebUICSRFProtectionEnabled(m_ui->checkCSRFProtection->isChecked());
|
||||
pref->setWebUISecureCookieEnabled(m_ui->checkSecureCookie->isChecked());
|
||||
pref->setWebUIHostHeaderValidationEnabled(m_ui->groupHostHeaderValidation->isChecked());
|
||||
pref->setServerDomains(m_ui->textServerDomains->text());
|
||||
// Custom HTTP headers
|
||||
|
@ -1517,53 +1543,37 @@ void OptionsDialog::on_buttonBox_accepted()
|
|||
{
|
||||
if (m_applyButton->isEnabled())
|
||||
{
|
||||
if (!schedTimesOk())
|
||||
{
|
||||
m_ui->tabSelection->setCurrentRow(TAB_SPEED);
|
||||
if (!applySettings())
|
||||
return;
|
||||
}
|
||||
#ifndef DISABLE_WEBUI
|
||||
if (!webUIAuthenticationOk())
|
||||
{
|
||||
m_ui->tabSelection->setCurrentRow(TAB_WEBUI);
|
||||
return;
|
||||
}
|
||||
if (!isAlternativeWebUIPathValid())
|
||||
{
|
||||
m_ui->tabSelection->setCurrentRow(TAB_WEBUI);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
m_applyButton->setEnabled(false);
|
||||
saveOptions();
|
||||
}
|
||||
|
||||
accept();
|
||||
}
|
||||
|
||||
void OptionsDialog::applySettings()
|
||||
bool OptionsDialog::applySettings()
|
||||
{
|
||||
if (!schedTimesOk())
|
||||
{
|
||||
m_ui->tabSelection->setCurrentRow(TAB_SPEED);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
#ifndef DISABLE_WEBUI
|
||||
if (!webUIAuthenticationOk())
|
||||
if (isWebUIEnabled() && !webUIAuthenticationOk())
|
||||
{
|
||||
m_ui->tabSelection->setCurrentRow(TAB_WEBUI);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if (!isAlternativeWebUIPathValid())
|
||||
{
|
||||
m_ui->tabSelection->setCurrentRow(TAB_WEBUI);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
m_applyButton->setEnabled(false);
|
||||
saveOptions();
|
||||
return true;
|
||||
}
|
||||
|
||||
void OptionsDialog::on_buttonBox_rejected()
|
||||
|
@ -1859,31 +1869,33 @@ void OptionsDialog::webUIHttpsKeyChanged(const Path &path)
|
|||
(isKeyValid ? u"security-high"_s : u"security-low"_s), 24));
|
||||
}
|
||||
|
||||
bool OptionsDialog::isWebUiEnabled() const
|
||||
bool OptionsDialog::isWebUIEnabled() const
|
||||
{
|
||||
return m_ui->checkWebUi->isChecked();
|
||||
return m_ui->checkWebUI->isChecked();
|
||||
}
|
||||
|
||||
QString OptionsDialog::webUiUsername() const
|
||||
QString OptionsDialog::webUIUsername() const
|
||||
{
|
||||
return m_ui->textWebUiUsername->text();
|
||||
return m_ui->textWebUIUsername->text();
|
||||
}
|
||||
|
||||
QString OptionsDialog::webUiPassword() const
|
||||
QString OptionsDialog::webUIPassword() const
|
||||
{
|
||||
return m_ui->textWebUiPassword->text();
|
||||
return m_ui->textWebUIPassword->text();
|
||||
}
|
||||
|
||||
bool OptionsDialog::webUIAuthenticationOk()
|
||||
{
|
||||
if (webUiUsername().length() < 3)
|
||||
if (!isValidWebUIUsername(webUIUsername()))
|
||||
{
|
||||
QMessageBox::warning(this, tr("Length Error"), tr("The Web UI username must be at least 3 characters long."));
|
||||
QMessageBox::warning(this, tr("Length Error"), tr("The WebUI username must be at least 3 characters long."));
|
||||
return false;
|
||||
}
|
||||
if (!webUiPassword().isEmpty() && (webUiPassword().length() < 6))
|
||||
|
||||
const bool dontChangePassword = webUIPassword().isEmpty() && !Preferences::instance()->getWebUIPassword().isEmpty();
|
||||
if (!isValidWebUIPassword(webUIPassword()) && !dontChangePassword)
|
||||
{
|
||||
QMessageBox::warning(this, tr("Length Error"), tr("The Web UI password must be at least 6 characters long."));
|
||||
QMessageBox::warning(this, tr("Length Error"), tr("The WebUI password must be at least 6 characters long."));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -1893,7 +1905,7 @@ bool OptionsDialog::isAlternativeWebUIPathValid()
|
|||
{
|
||||
if (m_ui->groupAltWebUI->isChecked() && m_ui->textWebUIRootFolder->selectedPath().isEmpty())
|
||||
{
|
||||
QMessageBox::warning(this, tr("Location Error"), tr("The alternative Web UI files location cannot be blank."));
|
||||
QMessageBox::warning(this, tr("Location Error"), tr("The alternative WebUI files location cannot be blank."));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
|
|
@ -88,7 +88,6 @@ private slots:
|
|||
void adjustProxyOptions();
|
||||
void on_buttonBox_accepted();
|
||||
void on_buttonBox_rejected();
|
||||
void applySettings();
|
||||
void enableApplyButton();
|
||||
void toggleComboRatioLimitAct();
|
||||
void changePage(QListWidgetItem *, QListWidgetItem *);
|
||||
|
@ -115,6 +114,7 @@ private:
|
|||
void showEvent(QShowEvent *e) override;
|
||||
|
||||
// Methods
|
||||
bool applySettings();
|
||||
void saveOptions() const;
|
||||
|
||||
void loadBehaviorTabOptions();
|
||||
|
@ -184,9 +184,9 @@ private:
|
|||
int getMaxActiveTorrents() const;
|
||||
// WebUI
|
||||
#ifndef DISABLE_WEBUI
|
||||
bool isWebUiEnabled() const;
|
||||
QString webUiUsername() const;
|
||||
QString webUiPassword() const;
|
||||
bool isWebUIEnabled() const;
|
||||
QString webUIUsername() const;
|
||||
QString webUIPassword() const;
|
||||
bool webUIAuthenticationOk();
|
||||
bool isAlternativeWebUIPathValid();
|
||||
#endif
|
||||
|
|
|
@ -3223,8 +3223,8 @@ Disable encryption: Only connect to peers without protocol encryption</string>
|
|||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tabWebuiPage">
|
||||
<layout class="QVBoxLayout" name="tabWebuiPageLayout">
|
||||
<widget class="QWidget" name="tabWebUIPage">
|
||||
<layout class="QVBoxLayout" name="tabWebUIPageLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
|
@ -3253,7 +3253,7 @@ Disable encryption: Only connect to peers without protocol encryption</string>
|
|||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_23">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="checkWebUi">
|
||||
<widget class="QGroupBox" name="checkWebUI">
|
||||
<property name="title">
|
||||
<string>Web User Interface (Remote control)</string>
|
||||
</property>
|
||||
|
@ -3264,17 +3264,29 @@ Disable encryption: Only connect to peers without protocol encryption</string>
|
|||
<bool>false</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="labelWebUIError">
|
||||
<property name="font">
|
||||
<font>
|
||||
<italic>true</italic>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="lblWebUiAddress">
|
||||
<widget class="QLabel" name="lblWebUIAddress">
|
||||
<property name="text">
|
||||
<string>IP address:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="textWebUiAddress">
|
||||
<widget class="QLineEdit" name="textWebUIAddress">
|
||||
<property name="toolTip">
|
||||
<string>IP address that the Web UI will bind to.
|
||||
Specify an IPv4 or IPv6 address. You can specify "0.0.0.0" for any IPv4 address,
|
||||
|
@ -3283,14 +3295,14 @@ Specify an IPv4 or IPv6 address. You can specify "0.0.0.0" for any IPv
|
|||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="lblWebUiPort">
|
||||
<widget class="QLabel" name="lblWebUIPort">
|
||||
<property name="text">
|
||||
<string>Port:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="spinWebUiPort">
|
||||
<widget class="QSpinBox" name="spinWebUIPort">
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
|
@ -3315,7 +3327,7 @@ Specify an IPv4 or IPv6 address. You can specify "0.0.0.0" for any IPv
|
|||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="checkWebUiHttps">
|
||||
<widget class="QGroupBox" name="checkWebUIHttps">
|
||||
<property name="title">
|
||||
<string>&Use HTTPS instead of HTTP</string>
|
||||
</property>
|
||||
|
@ -3327,14 +3339,14 @@ Specify an IPv4 or IPv6 address. You can specify "0.0.0.0" for any IPv
|
|||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_11">
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="lblWebUiKey">
|
||||
<widget class="QLabel" name="lblWebUIKey">
|
||||
<property name="text">
|
||||
<string>Key:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="lblWebUiCrt">
|
||||
<widget class="QLabel" name="lblWebUICrt">
|
||||
<property name="text">
|
||||
<string>Certificate:</string>
|
||||
</property>
|
||||
|
@ -3366,7 +3378,7 @@ Specify an IPv4 or IPv6 address. You can specify "0.0.0.0" for any IPv
|
|||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupWebUiAuth">
|
||||
<widget class="QGroupBox" name="groupWebUIAuth">
|
||||
<property name="title">
|
||||
<string>Authentication</string>
|
||||
</property>
|
||||
|
@ -3374,24 +3386,24 @@ Specify an IPv4 or IPv6 address. You can specify "0.0.0.0" for any IPv
|
|||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout_8">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="lblWebUiUsername">
|
||||
<widget class="QLabel" name="lblWebUIUsername">
|
||||
<property name="text">
|
||||
<string>Username:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="textWebUiUsername"/>
|
||||
<widget class="QLineEdit" name="textWebUIUsername"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="lblWebUiPassword">
|
||||
<widget class="QLabel" name="lblWebUIPassword">
|
||||
<property name="text">
|
||||
<string>Password:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="textWebUiPassword">
|
||||
<widget class="QLineEdit" name="textWebUIPassword">
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
|
@ -3819,13 +3831,13 @@ Use ';' to split multiple entries. Can use wildcard '*'.</string>
|
|||
<tabstop>stopConditionComboBox</tabstop>
|
||||
<tabstop>spinPort</tabstop>
|
||||
<tabstop>checkUPnP</tabstop>
|
||||
<tabstop>textWebUiUsername</tabstop>
|
||||
<tabstop>checkWebUi</tabstop>
|
||||
<tabstop>textWebUIUsername</tabstop>
|
||||
<tabstop>checkWebUI</tabstop>
|
||||
<tabstop>textSavePath</tabstop>
|
||||
<tabstop>scrollArea_7</tabstop>
|
||||
<tabstop>scrollArea_2</tabstop>
|
||||
<tabstop>spinWebUiPort</tabstop>
|
||||
<tabstop>textWebUiPassword</tabstop>
|
||||
<tabstop>spinWebUIPort</tabstop>
|
||||
<tabstop>textWebUIPassword</tabstop>
|
||||
<tabstop>buttonBox</tabstop>
|
||||
<tabstop>tabSelection</tabstop>
|
||||
<tabstop>scrollArea</tabstop>
|
||||
|
@ -3915,7 +3927,7 @@ Use ';' to split multiple entries. Can use wildcard '*'.</string>
|
|||
<tabstop>spinMaxActiveUploads</tabstop>
|
||||
<tabstop>spinMaxActiveTorrents</tabstop>
|
||||
<tabstop>checkWebUIUPnP</tabstop>
|
||||
<tabstop>checkWebUiHttps</tabstop>
|
||||
<tabstop>checkWebUIHttps</tabstop>
|
||||
<tabstop>checkBypassLocalAuth</tabstop>
|
||||
<tabstop>checkBypassAuthSubnetWhitelist</tabstop>
|
||||
<tabstop>IPSubnetWhitelistButton</tabstop>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
|
@ -30,13 +31,12 @@
|
|||
|
||||
#include <QModelIndex>
|
||||
#include <QPainter>
|
||||
#include <QStyleOptionViewItem>
|
||||
|
||||
#include "base/utils/misc.h"
|
||||
#include "base/utils/string.h"
|
||||
#include "previewselectdialog.h"
|
||||
|
||||
PreviewListDelegate::PreviewListDelegate(QObject *parent)
|
||||
: QItemDelegate(parent)
|
||||
: QStyledItemDelegate(parent)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -44,15 +44,8 @@ void PreviewListDelegate::paint(QPainter *painter, const QStyleOptionViewItem &o
|
|||
{
|
||||
painter->save();
|
||||
|
||||
QStyleOptionViewItem opt = QItemDelegate::setOptions(index, option);
|
||||
drawBackground(painter, opt, index);
|
||||
|
||||
switch (index.column())
|
||||
{
|
||||
case PreviewSelectDialog::SIZE:
|
||||
QItemDelegate::drawDisplay(painter, opt, option.rect, Utils::Misc::friendlyUnit(index.data().toLongLong()));
|
||||
break;
|
||||
|
||||
case PreviewSelectDialog::PROGRESS:
|
||||
{
|
||||
const qreal progress = (index.data().toReal() * 100);
|
||||
|
@ -65,7 +58,7 @@ void PreviewListDelegate::paint(QPainter *painter, const QStyleOptionViewItem &o
|
|||
break;
|
||||
|
||||
default:
|
||||
QItemDelegate::paint(painter, option, index);
|
||||
QStyledItemDelegate::paint(painter, option, index);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
|
@ -28,11 +29,11 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <QItemDelegate>
|
||||
#include <QStyledItemDelegate>
|
||||
|
||||
#include "progressbarpainter.h"
|
||||
|
||||
class PreviewListDelegate final : public QItemDelegate
|
||||
class PreviewListDelegate final : public QStyledItemDelegate
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(PreviewListDelegate)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2011 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
|
@ -70,16 +71,19 @@ PreviewSelectDialog::PreviewSelectDialog(QWidget *parent, const BitTorrent::Torr
|
|||
|
||||
const Preferences *pref = Preferences::instance();
|
||||
// Preview list
|
||||
m_previewListModel = new QStandardItemModel(0, NB_COLUMNS, this);
|
||||
m_previewListModel->setHeaderData(NAME, Qt::Horizontal, tr("Name"));
|
||||
m_previewListModel->setHeaderData(SIZE, Qt::Horizontal, tr("Size"));
|
||||
m_previewListModel->setHeaderData(PROGRESS, Qt::Horizontal, tr("Progress"));
|
||||
auto *previewListModel = new QStandardItemModel(0, NB_COLUMNS, this);
|
||||
previewListModel->setHeaderData(NAME, Qt::Horizontal, tr("Name"));
|
||||
previewListModel->setHeaderData(SIZE, Qt::Horizontal, tr("Size"));
|
||||
previewListModel->setHeaderData(PROGRESS, Qt::Horizontal, tr("Progress"));
|
||||
|
||||
m_ui->previewList->setAlternatingRowColors(pref->useAlternatingRowColors());
|
||||
m_ui->previewList->setModel(m_previewListModel);
|
||||
m_ui->previewList->setUniformRowHeights(true);
|
||||
m_ui->previewList->setModel(previewListModel);
|
||||
m_ui->previewList->hideColumn(FILE_INDEX);
|
||||
m_listDelegate = new PreviewListDelegate(this);
|
||||
m_ui->previewList->setItemDelegate(m_listDelegate);
|
||||
|
||||
auto *listDelegate = new PreviewListDelegate(this);
|
||||
m_ui->previewList->setItemDelegate(listDelegate);
|
||||
|
||||
// Fill list in
|
||||
const QVector<qreal> fp = torrent->filesProgress();
|
||||
for (int i = 0; i < torrent->filesCount(); ++i)
|
||||
|
@ -87,20 +91,20 @@ PreviewSelectDialog::PreviewSelectDialog(QWidget *parent, const BitTorrent::Torr
|
|||
const Path filePath = torrent->filePath(i);
|
||||
if (Utils::Misc::isPreviewable(filePath))
|
||||
{
|
||||
int row = m_previewListModel->rowCount();
|
||||
m_previewListModel->insertRow(row);
|
||||
m_previewListModel->setData(m_previewListModel->index(row, NAME), filePath.filename());
|
||||
m_previewListModel->setData(m_previewListModel->index(row, SIZE), torrent->fileSize(i));
|
||||
m_previewListModel->setData(m_previewListModel->index(row, PROGRESS), fp[i]);
|
||||
m_previewListModel->setData(m_previewListModel->index(row, FILE_INDEX), i);
|
||||
int row = previewListModel->rowCount();
|
||||
previewListModel->insertRow(row);
|
||||
previewListModel->setData(previewListModel->index(row, NAME), filePath.filename());
|
||||
previewListModel->setData(previewListModel->index(row, SIZE), Utils::Misc::friendlyUnit(torrent->fileSize(i)));
|
||||
previewListModel->setData(previewListModel->index(row, PROGRESS), fp[i]);
|
||||
previewListModel->setData(previewListModel->index(row, FILE_INDEX), i);
|
||||
}
|
||||
}
|
||||
|
||||
m_previewListModel->sort(NAME);
|
||||
previewListModel->sort(NAME);
|
||||
m_ui->previewList->header()->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
m_ui->previewList->header()->setFirstSectionMovable(true);
|
||||
m_ui->previewList->header()->setSortIndicator(0, Qt::AscendingOrder);
|
||||
m_ui->previewList->selectionModel()->select(m_previewListModel->index(0, NAME), QItemSelectionModel::Select | QItemSelectionModel::Rows);
|
||||
m_ui->previewList->selectionModel()->select(previewListModel->index(0, NAME), QItemSelectionModel::Select | QItemSelectionModel::Rows);
|
||||
|
||||
connect(m_ui->previewList->header(), &QWidget::customContextMenuRequested, this, &PreviewSelectDialog::displayColumnHeaderMenu);
|
||||
|
||||
|
@ -129,7 +133,7 @@ void PreviewSelectDialog::previewButtonClicked()
|
|||
// File
|
||||
if (!path.exists())
|
||||
{
|
||||
const bool isSingleFile = (m_previewListModel->rowCount() == 1);
|
||||
const bool isSingleFile = (m_ui->previewList->model()->rowCount() == 1);
|
||||
QWidget *parent = isSingleFile ? this->parentWidget() : this;
|
||||
QMessageBox::critical(parent, tr("Preview impossible")
|
||||
, tr("Sorry, we can't preview this file: \"%1\".").arg(path.toString()));
|
||||
|
@ -199,6 +203,6 @@ void PreviewSelectDialog::showEvent(QShowEvent *event)
|
|||
}
|
||||
|
||||
// Only one file, no choice
|
||||
if (m_previewListModel->rowCount() <= 1)
|
||||
if (m_ui->previewList->model()->rowCount() <= 1)
|
||||
previewButtonClicked();
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2011 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
|
@ -33,8 +34,6 @@
|
|||
#include "base/path.h"
|
||||
#include "base/settingvalue.h"
|
||||
|
||||
class QStandardItemModel;
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
class Torrent;
|
||||
|
@ -44,7 +43,6 @@ namespace Ui
|
|||
{
|
||||
class PreviewSelectDialog;
|
||||
}
|
||||
class PreviewListDelegate;
|
||||
|
||||
class PreviewSelectDialog final : public QDialog
|
||||
{
|
||||
|
@ -79,8 +77,6 @@ private:
|
|||
void saveWindowState();
|
||||
|
||||
Ui::PreviewSelectDialog *m_ui = nullptr;
|
||||
QStandardItemModel *m_previewListModel = nullptr;
|
||||
PreviewListDelegate *m_listDelegate = nullptr;
|
||||
const BitTorrent::Torrent *m_torrent = nullptr;
|
||||
bool m_headerStateInitialized = false;
|
||||
|
||||
|
|
|
@ -82,6 +82,7 @@ PropertiesWidget::PropertiesWidget(QWidget *parent)
|
|||
m_ui->contentFilterLayout->insertWidget(3, m_contentFilterLine);
|
||||
|
||||
m_ui->filesList->setDoubleClickAction(TorrentContentWidget::DoubleClickAction::Open);
|
||||
m_ui->filesList->setOpenByEnterKey(true);
|
||||
|
||||
// SIGNAL/SLOTS
|
||||
connect(m_ui->selectAllButton, &QPushButton::clicked, m_ui->filesList, &TorrentContentWidget::checkAll);
|
||||
|
|
|
@ -192,7 +192,7 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="3" colspan="4">
|
||||
<item row="2" column="3">
|
||||
<widget class="QLabel" name="labelUpSpeedVal">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
|
@ -382,7 +382,7 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="3" colspan="4">
|
||||
<item row="1" column="3">
|
||||
<widget class="QLabel" name="labelUpTotalVal">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
|
|
|
@ -89,10 +89,6 @@ TorrentContentWidget::TorrentContentWidget(QWidget *parent)
|
|||
|
||||
const auto *renameFileHotkey = new QShortcut(Qt::Key_F2, this, nullptr, nullptr, Qt::WidgetShortcut);
|
||||
connect(renameFileHotkey, &QShortcut::activated, this, &TorrentContentWidget::renameSelectedFile);
|
||||
const auto *openFileHotkeyReturn = new QShortcut(Qt::Key_Return, this, nullptr, nullptr, Qt::WidgetShortcut);
|
||||
connect(openFileHotkeyReturn, &QShortcut::activated, this, &TorrentContentWidget::openSelectedFile);
|
||||
const auto *openFileHotkeyEnter = new QShortcut(Qt::Key_Enter, this, nullptr, nullptr, Qt::WidgetShortcut);
|
||||
connect(openFileHotkeyEnter, &QShortcut::activated, this, &TorrentContentWidget::openSelectedFile);
|
||||
|
||||
connect(model(), &QAbstractItemModel::modelReset, this, &TorrentContentWidget::expandRecursively);
|
||||
}
|
||||
|
@ -118,6 +114,32 @@ void TorrentContentWidget::refresh()
|
|||
setUpdatesEnabled(true);
|
||||
}
|
||||
|
||||
bool TorrentContentWidget::openByEnterKey() const
|
||||
{
|
||||
return m_openFileHotkeyEnter;
|
||||
}
|
||||
|
||||
void TorrentContentWidget::setOpenByEnterKey(const bool value)
|
||||
{
|
||||
if (value == openByEnterKey())
|
||||
return;
|
||||
|
||||
if (value)
|
||||
{
|
||||
m_openFileHotkeyReturn = new QShortcut(Qt::Key_Return, this, nullptr, nullptr, Qt::WidgetShortcut);
|
||||
connect(m_openFileHotkeyReturn, &QShortcut::activated, this, &TorrentContentWidget::openSelectedFile);
|
||||
m_openFileHotkeyEnter = new QShortcut(Qt::Key_Enter, this, nullptr, nullptr, Qt::WidgetShortcut);
|
||||
connect(m_openFileHotkeyEnter, &QShortcut::activated, this, &TorrentContentWidget::openSelectedFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
delete m_openFileHotkeyEnter;
|
||||
m_openFileHotkeyEnter = nullptr;
|
||||
delete m_openFileHotkeyReturn;
|
||||
m_openFileHotkeyReturn = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
TorrentContentWidget::DoubleClickAction TorrentContentWidget::doubleClickAction() const
|
||||
{
|
||||
return m_doubleClickAction;
|
||||
|
|
|
@ -34,6 +34,8 @@
|
|||
#include "base/bittorrent/downloadpriority.h"
|
||||
#include "base/pathfwd.h"
|
||||
|
||||
class QShortcut;
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
class Torrent;
|
||||
|
@ -78,6 +80,9 @@ public:
|
|||
BitTorrent::TorrentContentHandler *contentHandler() const;
|
||||
void refresh();
|
||||
|
||||
bool openByEnterKey() const;
|
||||
void setOpenByEnterKey(bool value);
|
||||
|
||||
DoubleClickAction doubleClickAction() const;
|
||||
void setDoubleClickAction(DoubleClickAction action);
|
||||
|
||||
|
@ -118,4 +123,6 @@ private:
|
|||
TorrentContentFilterModel *m_filterModel;
|
||||
DoubleClickAction m_doubleClickAction = DoubleClickAction::Rename;
|
||||
ColumnsVisibilityMode m_columnsVisibilityMode = ColumnsVisibilityMode::Editable;
|
||||
QShortcut *m_openFileHotkeyEnter = nullptr;
|
||||
QShortcut *m_openFileHotkeyReturn = nullptr;
|
||||
};
|
||||
|
|
|
@ -117,6 +117,7 @@ TransferListModel::TransferListModel(QObject *parent)
|
|||
, m_completedIcon {UIThemeManager::instance()->getIcon(u"checked-completed"_s, u"completed"_s)}
|
||||
, m_downloadingIcon {UIThemeManager::instance()->getIcon(u"downloading"_s)}
|
||||
, m_errorIcon {UIThemeManager::instance()->getIcon(u"error"_s)}
|
||||
, m_movingIcon {UIThemeManager::instance()->getIcon(u"set-location"_s)}
|
||||
, m_pausedIcon {UIThemeManager::instance()->getIcon(u"stopped"_s, u"media-playback-pause"_s)}
|
||||
, m_queuedIcon {UIThemeManager::instance()->getIcon(u"queued"_s)}
|
||||
, m_stalledDLIcon {UIThemeManager::instance()->getIcon(u"stalledDL"_s)}
|
||||
|
@ -710,8 +711,9 @@ QIcon TransferListModel::getIconByState(const BitTorrent::TorrentState state) co
|
|||
case BitTorrent::TorrentState::CheckingDownloading:
|
||||
case BitTorrent::TorrentState::CheckingUploading:
|
||||
case BitTorrent::TorrentState::CheckingResumeData:
|
||||
case BitTorrent::TorrentState::Moving:
|
||||
return m_checkingIcon;
|
||||
case BitTorrent::TorrentState::Moving:
|
||||
return m_movingIcon;
|
||||
case BitTorrent::TorrentState::Unknown:
|
||||
case BitTorrent::TorrentState::MissingFiles:
|
||||
case BitTorrent::TorrentState::Error:
|
||||
|
|
|
@ -137,6 +137,7 @@ private:
|
|||
QIcon m_completedIcon;
|
||||
QIcon m_downloadingIcon;
|
||||
QIcon m_errorIcon;
|
||||
QIcon m_movingIcon;
|
||||
QIcon m_pausedIcon;
|
||||
QIcon m_queuedIcon;
|
||||
QIcon m_stalledDLIcon;
|
||||
|
|
|
@ -327,6 +327,7 @@
|
|||
<file>qbittorrent-tray-dark.svg</file>
|
||||
<file>qbittorrent-tray-light.svg</file>
|
||||
<file>qbittorrent-tray.svg</file>
|
||||
<file alias="qbittorrent.svg">qbittorrent-tray.svg</file>
|
||||
<file>queued.svg</file>
|
||||
<file>ratio.svg</file>
|
||||
<file>reannounce.svg</file>
|
||||
|
|
|
@ -4,7 +4,6 @@ add_library(qbt_webui STATIC
|
|||
api/apierror.h
|
||||
api/appcontroller.h
|
||||
api/authcontroller.h
|
||||
api/freediskspacechecker.h
|
||||
api/isessionmanager.h
|
||||
api/logcontroller.h
|
||||
api/rsscontroller.h
|
||||
|
@ -13,6 +12,7 @@ add_library(qbt_webui STATIC
|
|||
api/torrentscontroller.h
|
||||
api/transfercontroller.h
|
||||
api/serialize/serialize_torrent.h
|
||||
freediskspacechecker.h
|
||||
webapplication.h
|
||||
webui.h
|
||||
|
||||
|
@ -21,7 +21,6 @@ add_library(qbt_webui STATIC
|
|||
api/apierror.cpp
|
||||
api/appcontroller.cpp
|
||||
api/authcontroller.cpp
|
||||
api/freediskspacechecker.cpp
|
||||
api/logcontroller.cpp
|
||||
api/rsscontroller.cpp
|
||||
api/searchcontroller.cpp
|
||||
|
@ -29,6 +28,7 @@ add_library(qbt_webui STATIC
|
|||
api/torrentscontroller.cpp
|
||||
api/transfercontroller.cpp
|
||||
api/serialize/serialize_torrent.cpp
|
||||
freediskspacechecker.cpp
|
||||
webapplication.cpp
|
||||
webui.cpp
|
||||
)
|
||||
|
|
|
@ -92,7 +92,7 @@ void AppController::buildInfoAction()
|
|||
void AppController::shutdownAction()
|
||||
{
|
||||
// Special handling for shutdown, we
|
||||
// need to reply to the Web UI before
|
||||
// need to reply to the WebUI before
|
||||
// actually shutting down.
|
||||
QTimer::singleShot(100ms, Qt::CoarseTimer, qApp, []
|
||||
{
|
||||
|
@ -193,6 +193,16 @@ void AppController::preferencesAction()
|
|||
data[u"max_uploads"_s] = session->maxUploads();
|
||||
data[u"max_uploads_per_torrent"_s] = session->maxUploadsPerTorrent();
|
||||
|
||||
// I2P
|
||||
data[u"i2p_enabled"_s] = session->isI2PEnabled();
|
||||
data[u"i2p_address"_s] = session->I2PAddress();
|
||||
data[u"i2p_port"_s] = session->I2PPort();
|
||||
data[u"i2p_mixed_mode"_s] = session->I2PMixedMode();
|
||||
data[u"i2p_inbound_quantity"_s] = session->I2PInboundQuantity();
|
||||
data[u"i2p_outbound_quantity"_s] = session->I2POutboundQuantity();
|
||||
data[u"i2p_inbound_length"_s] = session->I2PInboundLength();
|
||||
data[u"i2p_outbound_length"_s] = session->I2POutboundLength();
|
||||
|
||||
// Proxy Server
|
||||
const auto *proxyManager = Net::ProxyConfigurationManager::instance();
|
||||
Net::ProxyConfiguration proxyConf = proxyManager->proxyConfiguration();
|
||||
|
@ -265,33 +275,33 @@ void AppController::preferencesAction()
|
|||
data[u"add_trackers_enabled"_s] = session->isAddTrackersEnabled();
|
||||
data[u"add_trackers"_s] = session->additionalTrackers();
|
||||
|
||||
// Web UI
|
||||
// WebUI
|
||||
// HTTP Server
|
||||
data[u"web_ui_domain_list"_s] = pref->getServerDomains();
|
||||
data[u"web_ui_address"_s] = pref->getWebUiAddress();
|
||||
data[u"web_ui_port"_s] = pref->getWebUiPort();
|
||||
data[u"web_ui_address"_s] = pref->getWebUIAddress();
|
||||
data[u"web_ui_port"_s] = pref->getWebUIPort();
|
||||
data[u"web_ui_upnp"_s] = pref->useUPnPForWebUIPort();
|
||||
data[u"use_https"_s] = pref->isWebUiHttpsEnabled();
|
||||
data[u"use_https"_s] = pref->isWebUIHttpsEnabled();
|
||||
data[u"web_ui_https_cert_path"_s] = pref->getWebUIHttpsCertificatePath().toString();
|
||||
data[u"web_ui_https_key_path"_s] = pref->getWebUIHttpsKeyPath().toString();
|
||||
// Authentication
|
||||
data[u"web_ui_username"_s] = pref->getWebUiUsername();
|
||||
data[u"bypass_local_auth"_s] = !pref->isWebUiLocalAuthEnabled();
|
||||
data[u"bypass_auth_subnet_whitelist_enabled"_s] = pref->isWebUiAuthSubnetWhitelistEnabled();
|
||||
data[u"web_ui_username"_s] = pref->getWebUIUsername();
|
||||
data[u"bypass_local_auth"_s] = !pref->isWebUILocalAuthEnabled();
|
||||
data[u"bypass_auth_subnet_whitelist_enabled"_s] = pref->isWebUIAuthSubnetWhitelistEnabled();
|
||||
QStringList authSubnetWhitelistStringList;
|
||||
for (const Utils::Net::Subnet &subnet : asConst(pref->getWebUiAuthSubnetWhitelist()))
|
||||
for (const Utils::Net::Subnet &subnet : asConst(pref->getWebUIAuthSubnetWhitelist()))
|
||||
authSubnetWhitelistStringList << Utils::Net::subnetToString(subnet);
|
||||
data[u"bypass_auth_subnet_whitelist"_s] = authSubnetWhitelistStringList.join(u'\n');
|
||||
data[u"web_ui_max_auth_fail_count"_s] = pref->getWebUIMaxAuthFailCount();
|
||||
data[u"web_ui_ban_duration"_s] = static_cast<int>(pref->getWebUIBanDuration().count());
|
||||
data[u"web_ui_session_timeout"_s] = pref->getWebUISessionTimeout();
|
||||
// Use alternative Web UI
|
||||
data[u"alternative_webui_enabled"_s] = pref->isAltWebUiEnabled();
|
||||
data[u"alternative_webui_path"_s] = pref->getWebUiRootFolder().toString();
|
||||
// Use alternative WebUI
|
||||
data[u"alternative_webui_enabled"_s] = pref->isAltWebUIEnabled();
|
||||
data[u"alternative_webui_path"_s] = pref->getWebUIRootFolder().toString();
|
||||
// Security
|
||||
data[u"web_ui_clickjacking_protection_enabled"_s] = pref->isWebUiClickjackingProtectionEnabled();
|
||||
data[u"web_ui_csrf_protection_enabled"_s] = pref->isWebUiCSRFProtectionEnabled();
|
||||
data[u"web_ui_secure_cookie_enabled"_s] = pref->isWebUiSecureCookieEnabled();
|
||||
data[u"web_ui_clickjacking_protection_enabled"_s] = pref->isWebUIClickjackingProtectionEnabled();
|
||||
data[u"web_ui_csrf_protection_enabled"_s] = pref->isWebUICSRFProtectionEnabled();
|
||||
data[u"web_ui_secure_cookie_enabled"_s] = pref->isWebUISecureCookieEnabled();
|
||||
data[u"web_ui_host_header_validation_enabled"_s] = pref->isWebUIHostHeaderValidationEnabled();
|
||||
// Custom HTTP headers
|
||||
data[u"web_ui_use_custom_http_headers_enabled"_s] = pref->isWebUICustomHTTPHeadersEnabled();
|
||||
|
@ -628,6 +638,24 @@ void AppController::setPreferencesAction()
|
|||
if (hasKey(u"max_uploads_per_torrent"_s))
|
||||
session->setMaxUploadsPerTorrent(it.value().toInt());
|
||||
|
||||
// I2P
|
||||
if (hasKey(u"i2p_enabled"_s))
|
||||
session->setI2PEnabled(it.value().toBool());
|
||||
if (hasKey(u"i2p_address"_s))
|
||||
session->setI2PAddress(it.value().toString());
|
||||
if (hasKey(u"i2p_port"_s))
|
||||
session->setI2PPort(it.value().toInt());
|
||||
if (hasKey(u"i2p_mixed_mode"_s))
|
||||
session->setI2PMixedMode(it.value().toBool());
|
||||
if (hasKey(u"i2p_inbound_quantity"_s))
|
||||
session->setI2PInboundQuantity(it.value().toInt());
|
||||
if (hasKey(u"i2p_outbound_quantity"_s))
|
||||
session->setI2POutboundQuantity(it.value().toInt());
|
||||
if (hasKey(u"i2p_inbound_length"_s))
|
||||
session->setI2PInboundLength(it.value().toInt());
|
||||
if (hasKey(u"i2p_outbound_length"_s))
|
||||
session->setI2POutboundLength(it.value().toInt());
|
||||
|
||||
// Proxy Server
|
||||
auto *proxyManager = Net::ProxyConfigurationManager::instance();
|
||||
Net::ProxyConfiguration proxyConf = proxyManager->proxyConfiguration();
|
||||
|
@ -754,35 +782,35 @@ void AppController::setPreferencesAction()
|
|||
if (hasKey(u"add_trackers"_s))
|
||||
session->setAdditionalTrackers(it.value().toString());
|
||||
|
||||
// Web UI
|
||||
// WebUI
|
||||
// HTTP Server
|
||||
if (hasKey(u"web_ui_domain_list"_s))
|
||||
pref->setServerDomains(it.value().toString());
|
||||
if (hasKey(u"web_ui_address"_s))
|
||||
pref->setWebUiAddress(it.value().toString());
|
||||
pref->setWebUIAddress(it.value().toString());
|
||||
if (hasKey(u"web_ui_port"_s))
|
||||
pref->setWebUiPort(it.value().value<quint16>());
|
||||
pref->setWebUIPort(it.value().value<quint16>());
|
||||
if (hasKey(u"web_ui_upnp"_s))
|
||||
pref->setUPnPForWebUIPort(it.value().toBool());
|
||||
if (hasKey(u"use_https"_s))
|
||||
pref->setWebUiHttpsEnabled(it.value().toBool());
|
||||
pref->setWebUIHttpsEnabled(it.value().toBool());
|
||||
if (hasKey(u"web_ui_https_cert_path"_s))
|
||||
pref->setWebUIHttpsCertificatePath(Path(it.value().toString()));
|
||||
if (hasKey(u"web_ui_https_key_path"_s))
|
||||
pref->setWebUIHttpsKeyPath(Path(it.value().toString()));
|
||||
// Authentication
|
||||
if (hasKey(u"web_ui_username"_s))
|
||||
pref->setWebUiUsername(it.value().toString());
|
||||
pref->setWebUIUsername(it.value().toString());
|
||||
if (hasKey(u"web_ui_password"_s))
|
||||
pref->setWebUIPassword(Utils::Password::PBKDF2::generate(it.value().toByteArray()));
|
||||
if (hasKey(u"bypass_local_auth"_s))
|
||||
pref->setWebUiLocalAuthEnabled(!it.value().toBool());
|
||||
pref->setWebUILocalAuthEnabled(!it.value().toBool());
|
||||
if (hasKey(u"bypass_auth_subnet_whitelist_enabled"_s))
|
||||
pref->setWebUiAuthSubnetWhitelistEnabled(it.value().toBool());
|
||||
pref->setWebUIAuthSubnetWhitelistEnabled(it.value().toBool());
|
||||
if (hasKey(u"bypass_auth_subnet_whitelist"_s))
|
||||
{
|
||||
// recognize new lines and commas as delimiters
|
||||
pref->setWebUiAuthSubnetWhitelist(it.value().toString().split(QRegularExpression(u"\n|,"_s), Qt::SkipEmptyParts));
|
||||
pref->setWebUIAuthSubnetWhitelist(it.value().toString().split(QRegularExpression(u"\n|,"_s), Qt::SkipEmptyParts));
|
||||
}
|
||||
if (hasKey(u"web_ui_max_auth_fail_count"_s))
|
||||
pref->setWebUIMaxAuthFailCount(it.value().toInt());
|
||||
|
@ -790,18 +818,18 @@ void AppController::setPreferencesAction()
|
|||
pref->setWebUIBanDuration(std::chrono::seconds {it.value().toInt()});
|
||||
if (hasKey(u"web_ui_session_timeout"_s))
|
||||
pref->setWebUISessionTimeout(it.value().toInt());
|
||||
// Use alternative Web UI
|
||||
// Use alternative WebUI
|
||||
if (hasKey(u"alternative_webui_enabled"_s))
|
||||
pref->setAltWebUiEnabled(it.value().toBool());
|
||||
pref->setAltWebUIEnabled(it.value().toBool());
|
||||
if (hasKey(u"alternative_webui_path"_s))
|
||||
pref->setWebUiRootFolder(Path(it.value().toString()));
|
||||
pref->setWebUIRootFolder(Path(it.value().toString()));
|
||||
// Security
|
||||
if (hasKey(u"web_ui_clickjacking_protection_enabled"_s))
|
||||
pref->setWebUiClickjackingProtectionEnabled(it.value().toBool());
|
||||
pref->setWebUIClickjackingProtectionEnabled(it.value().toBool());
|
||||
if (hasKey(u"web_ui_csrf_protection_enabled"_s))
|
||||
pref->setWebUiCSRFProtectionEnabled(it.value().toBool());
|
||||
pref->setWebUICSRFProtectionEnabled(it.value().toBool());
|
||||
if (hasKey(u"web_ui_secure_cookie_enabled"_s))
|
||||
pref->setWebUiSecureCookieEnabled(it.value().toBool());
|
||||
pref->setWebUISecureCookieEnabled(it.value().toBool());
|
||||
if (hasKey(u"web_ui_host_header_validation_enabled"_s))
|
||||
pref->setWebUIHostHeaderValidationEnabled(it.value().toBool());
|
||||
// Custom HTTP headers
|
||||
|
|
|
@ -43,6 +43,16 @@ AuthController::AuthController(ISessionManager *sessionManager, IApplication *ap
|
|||
{
|
||||
}
|
||||
|
||||
void AuthController::setUsername(const QString &username)
|
||||
{
|
||||
m_username = username;
|
||||
}
|
||||
|
||||
void AuthController::setPasswordHash(const QByteArray &passwordHash)
|
||||
{
|
||||
m_passwordHash = passwordHash;
|
||||
}
|
||||
|
||||
void AuthController::loginAction()
|
||||
{
|
||||
if (m_sessionManager->session())
|
||||
|
@ -51,9 +61,9 @@ void AuthController::loginAction()
|
|||
return;
|
||||
}
|
||||
|
||||
const QString clientAddr {m_sessionManager->clientId()};
|
||||
const QString usernameFromWeb {params()[u"username"_s]};
|
||||
const QString passwordFromWeb {params()[u"password"_s]};
|
||||
const QString clientAddr = m_sessionManager->clientId();
|
||||
const QString usernameFromWeb = params()[u"username"_s];
|
||||
const QString passwordFromWeb = params()[u"password"_s];
|
||||
|
||||
if (isBanned())
|
||||
{
|
||||
|
@ -64,12 +74,8 @@ void AuthController::loginAction()
|
|||
, tr("Your IP address has been banned after too many failed authentication attempts."));
|
||||
}
|
||||
|
||||
const Preferences *pref = Preferences::instance();
|
||||
|
||||
const QString username {pref->getWebUiUsername()};
|
||||
const QByteArray secret {pref->getWebUIPassword()};
|
||||
const bool usernameEqual = Utils::Password::slowEquals(usernameFromWeb.toUtf8(), username.toUtf8());
|
||||
const bool passwordEqual = Utils::Password::PBKDF2::verify(secret, passwordFromWeb);
|
||||
const bool usernameEqual = Utils::Password::slowEquals(usernameFromWeb.toUtf8(), m_username.toUtf8());
|
||||
const bool passwordEqual = Utils::Password::PBKDF2::verify(m_passwordHash, passwordFromWeb);
|
||||
|
||||
if (usernameEqual && passwordEqual)
|
||||
{
|
||||
|
|
|
@ -28,8 +28,10 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QDeadlineTimer>
|
||||
#include <QHash>
|
||||
#include <QString>
|
||||
|
||||
#include "apicontroller.h"
|
||||
|
||||
|
@ -45,6 +47,9 @@ class AuthController : public APIController
|
|||
public:
|
||||
explicit AuthController(ISessionManager *sessionManager, IApplication *app, QObject *parent = nullptr);
|
||||
|
||||
void setUsername(const QString &username);
|
||||
void setPasswordHash(const QByteArray &passwordHash);
|
||||
|
||||
private slots:
|
||||
void loginAction();
|
||||
void logoutAction() const;
|
||||
|
@ -56,6 +61,9 @@ private:
|
|||
|
||||
ISessionManager *m_sessionManager = nullptr;
|
||||
|
||||
QString m_username;
|
||||
QByteArray m_passwordHash;
|
||||
|
||||
struct FailedLogin
|
||||
{
|
||||
int failedAttemptsCount = 0;
|
||||
|
|
|
@ -33,7 +33,6 @@
|
|||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QMetaObject>
|
||||
#include <QThreadPool>
|
||||
|
||||
#include "base/algorithm.h"
|
||||
#include "base/bittorrent/cachestatus.h"
|
||||
|
@ -50,13 +49,10 @@
|
|||
#include "base/preferences.h"
|
||||
#include "base/utils/string.h"
|
||||
#include "apierror.h"
|
||||
#include "freediskspacechecker.h"
|
||||
#include "serialize/serialize_torrent.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
const int FREEDISKSPACE_CHECK_TIMEOUT = 30000;
|
||||
|
||||
// Sync main data keys
|
||||
const QString KEY_SYNC_MAINDATA_QUEUEING = u"queueing"_s;
|
||||
const QString KEY_SYNC_MAINDATA_REFRESH_INTERVAL = u"refresh_interval"_s;
|
||||
|
@ -391,8 +387,11 @@ namespace
|
|||
SyncController::SyncController(IApplication *app, QObject *parent)
|
||||
: APIController(app, parent)
|
||||
{
|
||||
invokeChecker();
|
||||
m_freeDiskSpaceElapsedTimer.start();
|
||||
}
|
||||
|
||||
void SyncController::updateFreeDiskSpace(const qint64 freeDiskSpace)
|
||||
{
|
||||
m_freeDiskSpace = freeDiskSpace;
|
||||
}
|
||||
|
||||
// The function returns the changed data from the server to synchronize with the web client.
|
||||
|
@ -552,7 +551,7 @@ void SyncController::makeMaindataSnapshot()
|
|||
}
|
||||
|
||||
m_maindataSnapshot.serverState = getTransferInfo();
|
||||
m_maindataSnapshot.serverState[KEY_TRANSFER_FREESPACEONDISK] = getFreeDiskSpace();
|
||||
m_maindataSnapshot.serverState[KEY_TRANSFER_FREESPACEONDISK] = m_freeDiskSpace;
|
||||
m_maindataSnapshot.serverState[KEY_SYNC_MAINDATA_QUEUEING] = session->isQueueingSystemEnabled();
|
||||
m_maindataSnapshot.serverState[KEY_SYNC_MAINDATA_USE_ALT_SPEED_LIMITS] = session->isAltGlobalSpeedLimitEnabled();
|
||||
m_maindataSnapshot.serverState[KEY_SYNC_MAINDATA_REFRESH_INTERVAL] = session->refreshInterval();
|
||||
|
@ -661,7 +660,7 @@ QJsonObject SyncController::generateMaindataSyncData(const int id, const bool fu
|
|||
m_removedTrackers.clear();
|
||||
|
||||
QVariantMap serverState = getTransferInfo();
|
||||
serverState[KEY_TRANSFER_FREESPACEONDISK] = getFreeDiskSpace();
|
||||
serverState[KEY_TRANSFER_FREESPACEONDISK] = m_freeDiskSpace;
|
||||
serverState[KEY_SYNC_MAINDATA_QUEUEING] = session->isQueueingSystemEnabled();
|
||||
serverState[KEY_SYNC_MAINDATA_USE_ALT_SPEED_LIMITS] = session->isAltGlobalSpeedLimitEnabled();
|
||||
serverState[KEY_SYNC_MAINDATA_REFRESH_INTERVAL] = session->refreshInterval();
|
||||
|
@ -782,34 +781,6 @@ void SyncController::torrentPeersAction()
|
|||
setResult(generateSyncData(acceptedResponseId, data, m_lastAcceptedPeersResponse, m_lastPeersResponse));
|
||||
}
|
||||
|
||||
qint64 SyncController::getFreeDiskSpace()
|
||||
{
|
||||
if (m_freeDiskSpaceElapsedTimer.hasExpired(FREEDISKSPACE_CHECK_TIMEOUT))
|
||||
invokeChecker();
|
||||
|
||||
return m_freeDiskSpace;
|
||||
}
|
||||
|
||||
void SyncController::invokeChecker()
|
||||
{
|
||||
if (m_isFreeDiskSpaceCheckerRunning)
|
||||
return;
|
||||
|
||||
auto *freeDiskSpaceChecker = new FreeDiskSpaceChecker;
|
||||
connect(freeDiskSpaceChecker, &FreeDiskSpaceChecker::checked, this, [this](const qint64 freeSpaceSize)
|
||||
{
|
||||
m_freeDiskSpace = freeSpaceSize;
|
||||
m_isFreeDiskSpaceCheckerRunning = false;
|
||||
m_freeDiskSpaceElapsedTimer.restart();
|
||||
});
|
||||
connect(freeDiskSpaceChecker, &FreeDiskSpaceChecker::checked, freeDiskSpaceChecker, &QObject::deleteLater);
|
||||
m_isFreeDiskSpaceCheckerRunning = true;
|
||||
QThreadPool::globalInstance()->start([freeDiskSpaceChecker]
|
||||
{
|
||||
freeDiskSpaceChecker->check();
|
||||
});
|
||||
}
|
||||
|
||||
void SyncController::onCategoryAdded(const QString &categoryName)
|
||||
{
|
||||
m_removedCategories.remove(categoryName);
|
||||
|
|
|
@ -28,22 +28,17 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <QElapsedTimer>
|
||||
#include <QVariantMap>
|
||||
#include <QSet>
|
||||
#include <QVariantMap>
|
||||
|
||||
#include "base/bittorrent/infohash.h"
|
||||
#include "apicontroller.h"
|
||||
|
||||
class QThread;
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
class Torrent;
|
||||
}
|
||||
|
||||
class FreeDiskSpaceChecker;
|
||||
|
||||
class SyncController : public APIController
|
||||
{
|
||||
Q_OBJECT
|
||||
|
@ -54,14 +49,14 @@ public:
|
|||
|
||||
explicit SyncController(IApplication *app, QObject *parent = nullptr);
|
||||
|
||||
public slots:
|
||||
void updateFreeDiskSpace(qint64 freeDiskSpace);
|
||||
|
||||
private slots:
|
||||
void maindataAction();
|
||||
void torrentPeersAction();
|
||||
|
||||
private:
|
||||
qint64 getFreeDiskSpace();
|
||||
void invokeChecker();
|
||||
|
||||
void makeMaindataSnapshot();
|
||||
QJsonObject generateMaindataSyncData(int id, bool fullUpdate);
|
||||
|
||||
|
@ -85,8 +80,6 @@ private:
|
|||
void onTorrentTrackersChanged(BitTorrent::Torrent *torrent);
|
||||
|
||||
qint64 m_freeDiskSpace = 0;
|
||||
QElapsedTimer m_freeDiskSpaceElapsedTimer;
|
||||
bool m_isFreeDiskSpaceCheckerRunning = false;
|
||||
|
||||
QVariantMap m_lastPeersResponse;
|
||||
QVariantMap m_lastAcceptedPeersResponse;
|
||||
|
|
|
@ -232,6 +232,11 @@ namespace
|
|||
}
|
||||
}
|
||||
|
||||
void TorrentsController::countAction()
|
||||
{
|
||||
setResult(QString::number(BitTorrent::Session::instance()->torrentsCount()));
|
||||
}
|
||||
|
||||
// Returns all the torrents in JSON format.
|
||||
// The return value is a JSON-formatted list of dictionaries.
|
||||
// The dictionary keys are:
|
||||
|
|
|
@ -39,6 +39,7 @@ public:
|
|||
using APIController::APIController;
|
||||
|
||||
private slots:
|
||||
void countAction();
|
||||
void infoAction();
|
||||
void propertiesAction();
|
||||
void trackersAction();
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2018 Thomas Piccirello <thomas.piccirello@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
|
@ -31,8 +32,13 @@
|
|||
#include "base/bittorrent/session.h"
|
||||
#include "base/utils/fs.h"
|
||||
|
||||
qint64 FreeDiskSpaceChecker::lastResult() const
|
||||
{
|
||||
return m_lastResult;
|
||||
}
|
||||
|
||||
void FreeDiskSpaceChecker::check()
|
||||
{
|
||||
const qint64 freeDiskSpace = Utils::Fs::freeDiskSpaceOnPath(BitTorrent::Session::instance()->savePath());
|
||||
emit checked(freeDiskSpace);
|
||||
m_lastResult = Utils::Fs::freeDiskSpaceOnPath(BitTorrent::Session::instance()->savePath());
|
||||
emit checked(m_lastResult);
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2018 Thomas Piccirello <thomas.piccirello@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
|
@ -38,9 +39,14 @@ class FreeDiskSpaceChecker final : public QObject
|
|||
public:
|
||||
using QObject::QObject;
|
||||
|
||||
qint64 lastResult() const;
|
||||
|
||||
public slots:
|
||||
void check();
|
||||
|
||||
signals:
|
||||
void checked(qint64 freeSpaceSize);
|
||||
|
||||
private:
|
||||
qint64 m_lastResult = 0;
|
||||
};
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2014, 2022 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2014, 2022-2023 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
|
||||
|
@ -29,16 +29,20 @@
|
|||
#include "webapplication.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QJsonDocument>
|
||||
#include <QMetaObject>
|
||||
#include <QMimeDatabase>
|
||||
#include <QMimeType>
|
||||
#include <QNetworkCookie>
|
||||
#include <QRegularExpression>
|
||||
#include <QThread>
|
||||
#include <QTimer>
|
||||
#include <QUrl>
|
||||
|
||||
#include "base/algorithm.h"
|
||||
|
@ -60,6 +64,7 @@
|
|||
#include "api/synccontroller.h"
|
||||
#include "api/torrentscontroller.h"
|
||||
#include "api/transfercontroller.h"
|
||||
#include "freediskspacechecker.h"
|
||||
|
||||
const int MAX_ALLOWED_FILESIZE = 10 * 1024 * 1024;
|
||||
const QString DEFAULT_SESSION_COOKIE_NAME = u"SID"_s;
|
||||
|
@ -68,6 +73,10 @@ const QString WWW_FOLDER = u":/www"_s;
|
|||
const QString PUBLIC_FOLDER = u"/public"_s;
|
||||
const QString PRIVATE_FOLDER = u"/private"_s;
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
const std::chrono::seconds FREEDISKSPACE_CHECK_TIMEOUT = 30s;
|
||||
|
||||
namespace
|
||||
{
|
||||
QStringMap parseCookie(const QStringView cookieStr)
|
||||
|
@ -147,6 +156,9 @@ WebApplication::WebApplication(IApplication *app, QObject *parent)
|
|||
, ApplicationComponent(app)
|
||||
, m_cacheID {QString::number(Utils::Random::rand(), 36)}
|
||||
, m_authController {new AuthController(this, app, this)}
|
||||
, m_workerThread {new QThread}
|
||||
, m_freeDiskSpaceChecker {new FreeDiskSpaceChecker}
|
||||
, m_freeDiskSpaceCheckingTimer {new QTimer(this)}
|
||||
{
|
||||
declarePublicAPI(u"auth/login"_s);
|
||||
|
||||
|
@ -163,6 +175,16 @@ WebApplication::WebApplication(IApplication *app, QObject *parent)
|
|||
}
|
||||
m_sessionCookieName = DEFAULT_SESSION_COOKIE_NAME;
|
||||
}
|
||||
|
||||
m_freeDiskSpaceChecker->moveToThread(m_workerThread.get());
|
||||
connect(m_workerThread.get(), &QThread::finished, m_freeDiskSpaceChecker, &QObject::deleteLater);
|
||||
m_workerThread->start();
|
||||
|
||||
m_freeDiskSpaceCheckingTimer->setInterval(FREEDISKSPACE_CHECK_TIMEOUT);
|
||||
m_freeDiskSpaceCheckingTimer->setSingleShot(true);
|
||||
connect(m_freeDiskSpaceCheckingTimer, &QTimer::timeout, m_freeDiskSpaceChecker, &FreeDiskSpaceChecker::check);
|
||||
connect(m_freeDiskSpaceChecker, &FreeDiskSpaceChecker::checked, m_freeDiskSpaceCheckingTimer, qOverload<>(&QTimer::start));
|
||||
QMetaObject::invokeMethod(m_freeDiskSpaceChecker, &FreeDiskSpaceChecker::check);
|
||||
}
|
||||
|
||||
WebApplication::~WebApplication()
|
||||
|
@ -269,6 +291,16 @@ const Http::Environment &WebApplication::env() const
|
|||
return m_env;
|
||||
}
|
||||
|
||||
void WebApplication::setUsername(const QString &username)
|
||||
{
|
||||
m_authController->setUsername(username);
|
||||
}
|
||||
|
||||
void WebApplication::setPasswordHash(const QByteArray &passwordHash)
|
||||
{
|
||||
m_authController->setPasswordHash(passwordHash);
|
||||
}
|
||||
|
||||
void WebApplication::doProcessRequest()
|
||||
{
|
||||
const QRegularExpressionMatch match = m_apiPathPattern.match(request().path);
|
||||
|
@ -357,17 +389,17 @@ void WebApplication::configure()
|
|||
{
|
||||
const auto *pref = Preferences::instance();
|
||||
|
||||
const bool isAltUIUsed = pref->isAltWebUiEnabled();
|
||||
const Path rootFolder = (!isAltUIUsed ? Path(WWW_FOLDER) : pref->getWebUiRootFolder());
|
||||
const bool isAltUIUsed = pref->isAltWebUIEnabled();
|
||||
const Path rootFolder = (!isAltUIUsed ? Path(WWW_FOLDER) : pref->getWebUIRootFolder());
|
||||
if ((isAltUIUsed != m_isAltUIUsed) || (rootFolder != m_rootFolder))
|
||||
{
|
||||
m_isAltUIUsed = isAltUIUsed;
|
||||
m_rootFolder = rootFolder;
|
||||
m_translatedFiles.clear();
|
||||
if (!m_isAltUIUsed)
|
||||
LogMsg(tr("Using built-in Web UI."));
|
||||
LogMsg(tr("Using built-in WebUI."));
|
||||
else
|
||||
LogMsg(tr("Using custom Web UI. Location: \"%1\".").arg(m_rootFolder.toString()));
|
||||
LogMsg(tr("Using custom WebUI. Location: \"%1\".").arg(m_rootFolder.toString()));
|
||||
}
|
||||
|
||||
const QString newLocale = pref->getLocale();
|
||||
|
@ -379,27 +411,27 @@ void WebApplication::configure()
|
|||
m_translationFileLoaded = m_translator.load((m_rootFolder / Path(u"translations/webui_"_s) + newLocale).data());
|
||||
if (m_translationFileLoaded)
|
||||
{
|
||||
LogMsg(tr("Web UI translation for selected locale (%1) has been successfully loaded.")
|
||||
LogMsg(tr("WebUI translation for selected locale (%1) has been successfully loaded.")
|
||||
.arg(newLocale));
|
||||
}
|
||||
else
|
||||
{
|
||||
LogMsg(tr("Couldn't load Web UI translation for selected locale (%1).").arg(newLocale), Log::WARNING);
|
||||
LogMsg(tr("Couldn't load WebUI translation for selected locale (%1).").arg(newLocale), Log::WARNING);
|
||||
}
|
||||
}
|
||||
|
||||
m_isLocalAuthEnabled = pref->isWebUiLocalAuthEnabled();
|
||||
m_isAuthSubnetWhitelistEnabled = pref->isWebUiAuthSubnetWhitelistEnabled();
|
||||
m_authSubnetWhitelist = pref->getWebUiAuthSubnetWhitelist();
|
||||
m_isLocalAuthEnabled = pref->isWebUILocalAuthEnabled();
|
||||
m_isAuthSubnetWhitelistEnabled = pref->isWebUIAuthSubnetWhitelistEnabled();
|
||||
m_authSubnetWhitelist = pref->getWebUIAuthSubnetWhitelist();
|
||||
m_sessionTimeout = pref->getWebUISessionTimeout();
|
||||
|
||||
m_domainList = pref->getServerDomains().split(u';', Qt::SkipEmptyParts);
|
||||
std::for_each(m_domainList.begin(), m_domainList.end(), [](QString &entry) { entry = entry.trimmed(); });
|
||||
|
||||
m_isCSRFProtectionEnabled = pref->isWebUiCSRFProtectionEnabled();
|
||||
m_isSecureCookieEnabled = pref->isWebUiSecureCookieEnabled();
|
||||
m_isCSRFProtectionEnabled = pref->isWebUICSRFProtectionEnabled();
|
||||
m_isSecureCookieEnabled = pref->isWebUISecureCookieEnabled();
|
||||
m_isHostHeaderValidationEnabled = pref->isWebUIHostHeaderValidationEnabled();
|
||||
m_isHttpsEnabled = pref->isWebUiHttpsEnabled();
|
||||
m_isHttpsEnabled = pref->isWebUIHttpsEnabled();
|
||||
|
||||
m_prebuiltHeaders.clear();
|
||||
m_prebuiltHeaders.push_back({Http::HEADER_X_XSS_PROTECTION, u"1; mode=block"_s});
|
||||
|
@ -411,7 +443,7 @@ void WebApplication::configure()
|
|||
m_prebuiltHeaders.push_back({Http::HEADER_REFERRER_POLICY, u"same-origin"_s});
|
||||
}
|
||||
|
||||
const bool isClickjackingProtectionEnabled = pref->isWebUiClickjackingProtectionEnabled();
|
||||
const bool isClickjackingProtectionEnabled = pref->isWebUIClickjackingProtectionEnabled();
|
||||
if (isClickjackingProtectionEnabled)
|
||||
m_prebuiltHeaders.push_back({Http::HEADER_X_FRAME_OPTIONS, u"SAMEORIGIN"_s});
|
||||
|
||||
|
@ -680,14 +712,18 @@ void WebApplication::sessionStart()
|
|||
});
|
||||
|
||||
m_currentSession = new WebSession(generateSid(), app());
|
||||
m_sessions[m_currentSession->id()] = m_currentSession;
|
||||
|
||||
m_currentSession->registerAPIController<AppController>(u"app"_s);
|
||||
m_currentSession->registerAPIController<LogController>(u"log"_s);
|
||||
m_currentSession->registerAPIController<RSSController>(u"rss"_s);
|
||||
m_currentSession->registerAPIController<SearchController>(u"search"_s);
|
||||
m_currentSession->registerAPIController<SyncController>(u"sync"_s);
|
||||
m_currentSession->registerAPIController<TorrentsController>(u"torrents"_s);
|
||||
m_currentSession->registerAPIController<TransferController>(u"transfer"_s);
|
||||
m_sessions[m_currentSession->id()] = m_currentSession;
|
||||
|
||||
auto *syncController = m_currentSession->registerAPIController<SyncController>(u"sync"_s);
|
||||
syncController->updateFreeDiskSpace(m_freeDiskSpaceChecker->lastResult());
|
||||
connect(m_freeDiskSpaceChecker, &FreeDiskSpaceChecker::checked, syncController, &SyncController::updateFreeDiskSpace);
|
||||
|
||||
QNetworkCookie cookie {m_sessionCookieName.toLatin1(), m_currentSession->id().toUtf8()};
|
||||
cookie.setHttpOnly(true);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2014, 2017, 2022 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2014, 2017, 2022-2023 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
|
||||
|
@ -49,13 +49,17 @@
|
|||
#include "base/http/types.h"
|
||||
#include "base/path.h"
|
||||
#include "base/utils/net.h"
|
||||
#include "base/utils/thread.h"
|
||||
#include "base/utils/version.h"
|
||||
#include "api/isessionmanager.h"
|
||||
|
||||
inline const Utils::Version<3, 2> API_VERSION {2, 9, 2};
|
||||
inline const Utils::Version<3, 2> API_VERSION {2, 9, 3};
|
||||
|
||||
class QTimer;
|
||||
|
||||
class APIController;
|
||||
class AuthController;
|
||||
class FreeDiskSpaceChecker;
|
||||
class WebApplication;
|
||||
|
||||
class WebSession final : public QObject, public ApplicationComponent, public ISession
|
||||
|
@ -69,10 +73,12 @@ public:
|
|||
void updateTimestamp();
|
||||
|
||||
template <typename T>
|
||||
void registerAPIController(const QString &scope)
|
||||
T *registerAPIController(const QString &scope)
|
||||
{
|
||||
static_assert(std::is_base_of_v<APIController, T>, "Class should be derived from APIController.");
|
||||
m_apiControllers[scope] = new T(app(), this);
|
||||
auto *controller = new T(app(), this);
|
||||
m_apiControllers[scope] = controller;
|
||||
return controller;
|
||||
}
|
||||
|
||||
APIController *getAPIController(const QString &scope) const;
|
||||
|
@ -97,15 +103,18 @@ public:
|
|||
|
||||
Http::Response processRequest(const Http::Request &request, const Http::Environment &env) override;
|
||||
|
||||
const Http::Request &request() const;
|
||||
const Http::Environment &env() const;
|
||||
|
||||
void setUsername(const QString &username);
|
||||
void setPasswordHash(const QByteArray &passwordHash);
|
||||
|
||||
private:
|
||||
QString clientId() const override;
|
||||
WebSession *session() override;
|
||||
void sessionStart() override;
|
||||
void sessionEnd() override;
|
||||
|
||||
const Http::Request &request() const;
|
||||
const Http::Environment &env() const;
|
||||
|
||||
private:
|
||||
void doProcessRequest();
|
||||
void configure();
|
||||
|
||||
|
@ -241,4 +250,8 @@ private:
|
|||
QHostAddress m_clientAddress;
|
||||
|
||||
QVector<Http::Header> m_prebuiltHeaders;
|
||||
|
||||
Utils::Thread::UniquePtr m_workerThread;
|
||||
FreeDiskSpaceChecker *m_freeDiskSpaceChecker = nullptr;
|
||||
QTimer *m_freeDiskSpaceCheckingTimer = nullptr;
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2015, 2023 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
|
||||
|
@ -28,6 +28,7 @@
|
|||
|
||||
#include "webui.h"
|
||||
|
||||
#include "base/global.h"
|
||||
#include "base/http/server.h"
|
||||
#include "base/logger.h"
|
||||
#include "base/net/dnsupdater.h"
|
||||
|
@ -36,10 +37,12 @@
|
|||
#include "base/preferences.h"
|
||||
#include "base/utils/io.h"
|
||||
#include "base/utils/net.h"
|
||||
#include "base/utils/password.h"
|
||||
#include "webapplication.h"
|
||||
|
||||
WebUI::WebUI(IApplication *app)
|
||||
WebUI::WebUI(IApplication *app, const QByteArray &tempPasswordHash)
|
||||
: ApplicationComponent(app)
|
||||
, m_passwordHash {tempPasswordHash}
|
||||
{
|
||||
configure();
|
||||
connect(Preferences::instance(), &Preferences::changed, this, &WebUI::configure);
|
||||
|
@ -49,12 +52,23 @@ void WebUI::configure()
|
|||
{
|
||||
m_isErrored = false; // clear previous error state
|
||||
|
||||
const QString portForwardingProfile = u"webui"_s;
|
||||
const Preferences *pref = Preferences::instance();
|
||||
const quint16 port = pref->getWebUiPort();
|
||||
const bool isEnabled = pref->isWebUIEnabled();
|
||||
const QString username = pref->getWebUIUsername();
|
||||
if (const QByteArray passwordHash = pref->getWebUIPassword(); !passwordHash.isEmpty())
|
||||
m_passwordHash = passwordHash;
|
||||
|
||||
if (pref->isWebUiEnabled())
|
||||
if (isEnabled && (username.isEmpty() || m_passwordHash.isEmpty()))
|
||||
{
|
||||
setError(tr("Credentials are not set"));
|
||||
}
|
||||
|
||||
const QString portForwardingProfile = u"webui"_s;
|
||||
|
||||
if (isEnabled && !m_isErrored)
|
||||
{
|
||||
const quint16 port = pref->getWebUIPort();
|
||||
|
||||
// Port forwarding
|
||||
auto *portForwarder = Net::PortForwarder::instance();
|
||||
if (pref->useUPnPForWebUIPort())
|
||||
|
@ -67,7 +81,7 @@ void WebUI::configure()
|
|||
}
|
||||
|
||||
// http server
|
||||
const QString serverAddressString = pref->getWebUiAddress();
|
||||
const QString serverAddressString = pref->getWebUIAddress();
|
||||
const auto serverAddress = ((serverAddressString == u"*") || serverAddressString.isEmpty())
|
||||
? QHostAddress::Any : QHostAddress(serverAddressString);
|
||||
|
||||
|
@ -82,7 +96,10 @@ void WebUI::configure()
|
|||
m_httpServer->close();
|
||||
}
|
||||
|
||||
if (pref->isWebUiHttpsEnabled())
|
||||
m_webapp->setUsername(username);
|
||||
m_webapp->setPasswordHash(m_passwordHash);
|
||||
|
||||
if (pref->isWebUIHttpsEnabled())
|
||||
{
|
||||
const auto readData = [](const Path &path) -> QByteArray
|
||||
{
|
||||
|
@ -94,9 +111,9 @@ void WebUI::configure()
|
|||
|
||||
const bool success = m_httpServer->setupHttps(cert, key);
|
||||
if (success)
|
||||
LogMsg(tr("Web UI: HTTPS setup successful"));
|
||||
LogMsg(tr("WebUI: HTTPS setup successful"));
|
||||
else
|
||||
LogMsg(tr("Web UI: HTTPS setup failed, fallback to HTTP"), Log::CRITICAL);
|
||||
LogMsg(tr("WebUI: HTTPS setup failed, fallback to HTTP"), Log::CRITICAL);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -108,17 +125,12 @@ void WebUI::configure()
|
|||
const bool success = m_httpServer->listen(serverAddress, port);
|
||||
if (success)
|
||||
{
|
||||
LogMsg(tr("Web UI: Now listening on IP: %1, port: %2").arg(serverAddressString).arg(port));
|
||||
LogMsg(tr("WebUI: Now listening on IP: %1, port: %2").arg(serverAddressString).arg(port));
|
||||
}
|
||||
else
|
||||
{
|
||||
const QString errorMsg = tr("Web UI: Unable to bind to IP: %1, port: %2. Reason: %3")
|
||||
.arg(serverAddressString).arg(port).arg(m_httpServer->errorString());
|
||||
LogMsg(errorMsg, Log::CRITICAL);
|
||||
qCritical() << errorMsg;
|
||||
|
||||
m_isErrored = true;
|
||||
emit fatalError();
|
||||
setError(tr("Unable to bind to IP: %1, port: %2. Reason: %3")
|
||||
.arg(serverAddressString).arg(port).arg(m_httpServer->errorString()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -145,7 +157,24 @@ void WebUI::configure()
|
|||
}
|
||||
}
|
||||
|
||||
void WebUI::setError(const QString &message)
|
||||
{
|
||||
m_isErrored = true;
|
||||
m_errorMsg = message;
|
||||
|
||||
const QString logMessage = u"WebUI: " + m_errorMsg;
|
||||
LogMsg(logMessage, Log::CRITICAL);
|
||||
qCritical() << logMessage;
|
||||
|
||||
emit fatalError();
|
||||
}
|
||||
|
||||
bool WebUI::isErrored() const
|
||||
{
|
||||
return m_isErrored;
|
||||
}
|
||||
|
||||
QString WebUI::errorMessage() const
|
||||
{
|
||||
return m_errorMsg;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2015, 2023 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
|
||||
|
@ -51,9 +51,10 @@ class WebUI : public QObject, public ApplicationComponent
|
|||
Q_DISABLE_COPY_MOVE(WebUI)
|
||||
|
||||
public:
|
||||
explicit WebUI(IApplication *app);
|
||||
explicit WebUI(IApplication *app, const QByteArray &tempPasswordHash = {});
|
||||
|
||||
bool isErrored() const;
|
||||
QString errorMessage() const;
|
||||
|
||||
signals:
|
||||
void fatalError();
|
||||
|
@ -62,8 +63,13 @@ private slots:
|
|||
void configure();
|
||||
|
||||
private:
|
||||
void setError(const QString &message);
|
||||
|
||||
bool m_isErrored = false;
|
||||
QString m_errorMsg;
|
||||
QPointer<Http::Server> m_httpServer;
|
||||
QPointer<Net::DNSUpdater> m_dnsUpdater;
|
||||
QPointer<WebApplication> m_webapp;
|
||||
|
||||
QByteArray m_passwordHash;
|
||||
};
|
||||
|
|
|
@ -3,7 +3,6 @@ HEADERS += \
|
|||
$$PWD/api/apierror.h \
|
||||
$$PWD/api/appcontroller.h \
|
||||
$$PWD/api/authcontroller.h \
|
||||
$$PWD/api/freediskspacechecker.h \
|
||||
$$PWD/api/isessionmanager.h \
|
||||
$$PWD/api/logcontroller.h \
|
||||
$$PWD/api/rsscontroller.h \
|
||||
|
@ -12,6 +11,7 @@ HEADERS += \
|
|||
$$PWD/api/torrentscontroller.h \
|
||||
$$PWD/api/transfercontroller.h \
|
||||
$$PWD/api/serialize/serialize_torrent.h \
|
||||
$$PWD/freediskspacechecker.h \
|
||||
$$PWD/webapplication.h \
|
||||
$$PWD/webui.h
|
||||
|
||||
|
@ -20,7 +20,6 @@ SOURCES += \
|
|||
$$PWD/api/apierror.cpp \
|
||||
$$PWD/api/appcontroller.cpp \
|
||||
$$PWD/api/authcontroller.cpp \
|
||||
$$PWD/api/freediskspacechecker.cpp \
|
||||
$$PWD/api/logcontroller.cpp \
|
||||
$$PWD/api/rsscontroller.cpp \
|
||||
$$PWD/api/searchcontroller.cpp \
|
||||
|
@ -28,6 +27,7 @@ SOURCES += \
|
|||
$$PWD/api/torrentscontroller.cpp \
|
||||
$$PWD/api/transfercontroller.cpp \
|
||||
$$PWD/api/serialize/serialize_torrent.cpp \
|
||||
$$PWD/freediskspacechecker.cpp \
|
||||
$$PWD/webapplication.cpp \
|
||||
$$PWD/webui.cpp
|
||||
|
||||
|
|
|
@ -178,8 +178,7 @@ a.propButton img {
|
|||
}
|
||||
|
||||
.scrollableMenu {
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
overflow: hidden auto;
|
||||
}
|
||||
|
||||
/* context menu specific */
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>QBT_TR(Renaming))QBT_TR[CONTEXT=TorrentContentTreeView]</title>
|
||||
<title>QBT_TR(Renaming)QBT_TR[CONTEXT=TorrentContentTreeView]</title>
|
||||
<script src="scripts/lib/MooTools-Core-1.6.0-compat-compressed.js"></script>
|
||||
<script src="scripts/lib/MooTools-More-1.6.0-compat-compressed.js"></script>
|
||||
<script src="scripts/filesystem.js?v=${CACHEID}"></script>
|
||||
|
|
|
@ -488,7 +488,21 @@ window.addEvent('load', function() {
|
|||
Object.each(category_list, function(category) {
|
||||
sortedCategories.push(category.name);
|
||||
});
|
||||
sortedCategories.sort();
|
||||
sortedCategories.sort(function(category1, category2) {
|
||||
for (let i = 0; i < Math.min(category1.length, category2.length); ++i) {
|
||||
if (category1[i] === "/" && category2[i] !== "/") {
|
||||
return -1;
|
||||
}
|
||||
else if (category1[i] !== "/" && category2[i] === "/") {
|
||||
return 1;
|
||||
}
|
||||
else if (category1[i] !== category2[i]) {
|
||||
return category1[i].localeCompare(category2[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return category1.length - category2.length;
|
||||
});
|
||||
|
||||
for (let i = 0; i < sortedCategories.length; ++i) {
|
||||
const categoryName = sortedCategories[i];
|
||||
|
|
|
@ -111,7 +111,8 @@ window.qBittorrent.DynamicTable = (function() {
|
|||
|
||||
let n = 2;
|
||||
|
||||
while (panel.clientWidth != panel.offsetWidth && n > 0) { // is panel vertical scrollbar visible ?
|
||||
// is panel vertical scrollbar visible or does panel content not fit?
|
||||
while (((panel.clientWidth != panel.offsetWidth) || (panel.clientHeight != panel.scrollHeight)) && (n > 0)) {
|
||||
--n;
|
||||
h -= 0.5;
|
||||
$(this.dynamicTableDivId).style.height = h + 'px';
|
||||
|
@ -1001,10 +1002,13 @@ window.qBittorrent.DynamicTable = (function() {
|
|||
case "checkingUP":
|
||||
case "queuedForChecking":
|
||||
case "checkingResumeData":
|
||||
case "moving":
|
||||
state = "force-recheck";
|
||||
img_path = "images/force-recheck.svg";
|
||||
break;
|
||||
case "moving":
|
||||
state = "moving";
|
||||
img_path = "images/set-location.svg";
|
||||
break;
|
||||
case "error":
|
||||
case "unknown":
|
||||
case "missingFiles":
|
||||
|
|
|
@ -167,7 +167,7 @@
|
|||
window.parent.closeWindows();
|
||||
});
|
||||
|
||||
if (Browser.platform === 'ios') {
|
||||
if ((Browser.platform === 'ios') || ((Browser.platform === 'mac') && (navigator.maxTouchPoints > 1))) {
|
||||
$('fileselect').accept = ".torrent";
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -349,6 +349,33 @@
|
|||
</table>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="settings">
|
||||
<legend>
|
||||
<input type="checkbox" id="i2pEnabledCheckbox" onclick="qBittorrent.Preferences.updateI2PSettingsEnabled();" />
|
||||
<label for="i2pEnabledCheckbox">QBT_TR(I2P (Experimental) (requires libtorrent >= 2.0))QBT_TR[CONTEXT=OptionsDialog]</label>
|
||||
</legend>
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="i2pAddress">QBT_TR(Host:)QBT_TR[CONTEXT=OptionsDialog]</label>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="i2pAddress" />
|
||||
</td>
|
||||
<td>
|
||||
<label for="i2pPort">QBT_TR(Port:)QBT_TR[CONTEXT=OptionsDialog]</label>
|
||||
</td>
|
||||
<td>
|
||||
<input type="number" id="i2pPort" min="0" max="65535" onchange="qBittorrent.Preferences.numberInputLimiter(this);" style="width: 5em;" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="formRow">
|
||||
<input type="checkbox" id="i2pMixedMode" title="QBT_TR(If "mixed mode" is enabled, I2P torrents are allowed to also get peers from other sources than the tracker, and connect to regular IPs, not providing any anonymization. This may be useful if the user is not interested in the anonymization of I2P, but still wants to be able to connect to I2P peers.)QBT_TR[CONTEXT=OptionsDialog]" />
|
||||
<label for="i2pMixedMode">QBT_TR(Mixed mode)QBT_TR[CONTEXT=OptionsDialog]</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="settings">
|
||||
<legend>QBT_TR(Proxy Server)QBT_TR[CONTEXT=OptionsDialog]</legend>
|
||||
<table>
|
||||
|
@ -1420,6 +1447,38 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD
|
|||
<input type="text" id="requestQueueSize" style="width: 15em;" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="i2pInboundQuantity">QBT_TR(I2P inbound quantity (requires libtorrent >= 2.0):)QBT_TR[CONTEXT=OptionsDialog] <a href="https://www.libtorrent.org/reference-Settings.html#i2p_inbound_quantity" target="_blank">(?)</a></label>
|
||||
</td>
|
||||
<td>
|
||||
<input id="i2pInboundQuantity" type="number" min="1" max="16" onchange="qBittorrent.Preferences.numberInputLimiter(this);" style="width: 15em;" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="i2pOutboundQuantity">QBT_TR(I2P outbound quantity (requires libtorrent >= 2.0):)QBT_TR[CONTEXT=OptionsDialog] <a href="https://www.libtorrent.org/reference-Settings.html#i2p_outbound_quantity" target="_blank">(?)</a></label>
|
||||
</td>
|
||||
<td>
|
||||
<input id="i2pOutboundQuantity" type="number" min="1" max="16" onchange="qBittorrent.Preferences.numberInputLimiter(this);" style="width: 15em;" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="i2pInboundLength">QBT_TR(I2P inbound length (requires libtorrent >= 2.0):)QBT_TR[CONTEXT=OptionsDialog] <a href="https://www.libtorrent.org/reference-Settings.html#i2p_inbound_length" target="_blank">(?)</a></label>
|
||||
</td>
|
||||
<td>
|
||||
<input id="i2pInboundLength" type="number" min="0" max="7" onchange="qBittorrent.Preferences.numberInputLimiter(this);" style="width: 15em;" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="i2pOutboundLength">QBT_TR(I2P outbound length (requires libtorrent >= 2.0):)QBT_TR[CONTEXT=OptionsDialog] <a href="https://www.libtorrent.org/reference-Settings.html#i2p_outbound_length" target="_blank">(?)</a></label>
|
||||
</td>
|
||||
<td>
|
||||
<input id="i2pOutboundLength" type="number" min="0" max="7" onchange="qBittorrent.Preferences.numberInputLimiter(this);" style="width: 15em;" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
@ -1455,6 +1514,7 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD
|
|||
updateMaxConnecPerTorrentEnabled: updateMaxConnecPerTorrentEnabled,
|
||||
updateMaxUploadsEnabled: updateMaxUploadsEnabled,
|
||||
updateMaxUploadsPerTorrentEnabled: updateMaxUploadsPerTorrentEnabled,
|
||||
updateI2PSettingsEnabled: updateI2PSettingsEnabled,
|
||||
updatePeerProxySettings: updatePeerProxySettings,
|
||||
updatePeerProxyAuthSettings: updatePeerProxyAuthSettings,
|
||||
updateFilterSettings: updateFilterSettings,
|
||||
|
@ -1644,6 +1704,13 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD
|
|||
$('max_uploads_per_torrent_value').setProperty('disabled', !isMaxUploadsPerTorrentEnabled);
|
||||
};
|
||||
|
||||
const updateI2PSettingsEnabled = function() {
|
||||
const isI2PEnabled = $('i2pEnabledCheckbox').getProperty('checked');
|
||||
$('i2pAddress').setProperty('disabled', !isI2PEnabled);
|
||||
$('i2pPort').setProperty('disabled', !isI2PEnabled);
|
||||
$('i2pMixedMode').setProperty('disabled', !isI2PEnabled);
|
||||
};
|
||||
|
||||
const updatePeerProxySettings = function() {
|
||||
const proxyType = $('peer_proxy_type_select').getProperty('value');
|
||||
const isProxyDisabled = (proxyType === "None");
|
||||
|
@ -2023,6 +2090,13 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD
|
|||
}
|
||||
updateMaxUploadsPerTorrentEnabled();
|
||||
|
||||
// I2P
|
||||
$('i2pEnabledCheckbox').setProperty('checked', pref.i2p_enabled);
|
||||
$('i2pAddress').setProperty('value', pref.i2p_address);
|
||||
$('i2pPort').setProperty('value', pref.i2p_port);
|
||||
$('i2pMixedMode').setProperty('checked', pref.i2p_mixed_mode);
|
||||
updateI2PSettingsEnabled();
|
||||
|
||||
// Proxy Server
|
||||
$('peer_proxy_type_select').setProperty('value', pref.proxy_type);
|
||||
$('peer_proxy_host_text').setProperty('value', pref.proxy_ip);
|
||||
|
@ -2241,6 +2315,10 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD
|
|||
$('peerTurnoverCutoff').setProperty('value', pref.peer_turnover_cutoff);
|
||||
$('peerTurnoverInterval').setProperty('value', pref.peer_turnover_interval);
|
||||
$('requestQueueSize').setProperty('value', pref.request_queue_size);
|
||||
$('i2pInboundQuantity').setProperty('value', pref.i2p_inbound_quantity);
|
||||
$('i2pOutboundQuantity').setProperty('value', pref.i2p_outbound_quantity);
|
||||
$('i2pInboundLength').setProperty('value', pref.i2p_inbound_length);
|
||||
$('i2pOutboundLength').setProperty('value', pref.i2p_outbound_length);
|
||||
}
|
||||
}
|
||||
}).send();
|
||||
|
@ -2360,6 +2438,12 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD
|
|||
}
|
||||
settings.set('max_uploads_per_torrent', max_uploads_per_torrent);
|
||||
|
||||
// I2P
|
||||
settings.set('i2p_enabled', $('i2pEnabledCheckbox').getProperty('checked'));
|
||||
settings.set('i2p_address', $('i2pAddress').getProperty('value'));
|
||||
settings.set('i2p_port', $('i2pPort').getProperty('value').toInt());
|
||||
settings.set('i2p_mixed_mode', $('i2pMixedMode').getProperty('checked'));
|
||||
|
||||
// Proxy Server
|
||||
settings.set('proxy_type', $('peer_proxy_type_select').getProperty('value'));
|
||||
settings.set('proxy_ip', $('peer_proxy_host_text').getProperty('value'));
|
||||
|
@ -2673,6 +2757,10 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD
|
|||
settings.set('peer_turnover_cutoff', $('peerTurnoverCutoff').getProperty('value'));
|
||||
settings.set('peer_turnover_interval', $('peerTurnoverInterval').getProperty('value'));
|
||||
settings.set('request_queue_size', $('requestQueueSize').getProperty('value'));
|
||||
settings.set('i2p_inbound_quantity', $('i2pInboundQuantity').getProperty('value'));
|
||||
settings.set('i2p_outbound_quantity', $('i2pOutboundQuantity').getProperty('value'));
|
||||
settings.set('i2p_inbound_length', $('i2pInboundLength').getProperty('value'));
|
||||
settings.set('i2p_outbound_length', $('i2pOutboundLength').getProperty('value'));
|
||||
|
||||
// Send it to qBT
|
||||
const json_str = JSON.encode(settings);
|
||||
|
|
|
@ -593,7 +593,10 @@ Supports the formats: S01E01, 1x1, 2017.12.31 and 31.12.2017 (Date formats also
|
|||
|
||||
rulesList[rule].torrentParams.category = $('assignCategoryCombobox').value;
|
||||
rulesList[rule].torrentParams.tags = $('ruleAddTags').value.split(',');
|
||||
rulesList[rule].torrentParams.save_path = $('savetoDifferentDir').checked ? $('saveToText').value : '';
|
||||
if ($('savetoDifferentDir').checked) {
|
||||
rulesList[rule].torrentParams.save_path = $('saveToText').value;
|
||||
rulesList[rule].torrentParams.use_auto_tmm = false;
|
||||
}
|
||||
|
||||
switch ($('addPausedCombobox').value) {
|
||||
case 'default':
|
||||
|
@ -715,8 +718,6 @@ Supports the formats: S01E01, 1x1, 2017.12.31 and 31.12.2017 (Date formats also
|
|||
$('assignCategoryCombobox').disabled = false;
|
||||
$('ruleAddTags').disabled = false;
|
||||
$('savetoDifferentDir').disabled = false;
|
||||
$('savetoDifferentDir').checked = rulesList[ruleName].torrentParams.save_path ? false : true;
|
||||
$('saveToText').disabled = rulesList[ruleName].torrentParams.save_path ? false : true;
|
||||
$('ignoreDaysValue').disabled = false;
|
||||
$('addPausedCombobox').disabled = false;
|
||||
$('contentLayoutCombobox').disabled = false;
|
||||
|
@ -731,6 +732,7 @@ Supports the formats: S01E01, 1x1, 2017.12.31 and 31.12.2017 (Date formats also
|
|||
$('assignCategoryCombobox').value = rulesList[ruleName].torrentParams.category ? rulesList[ruleName].torrentParams.category : 'default';
|
||||
$('ruleAddTags').value = rulesList[ruleName].torrentParams.tags.join(',');
|
||||
$('savetoDifferentDir').checked = rulesList[ruleName].torrentParams.save_path !== '';
|
||||
$('saveToText').disabled = !$('savetoDifferentDir').checked;
|
||||
$('saveToText').value = rulesList[ruleName].torrentParams.save_path;
|
||||
$('ignoreDaysValue').value = rulesList[ruleName].ignoreDays;
|
||||
|
||||
|
|
|
@ -23,14 +23,14 @@
|
|||
<img src="images/qbittorrent-tray.svg" alt="qBittorrent logo" />
|
||||
</div>
|
||||
<div id="formplace" class="col">
|
||||
<form id="loginform" method="post" onsubmit="submitLoginForm();">
|
||||
<form id="loginform" method="post" onsubmit="submitLoginForm(event);">
|
||||
<div class="row">
|
||||
<label for="username">QBT_TR(Username)QBT_TR[CONTEXT=HttpServer]</label><br />
|
||||
<input type="text" id="username" name="username" autocomplete="username" />
|
||||
<input type="text" id="username" name="username" autocomplete="username" required />
|
||||
</div>
|
||||
<div class="row">
|
||||
<label for="password">QBT_TR(Password)QBT_TR[CONTEXT=HttpServer]</label><br />
|
||||
<input type="password" id="password" name="password" autocomplete="current-password" />
|
||||
<input type="password" id="password" name="password" autocomplete="current-password" required />
|
||||
</div>
|
||||
<div class="row">
|
||||
<input type="submit" id="login" value="QBT_TR(Login)QBT_TR[CONTEXT=HttpServer]" />
|
||||
|
|
|
@ -31,13 +31,10 @@
|
|||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.getElementById('username').focus();
|
||||
document.getElementById('username').select();
|
||||
|
||||
document.getElementById('loginform').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
});
|
||||
});
|
||||
|
||||
function submitLoginForm() {
|
||||
function submitLoginForm(event) {
|
||||
event.preventDefault();
|
||||
const errorMsgElement = document.getElementById('error_msg');
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
|
|
Loading…
Reference in a new issue