fix: fix local proxy settings and restart logic

- Added local proxy restart token management in Settings.
- Implemented logic to handle local proxy state on application quit.
- Updated ProxyServer to restart based on configuration changes.
- Enhanced API configuration updates to bump restart token when necessary.
- Improved UI components to reflect local proxy availability and state.
- Added new error handling and notifications for local proxy operations.
This commit is contained in:
aiamnezia
2026-04-13 07:14:42 +04:00
parent 850b8ea03b
commit be692001b0
10 changed files with 328 additions and 277 deletions
+2 -1
View File
@@ -10,7 +10,8 @@ deploy/build_64/*
winbuild*.bat winbuild*.bat
.cache/ .cache/
.vscode/ .vscode/
.cursorignore
.cursor/
# Qt-es # Qt-es
/.qmake.cache /.qmake.cache
@@ -1,5 +1,6 @@
#include "coreController.h" #include "coreController.h"
#include <QCoreApplication>
#include <QDirIterator> #include <QDirIterator>
#include <QDebug> #include <QDebug>
#include <QTranslator> #include <QTranslator>
@@ -41,6 +42,12 @@ void CoreController::initLocalProxy()
m_proxyServer.reset(new ProxyServer(m_settings, this)); m_proxyServer.reset(new ProxyServer(m_settings, this));
QObject::connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, [this]() {
if (m_settings && m_settings->isLocalProxyHttpEnabled()) {
m_settings->setLocalProxyHttpEnabled(false);
}
});
auto syncLocalProxy = [this]() { auto syncLocalProxy = [this]() {
if (!m_proxyServer) { if (!m_proxyServer) {
return; return;
+14
View File
@@ -8,6 +8,7 @@ ProxyServer::ProxyServer(const std::shared_ptr<Settings> &settings, QObject *par
, m_settings(settings) , m_settings(settings)
, m_service(new ProxyService(settings, this)) , m_service(new ProxyService(settings, this))
{ {
m_lastRestartToken = m_settings ? m_settings->localProxyRestartToken() : 0;
} }
ProxyServer::~ProxyServer() ProxyServer::~ProxyServer()
@@ -73,6 +74,7 @@ bool ProxyServer::syncSettings()
} }
const quint16 newProxyPort = m_settings ? m_settings->localProxyPort() : 0; const quint16 newProxyPort = m_settings ? m_settings->localProxyPort() : 0;
const int restartToken = m_settings ? m_settings->localProxyRestartToken() : 0;
const bool xrayRunning = m_service->isXrayRunning(); const bool xrayRunning = m_service->isXrayRunning();
if (!xrayRunning) { if (!xrayRunning) {
@@ -80,15 +82,27 @@ bool ProxyServer::syncSettings()
const bool started = startXrayProcess(); const bool started = startXrayProcess();
if (started) { if (started) {
m_currentProxyPort = newProxyPort; m_currentProxyPort = newProxyPort;
m_lastRestartToken = restartToken;
} }
return started; return started;
} }
if (m_lastRestartToken != restartToken) {
qInfo() << "Local proxy: restarting Xray due to config change token";
const bool restarted = m_service->restartXray();
if (restarted) {
m_currentProxyPort = newProxyPort;
m_lastRestartToken = restartToken;
}
return restarted;
}
if (m_currentProxyPort != newProxyPort) { if (m_currentProxyPort != newProxyPort) {
qInfo() << "Local proxy: proxy port changed from" << m_currentProxyPort << "to" << newProxyPort; qInfo() << "Local proxy: proxy port changed from" << m_currentProxyPort << "to" << newProxyPort;
const bool restarted = m_service->restartXray(); const bool restarted = m_service->restartXray();
if (restarted) { if (restarted) {
m_currentProxyPort = newProxyPort; m_currentProxyPort = newProxyPort;
m_lastRestartToken = restartToken;
} }
return restarted; return restarted;
} }
+1
View File
@@ -32,4 +32,5 @@ private:
bool m_isRunning {false}; bool m_isRunning {false};
quint16 m_currentApiPort {0}; quint16 m_currentApiPort {0};
quint16 m_currentProxyPort {0}; quint16 m_currentProxyPort {0};
int m_lastRestartToken {0};
}; };
+22
View File
@@ -3,6 +3,8 @@
#include "QCoreApplication" #include "QCoreApplication"
#include "QThread" #include "QThread"
#include <limits>
#include "core/networkUtilities.h" #include "core/networkUtilities.h"
#include "version.h" #include "version.h"
@@ -84,8 +86,15 @@ void Settings::removeServer(int index)
if (index >= servers.size()) if (index >= servers.size())
return; return;
const QString removedUuid = servers.at(index).toObject().value(config_key::server_uuid).toString();
servers.removeAt(index); servers.removeAt(index);
setServersArray(servers); setServersArray(servers);
if (!removedUuid.isEmpty() && removedUuid == localProxyOwnerUuid()) {
m_settings.setValue("Conf/localProxyHttpEnabled", false);
m_settings.setValue("Conf/localProxyOwnerUuid", "");
emit localProxySettingsChanged();
}
emit serverRemoved(index); emit serverRemoved(index);
} }
@@ -644,3 +653,16 @@ void Settings::setLocalProxyHttpEnabled(bool enabled)
m_settings.setValue("Conf/localProxyHttpEnabled", enabled); m_settings.setValue("Conf/localProxyHttpEnabled", enabled);
emit localProxySettingsChanged(); emit localProxySettingsChanged();
} }
int Settings::localProxyRestartToken() const
{
return m_settings.value("Conf/localProxyRestartToken", 0).toInt();
}
void Settings::bumpLocalProxyRestartToken()
{
const int current = localProxyRestartToken();
const int next = (current == std::numeric_limits<int>::max()) ? 0 : (current + 1);
m_settings.setValue("Conf/localProxyRestartToken", next);
emit localProxySettingsChanged();
}
+2
View File
@@ -256,6 +256,8 @@ public:
void setLocalProxyPortUserDefined(bool userDefined); void setLocalProxyPortUserDefined(bool userDefined);
bool isLocalProxyHttpEnabled() const; bool isLocalProxyHttpEnabled() const;
void setLocalProxyHttpEnabled(bool enabled); void setLocalProxyHttpEnabled(bool enabled);
int localProxyRestartToken() const;
void bumpLocalProxyRestartToken();
signals: signals:
void saveLogsChanged(bool enabled); void saveLogsChanged(bool enabled);
@@ -933,6 +933,7 @@ bool ApiConfigsController::importTrialFromGateway(const QString &email)
bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const QString &newCountryCode, const QString &newCountryName, bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const QString &newCountryCode, const QString &newCountryName,
bool reloadServiceConfig) bool reloadServiceConfig)
{ {
const QString serverUuid = m_serversModel->getServerUuid(serverIndex);
auto serverConfig = m_serversModel->getServerConfig(serverIndex); auto serverConfig = m_serversModel->getServerConfig(serverIndex);
auto apiConfig = serverConfig.value(configKey::apiConfig).toObject(); auto apiConfig = serverConfig.value(configKey::apiConfig).toObject();
@@ -994,6 +995,14 @@ bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const
emit subscriptionRefreshNeeded(); emit subscriptionRefreshNeeded();
} }
if (!serverUuid.isEmpty()
&& m_settings
&& m_settings->isLocalProxyHttpEnabled()
&& m_settings->localProxyOwnerUuid() == serverUuid) {
m_settings->bumpLocalProxyRestartToken();
}
if (reloadServiceConfig) { if (reloadServiceConfig) {
emit reloadServerFromApiFinished(tr("API config reloaded")); emit reloadServerFromApiFinished(tr("API config reloaded"));
} else if (newCountryName.isEmpty()) { } else if (newCountryName.isEmpty()) {
@@ -1030,6 +1039,7 @@ bool ApiConfigsController::updateServiceFromTelegram(const int serverIndex)
m_settings->isStrictKillSwitchEnabled()); m_settings->isStrictKillSwitchEnabled());
auto serverConfig = m_serversModel->getServerConfig(serverIndex); auto serverConfig = m_serversModel->getServerConfig(serverIndex);
const QString serverUuid = m_serversModel->getServerUuid(serverIndex);
auto installationUuid = m_settings->getInstallationUuid(true); auto installationUuid = m_settings->getInstallationUuid(true);
QString serviceProtocol = serverConfig.value(configKey::protocol).toString(); QString serviceProtocol = serverConfig.value(configKey::protocol).toString();
@@ -1054,6 +1064,14 @@ bool ApiConfigsController::updateServiceFromTelegram(const int serverIndex)
} }
m_serversModel->editServer(serverConfig, serverIndex); m_serversModel->editServer(serverConfig, serverIndex);
if (!serverUuid.isEmpty()
&& m_settings
&& m_settings->isLocalProxyHttpEnabled()
&& m_settings->localProxyOwnerUuid() == serverUuid) {
m_settings->bumpLocalProxyRestartToken();
}
emit updateServerFromApiFinished(); emit updateServerFromApiFinished();
return true; return true;
} else { } else {
@@ -185,17 +185,18 @@ Item {
BasicButtonType { BasicButtonType {
visible: (root.buttonText !== "") || (root.buttonImageSource !== "") visible: (root.buttonText !== "") || (root.buttonImageSource !== "")
parent: backgroud
focusPolicy: Qt.NoFocus focusPolicy: Qt.NoFocus
text: root.buttonText text: root.buttonText
leftImageSource: root.buttonImageSource leftImageSource: root.buttonImageSource
anchors.top: content.top anchors.top: parent.top
anchors.bottom: content.bottom anchors.bottom: parent.bottom
anchors.right: content.right anchors.right: parent.right
height: content.implicitHeight height: parent.height
width: content.implicitHeight width: Math.max(height, implicitWidth)
squareLeftSide: true squareLeftSide: true
clickedFunc: function() { clickedFunc: function() {
@@ -64,7 +64,7 @@ PageType {
rightImageSource: "qrc:/images/controls/chevron-right.svg" rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() { clickedFunction: function() {
PageController.goToPage(PageEnum.PageSettingsConnectionProtocols) PageController.goToPage(PageEnum.PageSettingsServerProtocols)
} }
} }
@@ -76,7 +76,7 @@ PageType {
Layout.fillWidth: true Layout.fillWidth: true
Layout.leftMargin: 16 Layout.leftMargin: 16
Layout.rightMargin: 16 Layout.rightMargin: 16
visible: SettingsController.isLocalProxySupported visible: SettingsController.isLocalProxySupported && ServersModel.processedServerIsPremium
Layout.preferredHeight: visible ? implicitHeight : 0 Layout.preferredHeight: visible ? implicitHeight : 0
text: qsTr("Local proxy") text: qsTr("Local proxy")
+28 -43
View File
@@ -22,18 +22,11 @@ PageType {
property int pendingStartAutoSelectedPort: -1 property int pendingStartAutoSelectedPort: -1
property bool pendingStartVpnWasActive: false property bool pendingStartVpnWasActive: false
Component.onCompleted: root.syncSwitchState()
function getPortField() { function getPortField() {
var item = listView.itemAtIndex(0) var item = listView.itemAtIndex(0)
return item !== null ? item.children[0] : null return item !== null ? item.children[0] : null
} }
function getHeaderBlock() {
var headerItem = listView.headerItem
return headerItem && headerItem.children.length > 0 ? headerItem.children[0] : null
}
function computePortErrorText() { function computePortErrorText() {
var portField = getPortField() var portField = getPortField()
if (portField === null) return "" if (portField === null) return ""
@@ -54,28 +47,22 @@ PageType {
return "" return ""
} }
function syncSwitchState() {
setSwitcherChecked(SettingsController.isLocalProxyHttpEnabled)
}
function setSwitcherChecked(value) {
var header = getHeaderBlock()
if (!header || header.switcher.checked === value) {
return
}
header.switcher.checked = value
}
function handleLocalProxyToggle(checked) { function handleLocalProxyToggle(checked) {
if (checked) { if (checked) {
if (!ServersModel.processedServerIsPremium) {
PageController.showNotificationMessage(qsTr("Local proxy is available only for Amnezia Premium"))
return
}
const wasVpnActive = ConnectionController.isConnected || ConnectionController.isConnectionInProgress const wasVpnActive = ConnectionController.isConnected || ConnectionController.isConnectionInProgress
if (wasVpnActive) { if (wasVpnActive) {
ConnectionController.closeConnection() ConnectionController.closeConnection()
} }
const serverUuid = ServersModel.processedServerUuid let serverUuid = ServersModel.processedServerUuid
if (!serverUuid && ServersModel.defaultIndex !== undefined) {
serverUuid = ServersModel.getServerUuid(ServersModel.defaultIndex)
}
if (!serverUuid) { if (!serverUuid) {
root.setSwitcherChecked(false)
PageController.showNotificationMessage(qsTr("Unable to determine the current server")) PageController.showNotificationMessage(qsTr("Unable to determine the current server"))
return return
} }
@@ -83,14 +70,12 @@ PageType {
if (SettingsController.isLocalProxyHttpEnabled if (SettingsController.isLocalProxyHttpEnabled
&& SettingsController.localProxyOwnerUuid && SettingsController.localProxyOwnerUuid
&& SettingsController.localProxyOwnerUuid !== serverUuid) { && SettingsController.localProxyOwnerUuid !== serverUuid) {
root.setSwitcherChecked(false)
PageController.showNotificationMessage(qsTr("Local proxy is already enabled for another server")) PageController.showNotificationMessage(qsTr("Local proxy is already enabled for another server"))
return return
} }
const requestedPort = SettingsController.localProxyPort const requestedPort = SettingsController.localProxyPort
if (requestedPort < root.localProxyPortMin || requestedPort > root.localProxyPortMax) { if (requestedPort < root.localProxyPortMin || requestedPort > root.localProxyPortMax) {
root.setSwitcherChecked(false)
PageController.showNotificationMessage(qsTr("Port must be between %1 and %2") PageController.showNotificationMessage(qsTr("Port must be between %1 and %2")
.arg(root.localProxyPortMin) .arg(root.localProxyPortMin)
.arg(root.localProxyPortMax)) .arg(root.localProxyPortMax))
@@ -103,7 +88,6 @@ PageType {
|| requestedPort !== root.defaultLocalProxyPort) { || requestedPort !== root.defaultLocalProxyPort) {
PageController.showNotificationMessage(qsTr("Port %1 is already in use on this device. Choose another one") PageController.showNotificationMessage(qsTr("Port %1 is already in use on this device. Choose another one")
.arg(requestedPort)) .arg(requestedPort))
root.setSwitcherChecked(false)
return return
} }
@@ -111,32 +95,28 @@ PageType {
if (autoSelectedPort <= 0) { if (autoSelectedPort <= 0) {
PageController.showNotificationMessage(qsTr("Port %1 is already in use on this device. Choose another one") PageController.showNotificationMessage(qsTr("Port %1 is already in use on this device. Choose another one")
.arg(requestedPort)) .arg(requestedPort))
root.setSwitcherChecked(false)
return return
} }
} }
const portToEnable = autoSelectedPort > 0 ? autoSelectedPort : requestedPort const portToEnable = autoSelectedPort > 0 ? autoSelectedPort : requestedPort
if (!SettingsController.enableLocalProxy(serverUuid, portToEnable)) { if (!SettingsController.enableLocalProxy(serverUuid, portToEnable)) {
root.setSwitcherChecked(false)
PageController.showNotificationMessage(qsTr("Failed to enable local proxy. Check the port (%1-%2).") PageController.showNotificationMessage(qsTr("Failed to enable local proxy. Check the port (%1-%2).")
.arg(root.localProxyPortMin) .arg(root.localProxyPortMin)
.arg(root.localProxyPortMax)) .arg(root.localProxyPortMax))
root.syncSwitchState()
return return
} }
root.pendingStartRequestedPort = requestedPort root.pendingStartRequestedPort = requestedPort
root.pendingStartAutoSelectedPort = autoSelectedPort root.pendingStartAutoSelectedPort = autoSelectedPort
root.pendingStartVpnWasActive = wasVpnActive root.pendingStartVpnWasActive = wasVpnActive
startSuccessToastTimer.restart() startSuccessToastTimer.restart()
root.syncSwitchState()
} else { } else {
startSuccessToastTimer.stop() startSuccessToastTimer.stop()
root.pendingStartRequestedPort = -1 root.pendingStartRequestedPort = -1
root.pendingStartAutoSelectedPort = -1 root.pendingStartAutoSelectedPort = -1
root.pendingStartVpnWasActive = false root.pendingStartVpnWasActive = false
SettingsController.disableLocalProxy() SettingsController.disableLocalProxy()
root.syncSwitchState() PageController.showNotificationMessage(qsTr("Local proxy stopped"))
} }
} }
@@ -174,14 +154,20 @@ PageType {
Layout.rightMargin: 16 Layout.rightMargin: 16
headerText: qsTr("Local Proxy") headerText: qsTr("Local Proxy")
descriptionText: qsTr("Use a proxy to route selected apps (for example, the CensorTracker extension) through Amnezia Premium.") descriptionText: qsTr("Use a proxy to route selected apps (for example, the CensorTracker extension) through Amnezia Premium.")
showSwitcher: true showSwitcher: ServersModel.processedServerIsPremium
Component.onCompleted: root.syncSwitchState() switcher {
checked: SettingsController.isLocalProxyHttpEnabled
}
switcherFunction: function(checked) { switcherFunction: function(checked) {
// Ignore UI sync toggles; react only to real state change intent. // Ignore UI sync toggles; react only to real state change intent.
if (checked === SettingsController.isLocalProxyHttpEnabled) { if (checked === SettingsController.isLocalProxyHttpEnabled) {
return return
} }
root.handleLocalProxyToggle(checked) root.handleLocalProxyToggle(checked)
// Keep checked declaratively linked after any user interaction path.
localProxyHeader.switcher.checked = Qt.binding(function() {
return SettingsController.isLocalProxyHttpEnabled
})
} }
} }
@@ -209,11 +195,10 @@ PageType {
text: qsTr("Learn more") text: qsTr("Learn more")
clickedFunc: function() { clickedFunc: function() {
const isRussian = LanguageModel.currentLanguageName === "Русский" const path = LanguageModel.currentLanguageName === "Русский"
const learnMoreUrl = isRussian ? "ru/documentation/instructions/local-proxy"
? "http://docs.amnezia.org/ru/documentation/instructions/local-proxy" : "documentation/instructions/local-proxy"
: "http://docs.amnezia.org/documentation/instructions/local-proxy" Qt.openUrlExternally(LanguageModel.getCurrentDocsUrl(path))
Qt.openUrlExternally(learnMoreUrl)
} }
} }
} }
@@ -245,9 +230,8 @@ PageType {
PageController.showNotificationMessage(qsTr("Copied: 127.0.0.1:%1").arg(portText)) PageController.showNotificationMessage(qsTr("Copied: 127.0.0.1:%1").arg(portText))
} }
textField.validator: IntValidator { textField.validator: RegularExpressionValidator {
bottom: root.localProxyPortMin regularExpression: /^[0-9]{0,5}$/
top: root.localProxyPortMax
} }
textField.leftPadding: portPrefix.implicitWidth textField.leftPadding: portPrefix.implicitWidth
textField.placeholderText: root.defaultLocalProxyPort.toString() textField.placeholderText: root.defaultLocalProxyPort.toString()
@@ -256,7 +240,7 @@ PageType {
function syncPortValue() { function syncPortValue() {
const port = SettingsController.localProxyPort const port = SettingsController.localProxyPort
const isValidPort = port >= root.localProxyPortMin && port <= root.localProxyPortMax const isValidPort = port >= root.localProxyPortMin && port <= root.localProxyPortMax
textField.text = (isValidPort && port !== root.defaultLocalProxyPort) ? port.toString() : "" textField.text = isValidPort ? port.toString() : ""
} }
function portValue() { function portValue() {
@@ -309,6 +293,10 @@ PageType {
enabled: true enabled: true
clickedFunc: function() { clickedFunc: function() {
if (SettingsController.isLocalProxyHttpEnabled) {
PageController.showNotificationMessage(qsTr("Disable Local Proxy to change the port"))
return
}
const validationError = root.computePortErrorText() const validationError = root.computePortErrorText()
root.portValidationError = validationError root.portValidationError = validationError
if (validationError !== "") { if (validationError !== "") {
@@ -361,7 +349,6 @@ PageType {
target: SettingsController target: SettingsController
function onLocalProxySettingsUpdated() { function onLocalProxySettingsUpdated() {
root.syncSwitchState()
var portField = root.getPortField() var portField = root.getPortField()
if (portField !== null && !portField.textField.activeFocus) { if (portField !== null && !portField.textField.activeFocus) {
portField.syncPortValue() portField.syncPortValue()
@@ -374,7 +361,6 @@ PageType {
root.pendingStartAutoSelectedPort = -1 root.pendingStartAutoSelectedPort = -1
root.pendingStartVpnWasActive = false root.pendingStartVpnWasActive = false
PageController.showNotificationMessage(message) PageController.showNotificationMessage(message)
root.syncSwitchState()
} }
} }
@@ -382,7 +368,6 @@ PageType {
target: ServersModel target: ServersModel
function onProcessedServerChanged() { function onProcessedServerChanged() {
root.syncSwitchState()
var portField = root.getPortField() var portField = root.getPortField()
if (portField !== null && !portField.textField.activeFocus) { if (portField !== null && !portField.textField.activeFocus) {
portField.syncPortValue() portField.syncPortValue()