feat: update ConfigManager and ProxyServer to utilize Settings

- Modified ConfigManager to accept a Settings object for improved configuration management.
- Updated ProxyServer to initialize with Settings, enhancing dependency injection.
This commit is contained in:
aiamnezia
2025-12-31 21:16:45 +04:00
parent 4492b0af7e
commit 412e69af9b
8 changed files with 229 additions and 715 deletions

View File

@@ -37,7 +37,7 @@ CoreController::CoreController(const QSharedPointer<VpnConnection> &vpnConnectio
void CoreController::initLocalProxy()
{
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
m_proxyServer.reset(new ProxyServer(this));
m_proxyServer.reset(new ProxyServer(m_settings, this));
auto syncLocalProxy = [this]() {
if (!m_proxyServer) {

View File

@@ -1,565 +1,193 @@
#include "configmanager.h"
#include "core/serialization/serialization.h"
#include "containers/containers_defs.h"
#include "core/api/apiUtils.h"
#include "proxylogger.h"
#include <QJsonDocument>
#include <QJsonArray>
#include "settings.h"
#include <QDir>
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonParseError>
#include <QSaveFile>
#include <QStandardPaths>
#include <QDebug>
#include <QUuid>
ConfigManager::ConfigManager()
ConfigManager::ConfigManager(const std::shared_ptr<Settings> &settings)
: m_settings(settings)
{
ProxyLogger::getInstance().debug("Initializing ConfigManager");
// Create configs directory if it doesn't exist
QString configPath = getConfigsPath();
ProxyLogger::getInstance().debug(QString("Ensuring config directory exists: %1").arg(configPath));
QDir().mkpath(configPath);
// Read active config UUID and initialize config count
QJsonObject configsInfo = readConfigsInfo();
m_activeConfigUuid = configsInfo["activeConfigUuid"].toString();
m_configCount = configsInfo["configs"].toObject().size();
ProxyLogger::getInstance().info(QString("Active config UUID: %1, Total configs: %2")
.arg(m_activeConfigUuid.isEmpty() ? "none" : m_activeConfigUuid)
.arg(m_configCount));
ProxyLogger::getInstance().debug("ConfigManager initialized (Settings-backed)");
}
QString ConfigManager::getConfigsPath() const
std::optional<ConfigManager::ConfigData> ConfigManager::buildConfig(QString &errorDescription) const
{
QString configDir = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
return QDir(configDir).filePath("xray_configs");
}
errorDescription.clear();
QString ConfigManager::getActiveConfigPath() const
{
return QDir(getConfigsPath()).filePath("active_config.json");
}
QString ConfigManager::getConfigsInfoPath() const
{
return QDir(getConfigsPath()).filePath("configs_info.json");
}
QJsonObject ConfigManager::readConfigsInfo() const
{
QFile file(getConfigsInfoPath());
if (!file.exists()) {
ProxyLogger::getInstance().info("Configs info file doesn't exist, creating new one");
// If file doesn't exist, return empty structure
QJsonObject configsInfo;
configsInfo["version"] = 1;
configsInfo["configs"] = QJsonObject();
return configsInfo;
if (!m_settings) {
const QString message = QStringLiteral("Settings backend is not available");
ProxyLogger::getInstance().error(message);
errorDescription = message;
return std::nullopt;
}
if (!file.open(QIODevice::ReadOnly)) {
ProxyLogger::getInstance().error(QString("Failed to open configs info file: %1").arg(file.errorString()));
return QJsonObject();
const QString ownerUuid = m_settings->localProxyOwnerUuid();
if (ownerUuid.isEmpty()) {
const QString message = QStringLiteral("Local proxy owner UUID is not configured");
ProxyLogger::getInstance().warning(message);
errorDescription = message;
return std::nullopt;
}
QByteArray data = file.readAll();
file.close();
const auto ownerServer = findServerByUuid(ownerUuid);
if (!ownerServer) {
const QString message = QStringLiteral("Owner server with UUID %1 not found in Settings").arg(ownerUuid);
ProxyLogger::getInstance().error(message);
errorDescription = message;
return std::nullopt;
}
if (!apiUtils::isPremiumServer(*ownerServer)) {
const QString message = QStringLiteral("Server %1 is not premium, local proxy is unavailable")
.arg(ownerServer->value(amnezia::config_key::name).toString());
ProxyLogger::getInstance().warning(message);
errorDescription = message;
return std::nullopt;
}
const auto serializedConfig = extractSerializedXrayConfig(*ownerServer);
if (!serializedConfig || serializedConfig->isEmpty()) {
const QString message = QStringLiteral("Server %1 lacks Xray last_config payload")
.arg(ownerServer->value(amnezia::config_key::name).toString());
ProxyLogger::getInstance().error(message);
errorDescription = message;
return std::nullopt;
}
QJsonParseError parseError;
QJsonDocument doc = QJsonDocument::fromJson(data, &parseError);
if (parseError.error != QJsonParseError::NoError) {
ProxyLogger::getInstance().error(QString("Failed to parse configs info file: %1").arg(parseError.errorString()));
return QJsonObject();
const QJsonDocument doc = QJsonDocument::fromJson(serializedConfig->toUtf8(), &parseError);
if (parseError.error != QJsonParseError::NoError || !doc.isObject()) {
const QString message = QStringLiteral("Failed to parse Xray config JSON: %1").arg(parseError.errorString());
ProxyLogger::getInstance().error(message);
errorDescription = message;
return std::nullopt;
}
ProxyLogger::getInstance().debug("Successfully read configs info file");
return doc.object();
ConfigData data;
data.ownerUuid = ownerUuid;
data.serverName = ownerServer->value(amnezia::config_key::name).toString();
data.serializedConfig = *serializedConfig;
data.parsedConfig = doc.object();
return data;
}
bool ConfigManager::writeConfigsInfo(const QJsonObject &configsInfo)
bool ConfigManager::writeTempConfig(const QString &serializedConfig, QString &configPath, QString &errorDescription) const
{
QFile file(getConfigsInfoPath());
errorDescription.clear();
configPath.clear();
if (!file.open(QIODevice::WriteOnly)) {
ProxyLogger::getInstance().error(QString("Failed to open configs info file for writing: %1").arg(file.errorString()));
const QString directory = tempDirectory();
if (!QDir().mkpath(directory)) {
const QString message = QStringLiteral("Failed to create temp config directory: %1").arg(directory);
ProxyLogger::getInstance().error(message);
errorDescription = message;
return false;
}
m_configCount = configsInfo["configs"].toObject().size();
ProxyLogger::getInstance().debug(QString("Updated config count: %1").arg(m_configCount));
QJsonDocument doc(configsInfo);
file.write(doc.toJson(QJsonDocument::Indented));
file.close();
ProxyLogger::getInstance().debug("Successfully wrote configs info file");
return true;
}
QJsonObject ConfigManager::readActiveConfig() const
{
QFile file(getActiveConfigPath());
if (!file.exists()) {
ProxyLogger::getInstance().warning(QString("Active config file not found at: %1").arg(getActiveConfigPath()));
return QJsonObject();
}
if (!file.open(QIODevice::ReadOnly)) {
ProxyLogger::getInstance().error(QString("Failed to open active config file: %1").arg(file.errorString()));
return QJsonObject();
}
QByteArray data = file.readAll();
file.close();
QJsonParseError parseError;
QJsonDocument doc = QJsonDocument::fromJson(data, &parseError);
if (parseError.error != QJsonParseError::NoError) {
ProxyLogger::getInstance().error(QString("Failed to parse active config file: %1").arg(parseError.errorString()));
return QJsonObject();
}
ProxyLogger::getInstance().debug("Successfully read active config file");
return doc.object();
}
bool ConfigManager::writeActiveConfig(const QJsonObject &config)
{
ProxyLogger::getInstance().info("Writing new active config");
QFile file(getActiveConfigPath());
if (!file.open(QIODevice::WriteOnly)) {
ProxyLogger::getInstance().error(QString("Failed to open active config file for writing: %1").arg(file.errorString()));
const QString path = tempConfigPath();
QSaveFile file(path);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
const QString message = QStringLiteral("Failed to open temp config file %1: %2").arg(path, file.errorString());
ProxyLogger::getInstance().error(message);
errorDescription = message;
return false;
}
QJsonDocument doc(config);
file.write(doc.toJson(QJsonDocument::Indented));
file.close();
if (file.write(serializedConfig.toUtf8()) == -1) {
const QString message = QStringLiteral("Failed to write temp config file %1: %2").arg(path, file.errorString());
ProxyLogger::getInstance().error(message);
errorDescription = message;
return false;
}
ProxyLogger::getInstance().debug("Successfully wrote active config file");
if (!file.commit()) {
const QString message = QStringLiteral("Failed to commit temp config file %1").arg(path);
ProxyLogger::getInstance().error(message);
errorDescription = message;
return false;
}
ProxyLogger::getInstance().info(QStringLiteral("Xray config saved to %1").arg(path));
configPath = path;
return true;
}
bool ConfigManager::removeActiveConfigFile()
bool ConfigManager::removeTempConfig() const
{
ProxyLogger::getInstance().info("Removing active config file");
QFile file(getActiveConfigPath());
if (file.exists()) {
if (file.remove()) {
ProxyLogger::getInstance().debug("Successfully removed active config file");
return true;
} else {
ProxyLogger::getInstance().error(QString("Failed to remove active config file: %1").arg(file.errorString()));
return false;
}
const QString path = tempConfigPath();
QFile file(path);
if (!file.exists()) {
return true;
}
ProxyLogger::getInstance().debug("Active config file does not exist, nothing to remove");
if (!file.remove()) {
ProxyLogger::getInstance().warning(QStringLiteral("Failed to remove temp config file %1: %2").arg(path, file.errorString()));
return false;
}
ProxyLogger::getInstance().debug(QStringLiteral("Removed temp config file %1").arg(path));
return true;
}
QString ConfigManager::generateUuid() const
QString ConfigManager::tempConfigPath() const
{
return QUuid::createUuid().toString(QUuid::WithoutBraces);
return QDir(tempDirectory()).filePath(QStringLiteral("xray_active.json"));
}
QString ConfigManager::getProtocolFromSerializedConfig(const QString &config) const
std::optional<QJsonObject> ConfigManager::findServerByUuid(const QString &uuid) const
{
if (config.startsWith("vless://")) return "vless";
if (config.startsWith("vmess://")) return "vmess";
if (config.startsWith("trojan://")) return "trojan";
if (config.startsWith("ss://")) return "ss";
return QString();
}
if (!m_settings) {
return std::nullopt;
}
QJsonObject ConfigManager::deserializeConfig(const QString &configStr, QString *prefix, QString *errorMsg)
{
ProxyLogger::getInstance().debug("Deserializing config");
QJsonObject outConfig;
QString localPrefix;
QString localErrorMsg;
QString* safePrefix = prefix ? prefix : &localPrefix;
QString* safeErrorMsg = errorMsg ? errorMsg : &localErrorMsg;
if (configStr.startsWith("vless://")) {
ProxyLogger::getInstance().debug("Deserializing VLESS config");
outConfig = amnezia::serialization::vless::Deserialize(configStr, safePrefix, safeErrorMsg);
if (safePrefix->contains(QRegularExpression("%[0-9A-Fa-f]{2}"))) {
*safePrefix = QString::fromUtf8(QByteArray::fromPercentEncoding(safePrefix->toUtf8()));
const QJsonArray servers = m_settings->serversArray();
for (const QJsonValue &value : servers) {
const QJsonObject server = value.toObject();
if (server.value(amnezia::config_key::server_uuid).toString() == uuid) {
return server;
}
}
if (configStr.startsWith("vmess://") && configStr.contains("@")) {
ProxyLogger::getInstance().debug("Deserializing new VMess config");
outConfig = amnezia::serialization::vmess_new::Deserialize(configStr, safePrefix, safeErrorMsg);
}
else if (configStr.startsWith("vmess://")) {
ProxyLogger::getInstance().debug("Deserializing VMess config");
outConfig = amnezia::serialization::vmess::Deserialize(configStr, safePrefix, safeErrorMsg);
}
if (configStr.startsWith("trojan://")) {
ProxyLogger::getInstance().debug("Deserializing Trojan config");
outConfig = amnezia::serialization::trojan::Deserialize(configStr, safePrefix, safeErrorMsg);
}
if (configStr.startsWith("ss://") && !configStr.contains("plugin=")) {
ProxyLogger::getInstance().debug("Deserializing Shadowsocks config");
outConfig = amnezia::serialization::ss::Deserialize(configStr, safePrefix, safeErrorMsg);
if (safePrefix->contains(QRegularExpression("%[0-9A-Fa-f]{2}"))) {
*safePrefix = QString::fromUtf8(QByteArray::fromPercentEncoding(safePrefix->toUtf8()));
}
}
if (!safeErrorMsg->isEmpty()) {
ProxyLogger::getInstance().error(QString("Config deserialization error: %1").arg(*safeErrorMsg));
}
return outConfig;
return std::nullopt;
}
QJsonObject ConfigManager::addInbounds(const QJsonObject &config)
std::optional<QString> ConfigManager::extractSerializedXrayConfig(const QJsonObject &server) const
{
ProxyLogger::getInstance().debug("Adding inbounds configuration");
QJsonObject resultConfig = config;
const QJsonArray containers = server.value(amnezia::config_key::containers).toArray();
const QString targetContainer = ContainerProps::containerToString(amnezia::DockerContainer::Xray);
const QString protoKey = ProtocolProps::protoToString(amnezia::Proto::Xray);
QJsonArray inbounds;
QJsonObject socksInbound;
socksInbound["listen"] = "127.0.0.1";
socksInbound["port"] = 10808;
socksInbound["protocol"] = "socks";
QJsonObject settings;
settings["udp"] = true;
socksInbound["settings"] = settings;
inbounds.append(socksInbound);
resultConfig["inbounds"] = inbounds;
ProxyLogger::getInstance().debug("Successfully added SOCKS inbound configuration (port: 10808)");
return resultConfig;
}
bool ConfigManager::addConfigs(const QStringList &serializedConfigs)
{
ProxyLogger::getInstance().info(QString("Adding %1 new config(s)").arg(serializedConfigs.size()));
QJsonObject configsInfo = readConfigsInfo();
QJsonObject configs = configsInfo["configs"].toObject();
QString activeUuid = getActiveConfigUuid();
QString firstUuid;
QSet<QString> existingConfigs;
for (auto it = configs.begin(); it != configs.end(); ++it) {
QJsonObject configInfo = it.value().toObject();
existingConfigs.insert(configInfo["serializedConfig"].toString());
}
for (const QString &serializedConfig : serializedConfigs)
{
if (existingConfigs.contains(serializedConfig)) {
ProxyLogger::getInstance().info("Skipping duplicate config");
for (const QJsonValue &value : containers) {
const QJsonObject container = value.toObject();
if (container.value(amnezia::config_key::container).toString() != targetContainer) {
continue;
}
QString uuid = generateUuid();
ProxyLogger::getInstance().debug(QString("Generated new UUID: %1").arg(uuid));
if (firstUuid.isEmpty()) {
firstUuid = uuid;
}
QString prefix;
QString errorMsg;
ProxyLogger::getInstance().debug(QString("Deserializing config: %1").arg(serializedConfig.left(50) + "..."));
QJsonObject config = deserializeConfig(serializedConfig, &prefix, &errorMsg);
if (!errorMsg.isEmpty()) {
ProxyLogger::getInstance().error(QString("Failed to deserialize config: %1").arg(errorMsg));
continue;
}
QJsonObject currentConfigInfo;
currentConfigInfo["created"] = QDateTime::currentDateTime().toString(Qt::ISODate);
currentConfigInfo["lastUsed"] = QDateTime::currentDateTime().toString(Qt::ISODate);
currentConfigInfo["protocol"] = getProtocolFromSerializedConfig(serializedConfig);
currentConfigInfo["serializedConfig"] = serializedConfig;
currentConfigInfo["name"] = prefix.isEmpty() ? uuid : prefix;
currentConfigInfo["isActive"] = false;
ProxyLogger::getInstance().info(QString("Adding config: UUID=%1, Protocol=%2, Name=%3")
.arg(uuid)
.arg(currentConfigInfo["protocol"].toString())
.arg(currentConfigInfo["name"].toString()));
configs[uuid] = currentConfigInfo;
}
configsInfo["configs"] = configs;
ProxyLogger::getInstance().debug("Writing updated configs info to file");
if (!writeConfigsInfo(configsInfo)) {
ProxyLogger::getInstance().error("Failed to write configs info");
return false;
}
// If there's no active config, activate the first added one
if (activeUuid.isEmpty() && !firstUuid.isEmpty())
{
ProxyLogger::getInstance().info(QString("No active config, activating first added config: %1").arg(firstUuid));
return activateConfig(firstUuid);
}
return true;
}
bool ConfigManager::removeConfig(const QString &uuid)
{
ProxyLogger::getInstance().info(QString("Removing config with UUID: %1").arg(uuid));
QJsonObject configsInfo = readConfigsInfo();
QJsonObject configs = configsInfo["configs"].toObject();
// Check if config exists
if (!configs.contains(uuid))
{
ProxyLogger::getInstance().warning(QString("Config with UUID %1 not found").arg(uuid));
return false;
}
QJsonObject configToRemove = configs[uuid].toObject();
ProxyLogger::getInstance().info(QString("Removing config: Name=%1, Protocol=%2")
.arg(configToRemove["name"].toString())
.arg(configToRemove["protocol"].toString()));
// Store current active config UUID
bool needToActivateNew = (getActiveConfigUuid() == uuid);
// Remove config from the list
configs.remove(uuid);
// Save updated configs list (without changing activeConfigUuid)
configsInfo["configs"] = configs;
ProxyLogger::getInstance().debug("Writing updated configs info to file");
if (!writeConfigsInfo(configsInfo))
{
ProxyLogger::getInstance().error("Failed to write configs info");
return false;
}
// If active config was removed, activate a new one
if (needToActivateNew)
{
ProxyLogger::getInstance().info("Removed active config, need to activate a new one");
if (configs.isEmpty())
{
ProxyLogger::getInstance().info("No configs left, clearing active config");
if (!activateConfig(QString()))
{
ProxyLogger::getInstance().error("Failed to clear active config");
return false;
}
}
else
{
QString newActiveUuid = configs.keys().first();
ProxyLogger::getInstance().info(QString("Activating new config: %1").arg(newActiveUuid));
if (!activateConfig(newActiveUuid))
{
ProxyLogger::getInstance().error(QString("Failed to activate new config: %1").arg(newActiveUuid));
return false;
}
}
}
return true;
}
bool ConfigManager::activateConfig(const QString &uuid)
{
ProxyLogger::getInstance().info(QString("Activating config: %1").arg(uuid.isEmpty() ? "none" : uuid));
QJsonObject configsInfo = readConfigsInfo();
QJsonObject configs = configsInfo["configs"].toObject();
// Reset isActive flag for all configs
for (auto it = configs.begin(); it != configs.end(); ++it) {
QJsonObject config = it.value().toObject();
config["isActive"] = false;
it.value() = config;
}
// If uuid is empty, just reset active config
if (uuid.isEmpty())
{
ProxyLogger::getInstance().info("Resetting active config");
m_activeConfigUuid = QString();
configsInfo["activeConfigUuid"] = QString();
configsInfo["configs"] = configs;
// Write changes to the configs info file
if (!writeConfigsInfo(configsInfo)) {
ProxyLogger::getInstance().error("Failed to write configs info file");
return false;
}
// Remove the active config file
ProxyLogger::getInstance().debug("Removing active config file");
return removeActiveConfigFile();
}
// Check if config exists
if (!configs.contains(uuid))
{
ProxyLogger::getInstance().error(QString("Config with UUID %1 not found").arg(uuid));
return false;
}
QJsonObject currentConfigInfo = configs[uuid].toObject();
ProxyLogger::getInstance().info(QString("Activating config: Name=%1, Protocol=%2")
.arg(currentConfigInfo["name"].toString())
.arg(currentConfigInfo["protocol"].toString()));
QString serializedConfig = currentConfigInfo["serializedConfig"].toString();
// Deserialize config and add inbounds
QString prefix;
QString errorMsg;
ProxyLogger::getInstance().debug("Deserializing config for activation");
QJsonObject currentConfig = deserializeConfig(serializedConfig, &prefix, &errorMsg);
if (currentConfig.isEmpty())
{
ProxyLogger::getInstance().error(QString("Failed to deserialize config: %1").arg(errorMsg));
return false;
}
ProxyLogger::getInstance().debug("Adding inbounds to config");
currentConfig = addInbounds(currentConfig);
// Update lastUsed and isActive
currentConfigInfo["lastUsed"] = QDateTime::currentDateTime().toString(Qt::ISODate);
currentConfigInfo["isActive"] = true;
configs[uuid] = currentConfigInfo;
configsInfo["configs"] = configs;
// Update active config
m_activeConfigUuid = uuid;
configsInfo["activeConfigUuid"] = uuid;
// Save changes
ProxyLogger::getInstance().debug("Writing updated configs info");
if (!writeConfigsInfo(configsInfo))
{
ProxyLogger::getInstance().error("Failed to write configs info file");
return false;
}
ProxyLogger::getInstance().debug("Writing new active config file");
return writeActiveConfig(currentConfig);
}
QJsonObject ConfigManager::getActiveConfig() const
{
ProxyLogger::getInstance().debug("Getting active config info");
QJsonObject configsInfo = readConfigsInfo();
QJsonObject configs = configsInfo["configs"].toObject();
QString activeUuid = getActiveConfigUuid();
if (activeUuid.isEmpty() || !configs.contains(activeUuid)) {
ProxyLogger::getInstance().debug("No active config found");
return QJsonObject();
}
ProxyLogger::getInstance().debug(QString("Retrieved active config info for UUID: %1").arg(activeUuid));
QJsonObject result = configs[activeUuid].toObject();
result["id"] = activeUuid;
return result;
}
QMap<QString, QJsonObject> ConfigManager::getAllConfigs() const
{
ProxyLogger::getInstance().debug("Getting all configs");
QJsonObject configsInfo = readConfigsInfo();
QJsonObject configs = configsInfo["configs"].toObject();
QMap<QString, QJsonObject> result;
for (auto it = configs.begin(); it != configs.end(); ++it)
{
result[it.key()] = it.value().toObject();
}
ProxyLogger::getInstance().debug(QString("Retrieved %1 configs").arg(result.size()));
return result;
}
QMap<QString, QJsonObject> ConfigManager::getConfigsByUuids(const QStringList &uuids) const
{
ProxyLogger::getInstance().debug(QString("Getting configs for %1 UUIDs").arg(uuids.size()));
QMap<QString, QJsonObject> allConfigs = getAllConfigs();
if (uuids.isEmpty())
{
ProxyLogger::getInstance().debug("UUID list is empty, returning all configs");
return allConfigs;
}
QMap<QString, QJsonObject> result;
for (const QString &uuid : uuids)
{
if (allConfigs.contains(uuid))
{
ProxyLogger::getInstance().debug(QString("Found config for UUID: %1").arg(uuid));
result[uuid] = allConfigs[uuid];
}
else
{
ProxyLogger::getInstance().warning(QString("Config not found for UUID: %1").arg(uuid));
result[uuid] = QJsonObject();
const QJsonObject proto = container.value(protoKey).toObject();
const QString serialized = proto.value(amnezia::config_key::last_config).toString();
if (!serialized.isEmpty()) {
return serialized;
}
}
ProxyLogger::getInstance().debug(QString("Retrieved %1 configs out of %2 requested").arg(result.size()).arg(uuids.size()));
return result;
return std::nullopt;
}
bool ConfigManager::updateAllConfigs(const QStringList &serializedConfigs)
QString ConfigManager::tempDirectory() const
{
ProxyLogger::getInstance().info(QString("Updating all configs with %1 new config(s)").arg(serializedConfigs.size()));
ProxyLogger::getInstance().debug("Clearing existing configs");
if (!clearConfigs()) {
ProxyLogger::getInstance().error("Failed to clear existing configs");
return false;
const QString baseDir = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
if (baseDir.isEmpty()) {
return QDir::temp().filePath(QStringLiteral("amnezia_local_proxy"));
}
ProxyLogger::getInstance().debug("Adding new configs");
bool success = addConfigs(serializedConfigs);
if (success) {
ProxyLogger::getInstance().info("Successfully updated all configs");
} else {
ProxyLogger::getInstance().error("Failed to add new configs");
}
return success;
}
bool ConfigManager::clearConfigs()
{
ProxyLogger::getInstance().info("Clearing all configs");
QJsonObject configsInfo = readConfigsInfo();
configsInfo["configs"] = QJsonObject();
if (!writeConfigsInfo(configsInfo)) {
ProxyLogger::getInstance().error("Failed to clear configs info");
return false;
}
ProxyLogger::getInstance().debug("Resetting active config");
bool success = activateConfig(QString());
if (success) {
ProxyLogger::getInstance().info("Successfully cleared all configs");
} else {
ProxyLogger::getInstance().error("Failed to reset active config");
}
return success;
return QDir(baseDir).filePath(QStringLiteral("local_proxy"));
}

View File

@@ -1,47 +1,33 @@
#pragma once
#include <memory>
#include <optional>
#include <QJsonObject>
#include <QString>
#include <QFile>
#include <QMap>
class Settings;
class ConfigManager {
public:
ConfigManager();
struct ConfigData {
QString ownerUuid;
QString serverName;
QString serializedConfig;
QJsonObject parsedConfig;
};
// Main config operations
bool addConfigs(const QStringList &serializedConfigs);
bool removeConfig(const QString &uuid);
bool activateConfig(const QString &uuid);
bool updateAllConfigs(const QStringList &serializedConfigs);
bool clearConfigs();
explicit ConfigManager(const std::shared_ptr<Settings> &settings);
// Information retrieval
QString getActiveConfigUuid() const { return m_activeConfigUuid; }
QJsonObject getActiveConfig() const;
QMap<QString, QJsonObject> getAllConfigs() const;
QMap<QString, QJsonObject> getConfigsByUuids(const QStringList &uuids) const;
QString getActiveConfigPath() const;
int getConfigCount() const { return m_configCount; }
std::optional<ConfigData> buildConfig(QString &errorDescription) const;
bool writeTempConfig(const QString &serializedConfig, QString &configPath, QString &errorDescription) const;
bool removeTempConfig() const;
QString tempConfigPath() const;
private:
// File paths
QString getConfigsPath() const;
QString getConfigsInfoPath() const;
std::optional<QJsonObject> findServerByUuid(const QString &uuid) const;
std::optional<QString> extractSerializedXrayConfig(const QJsonObject &server) const;
QString tempDirectory() const;
// File operations
QJsonObject readConfigsInfo() const;
bool writeConfigsInfo(const QJsonObject &configsInfo);
QJsonObject readActiveConfig() const;
bool writeActiveConfig(const QJsonObject &config);
bool removeActiveConfigFile();
// Helper methods
QString generateUuid() const;
QString getProtocolFromSerializedConfig(const QString &config) const;
QJsonObject deserializeConfig(const QString &configStr, QString *prefix = nullptr, QString *errorMsg = nullptr);
QJsonObject addInbounds(const QJsonObject &config);
QString m_activeConfigUuid;
int m_configCount{0};
};
std::shared_ptr<Settings> m_settings;
};

View File

@@ -1,25 +1,14 @@
#pragma once
#include <QJsonObject>
#include <QMap>
#include <QString>
class IProxyService {
public:
virtual ~IProxyService() = default;
// Config operations
virtual QJsonObject getConfig() const = 0;
virtual bool updateConfig(const QString& configStr) = 0;
virtual QMap<QString, QJsonObject> getAllConfigs() const = 0;
virtual QMap<QString, QJsonObject> getConfigsByUuids(const QStringList &uuids) const = 0;
virtual bool addConfigs(const QStringList &serializedConfigs) = 0;
virtual bool removeConfig(const QString &uuid) = 0;
virtual bool activateConfig(const QString &uuid) = 0;
virtual QJsonObject getActiveConfig() const = 0;
virtual bool updateAllConfigs(const QStringList &serializedConfigs) = 0;
virtual int getConfigCount() const = 0;
virtual QJsonObject getConfig() = 0;
// Xray process operations
virtual bool startXray() = 0;
virtual bool stopXray() = 0;
virtual bool isXrayRunning() const = 0;

View File

@@ -10,15 +10,13 @@
#include "proxylogger.h"
ProxyServer::ProxyServer(QObject *parent)
ProxyServer::ProxyServer(const std::shared_ptr<Settings> &settings, QObject *parent)
: QObject(parent)
, m_service(new ProxyService(this))
, m_service(new ProxyService(settings, this))
{
const QString logDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/logs";
ProxyLogger::getInstance().init(logDir + "/proxy.log");
ProxyLogger::getInstance().setLogLevel(ProxyLogger::LogLevel::Info);
connect(m_service.data(), &ProxyService::configsChanged, this, &ProxyServer::startXrayProcess);
}
ProxyServer::~ProxyServer()

View File

@@ -3,15 +3,19 @@
#include <QObject>
#include <QScopedPointer>
#include <QSharedPointer>
#include "proxyservice.h"
#include <memory>
#include "httpapi.h"
#include "proxyservice.h"
class Settings;
class ProxyServer : public QObject
{
Q_OBJECT
public:
explicit ProxyServer(QObject *parent = nullptr);
explicit ProxyServer(const std::shared_ptr<Settings> &settings, QObject *parent = nullptr);
~ProxyServer();
bool start(quint16 port = 8080);

View File

@@ -1,44 +1,75 @@
#include "proxyservice.h"
#include "proxylogger.h"
ProxyService::ProxyService(QObject* parent)
namespace {
void logConfigError(const QString &errorMessage)
{
if (!errorMessage.isEmpty()) {
ProxyLogger::getInstance().error(errorMessage);
}
}
} // namespace
ProxyService::ProxyService(const std::shared_ptr<Settings> &settings, QObject* parent)
: QObject(parent)
, m_configManager(new ConfigManager())
, m_configManager(new ConfigManager(settings))
, m_xrayController(new XrayController())
{
ProxyLogger::getInstance().debug("ProxyService initialized");
}
QJsonObject ProxyService::getConfig() const
QJsonObject ProxyService::getConfig()
{
ProxyLogger::getInstance().debug("Getting active config");
return m_configManager->getActiveConfig();
}
bool ProxyService::updateConfig(const QString& configStr)
{
ProxyLogger::getInstance().info("Updating config");
bool success = m_configManager->updateAllConfigs({configStr});
if (success) {
ProxyLogger::getInstance().info("Config updated successfully");
emit configsChanged();
} else {
ProxyLogger::getInstance().error("Failed to update config");
if (!m_cachedConfig.isEmpty()) {
return m_cachedConfig;
}
return success;
QString error;
const auto configData = m_configManager->buildConfig(error);
if (!configData) {
logConfigError(error);
return {};
}
m_cachedConfig = configData->parsedConfig;
return m_cachedConfig;
}
bool ProxyService::startXray()
{
ProxyLogger::getInstance().info("Starting Xray");
bool success = m_xrayController->start(m_configManager->getActiveConfigPath());
QString error;
const auto configData = m_configManager->buildConfig(error);
qDebug() << "configData:" << configData.value().serializedConfig;
qDebug() << "configData:" << configData.value().parsedConfig;
if (!configData) {
logConfigError(error);
return false;
}
QString configPath;
if (!m_configManager->writeTempConfig(configData->serializedConfig, configPath, error)) {
qDebug() << "configPath:" << configPath;
logConfigError(error);
return false;
}
qDebug() << "configPath:" << configPath;
m_cachedConfig = configData->parsedConfig;
const bool success = m_xrayController->start(configPath);
if (success) {
ProxyLogger::getInstance().info("Xray started successfully");
emit xrayStatusChanged(true);
} else {
ProxyLogger::getInstance().error("Failed to start Xray");
return true;
}
return success;
ProxyLogger::getInstance().error(QStringLiteral("Failed to start Xray: %1").arg(m_xrayController->getError()));
return false;
}
bool ProxyService::stopXray()
@@ -47,11 +78,12 @@ bool ProxyService::stopXray()
const bool stopped = m_xrayController->stop();
if (stopped) {
ProxyLogger::getInstance().info("Xray stopped");
m_configManager->removeTempConfig();
emit xrayStatusChanged(false);
return true;
}
ProxyLogger::getInstance().warning(QString("Failed to stop Xray: %1").arg(m_xrayController->getError()));
ProxyLogger::getInstance().warning(QStringLiteral("Failed to stop Xray: %1").arg(m_xrayController->getError()));
return false;
}
@@ -68,121 +100,4 @@ qint64 ProxyService::getXrayProcessId() const
QString ProxyService::getXrayError() const
{
return m_xrayController->getError();
}
QMap<QString, QJsonObject> ProxyService::getAllConfigs() const
{
ProxyLogger::getInstance().debug("Getting all configs");
return m_configManager->getAllConfigs();
}
QMap<QString, QJsonObject> ProxyService::getConfigsByUuids(const QStringList &uuids) const
{
ProxyLogger::getInstance().debug(QString("Getting configs for UUIDs: %1").arg(uuids.join(", ")));
return m_configManager->getConfigsByUuids(uuids);
}
bool ProxyService::addConfigs(const QStringList &serializedConfigs)
{
ProxyLogger::getInstance().info(QString("Adding %1 new config(s)").arg(serializedConfigs.size()));
bool success = m_configManager->addConfigs(serializedConfigs);
if (success) {
ProxyLogger::getInstance().info("Configs added successfully");
emit configsChanged();
} else {
ProxyLogger::getInstance().error("Failed to add configs");
}
return success;
}
bool ProxyService::removeConfig(const QString &uuid)
{
ProxyLogger::getInstance().info(QString("Removing config with UUID: %1").arg(uuid));
// Store current active config UUID before removal
QString activeUuid = m_configManager->getActiveConfigUuid();
// Try to remove the config
bool removed = m_configManager->removeConfig(uuid);
if (removed) {
ProxyLogger::getInstance().info("Config removed successfully");
emit configsChanged();
if (uuid == activeUuid && isXrayRunning()) {
ProxyLogger::getInstance().info("Removed active config, restarting Xray");
if (!stopXray()) {
ProxyLogger::getInstance().warning("Failed to stop Xray after removing active config");
return false;
}
// Check if there are any configs left
QString newActiveUuid = m_configManager->getActiveConfigUuid();
if (!newActiveUuid.isEmpty()) {
ProxyLogger::getInstance().info(QString("Starting Xray with new active config: %1").arg(newActiveUuid));
return startXray();
} else {
ProxyLogger::getInstance().info("No configs left after removal");
}
}
} else {
ProxyLogger::getInstance().error(QString("Failed to remove config with UUID: %1").arg(uuid));
}
return removed;
}
bool ProxyService::activateConfig(const QString &uuid)
{
ProxyLogger::getInstance().info(QString("Activating config with UUID: %1").arg(uuid));
if (m_configManager->activateConfig(uuid)) {
ProxyLogger::getInstance().info("Config activated successfully");
emit configsChanged();
// If config is successfully activated, restart Xray
if (isXrayRunning()) {
ProxyLogger::getInstance().info("Restarting Xray with new config");
if (!stopXray()) {
ProxyLogger::getInstance().warning("Failed to stop Xray while activating new config");
return false;
}
return startXray();
}
return true;
}
ProxyLogger::getInstance().error(QString("Failed to activate config with UUID: %1").arg(uuid));
return false;
}
QJsonObject ProxyService::getActiveConfig() const
{
ProxyLogger::getInstance().debug("Getting active config");
return m_configManager->getActiveConfig();
}
bool ProxyService::updateAllConfigs(const QStringList &serializedConfigs)
{
ProxyLogger::getInstance().info(QString("Updating all configs with %1 new config(s)").arg(serializedConfigs.size()));
bool success = m_configManager->updateAllConfigs(serializedConfigs);
if (success) {
ProxyLogger::getInstance().info("All configs updated successfully");
emit configsChanged();
if (isXrayRunning()) {
ProxyLogger::getInstance().info("Restarting Xray with updated configs");
if (!stopXray()) {
ProxyLogger::getInstance().warning("Failed to stop Xray while updating configs");
return false;
}
return startXray();
}
} else {
ProxyLogger::getInstance().error("Failed to update all configs");
}
return success;
}
QString ProxyService::getActiveConfigUuid() const
{
return m_configManager->getActiveConfigUuid();
}
int ProxyService::getConfigCount() const
{
return m_configManager->getConfigCount();
}
}

View File

@@ -1,30 +1,24 @@
#pragma once
#include "iproxyservice.h"
#include "configmanager.h"
#include "iproxyservice.h"
#include "xraycontroller.h"
#include <QObject>
#include <QScopedPointer>
#include <QJsonObject>
#include <memory>
class Settings;
class ProxyService : public QObject, public IProxyService {
Q_OBJECT
public:
explicit ProxyService(QObject* parent = nullptr);
explicit ProxyService(const std::shared_ptr<Settings> &settings, QObject* parent = nullptr);
~ProxyService() = default;
QJsonObject getConfig() const override;
bool updateConfig(const QString& configStr) override;
QMap<QString, QJsonObject> getAllConfigs() const override;
QMap<QString, QJsonObject> getConfigsByUuids(const QStringList &uuids) const override;
bool addConfigs(const QStringList &serializedConfigs) override;
bool removeConfig(const QString &uuid) override;
bool activateConfig(const QString &uuid) override;
QJsonObject getActiveConfig() const override;
bool updateAllConfigs(const QStringList &serializedConfigs) override;
QString getActiveConfigUuid() const;
int getConfigCount() const override;
QJsonObject getConfig() override;
bool startXray() override;
bool stopXray() override;
bool isXrayRunning() const override;
@@ -32,10 +26,10 @@ public:
QString getXrayError() const override;
signals:
void configsChanged();
void xrayStatusChanged(bool running);
private:
QScopedPointer<ConfigManager> m_configManager;
QScopedPointer<XrayController> m_xrayController;
QJsonObject m_cachedConfig;
};