You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
159 lines
5.4 KiB
159 lines
5.4 KiB
// config_updater.cpp — Universal INI config file updater (like insert_or_replace_option in utun) |
|
#include "config_updater.h" |
|
#include <QFile> |
|
#include <QTextStream> |
|
|
|
static std::pair<int,int> find_section_bounds(const QStringList& lines, const QString& section) { |
|
QString tag = "[" + section + "]"; |
|
for (int i = 0; i < lines.size(); i++) { |
|
if (lines[i].trimmed() == tag) { |
|
int se = lines.size(); |
|
for (int j = i + 1; j < lines.size(); j++) { |
|
QString t = lines[j].trimmed(); |
|
if (t.startsWith('[') && t.endsWith(']')) { se = j; break; } |
|
} |
|
return {i, se}; |
|
} |
|
} |
|
return {-1, -1}; |
|
} |
|
|
|
bool config_update_in_lines(QStringList& lines, const QString& section, |
|
const QString& key, const QString& value, bool replace) { |
|
auto bounds = find_section_bounds(lines, section); |
|
if (bounds.first < 0) { |
|
if (!lines.isEmpty() && !lines.last().trimmed().isEmpty()) |
|
lines.append(""); |
|
lines.append("[" + section + "]"); |
|
lines.append(key + "=" + value); |
|
return true; |
|
} |
|
|
|
int si = bounds.first, se = bounds.second; |
|
int ki = -1; |
|
for (int i = si + 1; i < se; i++) { |
|
int eq = lines[i].indexOf('='); |
|
if (eq > 0 && lines[i].left(eq).trimmed() == key) { ki = i; break; } |
|
} |
|
|
|
QString newLine = key + "=" + value; |
|
|
|
if (replace) { |
|
if (ki >= 0) |
|
lines[ki] = newLine; |
|
else |
|
lines.insert(si + 1, newLine); |
|
} else { |
|
if (ki >= 0) { |
|
int lastKi = ki; |
|
for (int i = ki + 1; i < se; i++) { |
|
int eq = lines[i].indexOf('='); |
|
if (eq > 0 && lines[i].left(eq).trimmed() == key) lastKi = i; |
|
} |
|
lines.insert(lastKi + 1, newLine); |
|
} else { |
|
lines.insert(si + 1, newLine); |
|
} |
|
} |
|
return true; |
|
} |
|
|
|
bool config_update_option(const QString& filePath, const QString& section, |
|
const QString& param, const QString& value, int flags) { |
|
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(); |
|
} |
|
|
|
bool replace = (flags != CONFIG_UPDATE_APPEND); |
|
if (!config_update_in_lines(lines, section, param, value, replace)) |
|
return false; |
|
|
|
if (!f.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) return false; |
|
QTextStream out(&f); |
|
for (int i = 0; i < lines.size(); i++) |
|
out << lines[i] << '\n'; |
|
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; |
|
}
|
|
|