diff --git a/src/base/preferences.cpp b/src/base/preferences.cpp index 5556c8add..56d7057a6 100644 --- a/src/base/preferences.cpp +++ b/src/base/preferences.cpp @@ -1487,6 +1487,19 @@ void Preferences::setTrackerPortForwardingEnabled(const bool enabled) setValue(u"Preferences/Advanced/trackerPortForwarding"_s, enabled); } +Path Preferences::getPythonExecutablePath() const +{ + return value(u"Preferences/Search/pythonExecutablePath"_s, Path()); +} + +void Preferences::setPythonExecutablePath(const Path &path) +{ + if (path == getPythonExecutablePath()) + return; + + setValue(u"Preferences/Search/pythonExecutablePath"_s, path); +} + #if defined(Q_OS_WIN) || defined(Q_OS_MACOS) bool Preferences::isUpdateCheckEnabled() const { diff --git a/src/base/preferences.h b/src/base/preferences.h index 28de28d83..44d357453 100644 --- a/src/base/preferences.h +++ b/src/base/preferences.h @@ -305,6 +305,8 @@ public: void setTrackerPort(int port); bool isTrackerPortForwardingEnabled() const; void setTrackerPortForwardingEnabled(bool enabled); + Path getPythonExecutablePath() const; + void setPythonExecutablePath(const Path &path); #if defined(Q_OS_WIN) || defined(Q_OS_MACOS) bool isUpdateCheckEnabled() const; void setUpdateCheckEnabled(bool enabled); diff --git a/src/base/utils/foreignapps.cpp b/src/base/utils/foreignapps.cpp index 3249b51e6..8048688c6 100644 --- a/src/base/utils/foreignapps.cpp +++ b/src/base/utils/foreignapps.cpp @@ -44,6 +44,8 @@ #include "base/global.h" #include "base/logger.h" +#include "base/path.h" +#include "base/preferences.h" #include "base/utils/bytearray.h" using namespace Utils::ForeignApps; @@ -52,6 +54,8 @@ namespace { bool testPythonInstallation(const QString &exeName, PythonInfo &info) { + info = {}; + QProcess proc; proc.start(exeName, {u"--version"_s}, QIODevice::ReadOnly); if (proc.waitForFinished() && (proc.exitCode() == QProcess::NormalExit)) @@ -77,7 +81,7 @@ namespace return false; info = {exeName, version}; - LogMsg(QCoreApplication::translate("Utils::ForeignApps", "Python detected, executable name: '%1', version: %2") + LogMsg(QCoreApplication::translate("Utils::ForeignApps", "Found Python executable. Name: \"%1\". Version: \"%2\"") .arg(info.executableName, info.version.toString()), Log::INFO); return true; } @@ -254,20 +258,43 @@ bool Utils::ForeignApps::PythonInfo::isSupportedVersion() const PythonInfo Utils::ForeignApps::pythonInfo() { static PythonInfo pyInfo; - if (!pyInfo.isValid()) + + const QString preferredPythonPath = Preferences::instance()->getPythonExecutablePath().toString(); + if (pyInfo.isValid() && (preferredPythonPath == pyInfo.executableName)) + return pyInfo; + + if (!preferredPythonPath.isEmpty()) { - if (testPythonInstallation(u"python3"_s, pyInfo)) + if (testPythonInstallation(preferredPythonPath, pyInfo)) return pyInfo; + LogMsg(QCoreApplication::translate("Utils::ForeignApps", "Failed to find Python executable. Path: \"%1\".") + .arg(preferredPythonPath), Log::WARNING); + } + else + { + // auto detect only when there are no preferred python path - if (testPythonInstallation(u"python"_s, pyInfo)) - return pyInfo; + if (!pyInfo.isValid()) + { + if (testPythonInstallation(u"python3"_s, pyInfo)) + return pyInfo; + LogMsg(QCoreApplication::translate("Utils::ForeignApps", "Failed to find `python3` executable in PATH environment variable. PATH: \"%1\"") + .arg(qEnvironmentVariable("PATH")), Log::INFO); + + if (testPythonInstallation(u"python"_s, pyInfo)) + return pyInfo; + LogMsg(QCoreApplication::translate("Utils::ForeignApps", "Failed to find `python` executable in PATH environment variable. PATH: \"%1\"") + .arg(qEnvironmentVariable("PATH")), Log::INFO); #if defined(Q_OS_WIN) - if (testPythonInstallation(findPythonPath(), pyInfo)) - return pyInfo; + if (testPythonInstallation(findPythonPath(), pyInfo)) + return pyInfo; + LogMsg(QCoreApplication::translate("Utils::ForeignApps", "Failed to find `python` executable in Windows Registry."), Log::INFO); #endif - LogMsg(QCoreApplication::translate("Utils::ForeignApps", "Python not detected"), Log::INFO); + LogMsg(QCoreApplication::translate("Utils::ForeignApps", "Failed to find Python executable"), Log::WARNING); + + } } return pyInfo; diff --git a/src/gui/advancedsettings.cpp b/src/gui/advancedsettings.cpp index c53dac9d2..f5af1a095 100644 --- a/src/gui/advancedsettings.cpp +++ b/src/gui/advancedsettings.cpp @@ -99,6 +99,7 @@ namespace TRACKER_STATUS, TRACKER_PORT, TRACKER_PORT_FORWARDING, + PYTHON_EXECUTABLE_PATH, // libtorrent section LIBTORRENT_HEADER, BDECODE_DEPTH_LIMIT, @@ -316,7 +317,8 @@ void AdvancedSettings::saveAdvancedSettings() const pref->setTrackerPort(m_spinBoxTrackerPort.value()); pref->setTrackerPortForwardingEnabled(m_checkBoxTrackerPortForwarding.isChecked()); session->setTrackerEnabled(m_checkBoxTrackerStatus.isChecked()); - + // Python executable path + pref->setPythonExecutablePath(Path(m_pythonExecutablePath.text().trimmed())); // Choking algorithm session->setChokingAlgorithm(m_comboBoxChokingAlgorithm.currentData().value()); // Seed choking algorithm @@ -810,6 +812,10 @@ void AdvancedSettings::loadAdvancedSettings() // Tracker port forwarding m_checkBoxTrackerPortForwarding.setChecked(pref->isTrackerPortForwardingEnabled()); addRow(TRACKER_PORT_FORWARDING, tr("Enable port forwarding for embedded tracker"), &m_checkBoxTrackerPortForwarding); + // Python executable path + m_pythonExecutablePath.setPlaceholderText(tr("(Auto detect if empty)")); + m_pythonExecutablePath.setText(pref->getPythonExecutablePath().toString()); + addRow(PYTHON_EXECUTABLE_PATH, tr("Python executable path (may require restart)"), &m_pythonExecutablePath); // Choking algorithm m_comboBoxChokingAlgorithm.addItem(tr("Fixed slots"), QVariant::fromValue(BitTorrent::ChokingAlgorithm::FixedSlots)); m_comboBoxChokingAlgorithm.addItem(tr("Upload rate based"), QVariant::fromValue(BitTorrent::ChokingAlgorithm::RateBased)); diff --git a/src/gui/advancedsettings.h b/src/gui/advancedsettings.h index 2813969b6..ed9a14148 100644 --- a/src/gui/advancedsettings.h +++ b/src/gui/advancedsettings.h @@ -81,7 +81,7 @@ private: m_checkBoxSuggestMode, m_checkBoxSpeedWidgetEnabled, m_checkBoxIDNSupport; QComboBox m_comboBoxInterface, m_comboBoxInterfaceAddress, m_comboBoxDiskIOReadMode, m_comboBoxDiskIOWriteMode, m_comboBoxUtpMixedMode, m_comboBoxChokingAlgorithm, m_comboBoxSeedChokingAlgorithm, m_comboBoxResumeDataStorage; - QLineEdit m_lineEditAnnounceIP, m_lineEditDHTBootstrapNodes; + QLineEdit m_pythonExecutablePath, m_lineEditAnnounceIP, m_lineEditDHTBootstrapNodes; #ifndef QBT_USES_LIBTORRENT2 QSpinBox m_spinBoxCache, m_spinBoxCacheTTL; diff --git a/src/gui/mainwindow.cpp b/src/gui/mainwindow.cpp index 83fd19670..c005e8366 100644 --- a/src/gui/mainwindow.cpp +++ b/src/gui/mainwindow.cpp @@ -1635,11 +1635,11 @@ void MainWindow::on_actionRSSReader_triggered() void MainWindow::on_actionSearchWidget_triggered() { - if (!m_hasPython && m_ui->actionSearchWidget->isChecked()) + if (m_ui->actionSearchWidget->isChecked()) { const Utils::ForeignApps::PythonInfo pyInfo = Utils::ForeignApps::pythonInfo(); - // Not installed + // Not found if (!pyInfo.isValid()) { m_ui->actionSearchWidget->setChecked(false); @@ -1679,7 +1679,6 @@ void MainWindow::on_actionSearchWidget_triggered() return; } - m_hasPython = true; m_ui->actionSearchWidget->setChecked(true); Preferences::instance()->setSearchEnabled(true); } diff --git a/src/gui/mainwindow.h b/src/gui/mainwindow.h index c70f4c8e8..17f90b19e 100644 --- a/src/gui/mainwindow.h +++ b/src/gui/mainwindow.h @@ -233,7 +233,6 @@ private: // Power Management PowerManagement *m_pwr = nullptr; QTimer *m_preventTimer = nullptr; - bool m_hasPython = false; QMenu *m_toolbarMenu = nullptr; SettingValue m_storeExecutionLogEnabled; diff --git a/src/webui/api/appcontroller.cpp b/src/webui/api/appcontroller.cpp index 700586c40..d28464d1e 100644 --- a/src/webui/api/appcontroller.cpp +++ b/src/webui/api/appcontroller.cpp @@ -404,6 +404,8 @@ void AppController::preferencesAction() data[u"enable_embedded_tracker"_s] = session->isTrackerEnabled(); data[u"embedded_tracker_port"_s] = pref->getTrackerPort(); data[u"embedded_tracker_port_forwarding"_s] = pref->isTrackerPortForwardingEnabled(); + // Python executable path + data[u"python_executable_path"_s] = pref->getPythonExecutablePath().toString(); // Choking algorithm data[u"upload_slots_behavior"_s] = static_cast(session->chokingAlgorithm()); // Seed choking algorithm @@ -990,6 +992,9 @@ void AppController::setPreferencesAction() pref->setTrackerPortForwardingEnabled(it.value().toBool()); if (hasKey(u"enable_embedded_tracker"_s)) session->setTrackerEnabled(it.value().toBool()); + // Python executable path + if (hasKey(u"python_executable_path"_s)) + pref->setPythonExecutablePath(Path(it.value().toString())); // Choking algorithm if (hasKey(u"upload_slots_behavior"_s)) session->setChokingAlgorithm(static_cast(it.value().toInt())); diff --git a/src/webui/webapplication.h b/src/webui/webapplication.h index 1e5d1c2c8..d28c8a851 100644 --- a/src/webui/webapplication.h +++ b/src/webui/webapplication.h @@ -52,7 +52,7 @@ #include "base/utils/version.h" #include "api/isessionmanager.h" -inline const Utils::Version<3, 2> API_VERSION {2, 9, 4}; +inline const Utils::Version<3, 2> API_VERSION {2, 9, 5}; class APIController; class AuthController; diff --git a/src/webui/www/private/views/preferences.html b/src/webui/www/private/views/preferences.html index c11170905..dc79b5d91 100644 --- a/src/webui/www/private/views/preferences.html +++ b/src/webui/www/private/views/preferences.html @@ -1050,6 +1050,14 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD + + + + + + + +
@@ -2238,6 +2246,7 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD $('enableEmbeddedTracker').setProperty('checked', pref.enable_embedded_tracker); $('embeddedTrackerPort').setProperty('value', pref.embedded_tracker_port); $('embeddedTrackerPortForwarding').setProperty('checked', pref.embedded_tracker_port_forwarding); + $('pythonExecutablePath').setProperty('value', pref.python_executable_path); $('uploadSlotsBehavior').setProperty('value', pref.upload_slots_behavior); $('uploadChokingAlgorithm').setProperty('value', pref.upload_choking_algorithm); $('announceAllTrackers').setProperty('checked', pref.announce_to_all_trackers); @@ -2671,6 +2680,7 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD settings.set('enable_embedded_tracker', $('enableEmbeddedTracker').getProperty('checked')); settings.set('embedded_tracker_port', $('embeddedTrackerPort').getProperty('value')); settings.set('embedded_tracker_port_forwarding', $('embeddedTrackerPortForwarding').getProperty('checked')); + settings.set('python_executable_path', $('pythonExecutablePath').getProperty('value')); settings.set('upload_slots_behavior', $('uploadSlotsBehavior').getProperty('value')); settings.set('upload_choking_algorithm', $('uploadChokingAlgorithm').getProperty('value')); settings.set('announce_to_all_trackers', $('announceAllTrackers').getProperty('checked'));