auto_socket: Windows-скан + список сокетов в Network Settings (add/remove, чистый rewrite конфига)
This commit is contained in:
1 parent
d5119ac7c6
commit
f82dca3185
8 files changed
+545
-160
No files matched your search
@@ -1,14 +1,19 @@
|
||||
// networksettingspage.cpp — Network configuration form
|
||||
// networksettingspage.cpp — Network configuration form (список listen-сокетов)
|
||||
#include "networksettingspage.h"
|
||||
#include "../transport/config_updater.h"
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QGridLayout>
|
||||
#include <QLabel>
|
||||
#include <QCheckBox>
|
||||
#include <QLineEdit>
|
||||
#include <QListWidget>
|
||||
#include <QPushButton>
|
||||
#include <QNetworkInterface>
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
||||
|
||||
static const char* SECTION = "server:lan";
|
||||
static const QString AUTO_IP_LABEL = "Auto (0.0.0.0)";
|
||||
|
||||
NetworkSettingsPage::NetworkSettingsPage(QWidget* parent)
|
||||
: QWidget(parent)
|
||||
@@ -17,140 +22,92 @@ NetworkSettingsPage::NetworkSettingsPage(QWidget* parent)
|
||||
layout->setSpacing(12);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
auto* title = new QLabel("Listen Address", this);
|
||||
auto* title = new QLabel("Listen Sockets", this);
|
||||
title->setStyleSheet("font-weight: bold; font-size: 13px;");
|
||||
layout->addWidget(title);
|
||||
|
||||
// Interface
|
||||
auto* ifRow = new QHBoxLayout();
|
||||
ifRow->addWidget(new QLabel("Interface:", this));
|
||||
m_interfaceCombo = new QComboBox(this);
|
||||
m_interfaceCombo->setMinimumWidth(180);
|
||||
ifRow->addWidget(m_interfaceCombo, 1);
|
||||
layout->addLayout(ifRow);
|
||||
m_autoSocketsCheck = new QCheckBox("Auto sockets (UDP+TCP from interfaces)", this);
|
||||
layout->addWidget(m_autoSocketsCheck);
|
||||
|
||||
// IP
|
||||
auto* ipRow = new QHBoxLayout();
|
||||
ipRow->addWidget(new QLabel("IP Address:", this));
|
||||
m_serverList = new QListWidget(this);
|
||||
m_serverList->setMinimumHeight(110);
|
||||
layout->addWidget(m_serverList);
|
||||
|
||||
auto* btnRow = new QHBoxLayout();
|
||||
m_addBtn = new QPushButton("Add", this);
|
||||
m_removeBtn = new QPushButton("Remove", this);
|
||||
btnRow->addWidget(m_addBtn);
|
||||
btnRow->addWidget(m_removeBtn);
|
||||
btnRow->addStretch();
|
||||
layout->addLayout(btnRow);
|
||||
|
||||
// --- форма правки выбранного сокета ---
|
||||
auto* formLayout = new QGridLayout();
|
||||
|
||||
formLayout->addWidget(new QLabel("Name:", this), 0, 0);
|
||||
m_nameEdit = new QLineEdit(this);
|
||||
m_nameEdit->setMinimumWidth(180);
|
||||
formLayout->addWidget(m_nameEdit, 0, 1);
|
||||
|
||||
formLayout->addWidget(new QLabel("IP Address:", this), 1, 0);
|
||||
m_ipCombo = new QComboBox(this);
|
||||
m_ipCombo->setEditable(true);
|
||||
m_ipCombo->setMinimumWidth(180);
|
||||
ipRow->addWidget(m_ipCombo, 1);
|
||||
layout->addLayout(ipRow);
|
||||
formLayout->addWidget(m_ipCombo, 1, 1);
|
||||
|
||||
// Port
|
||||
auto* portRow = new QHBoxLayout();
|
||||
portRow->addWidget(new QLabel("Port:", this));
|
||||
formLayout->addWidget(new QLabel("Port:", this), 2, 0);
|
||||
m_portSpin = new QSpinBox(this);
|
||||
m_portSpin->setRange(1, 65535);
|
||||
m_portSpin->setValue(1333);
|
||||
m_portSpin->setMinimumWidth(100);
|
||||
portRow->addWidget(m_portSpin);
|
||||
portRow->addStretch();
|
||||
layout->addLayout(portRow);
|
||||
formLayout->addWidget(m_portSpin, 2, 1);
|
||||
|
||||
// Protocol
|
||||
auto* protoRow = new QHBoxLayout();
|
||||
protoRow->addWidget(new QLabel("Protocol:", this));
|
||||
formLayout->addWidget(new QLabel("Protocol:", this), 3, 0);
|
||||
m_protoCombo = new QComboBox(this);
|
||||
m_protoCombo->addItems({"UDP", "TCP"});
|
||||
m_protoCombo->setMinimumWidth(100);
|
||||
protoRow->addWidget(m_protoCombo);
|
||||
protoRow->addStretch();
|
||||
layout->addLayout(protoRow);
|
||||
formLayout->addWidget(m_protoCombo, 3, 1);
|
||||
|
||||
formLayout->addWidget(new QLabel("Type:", this), 4, 0);
|
||||
m_typeCombo = new QComboBox(this);
|
||||
m_typeCombo->addItems({"public", "nat", "private", "local"});
|
||||
formLayout->addWidget(m_typeCombo, 4, 1);
|
||||
|
||||
formLayout->addWidget(new QLabel("Interface:", this), 5, 0);
|
||||
m_interfaceCombo = new QComboBox(this);
|
||||
m_interfaceCombo->setMinimumWidth(180);
|
||||
formLayout->addWidget(m_interfaceCombo, 5, 1);
|
||||
|
||||
layout->addLayout(formLayout);
|
||||
|
||||
auto* note = new QLabel("Changes take effect after restart.", this);
|
||||
note->setStyleSheet("color: gray; font-size: 11px;");
|
||||
layout->addWidget(note);
|
||||
|
||||
layout->addStretch();
|
||||
|
||||
populateInterfaces();
|
||||
connect(m_interfaceCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
|
||||
this, &NetworkSettingsPage::onInterfaceChanged);
|
||||
populateAddresses();
|
||||
|
||||
connect(m_autoSocketsCheck, &QCheckBox::toggled, this, &NetworkSettingsPage::onAutoSocketsToggled);
|
||||
connect(m_addBtn, &QPushButton::clicked, this, &NetworkSettingsPage::onAddServer);
|
||||
connect(m_removeBtn, &QPushButton::clicked, this, &NetworkSettingsPage::onRemoveServer);
|
||||
connect(m_serverList, &QListWidget::currentRowChanged, this, [this](int) { onServerSelected(); });
|
||||
|
||||
connect(m_nameEdit, &QLineEdit::textChanged, this, [this](const QString&) { onFieldChanged(); });
|
||||
connect(m_ipCombo, &QComboBox::editTextChanged, this, [this](const QString&) { onFieldChanged(); });
|
||||
connect(m_portSpin, QOverload<int>::of(&QSpinBox::valueChanged), this, [this](int) { onFieldChanged(); });
|
||||
connect(m_protoCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int) { onFieldChanged(); });
|
||||
connect(m_typeCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int) { onFieldChanged(); });
|
||||
connect(m_interfaceCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int) { onFieldChanged(); });
|
||||
}
|
||||
|
||||
void NetworkSettingsPage::loadFromConfig(const QString& configPath) {
|
||||
// Read [server:lan] section from INI file
|
||||
QString addr, netif, transport;
|
||||
QFile f(configPath);
|
||||
if (f.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
bool inSection = false;
|
||||
QTextStream in(&f);
|
||||
while (!in.atEnd()) {
|
||||
QString line = in.readLine();
|
||||
if (line.startsWith('[') && line.endsWith(']')) {
|
||||
inSection = (line.mid(1, line.length() - 2) == SECTION);
|
||||
continue;
|
||||
}
|
||||
if (!inSection || !line.contains('=')) continue;
|
||||
QString key = line.section('=', 0, 0).trimmed();
|
||||
QString val = line.section('=', 1).trimmed();
|
||||
if (key == "addr") addr = val;
|
||||
else if (key == "netif") netif = val;
|
||||
else if (key == "transport") transport = val;
|
||||
}
|
||||
f.close();
|
||||
static void splitAddr(const QString& addr, QString& ip, int& port) {
|
||||
int lastColon = addr.lastIndexOf(':');
|
||||
if (lastColon > 0) {
|
||||
ip = addr.left(lastColon);
|
||||
port = addr.mid(lastColon + 1).toInt();
|
||||
} else {
|
||||
ip = addr;
|
||||
port = 0;
|
||||
}
|
||||
|
||||
// Parse addr: IP:port
|
||||
QString ip;
|
||||
if (!addr.isEmpty()) {
|
||||
int lastColon = addr.lastIndexOf(':');
|
||||
if (lastColon > 0) {
|
||||
ip = addr.left(lastColon);
|
||||
int port = addr.mid(lastColon + 1).toInt();
|
||||
if (port > 0 && port <= 65535) m_portSpin->setValue(port);
|
||||
}
|
||||
}
|
||||
|
||||
// Set protocol
|
||||
int protoIdx = (transport == "tcp") ? 1 : 0;
|
||||
m_protoCombo->setCurrentIndex(protoIdx);
|
||||
|
||||
// Set interface
|
||||
int ifIdx = m_interfaceCombo->findText(netif);
|
||||
if (ifIdx >= 0) m_interfaceCombo->setCurrentIndex(ifIdx);
|
||||
|
||||
// Set IP — populate addresses first, then select
|
||||
populateAddresses(netif.isEmpty() ? QString() : netif);
|
||||
if (ip.isEmpty() || ip == "0.0.0.0") ip = "Auto (0.0.0.0)";
|
||||
int ipIdx = m_ipCombo->findText(ip);
|
||||
if (ipIdx >= 0)
|
||||
m_ipCombo->setCurrentIndex(ipIdx);
|
||||
else
|
||||
m_ipCombo->setCurrentText(ip);
|
||||
}
|
||||
|
||||
void NetworkSettingsPage::saveToConfig(const QString& configPath) const {
|
||||
// addr
|
||||
QString ip = m_ipCombo->currentText().trimmed();
|
||||
if (ip.isEmpty() || ip == "Auto (0.0.0.0)") ip = "0.0.0.0";
|
||||
config_update_option(configPath, SECTION, "addr",
|
||||
QString("%1:%2").arg(ip).arg(m_portSpin->value()),
|
||||
CONFIG_UPDATE_REPLACE);
|
||||
|
||||
// netif: set or remove (empty value means no netif binding)
|
||||
QString iface = m_interfaceCombo->currentText();
|
||||
if (iface.startsWith("Auto"))
|
||||
iface.clear();
|
||||
config_update_option(configPath, SECTION, "netif", iface, CONFIG_UPDATE_REPLACE);
|
||||
|
||||
// transport
|
||||
config_update_option(configPath, SECTION, "transport",
|
||||
m_protoCombo->currentText().toLower(),
|
||||
CONFIG_UPDATE_REPLACE);
|
||||
|
||||
// IPv6 dual-stack: always add [server:lan6] with same port
|
||||
config_update_option(configPath, "server:lan6", "addr",
|
||||
QString("[::]:%1").arg(m_portSpin->value()),
|
||||
CONFIG_UPDATE_REPLACE);
|
||||
}
|
||||
|
||||
void NetworkSettingsPage::onInterfaceChanged(int index) {
|
||||
Q_UNUSED(index);
|
||||
QString iface = m_interfaceCombo->currentText();
|
||||
populateAddresses(iface.startsWith("Auto") ? QString() : iface);
|
||||
}
|
||||
|
||||
void NetworkSettingsPage::populateInterfaces() {
|
||||
@@ -162,30 +119,201 @@ void NetworkSettingsPage::populateInterfaces() {
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkSettingsPage::populateAddresses(const QString& ifaceName) {
|
||||
QString current = m_ipCombo->currentText();
|
||||
m_ipCombo->clear();
|
||||
m_ipCombo->addItem("Auto (0.0.0.0)");
|
||||
|
||||
if (ifaceName.isEmpty()) {
|
||||
for (const QNetworkInterface& iface : QNetworkInterface::allInterfaces()) {
|
||||
if (iface.flags() & QNetworkInterface::IsLoopBack) continue;
|
||||
if (!(iface.flags() & QNetworkInterface::IsUp)) continue;
|
||||
for (const QNetworkAddressEntry& entry : iface.addressEntries()) {
|
||||
if (entry.ip().protocol() == QAbstractSocket::IPv4Protocol)
|
||||
m_ipCombo->addItem(entry.ip().toString());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
QNetworkInterface iface = QNetworkInterface::interfaceFromName(ifaceName);
|
||||
if (iface.isValid()) {
|
||||
for (const QNetworkAddressEntry& entry : iface.addressEntries()) {
|
||||
if (entry.ip().protocol() == QAbstractSocket::IPv4Protocol)
|
||||
m_ipCombo->addItem(entry.ip().toString());
|
||||
}
|
||||
void NetworkSettingsPage::populateAddresses() {
|
||||
m_ipCombo->addItem(AUTO_IP_LABEL);
|
||||
for (const QNetworkInterface& iface : QNetworkInterface::allInterfaces()) {
|
||||
if (iface.flags() & QNetworkInterface::IsLoopBack) continue;
|
||||
if (!(iface.flags() & QNetworkInterface::IsUp)) continue;
|
||||
for (const QNetworkAddressEntry& entry : iface.addressEntries()) {
|
||||
if (entry.ip().protocol() == QAbstractSocket::IPv4Protocol)
|
||||
m_ipCombo->addItem(entry.ip().toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int idx = m_ipCombo->findText(current);
|
||||
if (idx >= 0) m_ipCombo->setCurrentIndex(idx);
|
||||
void NetworkSettingsPage::applyAutoSocketsState() {
|
||||
bool manual = !m_autoSocketsCheck->isChecked();
|
||||
m_serverList->setEnabled(manual);
|
||||
m_addBtn->setEnabled(manual);
|
||||
m_removeBtn->setEnabled(manual);
|
||||
m_nameEdit->setEnabled(manual);
|
||||
m_ipCombo->setEnabled(manual);
|
||||
m_portSpin->setEnabled(manual);
|
||||
m_protoCombo->setEnabled(manual);
|
||||
m_typeCombo->setEnabled(manual);
|
||||
m_interfaceCombo->setEnabled(manual);
|
||||
}
|
||||
|
||||
void NetworkSettingsPage::refreshServerList(int selectRow) {
|
||||
m_updating = true;
|
||||
m_serverList->clear();
|
||||
for (const ConfigServer& s : m_servers) {
|
||||
m_serverList->addItem(QString("%1 %2 (%3)").arg(s.name, s.addr, s.transport));
|
||||
}
|
||||
if (selectRow >= 0 && selectRow < m_serverList->count())
|
||||
m_serverList->setCurrentRow(selectRow);
|
||||
else if (m_serverList->count() > 0)
|
||||
m_serverList->setCurrentRow(0);
|
||||
m_updating = false;
|
||||
}
|
||||
|
||||
void NetworkSettingsPage::loadFormFromSelected() {
|
||||
int row = m_serverList->currentRow();
|
||||
if (row < 0 || row >= m_servers.size()) return;
|
||||
const ConfigServer& s = m_servers[row];
|
||||
|
||||
m_updating = true;
|
||||
m_nameEdit->setText(s.name);
|
||||
QString ip; int port = 0;
|
||||
splitAddr(s.addr, ip, port);
|
||||
if (ip == "0.0.0.0") ip = AUTO_IP_LABEL;
|
||||
m_ipCombo->setCurrentText(ip);
|
||||
m_portSpin->setValue(port > 0 ? port : 1333);
|
||||
m_protoCombo->setCurrentIndex(s.transport == "tcp" ? 1 : 0);
|
||||
int typeIdx = m_typeCombo->findText(s.type);
|
||||
m_typeCombo->setCurrentIndex(typeIdx >= 0 ? typeIdx : 0);
|
||||
int ifIdx = m_interfaceCombo->findText(s.netif);
|
||||
m_interfaceCombo->setCurrentIndex(ifIdx >= 0 ? ifIdx : 0);
|
||||
m_updating = false;
|
||||
}
|
||||
|
||||
void NetworkSettingsPage::updateSelectedServerFromForm() {
|
||||
int row = m_serverList->currentRow();
|
||||
if (row < 0 || row >= m_servers.size()) return;
|
||||
|
||||
ConfigServer& s = m_servers[row];
|
||||
s.name = m_nameEdit->text().trimmed();
|
||||
|
||||
QString ip = m_ipCombo->currentText().trimmed();
|
||||
if (ip == AUTO_IP_LABEL) ip = "0.0.0.0";
|
||||
s.addr = ip + ":" + QString::number(m_portSpin->value());
|
||||
|
||||
s.transport = (m_protoCombo->currentIndex() == 1) ? "tcp" : "udp";
|
||||
s.type = m_typeCombo->currentText().trimmed();
|
||||
QString iface = m_interfaceCombo->currentText();
|
||||
s.netif = iface.startsWith("Auto") ? QString() : iface;
|
||||
}
|
||||
|
||||
void NetworkSettingsPage::updateSelectedListItemText() {
|
||||
int row = m_serverList->currentRow();
|
||||
if (row < 0 || row >= m_servers.size()) return;
|
||||
const ConfigServer& s = m_servers[row];
|
||||
m_updating = true;
|
||||
m_serverList->item(row)->setText(QString("%1 %2 (%3)").arg(s.name, s.addr, s.transport));
|
||||
m_updating = false;
|
||||
}
|
||||
|
||||
void NetworkSettingsPage::loadFromConfig(const QString& configPath) {
|
||||
m_configPath = configPath;
|
||||
m_servers.clear();
|
||||
|
||||
QFile f(configPath);
|
||||
if (f.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
QString section;
|
||||
ConfigServer cur;
|
||||
bool inServer = false;
|
||||
QTextStream in(&f);
|
||||
while (!in.atEnd()) {
|
||||
QString line = in.readLine().trimmed();
|
||||
if (line.startsWith('[') && line.endsWith(']')) {
|
||||
if (inServer) m_servers.append(cur);
|
||||
inServer = false;
|
||||
section = line.mid(1, line.length() - 2);
|
||||
if (section.startsWith("server:")) {
|
||||
cur = ConfigServer();
|
||||
cur.name = section.mid(7);
|
||||
cur.transport = "udp";
|
||||
cur.type = "public";
|
||||
inServer = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!inServer || !line.contains('=')) continue;
|
||||
QString key = line.section('=', 0, 0).trimmed();
|
||||
QString val = line.section('=', 1).trimmed();
|
||||
if (key == "addr") cur.addr = val;
|
||||
else if (key == "transport") cur.transport = val;
|
||||
else if (key == "type") cur.type = val;
|
||||
else if (key == "netif") cur.netif = val;
|
||||
}
|
||||
if (inServer) m_servers.append(cur);
|
||||
f.close();
|
||||
}
|
||||
|
||||
/* auto_sockets из [global] */
|
||||
bool autoSockets = false;
|
||||
f.setFileName(configPath);
|
||||
if (f.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
QString section;
|
||||
QTextStream in(&f);
|
||||
while (!in.atEnd()) {
|
||||
QString line = in.readLine().trimmed();
|
||||
if (line.startsWith('[') && line.endsWith(']')) {
|
||||
section = line.mid(1, line.length() - 2);
|
||||
continue;
|
||||
}
|
||||
if ((section == "global" || section.isEmpty()) && line.startsWith("auto_sockets=")) {
|
||||
QString v = line.section('=', 1).trimmed();
|
||||
autoSockets = (v.compare("yes", Qt::CaseInsensitive) == 0 || v == "1");
|
||||
break;
|
||||
}
|
||||
}
|
||||
f.close();
|
||||
}
|
||||
m_autoSocketsCheck->setChecked(autoSockets);
|
||||
|
||||
refreshServerList();
|
||||
applyAutoSocketsState();
|
||||
}
|
||||
|
||||
void NetworkSettingsPage::saveToConfig(const QString& configPath) const {
|
||||
/* 1. Серверные секции — полная перезапись */
|
||||
config_rewrite_servers(configPath, m_servers);
|
||||
|
||||
/* 2. auto_sockets */
|
||||
config_update_option(configPath, "global", "auto_sockets",
|
||||
m_autoSocketsCheck->isChecked() ? "yes" : "no",
|
||||
CONFIG_UPDATE_REPLACE);
|
||||
}
|
||||
|
||||
void NetworkSettingsPage::onAutoSocketsToggled(bool /*on*/) {
|
||||
applyAutoSocketsState();
|
||||
}
|
||||
|
||||
void NetworkSettingsPage::onAddServer() {
|
||||
int n = m_servers.size() + 1;
|
||||
QString name;
|
||||
for (;;) {
|
||||
name = QString("srv%1").arg(n);
|
||||
bool used = false;
|
||||
for (const ConfigServer& s : m_servers) if (s.name == name) { used = true; break; }
|
||||
if (!used) break;
|
||||
n++;
|
||||
}
|
||||
ConfigServer s;
|
||||
s.name = name;
|
||||
s.addr = "0.0.0.0:1333";
|
||||
s.transport = "udp";
|
||||
s.type = "public";
|
||||
m_servers.append(s);
|
||||
refreshServerList(m_servers.size() - 1);
|
||||
loadFormFromSelected();
|
||||
}
|
||||
|
||||
void NetworkSettingsPage::onRemoveServer() {
|
||||
int row = m_serverList->currentRow();
|
||||
if (row < 0 || row >= m_servers.size()) return;
|
||||
m_servers.removeAt(row);
|
||||
refreshServerList(row);
|
||||
loadFormFromSelected();
|
||||
}
|
||||
|
||||
void NetworkSettingsPage::onServerSelected() {
|
||||
loadFormFromSelected();
|
||||
}
|
||||
|
||||
void NetworkSettingsPage::onFieldChanged() {
|
||||
if (m_updating) return;
|
||||
updateSelectedServerFromForm();
|
||||
updateSelectedListItemText();
|
||||
}
|
||||
@@ -1,10 +1,17 @@
|
||||
// networksettingspage.h — Network configuration form
|
||||
// networksettingspage.h — Network configuration form (список listen-сокетов)
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
#include <QComboBox>
|
||||
#include <QSpinBox>
|
||||
#include <QString>
|
||||
#include <QList>
|
||||
#include "../transport/config_updater.h"
|
||||
|
||||
class QCheckBox;
|
||||
class QLineEdit;
|
||||
class QListWidget;
|
||||
class QPushButton;
|
||||
|
||||
class NetworkSettingsPage : public QWidget {
|
||||
Q_OBJECT
|
||||
@@ -15,14 +22,33 @@ public:
|
||||
void saveToConfig(const QString& configPath) const;
|
||||
|
||||
private slots:
|
||||
void onInterfaceChanged(int index);
|
||||
void onAutoSocketsToggled(bool on);
|
||||
void onAddServer();
|
||||
void onRemoveServer();
|
||||
void onServerSelected();
|
||||
void onFieldChanged();
|
||||
|
||||
private:
|
||||
void populateInterfaces();
|
||||
void populateAddresses(const QString& ifaceName);
|
||||
void populateAddresses();
|
||||
void applyAutoSocketsState();
|
||||
void refreshServerList(int selectRow = -1);
|
||||
void loadFormFromSelected();
|
||||
void updateSelectedServerFromForm();
|
||||
void updateSelectedListItemText();
|
||||
|
||||
QComboBox* m_interfaceCombo;
|
||||
QComboBox* m_ipCombo;
|
||||
QSpinBox* m_portSpin;
|
||||
QComboBox* m_protoCombo;
|
||||
QCheckBox* m_autoSocketsCheck;
|
||||
QListWidget* m_serverList;
|
||||
QPushButton* m_addBtn;
|
||||
QPushButton* m_removeBtn;
|
||||
QLineEdit* m_nameEdit;
|
||||
QComboBox* m_ipCombo;
|
||||
QSpinBox* m_portSpin;
|
||||
QComboBox* m_protoCombo;
|
||||
QComboBox* m_typeCombo;
|
||||
QComboBox* m_interfaceCombo;
|
||||
|
||||
QList<ConfigServer> m_servers;
|
||||
QString m_configPath;
|
||||
bool m_updating = false;
|
||||
};
|
||||
@@ -85,3 +85,75 @@ bool config_update_option(const QString& filePath, const QString& section,
|
||||
f.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool is_section_header(const QString& line) {
|
||||
QString t = line.trimmed();
|
||||
return t.startsWith('[') && t.endsWith(']');
|
||||
}
|
||||
|
||||
bool config_rewrite_servers(const QString& filePath, const QList<ConfigServer>& servers) {
|
||||
QFile f(filePath);
|
||||
QStringList lines;
|
||||
|
||||
if (f.exists()) {
|
||||
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) return false;
|
||||
QTextStream in(&f);
|
||||
while (!in.atEnd()) {
|
||||
QString line = in.readLine(0);
|
||||
if (line.endsWith('\r')) line.chop(1);
|
||||
lines.append(line);
|
||||
}
|
||||
f.close();
|
||||
}
|
||||
|
||||
/* 1. Удалить все [server:*] секции (заголовок + ключи + хвостовую пустую строку-разделитель) */
|
||||
QStringList filtered;
|
||||
filtered.reserve(lines.size());
|
||||
for (int i = 0; i < lines.size(); i++) {
|
||||
QString t = lines[i].trimmed();
|
||||
if (t.startsWith("[server:") && t.endsWith(']')) {
|
||||
int j = i + 1;
|
||||
while (j < lines.size() && !is_section_header(lines[j])) j++;
|
||||
i = j - 1; /* for-loop сделает i++ → j: заголовок+ключи+пустая строка пропущены */
|
||||
continue;
|
||||
}
|
||||
filtered << lines[i];
|
||||
}
|
||||
lines = filtered;
|
||||
|
||||
/* 2. Собрать блок секций [server:*] */
|
||||
QStringList block;
|
||||
for (const ConfigServer& s : servers) {
|
||||
block << ("[server:" + s.name + "]");
|
||||
block << ("addr=" + s.addr);
|
||||
block << ("transport=" + (s.transport.isEmpty() ? QString("udp") : s.transport));
|
||||
block << ("type=" + (s.type.isEmpty() ? QString("public") : s.type));
|
||||
if (!s.netif.isEmpty()) block << ("netif=" + s.netif);
|
||||
block << ""; /* разделитель: одна пустая строка между секциями */
|
||||
}
|
||||
|
||||
/* 3. Точка вставки: после секции [global] (перед следующей секцией) */
|
||||
int insertPos = lines.size();
|
||||
for (int i = 0; i < lines.size(); i++) {
|
||||
if (lines[i].trimmed() == "[global]") {
|
||||
int j = i + 1;
|
||||
while (j < lines.size() && !is_section_header(lines[j])) j++;
|
||||
insertPos = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* 4. Вставить блок (если непустой) */
|
||||
if (!block.isEmpty()) {
|
||||
for (int i = block.size() - 1; i >= 0; i--)
|
||||
lines.insert(insertPos, block[i]);
|
||||
}
|
||||
|
||||
/* 5. Записать */
|
||||
if (!f.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) return false;
|
||||
QTextStream out(&f);
|
||||
for (const QString& l : lines)
|
||||
out << l << '\n';
|
||||
f.close();
|
||||
return true;
|
||||
}
|
||||
@@ -4,12 +4,22 @@
|
||||
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QList>
|
||||
|
||||
enum ConfigUpdateFlag {
|
||||
CONFIG_UPDATE_REPLACE = 0, // replace existing param (single value)
|
||||
CONFIG_UPDATE_APPEND = 1 // add new param even if exists (multi-value)
|
||||
};
|
||||
|
||||
/* Один listen-сокет = секция [server:name]. */
|
||||
struct ConfigServer {
|
||||
QString name; // суффикс секции: "lan", "lan6"
|
||||
QString addr; // "0.0.0.0:1333" | "[::]:1333"
|
||||
QString transport; // "udp" | "tcp"
|
||||
QString type; // "public" | "nat" | "private" | "local"
|
||||
QString netif; // "" = не привязывать к интерфейсу
|
||||
};
|
||||
|
||||
// Batch update — for callers that read/write the file once (e.g. NodeConfig::save)
|
||||
bool config_update_in_lines(QStringList& lines, const QString& section,
|
||||
const QString& key, const QString& value, bool replace);
|
||||
@@ -19,4 +29,8 @@ bool config_update_option(const QString& filePath, const QString& section,
|
||||
const QString& param, const QString& value,
|
||||
int flags = CONFIG_UPDATE_REPLACE);
|
||||
|
||||
// Полностью заменяет секции [server:*]: удаляет старые, вставляет новые после [global].
|
||||
// Внутри секции без пустых строк, между секциями ровно одна пустая строка.
|
||||
bool config_rewrite_servers(const QString& filePath, const QList<ConfigServer>& servers);
|
||||
|
||||
#endif // CONFIG_UPDATER_H
|
||||
@@ -178,10 +178,8 @@ bool NodeConfig::create(const QString& nodeName, quint16 listenPort) {
|
||||
m_controlAddr = "127.0.0.1:9999";
|
||||
m_servers.clear();
|
||||
m_clients.clear();
|
||||
if (listenPort > 0) {
|
||||
m_servers.append({"lan", QString("0.0.0.0:%1").arg(listenPort)});
|
||||
m_servers.append({"lan6", QString("[::]:%1").arg(listenPort)});
|
||||
}
|
||||
(void)listenPort; /* автосокеты сами создают UDP+TCP на случайных портах */
|
||||
m_autoSockets = true;
|
||||
if (m_dbPath.isEmpty())
|
||||
m_dbPath = "chat_data";
|
||||
return save();
|
||||
@@ -277,6 +275,7 @@ bool NodeConfig::load() {
|
||||
else if (key == "my_node_id") m_nodeId = val;
|
||||
else if (key == "my_public_key") m_pubKey = val;
|
||||
else if (key == "my_private_key") m_privKey = val;
|
||||
else if (key == "auto_sockets") m_autoSockets = (val.compare("yes", Qt::CaseInsensitive) == 0 || val == "1");
|
||||
} else if (section.startsWith("server:")) {
|
||||
QString srvName = section.mid(7);
|
||||
if (key == "addr") m_servers.append({srvName, val});
|
||||
@@ -328,6 +327,7 @@ bool NodeConfig::saveFull() {
|
||||
if (!m_nodeId.isEmpty()) out << "my_node_id=" << m_nodeId << "\n";
|
||||
if (!m_privKey.isEmpty()) out << "my_private_key=" << m_privKey << "\n";
|
||||
if (!m_pubKey.isEmpty()) out << "my_public_key=" << m_pubKey << "\n";
|
||||
out << "auto_sockets=" << (m_autoSockets ? "yes" : "no") << "\n";
|
||||
out << "\n";
|
||||
|
||||
for (auto& srv : m_servers) {
|
||||
@@ -403,6 +403,7 @@ bool NodeConfig::save() {
|
||||
config_update_in_lines(lines, "global", "my_node_id", m_nodeId, true);
|
||||
config_update_in_lines(lines, "global", "my_private_key", m_privKey, true);
|
||||
config_update_in_lines(lines, "global", "my_public_key", m_pubKey, true);
|
||||
config_update_in_lines(lines, "global", "auto_sockets", m_autoSockets ? "yes" : "no", true);
|
||||
|
||||
if (!m_controlAddr.isEmpty()) {
|
||||
int sep = m_controlAddr.lastIndexOf(':');
|
||||
|
||||
@@ -40,6 +40,9 @@ public:
|
||||
quint16 controlPort() const;
|
||||
void setControlPort(quint16 p);
|
||||
|
||||
bool autoSockets() const { return m_autoSockets; }
|
||||
void setAutoSockets(bool v) { m_autoSockets = v; }
|
||||
|
||||
QString filePath() const { return m_path; }
|
||||
|
||||
/* GUI settings (from [gui] section) */
|
||||
@@ -65,6 +68,7 @@ private:
|
||||
QList<QPair<QString,QString>> m_servers;
|
||||
QList<NodeClient> m_clients;
|
||||
QString m_controlAddr;
|
||||
bool m_autoSockets = true;
|
||||
QString m_dbPath;
|
||||
QString m_debugFile;
|
||||
QString m_debugLevel;
|
||||
|
||||
Reference in new issue
Block a user