|
- #include "application_settings_loader.h"
-
- #include "domain/project_limits.h"
-
- #include <QCoreApplication>
- #include <QDir>
- #include <QFile>
- #include <QFileInfo>
- #include <QMap>
- #include <QSaveFile>
- #include <QSet>
- #include <QStringList>
- #include <QTextCodec>
-
- #include <initializer_list>
-
- namespace {
-
- constexpr int kCurrentConfigurationVersion = 1;
-
- struct ParsedEntry
- {
- QString value;
- int line = 0;
- };
-
- std::string toUtf8(const QString &value)
- {
- const QByteArray bytes = value.toUtf8();
- return std::string(bytes.constData(), static_cast<std::size_t>(bytes.size()));
- }
-
- QString defaultFileContents()
- {
- return QStringLiteral(
- "[Config]\n"
- "Version=1\n"
- "\n"
- "[ProjectLimits]\n"
- "MaxHmiPages=32\n"
- "MaxHmiControlsPerPage=128\n"
- "MaxAlarmDefinitions=256\n"
- "MaxControlLogics=32\n"
- "MaxRungsPerLogic=256\n"
- "MaxOutputMessages=1000\n"
- "\n"
- "[HmiDefaults]\n"
- "DefaultPageWidth=800\n"
- "DefaultPageHeight=400\n"
- "\n"
- "[PlcDefaults]\n"
- "PortName=COM3\n"
- "ServerAddress=1\n"
- "BaudRate=9600\n"
- "DataBits=8\n"
- "Parity=2\n"
- "StopBits=1\n");
- }
-
- ApplicationSettingsLoadResult failureResult(
- const QString &file_path, const QString &reason)
- {
- ApplicationSettingsLoadResult result;
- result.warningRequired = true;
- result.warningMessage = toUtf8(
- QStringLiteral("应用配置无法使用,本次已全部采用代码默认值。\n%1")
- .arg(reason));
- result.messages.push_back(toUtf8(
- QStringLiteral("应用配置错误:%1(%2)")
- .arg(reason, QDir::toNativeSeparators(file_path))));
- result.messages.push_back(
- "应用配置:本次运行全部使用代码默认值");
- return result;
- }
-
- bool containsValue(std::initializer_list<int> values, int value)
- {
- for (int candidate : values)
- {
- if (candidate == value)
- {
- return true;
- }
- }
- return false;
- }
-
- bool readInteger(
- const QMap<QString, ParsedEntry> &entries,
- const QString &path,
- qint64 minimum,
- qint64 maximum,
- qint64 default_value,
- qint64 *value,
- QStringList *notices,
- QStringList *errors,
- bool required = false)
- {
- const auto entry = entries.constFind(path);
- if (entry == entries.cend())
- {
- if (required)
- {
- errors->append(QStringLiteral("缺少必填字段 %1").arg(path));
- }
- else
- {
- notices->append(
- QStringLiteral("配置字段 %1 缺失,使用代码默认值 %2")
- .arg(path)
- .arg(default_value));
- }
- *value = default_value;
- return !required;
- }
-
- bool converted = false;
- const qint64 parsed = entry->value.toLongLong(&converted, 10);
- if (!converted || parsed < minimum || parsed > maximum)
- {
- errors->append(
- QStringLiteral("第 %1 行字段 %2 的值“%3”无效,允许范围为 %4~%5")
- .arg(entry->line)
- .arg(path, entry->value)
- .arg(minimum)
- .arg(maximum));
- return false;
- }
- *value = parsed;
- return true;
- }
-
- void addUnsupportedValueError(
- const QMap<QString, ParsedEntry> &entries,
- const QString &path,
- const QString &allowed,
- QStringList *errors)
- {
- const ParsedEntry &entry = entries[path];
- errors->append(
- QStringLiteral("第 %1 行字段 %2 的值“%3”无效,只允许 %4")
- .arg(entry.line)
- .arg(path, entry.value, allowed));
- }
-
- } // namespace
-
- QString ApplicationSettingsLoader::defaultFilePath()
- {
- return QDir(QCoreApplication::applicationDirPath()).filePath(
- QStringLiteral("config/application.ini"));
- }
-
- ApplicationSettingsLoadResult ApplicationSettingsLoader::load(
- const QString &file_path)
- {
- if (!QFileInfo::exists(file_path))
- {
- const QString directory = QFileInfo(file_path).absolutePath();
- if (!QDir().mkpath(directory))
- {
- return failureResult(
- file_path,
- QStringLiteral("无法创建配置目录 %1,可能没有写入权限")
- .arg(QDir::toNativeSeparators(directory)));
- }
-
- QSaveFile file(file_path);
- if (!file.open(QIODevice::WriteOnly))
- {
- return failureResult(file_path, file.errorString());
- }
- const QByteArray data = defaultFileContents().toUtf8();
- if (file.write(data) != data.size())
- {
- const QString reason = file.errorString();
- file.cancelWriting();
- return failureResult(file_path, reason);
- }
- if (!file.commit())
- {
- return failureResult(file_path, file.errorString());
- }
-
- ApplicationSettingsLoadResult result;
- result.messages.push_back(toUtf8(
- QStringLiteral("应用配置:已创建默认配置文件 %1")
- .arg(QDir::toNativeSeparators(file_path))));
- result.messages.push_back(
- "应用配置:首次运行使用代码默认值,修改后请重启程序");
- return result;
- }
-
- QFile file(file_path);
- if (!file.open(QIODevice::ReadOnly))
- {
- return failureResult(file_path, file.errorString());
- }
- const QByteArray data = file.readAll();
- if (file.error() != QFileDevice::NoError)
- {
- return failureResult(file_path, file.errorString());
- }
-
- QTextCodec::ConverterState converter_state;
- const QString text = QTextCodec::codecForName("UTF-8")->toUnicode(
- data.constData(), data.size(), &converter_state);
- if (converter_state.invalidChars > 0)
- {
- return failureResult(file_path, QStringLiteral("配置文件不是有效的 UTF-8 文本"));
- }
-
- const QSet<QString> known_fields{
- QStringLiteral("Config.Version"),
- QStringLiteral("ProjectLimits.MaxHmiPages"),
- QStringLiteral("ProjectLimits.MaxHmiControlsPerPage"),
- QStringLiteral("ProjectLimits.MaxAlarmDefinitions"),
- QStringLiteral("ProjectLimits.MaxControlLogics"),
- QStringLiteral("ProjectLimits.MaxRungsPerLogic"),
- QStringLiteral("ProjectLimits.MaxOutputMessages"),
- QStringLiteral("HmiDefaults.DefaultPageWidth"),
- QStringLiteral("HmiDefaults.DefaultPageHeight"),
- QStringLiteral("PlcDefaults.PortName"),
- QStringLiteral("PlcDefaults.ServerAddress"),
- QStringLiteral("PlcDefaults.BaudRate"),
- QStringLiteral("PlcDefaults.DataBits"),
- QStringLiteral("PlcDefaults.Parity"),
- QStringLiteral("PlcDefaults.StopBits")};
-
- QMap<QString, ParsedEntry> entries;
- QStringList notices;
- QStringList errors;
- QString current_section;
- QString normalized_text = text;
- if (!normalized_text.isEmpty()
- && normalized_text.front() == QChar::ByteOrderMark)
- {
- normalized_text.remove(0, 1);
- }
- const QStringList lines = normalized_text.split(QLatin1Char('\n'));
- for (int index = 0; index < lines.size(); ++index)
- {
- QString line = lines.at(index);
- if (line.endsWith(QLatin1Char('\r')))
- {
- line.chop(1);
- }
- const QString trimmed = line.trimmed();
- const int line_number = index + 1;
- if (trimmed.isEmpty() || trimmed.startsWith(QLatin1Char(';'))
- || trimmed.startsWith(QLatin1Char('#')))
- {
- continue;
- }
- if (trimmed.startsWith(QLatin1Char('[')))
- {
- if (!trimmed.endsWith(QLatin1Char(']')) || trimmed.size() < 3)
- {
- errors.append(
- QStringLiteral("第 %1 行分区格式无效:%2")
- .arg(line_number)
- .arg(trimmed));
- current_section.clear();
- continue;
- }
- current_section = trimmed.mid(1, trimmed.size() - 2).trimmed();
- if (current_section.isEmpty())
- {
- errors.append(QStringLiteral("第 %1 行分区名称不能为空").arg(line_number));
- }
- continue;
- }
-
- const int separator = trimmed.indexOf(QLatin1Char('='));
- if (separator <= 0 || current_section.isEmpty())
- {
- errors.append(
- QStringLiteral("第 %1 行字段格式无效:%2")
- .arg(line_number)
- .arg(trimmed));
- continue;
- }
- const QString key = trimmed.left(separator).trimmed();
- const QString value = trimmed.mid(separator + 1).trimmed();
- if (key.isEmpty())
- {
- errors.append(QStringLiteral("第 %1 行字段名称不能为空").arg(line_number));
- continue;
- }
- const QString path = current_section + QLatin1Char('.') + key;
- if (entries.contains(path))
- {
- errors.append(
- QStringLiteral("第 %1 行字段 %2 重复,首次出现在第 %3 行")
- .arg(line_number)
- .arg(path)
- .arg(entries.value(path).line));
- continue;
- }
- entries.insert(path, {value, line_number});
- if (!known_fields.contains(path))
- {
- notices.append(
- QStringLiteral("第 %1 行未知字段 %2 已忽略")
- .arg(line_number)
- .arg(path));
- }
- }
-
- ApplicationSettings candidate;
- qint64 parsed = 0;
- if (readInteger(
- entries, QStringLiteral("Config.Version"),
- kCurrentConfigurationVersion, kCurrentConfigurationVersion,
- kCurrentConfigurationVersion, &parsed, ¬ices, &errors, true))
- {
- candidate.version = static_cast<int>(parsed);
- }
-
- if (readInteger(
- entries, QStringLiteral("ProjectLimits.MaxHmiPages"),
- 1, ProjectLimits::kMaximumHmiPages,
- ProjectLimits::kMaximumHmiPages, &parsed, ¬ices, &errors))
- {
- candidate.projectLimits.maximumHmiPages = static_cast<std::size_t>(parsed);
- }
- if (readInteger(
- entries, QStringLiteral("ProjectLimits.MaxHmiControlsPerPage"),
- 1, ProjectLimits::kMaximumHmiControlsPerPage,
- ProjectLimits::kMaximumHmiControlsPerPage,
- &parsed, ¬ices, &errors))
- {
- candidate.projectLimits.maximumHmiControlsPerPage =
- static_cast<std::size_t>(parsed);
- }
- if (readInteger(
- entries, QStringLiteral("ProjectLimits.MaxAlarmDefinitions"),
- 0, ProjectLimits::kMaximumAlarmDefinitions,
- ProjectLimits::kMaximumAlarmDefinitions,
- &parsed, ¬ices, &errors))
- {
- candidate.projectLimits.maximumAlarmDefinitions =
- static_cast<std::size_t>(parsed);
- }
- if (readInteger(
- entries, QStringLiteral("ProjectLimits.MaxControlLogics"),
- 1, ProjectLimits::kMaximumControlLogics,
- ProjectLimits::kMaximumControlLogics,
- &parsed, ¬ices, &errors))
- {
- candidate.projectLimits.maximumControlLogics =
- static_cast<std::size_t>(parsed);
- }
- if (readInteger(
- entries, QStringLiteral("ProjectLimits.MaxRungsPerLogic"),
- 0, ProjectLimits::kMaximumRungsPerLogic,
- ProjectLimits::kMaximumRungsPerLogic,
- &parsed, ¬ices, &errors))
- {
- candidate.projectLimits.maximumRungsPerLogic =
- static_cast<std::size_t>(parsed);
- }
- if (readInteger(
- entries, QStringLiteral("ProjectLimits.MaxOutputMessages"),
- 1, ProjectLimits::kMaximumOutputMessages,
- ProjectLimits::kMaximumOutputMessages,
- &parsed, ¬ices, &errors))
- {
- candidate.projectLimits.maximumOutputMessages = static_cast<int>(parsed);
- }
-
- if (readInteger(
- entries, QStringLiteral("HmiDefaults.DefaultPageWidth"),
- ProjectLimits::kMinimumHmiPageWidth,
- ProjectLimits::kMaximumHmiPageWidth,
- ProjectLimits::kDefaultHmiPageWidth,
- &parsed, ¬ices, &errors))
- {
- candidate.hmiDefaults.pageWidth = static_cast<int>(parsed);
- }
- if (readInteger(
- entries, QStringLiteral("HmiDefaults.DefaultPageHeight"),
- ProjectLimits::kMinimumHmiPageHeight,
- ProjectLimits::kMaximumHmiPageHeight,
- ProjectLimits::kDefaultHmiPageHeight,
- &parsed, ¬ices, &errors))
- {
- candidate.hmiDefaults.pageHeight = static_cast<int>(parsed);
- }
-
- const QString port_path = QStringLiteral("PlcDefaults.PortName");
- const auto port_entry = entries.constFind(port_path);
- if (port_entry == entries.cend())
- {
- notices.append(
- QStringLiteral("配置字段 %1 缺失,使用代码默认值 COM3")
- .arg(port_path));
- }
- else
- {
- const QByteArray port_bytes = port_entry->value.toUtf8();
- if (port_entry->value.trimmed().isEmpty()
- || port_bytes.size() > static_cast<int>(ProjectLimits::kMaximumIdBytes))
- {
- errors.append(
- QStringLiteral("第 %1 行字段 %2 的值“%3”无效,端口名称不能为空且不能超过 128 个 UTF-8 字节")
- .arg(port_entry->line)
- .arg(port_path, port_entry->value));
- }
- else
- {
- candidate.plcDefaults.portName = toUtf8(port_entry->value.trimmed());
- }
- }
-
- if (readInteger(
- entries, QStringLiteral("PlcDefaults.ServerAddress"),
- ProjectLimits::kMinimumPlcServerAddress,
- ProjectLimits::kMaximumPlcServerAddress,
- candidate.plcDefaults.serverAddress,
- &parsed, ¬ices, &errors))
- {
- candidate.plcDefaults.serverAddress = static_cast<int>(parsed);
- }
-
- int baud_rate = candidate.plcDefaults.baudRate;
- if (readInteger(
- entries, QStringLiteral("PlcDefaults.BaudRate"),
- 9600, 115200, candidate.plcDefaults.baudRate,
- &parsed, ¬ices, &errors))
- {
- baud_rate = static_cast<int>(parsed);
- if (!containsValue({9600, 19200, 38400, 57600, 115200}, baud_rate))
- {
- addUnsupportedValueError(
- entries, QStringLiteral("PlcDefaults.BaudRate"),
- QStringLiteral("9600、19200、38400、57600 或 115200"), &errors);
- }
- else
- {
- candidate.plcDefaults.baudRate = baud_rate;
- }
- }
-
- if (readInteger(
- entries, QStringLiteral("PlcDefaults.DataBits"),
- 7, 8, candidate.plcDefaults.dataBits,
- &parsed, ¬ices, &errors))
- {
- candidate.plcDefaults.dataBits = static_cast<int>(parsed);
- }
-
- int parity = candidate.plcDefaults.parity;
- if (readInteger(
- entries, QStringLiteral("PlcDefaults.Parity"),
- 0, 3, candidate.plcDefaults.parity,
- &parsed, ¬ices, &errors))
- {
- parity = static_cast<int>(parsed);
- if (!containsValue({0, 2, 3}, parity))
- {
- addUnsupportedValueError(
- entries, QStringLiteral("PlcDefaults.Parity"),
- QStringLiteral("0、2 或 3"), &errors);
- }
- else
- {
- candidate.plcDefaults.parity = parity;
- }
- }
-
- if (readInteger(
- entries, QStringLiteral("PlcDefaults.StopBits"),
- 1, 2, candidate.plcDefaults.stopBits,
- &parsed, ¬ices, &errors))
- {
- candidate.plcDefaults.stopBits = static_cast<int>(parsed);
- }
-
- if (!errors.isEmpty())
- {
- ApplicationSettingsLoadResult result;
- result.warningRequired = true;
- result.warningMessage =
- "应用配置存在严重错误,本次已全部采用代码默认值。详细原因请查看底部输出面板。";
- for (const QString &error : errors)
- {
- result.messages.push_back(toUtf8(QStringLiteral("应用配置错误:") + error));
- }
- for (const QString ¬ice : notices)
- {
- result.messages.push_back(toUtf8(QStringLiteral("应用配置提示:") + notice));
- }
- result.messages.push_back(
- "应用配置:整份配置不生效,本次运行全部使用代码默认值");
- return result;
- }
-
- ApplicationSettingsLoadResult result;
- result.settings = candidate;
- result.messages.push_back(toUtf8(
- QStringLiteral("应用配置:已加载 %1,修改后需重启程序")
- .arg(QDir::toNativeSeparators(file_path))));
- for (const QString ¬ice : notices)
- {
- result.messages.push_back(toUtf8(QStringLiteral("应用配置提示:") + notice));
- }
- return result;
- }
|