765 lines
28 KiB
C#
765 lines
28 KiB
C#
|
|
using System;
|
|||
|
|
using System.Collections.Generic;
|
|||
|
|
using System.IO;
|
|||
|
|
using System.Globalization;
|
|||
|
|
using System.Text;
|
|||
|
|
using UnityEngine;
|
|||
|
|
using LitJson;
|
|||
|
|
using GeneralTools;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 数据持久化服务实现
|
|||
|
|
/// 使用JSON格式存储应用数据,INI格式存储系统配置
|
|||
|
|
/// </summary>
|
|||
|
|
public class DataPersistenceService : IDataPersistenceService
|
|||
|
|
{
|
|||
|
|
private readonly string _settingsPath;
|
|||
|
|
private readonly string _userPrefsPath;
|
|||
|
|
private readonly string _appConfigPath;
|
|||
|
|
private readonly string _configIniPath;
|
|||
|
|
private readonly string _streamingConfigIniPath;
|
|||
|
|
|
|||
|
|
public DataPersistenceService()
|
|||
|
|
{
|
|||
|
|
// 设置存储路径
|
|||
|
|
var dataPath = Application.persistentDataPath;
|
|||
|
|
_settingsPath = Path.Combine(dataPath, "settings.json");
|
|||
|
|
_userPrefsPath = Path.Combine(dataPath, "UserPrefs");
|
|||
|
|
_appConfigPath = Path.Combine(dataPath, "appconfig.json");
|
|||
|
|
_configIniPath = Path.Combine(dataPath, "config.ini");
|
|||
|
|
_streamingConfigIniPath = Path.Combine(Application.streamingAssetsPath, "config.ini");
|
|||
|
|
|
|||
|
|
// 确保目录存在
|
|||
|
|
EnsureDirectoryExists(_userPrefsPath);
|
|||
|
|
TryCopyIniToPersistent();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public bool SaveSettings(SystemSettings settings)
|
|||
|
|
{
|
|||
|
|
bool jsonSaved = false;
|
|||
|
|
bool iniSaved = false;
|
|||
|
|
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
var json = JsonMapper.ToJson(settings);
|
|||
|
|
jsonSaved = WriteTextAtomically(_settingsPath, json);
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
Debug.LogError($"保存JSON设置失败: {ex.Message}");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
iniSaved = SaveToIniFile(settings);
|
|||
|
|
|
|||
|
|
if (jsonSaved || iniSaved)
|
|||
|
|
{
|
|||
|
|
Debug.Log($"设置已保存 - JSON: {jsonSaved}, INI: {iniSaved}");
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Debug.LogError("保存设置失败:JSON和INI都未成功写入");
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public SystemSettings LoadSettings()
|
|||
|
|
{
|
|||
|
|
if (TryLoadJsonSettings(out var settings, out var jsonSourcePath))
|
|||
|
|
{
|
|||
|
|
ApplyNewestIniOverrides(settings, jsonSourcePath);
|
|||
|
|
return settings;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (TryLoadIniSettings(_configIniPath, "持久化INI配置", out settings))
|
|||
|
|
{
|
|||
|
|
return settings;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (TryLoadIniSettings(_configIniPath + ".bak", "持久化INI备份配置", out settings))
|
|||
|
|
{
|
|||
|
|
return settings;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (File.Exists(_streamingConfigIniPath))
|
|||
|
|
{
|
|||
|
|
settings = LoadFromIniFile(_streamingConfigIniPath);
|
|||
|
|
Debug.Log($"从默认INI配置加载设置成功: {_streamingConfigIniPath}");
|
|||
|
|
return settings;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Debug.Log("使用默认设置");
|
|||
|
|
return new SystemSettings();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public bool SaveUserPreferences(string userId, UserPreferences preferences)
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
var filePath = Path.Combine(_userPrefsPath, $"{userId}_prefs.json");
|
|||
|
|
var json = JsonMapper.ToJson(preferences);
|
|||
|
|
File.WriteAllText(filePath, json);
|
|||
|
|
|
|||
|
|
Debug.Log($"用户偏好已保存: {userId}");
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
Debug.LogError($"保存用户偏好失败: {ex.Message}");
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public UserPreferences LoadUserPreferences(string userId)
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
var filePath = Path.Combine(_userPrefsPath, $"{userId}_prefs.json");
|
|||
|
|
if (File.Exists(filePath))
|
|||
|
|
{
|
|||
|
|
var json = File.ReadAllText(filePath);
|
|||
|
|
return JsonMapper.ToObject<UserPreferences>(json);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
Debug.LogError($"加载用户偏好失败: {ex.Message}");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return new UserPreferences { userId = userId };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public bool SaveAppConfig(AppConfig config)
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
var json = JsonMapper.ToJson(config);
|
|||
|
|
File.WriteAllText(_appConfigPath, json);
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
Debug.LogError($"保存应用配置失败: {ex.Message}");
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public AppConfig LoadAppConfig()
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
if (File.Exists(_appConfigPath))
|
|||
|
|
{
|
|||
|
|
var json = File.ReadAllText(_appConfigPath);
|
|||
|
|
return JsonMapper.ToObject<AppConfig>(json);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
Debug.LogError($"加载应用配置失败: {ex.Message}");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return new AppConfig();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public bool ExportData(string filePath, ExportDataType dataType)
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
var exportData = new
|
|||
|
|
{
|
|||
|
|
ExportTime = DateTime.Now,
|
|||
|
|
DataType = dataType.ToString(),
|
|||
|
|
Data = GetExportData(dataType)
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
var json = JsonMapper.ToJson(exportData);
|
|||
|
|
File.WriteAllText(filePath, json);
|
|||
|
|
|
|||
|
|
Debug.Log($"数据导出成功: {filePath}");
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
Debug.LogError($"导出数据失败: {ex.Message}");
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void ClearAllData()
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
if (File.Exists(_settingsPath))
|
|||
|
|
File.Delete(_settingsPath);
|
|||
|
|
if (File.Exists(_appConfigPath))
|
|||
|
|
File.Delete(_appConfigPath);
|
|||
|
|
if (Directory.Exists(_userPrefsPath))
|
|||
|
|
Directory.Delete(_userPrefsPath, true);
|
|||
|
|
|
|||
|
|
Debug.Log("所有持久化数据已清除");
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
Debug.LogError($"清除数据失败: {ex.Message}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private bool SaveToIniFile(SystemSettings settings)
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
var sb = new StringBuilder();
|
|||
|
|
|
|||
|
|
AppendIniSection(sb, "Display",
|
|||
|
|
("brightness", settings.brightness.ToString("F1", CultureInfo.InvariantCulture)));
|
|||
|
|
|
|||
|
|
AppendIniSection(sb, "Audio",
|
|||
|
|
("volume", settings.volume.ToString(CultureInfo.InvariantCulture)),
|
|||
|
|
("muteDurationMinutes", settings.muteDurationMinutes.ToString(CultureInfo.InvariantCulture)));
|
|||
|
|
|
|||
|
|
AppendIniSection(sb, "SerialPort",
|
|||
|
|
("serialPortName", settings.serialPortName ?? string.Empty),
|
|||
|
|
("serialBaudRate", settings.serialBaudRate.ToString(CultureInfo.InvariantCulture)),
|
|||
|
|
("enableSerialCommunication", settings.enableSerialCommunication.ToString()));
|
|||
|
|
|
|||
|
|
AppendIniSection(sb, "BFI",
|
|||
|
|
("bfiLowThreshold", settings.bfiLowThreshold.ToString("F1", CultureInfo.InvariantCulture)),
|
|||
|
|
("bfiHighThreshold", settings.bfiHighThreshold.ToString("F1", CultureInfo.InvariantCulture)),
|
|||
|
|
("bfiAlarmPriority", settings.bfiAlarmPriority.ToString(CultureInfo.InvariantCulture)),
|
|||
|
|
("enableBFIAlarm", settings.enableBFIAlarm.ToString()));
|
|||
|
|
|
|||
|
|
AppendIniSection(sb, "Time",
|
|||
|
|
("useCustomSystemTime", settings.useCustomSystemTime.ToString()),
|
|||
|
|
("customTimeOffsetTicks", settings.customTimeOffsetTicks.ToString(CultureInfo.InvariantCulture)),
|
|||
|
|
("systemTime", settings.systemTime == DateTime.MinValue ? string.Empty : settings.systemTime.ToString("o", CultureInfo.InvariantCulture)));
|
|||
|
|
|
|||
|
|
AppendIniSection(sb, "Network",
|
|||
|
|
("mode", settings.network.Mode.ToString()),
|
|||
|
|
("ipv4", settings.network.IPv4 ?? string.Empty),
|
|||
|
|
("mask", settings.network.Mask ?? string.Empty),
|
|||
|
|
("gateway", settings.network.Gateway ?? string.Empty),
|
|||
|
|
("dns1", settings.network.Dns1 ?? string.Empty),
|
|||
|
|
("dns2", settings.network.Dns2 ?? string.Empty));
|
|||
|
|
|
|||
|
|
AppendIniSection(sb, "System",
|
|||
|
|
("autoBackup", settings.autoBackup.ToString()),
|
|||
|
|
("dataRetentionDays", settings.dataRetentionDays.ToString(CultureInfo.InvariantCulture)));
|
|||
|
|
|
|||
|
|
bool saved = WriteTextAtomically(_configIniPath, sb.ToString());
|
|||
|
|
if (saved)
|
|||
|
|
{
|
|||
|
|
IniFile.ClearCache(_configIniPath);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return saved;
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
Debug.LogError($"保存到INI文件失败: {ex.Message}");
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private SystemSettings LoadFromIniFile(string iniPath)
|
|||
|
|
{
|
|||
|
|
var settings = new SystemSettings();
|
|||
|
|
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
IniFile.ClearCache(iniPath);
|
|||
|
|
|
|||
|
|
// 显示设置,使用公开的方法
|
|||
|
|
var brightness = IniFile.ReadIniData("Display", "brightness", "50", iniPath);
|
|||
|
|
if (float.TryParse(brightness, NumberStyles.Float, CultureInfo.InvariantCulture, out float b))
|
|||
|
|
settings.brightness = b;
|
|||
|
|
|
|||
|
|
// 音频设置
|
|||
|
|
var volume = IniFile.ReadIniData("Audio", "volume", "50", iniPath);
|
|||
|
|
if (int.TryParse(volume, NumberStyles.Integer, CultureInfo.InvariantCulture, out int v))
|
|||
|
|
settings.volume = v;
|
|||
|
|
|
|||
|
|
var muteDuration = IniFile.ReadIniData("Audio", "muteDurationMinutes", "3", iniPath);
|
|||
|
|
if (int.TryParse(muteDuration, NumberStyles.Integer, CultureInfo.InvariantCulture, out int md))
|
|||
|
|
settings.muteDurationMinutes = md;
|
|||
|
|
|
|||
|
|
var serialPortName = IniFile.ReadIniData("SerialPort", "serialPortName", settings.serialPortName, iniPath);
|
|||
|
|
if (!string.IsNullOrWhiteSpace(serialPortName))
|
|||
|
|
settings.serialPortName = serialPortName;
|
|||
|
|
|
|||
|
|
var serialBaudRate = IniFile.ReadIniData("SerialPort", "serialBaudRate", settings.serialBaudRate.ToString(), iniPath);
|
|||
|
|
if (int.TryParse(serialBaudRate, NumberStyles.Integer, CultureInfo.InvariantCulture, out int sbr))
|
|||
|
|
settings.serialBaudRate = sbr;
|
|||
|
|
|
|||
|
|
var enableSerial = IniFile.ReadIniData("SerialPort", "enableSerialCommunication", settings.enableSerialCommunication.ToString(), iniPath);
|
|||
|
|
if (bool.TryParse(enableSerial, out bool esc))
|
|||
|
|
settings.enableSerialCommunication = esc;
|
|||
|
|
|
|||
|
|
var bfiLowThreshold = IniFile.ReadIniData("BFI", "bfiLowThreshold", settings.bfiLowThreshold.ToString(CultureInfo.InvariantCulture), iniPath);
|
|||
|
|
if (float.TryParse(bfiLowThreshold, NumberStyles.Float, CultureInfo.InvariantCulture, out float bfiLow))
|
|||
|
|
settings.bfiLowThreshold = bfiLow;
|
|||
|
|
|
|||
|
|
var bfiHighThreshold = IniFile.ReadIniData("BFI", "bfiHighThreshold", settings.bfiHighThreshold.ToString(CultureInfo.InvariantCulture), iniPath);
|
|||
|
|
if (float.TryParse(bfiHighThreshold, NumberStyles.Float, CultureInfo.InvariantCulture, out float bfiHigh))
|
|||
|
|
settings.bfiHighThreshold = bfiHigh;
|
|||
|
|
|
|||
|
|
var bfiAlarmPriority = IniFile.ReadIniData("BFI", "bfiAlarmPriority", settings.bfiAlarmPriority.ToString(), iniPath);
|
|||
|
|
if (int.TryParse(bfiAlarmPriority, NumberStyles.Integer, CultureInfo.InvariantCulture, out int priority))
|
|||
|
|
settings.bfiAlarmPriority = priority;
|
|||
|
|
|
|||
|
|
var enableBfiAlarm = IniFile.ReadIniData("BFI", "enableBFIAlarm", settings.enableBFIAlarm.ToString(), iniPath);
|
|||
|
|
if (bool.TryParse(enableBfiAlarm, out bool enableAlarm))
|
|||
|
|
settings.enableBFIAlarm = enableAlarm;
|
|||
|
|
|
|||
|
|
var useCustomSystemTime = IniFile.ReadIniData("Time", "useCustomSystemTime", settings.useCustomSystemTime.ToString(), iniPath);
|
|||
|
|
if (bool.TryParse(useCustomSystemTime, out bool customTime))
|
|||
|
|
settings.useCustomSystemTime = customTime;
|
|||
|
|
|
|||
|
|
var customTimeOffsetTicks = IniFile.ReadIniData("Time", "customTimeOffsetTicks", settings.customTimeOffsetTicks.ToString(), iniPath);
|
|||
|
|
if (long.TryParse(customTimeOffsetTicks, NumberStyles.Integer, CultureInfo.InvariantCulture, out long offsetTicks))
|
|||
|
|
settings.customTimeOffsetTicks = offsetTicks;
|
|||
|
|
|
|||
|
|
var systemTime = IniFile.ReadIniData("Time", "systemTime", "", iniPath);
|
|||
|
|
if (!string.IsNullOrWhiteSpace(systemTime) &&
|
|||
|
|
DateTime.TryParse(systemTime, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out DateTime parsedTime))
|
|||
|
|
{
|
|||
|
|
settings.systemTime = parsedTime;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 网络设置
|
|||
|
|
var networkMode = IniFile.ReadIniData("Network", "mode", "Dhcp", iniPath);
|
|||
|
|
if (Enum.TryParse<NetworkMode>(networkMode, out NetworkMode nm))
|
|||
|
|
settings.network.Mode = nm;
|
|||
|
|
|
|||
|
|
settings.network.IPv4 = IniFile.ReadIniData("Network", "ipv4", "", iniPath);
|
|||
|
|
settings.network.Mask = IniFile.ReadIniData("Network", "mask", "", iniPath);
|
|||
|
|
settings.network.Gateway = IniFile.ReadIniData("Network", "gateway", "", iniPath);
|
|||
|
|
settings.network.Dns1 = IniFile.ReadIniData("Network", "dns1", "", iniPath);
|
|||
|
|
settings.network.Dns2 = IniFile.ReadIniData("Network", "dns2", "", iniPath);
|
|||
|
|
|
|||
|
|
// 系统设置
|
|||
|
|
var autoBackup = IniFile.ReadIniData("System", "autoBackup", "true", iniPath);
|
|||
|
|
if (bool.TryParse(autoBackup, out bool ab))
|
|||
|
|
settings.autoBackup = ab;
|
|||
|
|
|
|||
|
|
var dataRetention = IniFile.ReadIniData("System", "dataRetentionDays", "30", iniPath);
|
|||
|
|
if (int.TryParse(dataRetention, NumberStyles.Integer, CultureInfo.InvariantCulture, out int dr))
|
|||
|
|
settings.dataRetentionDays = dr;
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
Debug.LogError($"从INI文件加载设置失败 ({iniPath}): {ex.Message}");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return settings;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private static void AppendIniSection(StringBuilder builder, string sectionName, params (string Key, string Value)[] values)
|
|||
|
|
{
|
|||
|
|
builder.Append('[').Append(sectionName).AppendLine("]");
|
|||
|
|
foreach (var pair in values)
|
|||
|
|
{
|
|||
|
|
builder.Append(pair.Key).Append('=').AppendLine(pair.Value ?? string.Empty);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
builder.AppendLine();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void ApplyIniOverrides(SystemSettings settings, string iniPath)
|
|||
|
|
{
|
|||
|
|
if (settings == null || string.IsNullOrWhiteSpace(iniPath) || !File.Exists(iniPath))
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
IniFile.ClearCache(iniPath);
|
|||
|
|
|
|||
|
|
const string missing = "__DCX_SETTING_MISSING__";
|
|||
|
|
|
|||
|
|
string brightness = IniFile.ReadIniData("Display", "brightness", missing, iniPath);
|
|||
|
|
if (brightness != missing && float.TryParse(brightness, NumberStyles.Float, CultureInfo.InvariantCulture, out float b))
|
|||
|
|
settings.brightness = b;
|
|||
|
|
|
|||
|
|
string volume = IniFile.ReadIniData("Audio", "volume", missing, iniPath);
|
|||
|
|
if (volume != missing && int.TryParse(volume, out int v))
|
|||
|
|
settings.volume = v;
|
|||
|
|
|
|||
|
|
string muteDuration = IniFile.ReadIniData("Audio", "muteDurationMinutes", missing, iniPath);
|
|||
|
|
if (muteDuration != missing && int.TryParse(muteDuration, out int md))
|
|||
|
|
settings.muteDurationMinutes = md;
|
|||
|
|
|
|||
|
|
string serialPortName = IniFile.ReadIniData("SerialPort", "serialPortName", missing, iniPath);
|
|||
|
|
if (serialPortName != missing && !string.IsNullOrWhiteSpace(serialPortName))
|
|||
|
|
settings.serialPortName = serialPortName;
|
|||
|
|
|
|||
|
|
string serialBaudRate = IniFile.ReadIniData("SerialPort", "serialBaudRate", missing, iniPath);
|
|||
|
|
if (serialBaudRate != missing && int.TryParse(serialBaudRate, out int sbr))
|
|||
|
|
settings.serialBaudRate = sbr;
|
|||
|
|
|
|||
|
|
string enableSerial = IniFile.ReadIniData("SerialPort", "enableSerialCommunication", missing, iniPath);
|
|||
|
|
if (enableSerial != missing && bool.TryParse(enableSerial, out bool esc))
|
|||
|
|
settings.enableSerialCommunication = esc;
|
|||
|
|
|
|||
|
|
string bfiLowThreshold = IniFile.ReadIniData("BFI", "bfiLowThreshold", missing, iniPath);
|
|||
|
|
if (bfiLowThreshold != missing && float.TryParse(bfiLowThreshold, NumberStyles.Float, CultureInfo.InvariantCulture, out float bfiLow))
|
|||
|
|
settings.bfiLowThreshold = bfiLow;
|
|||
|
|
|
|||
|
|
string bfiHighThreshold = IniFile.ReadIniData("BFI", "bfiHighThreshold", missing, iniPath);
|
|||
|
|
if (bfiHighThreshold != missing && float.TryParse(bfiHighThreshold, NumberStyles.Float, CultureInfo.InvariantCulture, out float bfiHigh))
|
|||
|
|
settings.bfiHighThreshold = bfiHigh;
|
|||
|
|
|
|||
|
|
string bfiAlarmPriority = IniFile.ReadIniData("BFI", "bfiAlarmPriority", missing, iniPath);
|
|||
|
|
if (bfiAlarmPriority != missing && int.TryParse(bfiAlarmPriority, out int priority))
|
|||
|
|
settings.bfiAlarmPriority = priority;
|
|||
|
|
|
|||
|
|
string enableBfiAlarm = IniFile.ReadIniData("BFI", "enableBFIAlarm", missing, iniPath);
|
|||
|
|
if (enableBfiAlarm != missing && bool.TryParse(enableBfiAlarm, out bool enableAlarm))
|
|||
|
|
settings.enableBFIAlarm = enableAlarm;
|
|||
|
|
|
|||
|
|
string useCustomSystemTime = IniFile.ReadIniData("Time", "useCustomSystemTime", missing, iniPath);
|
|||
|
|
if (useCustomSystemTime != missing && bool.TryParse(useCustomSystemTime, out bool customTime))
|
|||
|
|
settings.useCustomSystemTime = customTime;
|
|||
|
|
|
|||
|
|
string customTimeOffsetTicks = IniFile.ReadIniData("Time", "customTimeOffsetTicks", missing, iniPath);
|
|||
|
|
if (customTimeOffsetTicks != missing && long.TryParse(customTimeOffsetTicks, out long offsetTicks))
|
|||
|
|
settings.customTimeOffsetTicks = offsetTicks;
|
|||
|
|
|
|||
|
|
string systemTime = IniFile.ReadIniData("Time", "systemTime", missing, iniPath);
|
|||
|
|
if (systemTime != missing && !string.IsNullOrWhiteSpace(systemTime) &&
|
|||
|
|
DateTime.TryParse(systemTime, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out DateTime parsedTime))
|
|||
|
|
{
|
|||
|
|
settings.systemTime = parsedTime;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
string networkMode = IniFile.ReadIniData("Network", "mode", missing, iniPath);
|
|||
|
|
if (networkMode != missing && Enum.TryParse<NetworkMode>(networkMode, out NetworkMode nm))
|
|||
|
|
settings.network.Mode = nm;
|
|||
|
|
|
|||
|
|
string ipv4 = IniFile.ReadIniData("Network", "ipv4", missing, iniPath);
|
|||
|
|
if (ipv4 != missing)
|
|||
|
|
settings.network.IPv4 = ipv4;
|
|||
|
|
|
|||
|
|
string mask = IniFile.ReadIniData("Network", "mask", missing, iniPath);
|
|||
|
|
if (mask != missing)
|
|||
|
|
settings.network.Mask = mask;
|
|||
|
|
|
|||
|
|
string gateway = IniFile.ReadIniData("Network", "gateway", missing, iniPath);
|
|||
|
|
if (gateway != missing)
|
|||
|
|
settings.network.Gateway = gateway;
|
|||
|
|
|
|||
|
|
string dns1 = IniFile.ReadIniData("Network", "dns1", missing, iniPath);
|
|||
|
|
if (dns1 != missing)
|
|||
|
|
settings.network.Dns1 = dns1;
|
|||
|
|
|
|||
|
|
string dns2 = IniFile.ReadIniData("Network", "dns2", missing, iniPath);
|
|||
|
|
if (dns2 != missing)
|
|||
|
|
settings.network.Dns2 = dns2;
|
|||
|
|
|
|||
|
|
string autoBackup = IniFile.ReadIniData("System", "autoBackup", missing, iniPath);
|
|||
|
|
if (autoBackup != missing && bool.TryParse(autoBackup, out bool ab))
|
|||
|
|
settings.autoBackup = ab;
|
|||
|
|
|
|||
|
|
string dataRetention = IniFile.ReadIniData("System", "dataRetentionDays", missing, iniPath);
|
|||
|
|
if (dataRetention != missing && int.TryParse(dataRetention, out int dr))
|
|||
|
|
settings.dataRetentionDays = dr;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private bool WriteTextAtomically(string filePath, string content)
|
|||
|
|
{
|
|||
|
|
var tempPath = string.IsNullOrWhiteSpace(filePath) ? null : filePath + ".tmp";
|
|||
|
|
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
var directory = Path.GetDirectoryName(filePath);
|
|||
|
|
if (!string.IsNullOrWhiteSpace(directory))
|
|||
|
|
{
|
|||
|
|
Directory.CreateDirectory(directory);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var backupPath = filePath + ".bak";
|
|||
|
|
byte[] bytes = new UTF8Encoding(false).GetBytes(content ?? string.Empty);
|
|||
|
|
|
|||
|
|
DeleteFileIfExists(tempPath);
|
|||
|
|
WriteBytesAndFlush(tempPath, bytes);
|
|||
|
|
|
|||
|
|
if (File.Exists(filePath))
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
CopyFileSynced(filePath, backupPath);
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
Debug.LogWarning($"备份旧配置文件失败,将继续写入新配置: {filePath}, {ex.Message}");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
DeleteFileIfExists(filePath);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
File.Move(tempPath, filePath);
|
|||
|
|
|
|||
|
|
var fileInfo = new FileInfo(filePath);
|
|||
|
|
if (!fileInfo.Exists || fileInfo.Length != bytes.Length)
|
|||
|
|
{
|
|||
|
|
throw new IOException($"写入校验失败,期望 {bytes.Length} 字节,实际 {(fileInfo.Exists ? fileInfo.Length : 0)} 字节");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
Debug.LogError($"原子写入失败: {filePath}, {ex.Message}");
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
finally
|
|||
|
|
{
|
|||
|
|
if (!string.IsNullOrWhiteSpace(tempPath))
|
|||
|
|
{
|
|||
|
|
DeleteFileIfExists(tempPath);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void TryCopyIniToPersistent()
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
if (File.Exists(_configIniPath) || File.Exists(_settingsPath))
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (File.Exists(_streamingConfigIniPath))
|
|||
|
|
{
|
|||
|
|
File.Copy(_streamingConfigIniPath, _configIniPath, true);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
Debug.LogWarning($"复制默认INI到持久化目录失败: {ex.Message}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private bool TryLoadJsonSettings(out SystemSettings settings, out string sourcePath)
|
|||
|
|
{
|
|||
|
|
settings = null;
|
|||
|
|
sourcePath = null;
|
|||
|
|
|
|||
|
|
string[] candidates =
|
|||
|
|
{
|
|||
|
|
_settingsPath,
|
|||
|
|
_settingsPath + ".bak",
|
|||
|
|
_settingsPath + ".tmp"
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
foreach (string candidate in GetExistingPathsNewestFirst(candidates))
|
|||
|
|
{
|
|||
|
|
if (TryLoadJsonSettings(candidate, out settings))
|
|||
|
|
{
|
|||
|
|
sourcePath = candidate;
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private IEnumerable<string> GetExistingPathsNewestFirst(IEnumerable<string> paths)
|
|||
|
|
{
|
|||
|
|
var existingPaths = new List<string>();
|
|||
|
|
foreach (string path in paths)
|
|||
|
|
{
|
|||
|
|
if (!string.IsNullOrWhiteSpace(path) && File.Exists(path))
|
|||
|
|
{
|
|||
|
|
existingPaths.Add(path);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
existingPaths.Sort((left, right) => GetLastWriteTimeUtcSafe(right).CompareTo(GetLastWriteTimeUtcSafe(left)));
|
|||
|
|
return existingPaths;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private bool TryLoadJsonSettings(string path, out SystemSettings settings)
|
|||
|
|
{
|
|||
|
|
settings = null;
|
|||
|
|
|
|||
|
|
if (string.IsNullOrWhiteSpace(path) || !File.Exists(path))
|
|||
|
|
{
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
var json = File.ReadAllText(path, Encoding.UTF8);
|
|||
|
|
if (string.IsNullOrWhiteSpace(json))
|
|||
|
|
{
|
|||
|
|
throw new InvalidDataException("文件为空");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
settings = JsonMapper.ToObject<SystemSettings>(json);
|
|||
|
|
if (settings == null)
|
|||
|
|
{
|
|||
|
|
throw new InvalidDataException("反序列化结果为空");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Debug.Log($"从JSON配置加载设置成功: {path}");
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
Debug.LogWarning($"从JSON配置加载设置失败: {path}, {ex.Message}");
|
|||
|
|
settings = null;
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void ApplyNewestIniOverrides(SystemSettings settings, string jsonSourcePath)
|
|||
|
|
{
|
|||
|
|
string iniPath = GetNewestExistingPath(_configIniPath, _configIniPath + ".bak");
|
|||
|
|
if (iniPath == null)
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (GetLastWriteTimeUtcSafe(iniPath) >= GetLastWriteTimeUtcSafe(jsonSourcePath))
|
|||
|
|
{
|
|||
|
|
ApplyIniOverrides(settings, iniPath);
|
|||
|
|
Debug.Log($"已合并INI覆盖项: {iniPath}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private bool TryLoadIniSettings(string path, string sourceName, out SystemSettings settings)
|
|||
|
|
{
|
|||
|
|
settings = null;
|
|||
|
|
if (string.IsNullOrWhiteSpace(path) || !File.Exists(path))
|
|||
|
|
{
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
settings = LoadFromIniFile(path);
|
|||
|
|
Debug.Log($"从{sourceName}加载设置成功: {path}");
|
|||
|
|
return settings != null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private string GetNewestExistingPath(string firstPath, string secondPath)
|
|||
|
|
{
|
|||
|
|
bool firstExists = !string.IsNullOrWhiteSpace(firstPath) && File.Exists(firstPath);
|
|||
|
|
bool secondExists = !string.IsNullOrWhiteSpace(secondPath) && File.Exists(secondPath);
|
|||
|
|
|
|||
|
|
if (firstExists && secondExists)
|
|||
|
|
{
|
|||
|
|
return GetLastWriteTimeUtcSafe(secondPath) > GetLastWriteTimeUtcSafe(firstPath) ? secondPath : firstPath;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (firstExists)
|
|||
|
|
{
|
|||
|
|
return firstPath;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return secondExists ? secondPath : null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private DateTime GetLastWriteTimeUtcSafe(string path)
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
return !string.IsNullOrWhiteSpace(path) && File.Exists(path)
|
|||
|
|
? File.GetLastWriteTimeUtc(path)
|
|||
|
|
: DateTime.MinValue;
|
|||
|
|
}
|
|||
|
|
catch
|
|||
|
|
{
|
|||
|
|
return DateTime.MinValue;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void WriteBytesAndFlush(string filePath, byte[] bytes)
|
|||
|
|
{
|
|||
|
|
using (var stream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.Read, 4096))
|
|||
|
|
{
|
|||
|
|
stream.Write(bytes, 0, bytes.Length);
|
|||
|
|
FlushFileStream(stream);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void CopyFileSynced(string sourcePath, string targetPath)
|
|||
|
|
{
|
|||
|
|
if (string.IsNullOrWhiteSpace(sourcePath) || !File.Exists(sourcePath))
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
byte[] bytes = File.ReadAllBytes(sourcePath);
|
|||
|
|
WriteBytesAndFlush(targetPath, bytes);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void FlushFileStream(FileStream stream)
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
stream.Flush(true);
|
|||
|
|
}
|
|||
|
|
catch
|
|||
|
|
{
|
|||
|
|
stream.Flush();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void DeleteFileIfExists(string path)
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
if (!string.IsNullOrWhiteSpace(path) && File.Exists(path))
|
|||
|
|
{
|
|||
|
|
File.Delete(path);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
Debug.LogWarning($"删除文件失败: {path}, {ex.Message}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private object GetExportData(ExportDataType dataType)
|
|||
|
|
{
|
|||
|
|
switch (dataType)
|
|||
|
|
{
|
|||
|
|
case ExportDataType.Settings:
|
|||
|
|
return LoadSettings();
|
|||
|
|
case ExportDataType.UserData:
|
|||
|
|
// 导出所有用户数据
|
|||
|
|
return "用户数据导出功能待实现";
|
|||
|
|
case ExportDataType.AlarmRecords:
|
|||
|
|
// 导出报警记录
|
|||
|
|
var records = DCSAlarmManager.Instance?.GetAlarmRecords(1000);
|
|||
|
|
return records != null ? records : "无报警记录";
|
|||
|
|
case ExportDataType.PatientInfo:
|
|||
|
|
// 导出病人信息
|
|||
|
|
var patient = ServiceLocator.Get<IPatientInfoService>()?.GetCurrentPatient();
|
|||
|
|
return patient != null ? patient : "无病人信息";
|
|||
|
|
case ExportDataType.SystemLogs:
|
|||
|
|
return "系统日志导出功能待实现";
|
|||
|
|
case ExportDataType.All:
|
|||
|
|
return new
|
|||
|
|
{
|
|||
|
|
Settings = LoadSettings(),
|
|||
|
|
AppConfig = LoadAppConfig(),
|
|||
|
|
SystemInfo = new
|
|||
|
|
{
|
|||
|
|
UnityVersion = Application.unityVersion,
|
|||
|
|
Platform = Application.platform.ToString(),
|
|||
|
|
SystemTime = DateTime.Now
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
default:
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void EnsureDirectoryExists(string path)
|
|||
|
|
{
|
|||
|
|
if (!Directory.Exists(path))
|
|||
|
|
{
|
|||
|
|
Directory.CreateDirectory(path);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|