综合平台编程器项目的远程存储
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.
 
 
 
 

510 regels
17 KiB

  1. #include "application_settings_loader.h"
  2. #include "domain/project_limits.h"
  3. #include <QCoreApplication>
  4. #include <QDir>
  5. #include <QFile>
  6. #include <QFileInfo>
  7. #include <QMap>
  8. #include <QSaveFile>
  9. #include <QSet>
  10. #include <QStringList>
  11. #include <QTextCodec>
  12. #include <initializer_list>
  13. namespace {
  14. constexpr int kCurrentConfigurationVersion = 1;
  15. struct ParsedEntry
  16. {
  17. QString value;
  18. int line = 0;
  19. };
  20. std::string toUtf8(const QString &value)
  21. {
  22. const QByteArray bytes = value.toUtf8();
  23. return std::string(bytes.constData(), static_cast<std::size_t>(bytes.size()));
  24. }
  25. QString defaultFileContents()
  26. {
  27. return QStringLiteral(
  28. "[Config]\n"
  29. "Version=1\n"
  30. "\n"
  31. "[ProjectLimits]\n"
  32. "MaxHmiPages=32\n"
  33. "MaxHmiControlsPerPage=128\n"
  34. "MaxAlarmDefinitions=256\n"
  35. "MaxControlLogics=32\n"
  36. "MaxRungsPerLogic=256\n"
  37. "MaxOutputMessages=1000\n"
  38. "\n"
  39. "[HmiDefaults]\n"
  40. "DefaultPageWidth=800\n"
  41. "DefaultPageHeight=400\n"
  42. "\n"
  43. "[PlcDefaults]\n"
  44. "PortName=COM3\n"
  45. "ServerAddress=1\n"
  46. "BaudRate=9600\n"
  47. "DataBits=8\n"
  48. "Parity=2\n"
  49. "StopBits=1\n");
  50. }
  51. ApplicationSettingsLoadResult failureResult(
  52. const QString &file_path, const QString &reason)
  53. {
  54. ApplicationSettingsLoadResult result;
  55. result.warningRequired = true;
  56. result.warningMessage = toUtf8(
  57. QStringLiteral("应用配置无法使用,本次已全部采用代码默认值。\n%1")
  58. .arg(reason));
  59. result.messages.push_back(toUtf8(
  60. QStringLiteral("应用配置错误:%1(%2)")
  61. .arg(reason, QDir::toNativeSeparators(file_path))));
  62. result.messages.push_back(
  63. "应用配置:本次运行全部使用代码默认值");
  64. return result;
  65. }
  66. bool containsValue(std::initializer_list<int> values, int value)
  67. {
  68. for (int candidate : values)
  69. {
  70. if (candidate == value)
  71. {
  72. return true;
  73. }
  74. }
  75. return false;
  76. }
  77. bool readInteger(
  78. const QMap<QString, ParsedEntry> &entries,
  79. const QString &path,
  80. qint64 minimum,
  81. qint64 maximum,
  82. qint64 default_value,
  83. qint64 *value,
  84. QStringList *notices,
  85. QStringList *errors,
  86. bool required = false)
  87. {
  88. const auto entry = entries.constFind(path);
  89. if (entry == entries.cend())
  90. {
  91. if (required)
  92. {
  93. errors->append(QStringLiteral("缺少必填字段 %1").arg(path));
  94. }
  95. else
  96. {
  97. notices->append(
  98. QStringLiteral("配置字段 %1 缺失,使用代码默认值 %2")
  99. .arg(path)
  100. .arg(default_value));
  101. }
  102. *value = default_value;
  103. return !required;
  104. }
  105. bool converted = false;
  106. const qint64 parsed = entry->value.toLongLong(&converted, 10);
  107. if (!converted || parsed < minimum || parsed > maximum)
  108. {
  109. errors->append(
  110. QStringLiteral("第 %1 行字段 %2 的值“%3”无效,允许范围为 %4~%5")
  111. .arg(entry->line)
  112. .arg(path, entry->value)
  113. .arg(minimum)
  114. .arg(maximum));
  115. return false;
  116. }
  117. *value = parsed;
  118. return true;
  119. }
  120. void addUnsupportedValueError(
  121. const QMap<QString, ParsedEntry> &entries,
  122. const QString &path,
  123. const QString &allowed,
  124. QStringList *errors)
  125. {
  126. const ParsedEntry &entry = entries[path];
  127. errors->append(
  128. QStringLiteral("第 %1 行字段 %2 的值“%3”无效,只允许 %4")
  129. .arg(entry.line)
  130. .arg(path, entry.value, allowed));
  131. }
  132. } // namespace
  133. QString ApplicationSettingsLoader::defaultFilePath()
  134. {
  135. return QDir(QCoreApplication::applicationDirPath()).filePath(
  136. QStringLiteral("config/application.ini"));
  137. }
  138. ApplicationSettingsLoadResult ApplicationSettingsLoader::load(
  139. const QString &file_path)
  140. {
  141. if (!QFileInfo::exists(file_path))
  142. {
  143. const QString directory = QFileInfo(file_path).absolutePath();
  144. if (!QDir().mkpath(directory))
  145. {
  146. return failureResult(
  147. file_path,
  148. QStringLiteral("无法创建配置目录 %1,可能没有写入权限")
  149. .arg(QDir::toNativeSeparators(directory)));
  150. }
  151. QSaveFile file(file_path);
  152. if (!file.open(QIODevice::WriteOnly))
  153. {
  154. return failureResult(file_path, file.errorString());
  155. }
  156. const QByteArray data = defaultFileContents().toUtf8();
  157. if (file.write(data) != data.size())
  158. {
  159. const QString reason = file.errorString();
  160. file.cancelWriting();
  161. return failureResult(file_path, reason);
  162. }
  163. if (!file.commit())
  164. {
  165. return failureResult(file_path, file.errorString());
  166. }
  167. ApplicationSettingsLoadResult result;
  168. result.messages.push_back(toUtf8(
  169. QStringLiteral("应用配置:已创建默认配置文件 %1")
  170. .arg(QDir::toNativeSeparators(file_path))));
  171. result.messages.push_back(
  172. "应用配置:首次运行使用代码默认值,修改后请重启程序");
  173. return result;
  174. }
  175. QFile file(file_path);
  176. if (!file.open(QIODevice::ReadOnly))
  177. {
  178. return failureResult(file_path, file.errorString());
  179. }
  180. const QByteArray data = file.readAll();
  181. if (file.error() != QFileDevice::NoError)
  182. {
  183. return failureResult(file_path, file.errorString());
  184. }
  185. QTextCodec::ConverterState converter_state;
  186. const QString text = QTextCodec::codecForName("UTF-8")->toUnicode(
  187. data.constData(), data.size(), &converter_state);
  188. if (converter_state.invalidChars > 0)
  189. {
  190. return failureResult(file_path, QStringLiteral("配置文件不是有效的 UTF-8 文本"));
  191. }
  192. const QSet<QString> known_fields{
  193. QStringLiteral("Config.Version"),
  194. QStringLiteral("ProjectLimits.MaxHmiPages"),
  195. QStringLiteral("ProjectLimits.MaxHmiControlsPerPage"),
  196. QStringLiteral("ProjectLimits.MaxAlarmDefinitions"),
  197. QStringLiteral("ProjectLimits.MaxControlLogics"),
  198. QStringLiteral("ProjectLimits.MaxRungsPerLogic"),
  199. QStringLiteral("ProjectLimits.MaxOutputMessages"),
  200. QStringLiteral("HmiDefaults.DefaultPageWidth"),
  201. QStringLiteral("HmiDefaults.DefaultPageHeight"),
  202. QStringLiteral("PlcDefaults.PortName"),
  203. QStringLiteral("PlcDefaults.ServerAddress"),
  204. QStringLiteral("PlcDefaults.BaudRate"),
  205. QStringLiteral("PlcDefaults.DataBits"),
  206. QStringLiteral("PlcDefaults.Parity"),
  207. QStringLiteral("PlcDefaults.StopBits")};
  208. QMap<QString, ParsedEntry> entries;
  209. QStringList notices;
  210. QStringList errors;
  211. QString current_section;
  212. QString normalized_text = text;
  213. if (!normalized_text.isEmpty()
  214. && normalized_text.front() == QChar::ByteOrderMark)
  215. {
  216. normalized_text.remove(0, 1);
  217. }
  218. const QStringList lines = normalized_text.split(QLatin1Char('\n'));
  219. for (int index = 0; index < lines.size(); ++index)
  220. {
  221. QString line = lines.at(index);
  222. if (line.endsWith(QLatin1Char('\r')))
  223. {
  224. line.chop(1);
  225. }
  226. const QString trimmed = line.trimmed();
  227. const int line_number = index + 1;
  228. if (trimmed.isEmpty() || trimmed.startsWith(QLatin1Char(';'))
  229. || trimmed.startsWith(QLatin1Char('#')))
  230. {
  231. continue;
  232. }
  233. if (trimmed.startsWith(QLatin1Char('[')))
  234. {
  235. if (!trimmed.endsWith(QLatin1Char(']')) || trimmed.size() < 3)
  236. {
  237. errors.append(
  238. QStringLiteral("第 %1 行分区格式无效:%2")
  239. .arg(line_number)
  240. .arg(trimmed));
  241. current_section.clear();
  242. continue;
  243. }
  244. current_section = trimmed.mid(1, trimmed.size() - 2).trimmed();
  245. if (current_section.isEmpty())
  246. {
  247. errors.append(QStringLiteral("第 %1 行分区名称不能为空").arg(line_number));
  248. }
  249. continue;
  250. }
  251. const int separator = trimmed.indexOf(QLatin1Char('='));
  252. if (separator <= 0 || current_section.isEmpty())
  253. {
  254. errors.append(
  255. QStringLiteral("第 %1 行字段格式无效:%2")
  256. .arg(line_number)
  257. .arg(trimmed));
  258. continue;
  259. }
  260. const QString key = trimmed.left(separator).trimmed();
  261. const QString value = trimmed.mid(separator + 1).trimmed();
  262. if (key.isEmpty())
  263. {
  264. errors.append(QStringLiteral("第 %1 行字段名称不能为空").arg(line_number));
  265. continue;
  266. }
  267. const QString path = current_section + QLatin1Char('.') + key;
  268. if (entries.contains(path))
  269. {
  270. errors.append(
  271. QStringLiteral("第 %1 行字段 %2 重复,首次出现在第 %3 行")
  272. .arg(line_number)
  273. .arg(path)
  274. .arg(entries.value(path).line));
  275. continue;
  276. }
  277. entries.insert(path, {value, line_number});
  278. if (!known_fields.contains(path))
  279. {
  280. notices.append(
  281. QStringLiteral("第 %1 行未知字段 %2 已忽略")
  282. .arg(line_number)
  283. .arg(path));
  284. }
  285. }
  286. ApplicationSettings candidate;
  287. qint64 parsed = 0;
  288. if (readInteger(
  289. entries, QStringLiteral("Config.Version"),
  290. kCurrentConfigurationVersion, kCurrentConfigurationVersion,
  291. kCurrentConfigurationVersion, &parsed, &notices, &errors, true))
  292. {
  293. candidate.version = static_cast<int>(parsed);
  294. }
  295. if (readInteger(
  296. entries, QStringLiteral("ProjectLimits.MaxHmiPages"),
  297. 1, ProjectLimits::kMaximumHmiPages,
  298. ProjectLimits::kMaximumHmiPages, &parsed, &notices, &errors))
  299. {
  300. candidate.projectLimits.maximumHmiPages = static_cast<std::size_t>(parsed);
  301. }
  302. if (readInteger(
  303. entries, QStringLiteral("ProjectLimits.MaxHmiControlsPerPage"),
  304. 1, ProjectLimits::kMaximumHmiControlsPerPage,
  305. ProjectLimits::kMaximumHmiControlsPerPage,
  306. &parsed, &notices, &errors))
  307. {
  308. candidate.projectLimits.maximumHmiControlsPerPage =
  309. static_cast<std::size_t>(parsed);
  310. }
  311. if (readInteger(
  312. entries, QStringLiteral("ProjectLimits.MaxAlarmDefinitions"),
  313. 0, ProjectLimits::kMaximumAlarmDefinitions,
  314. ProjectLimits::kMaximumAlarmDefinitions,
  315. &parsed, &notices, &errors))
  316. {
  317. candidate.projectLimits.maximumAlarmDefinitions =
  318. static_cast<std::size_t>(parsed);
  319. }
  320. if (readInteger(
  321. entries, QStringLiteral("ProjectLimits.MaxControlLogics"),
  322. 1, ProjectLimits::kMaximumControlLogics,
  323. ProjectLimits::kMaximumControlLogics,
  324. &parsed, &notices, &errors))
  325. {
  326. candidate.projectLimits.maximumControlLogics =
  327. static_cast<std::size_t>(parsed);
  328. }
  329. if (readInteger(
  330. entries, QStringLiteral("ProjectLimits.MaxRungsPerLogic"),
  331. 0, ProjectLimits::kMaximumRungsPerLogic,
  332. ProjectLimits::kMaximumRungsPerLogic,
  333. &parsed, &notices, &errors))
  334. {
  335. candidate.projectLimits.maximumRungsPerLogic =
  336. static_cast<std::size_t>(parsed);
  337. }
  338. if (readInteger(
  339. entries, QStringLiteral("ProjectLimits.MaxOutputMessages"),
  340. 1, ProjectLimits::kMaximumOutputMessages,
  341. ProjectLimits::kMaximumOutputMessages,
  342. &parsed, &notices, &errors))
  343. {
  344. candidate.projectLimits.maximumOutputMessages = static_cast<int>(parsed);
  345. }
  346. if (readInteger(
  347. entries, QStringLiteral("HmiDefaults.DefaultPageWidth"),
  348. ProjectLimits::kMinimumHmiPageWidth,
  349. ProjectLimits::kMaximumHmiPageWidth,
  350. ProjectLimits::kDefaultHmiPageWidth,
  351. &parsed, &notices, &errors))
  352. {
  353. candidate.hmiDefaults.pageWidth = static_cast<int>(parsed);
  354. }
  355. if (readInteger(
  356. entries, QStringLiteral("HmiDefaults.DefaultPageHeight"),
  357. ProjectLimits::kMinimumHmiPageHeight,
  358. ProjectLimits::kMaximumHmiPageHeight,
  359. ProjectLimits::kDefaultHmiPageHeight,
  360. &parsed, &notices, &errors))
  361. {
  362. candidate.hmiDefaults.pageHeight = static_cast<int>(parsed);
  363. }
  364. const QString port_path = QStringLiteral("PlcDefaults.PortName");
  365. const auto port_entry = entries.constFind(port_path);
  366. if (port_entry == entries.cend())
  367. {
  368. notices.append(
  369. QStringLiteral("配置字段 %1 缺失,使用代码默认值 COM3")
  370. .arg(port_path));
  371. }
  372. else
  373. {
  374. const QByteArray port_bytes = port_entry->value.toUtf8();
  375. if (port_entry->value.trimmed().isEmpty()
  376. || port_bytes.size() > static_cast<int>(ProjectLimits::kMaximumIdBytes))
  377. {
  378. errors.append(
  379. QStringLiteral("第 %1 行字段 %2 的值“%3”无效,端口名称不能为空且不能超过 128 个 UTF-8 字节")
  380. .arg(port_entry->line)
  381. .arg(port_path, port_entry->value));
  382. }
  383. else
  384. {
  385. candidate.plcDefaults.portName = toUtf8(port_entry->value.trimmed());
  386. }
  387. }
  388. if (readInteger(
  389. entries, QStringLiteral("PlcDefaults.ServerAddress"),
  390. ProjectLimits::kMinimumPlcServerAddress,
  391. ProjectLimits::kMaximumPlcServerAddress,
  392. candidate.plcDefaults.serverAddress,
  393. &parsed, &notices, &errors))
  394. {
  395. candidate.plcDefaults.serverAddress = static_cast<int>(parsed);
  396. }
  397. int baud_rate = candidate.plcDefaults.baudRate;
  398. if (readInteger(
  399. entries, QStringLiteral("PlcDefaults.BaudRate"),
  400. 9600, 115200, candidate.plcDefaults.baudRate,
  401. &parsed, &notices, &errors))
  402. {
  403. baud_rate = static_cast<int>(parsed);
  404. if (!containsValue({9600, 19200, 38400, 57600, 115200}, baud_rate))
  405. {
  406. addUnsupportedValueError(
  407. entries, QStringLiteral("PlcDefaults.BaudRate"),
  408. QStringLiteral("9600、19200、38400、57600 或 115200"), &errors);
  409. }
  410. else
  411. {
  412. candidate.plcDefaults.baudRate = baud_rate;
  413. }
  414. }
  415. if (readInteger(
  416. entries, QStringLiteral("PlcDefaults.DataBits"),
  417. 7, 8, candidate.plcDefaults.dataBits,
  418. &parsed, &notices, &errors))
  419. {
  420. candidate.plcDefaults.dataBits = static_cast<int>(parsed);
  421. }
  422. int parity = candidate.plcDefaults.parity;
  423. if (readInteger(
  424. entries, QStringLiteral("PlcDefaults.Parity"),
  425. 0, 3, candidate.plcDefaults.parity,
  426. &parsed, &notices, &errors))
  427. {
  428. parity = static_cast<int>(parsed);
  429. if (!containsValue({0, 2, 3}, parity))
  430. {
  431. addUnsupportedValueError(
  432. entries, QStringLiteral("PlcDefaults.Parity"),
  433. QStringLiteral("0、2 或 3"), &errors);
  434. }
  435. else
  436. {
  437. candidate.plcDefaults.parity = parity;
  438. }
  439. }
  440. if (readInteger(
  441. entries, QStringLiteral("PlcDefaults.StopBits"),
  442. 1, 2, candidate.plcDefaults.stopBits,
  443. &parsed, &notices, &errors))
  444. {
  445. candidate.plcDefaults.stopBits = static_cast<int>(parsed);
  446. }
  447. if (!errors.isEmpty())
  448. {
  449. ApplicationSettingsLoadResult result;
  450. result.warningRequired = true;
  451. result.warningMessage =
  452. "应用配置存在严重错误,本次已全部采用代码默认值。详细原因请查看底部输出面板。";
  453. for (const QString &error : errors)
  454. {
  455. result.messages.push_back(toUtf8(QStringLiteral("应用配置错误:") + error));
  456. }
  457. for (const QString &notice : notices)
  458. {
  459. result.messages.push_back(toUtf8(QStringLiteral("应用配置提示:") + notice));
  460. }
  461. result.messages.push_back(
  462. "应用配置:整份配置不生效,本次运行全部使用代码默认值");
  463. return result;
  464. }
  465. ApplicationSettingsLoadResult result;
  466. result.settings = candidate;
  467. result.messages.push_back(toUtf8(
  468. QStringLiteral("应用配置:已加载 %1,修改后需重启程序")
  469. .arg(QDir::toNativeSeparators(file_path))));
  470. for (const QString &notice : notices)
  471. {
  472. result.messages.push_back(toUtf8(QStringLiteral("应用配置提示:") + notice));
  473. }
  474. return result;
  475. }