using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using WIDESEAWCS_S7Simulator.Core.Entities;
using WIDESEAWCS_S7Simulator.Core.Enums;
using WIDESEAWCS_S7Simulator.Core.Interfaces;
using WIDESEAWCS_S7Simulator.Core.Persistence.Models;
namespace WIDESEAWCS_S7Simulator.Core.Persistence
{
///
/// 鏂囦欢鎸佷箙鍖栨湇鍔″疄鐜?
/// 灏嗗疄渚嬮厤缃拰鍐呭瓨鏁版嵁淇濆瓨鍒版湰鍦癑SON鏂囦欢
///
public class FilePersistenceService : IPersistenceService
{
///
/// 鏁版嵁鐩綍璺緞
///
private readonly string _dataPath;
///
/// JSON搴忓垪鍖栭€夐」锛堢嚎绋嬪畨鍏級
///
private static readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions
{
WriteIndented = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
///
/// 鏂囦欢鎿嶄綔閿侊紙绾跨▼瀹夊叏锛?
///
private readonly SemaphoreSlim _fileLock = new SemaphoreSlim(1, 1);
///
/// 鏋勯€犲嚱鏁?
///
/// 鏁版嵁鐩綍璺緞
public FilePersistenceService(string dataPath = "Data")
{
// 杞崲涓虹粷瀵硅矾寰勶紙鍩轰簬褰撳墠宸ヤ綔鐩綍锛?
_dataPath = Path.GetFullPath(dataPath);
try
{
// 纭繚鏁版嵁鐩綍瀛樺湪
if (!Directory.Exists(_dataPath))
{
Directory.CreateDirectory(_dataPath);
}
}
catch (Exception ex)
{
Console.WriteLine($"Error creating data directory '{_dataPath}': {ex.Message}");
throw;
}
}
///
/// 淇濆瓨瀹炰緥閰嶇疆
///
public async Task SaveInstanceConfigAsync(InstanceConfig config)
{
if (config == null)
throw new ArgumentNullException(nameof(config));
await _fileLock.WaitAsync();
try
{
var instanceDir = GetInstanceDirectory(config.Id);
if (!Directory.Exists(instanceDir))
{
Directory.CreateDirectory(instanceDir);
}
var configPath = Path.Combine(instanceDir, "config.json");
var model = ToDataModel(config);
var json = JsonSerializer.Serialize(model, _jsonOptions);
await WriteFileAtomicAsync(configPath, json);
}
catch (Exception ex)
{
Console.WriteLine($"Error saving instance config for '{config.Id}': {ex.Message}");
throw;
}
finally
{
_fileLock.Release();
}
}
///
/// 鍔犺浇瀹炰緥閰嶇疆
///
public async Task LoadInstanceConfigAsync(string instanceId)
{
if (string.IsNullOrWhiteSpace(instanceId))
throw new ArgumentException("瀹炰緥ID涓嶈兘涓虹┖", nameof(instanceId));
ValidateInstanceId(instanceId);
await _fileLock.WaitAsync();
try
{
var configPath = Path.Combine(GetInstanceDirectory(instanceId), "config.json");
if (!File.Exists(configPath))
throw new FileNotFoundException($"瀹炰緥閰嶇疆鏂囦欢涓嶅瓨鍦? {configPath}");
var json = await File.ReadAllTextAsync(configPath);
var model = JsonSerializer.Deserialize(json, _jsonOptions);
if (model == null)
throw new InvalidOperationException("鏃犳硶鍙嶅簭鍒楀寲瀹炰緥閰嶇疆");
return ToEntity(model);
}
catch (Exception ex) when (!(ex is FileNotFoundException || ex is InvalidOperationException || ex is ArgumentException))
{
Console.WriteLine($"Error loading instance config for '{instanceId}': {ex.Message}");
throw;
}
finally
{
_fileLock.Release();
}
}
///
/// 鍔犺浇鎵€鏈夊疄渚嬮厤缃?
///
public async Task> LoadAllInstanceConfigsAsync()
{
await _fileLock.WaitAsync();
try
{
var configs = new List();
if (!Directory.Exists(_dataPath))
return configs;
var instanceDirs = Directory.GetDirectories(_dataPath)
.Where(d => Path.GetFileName(d).StartsWith("instance-"))
.ToList();
foreach (var dir in instanceDirs)
{
var configPath = Path.Combine(dir, "config.json");
if (File.Exists(configPath))
{
try
{
var json = await File.ReadAllTextAsync(configPath);
var model = JsonSerializer.Deserialize(json, _jsonOptions);
if (model != null)
{
configs.Add(ToEntity(model));
}
}
catch (Exception ex)
{
Console.WriteLine($"Error loading instance config from '{configPath}': {ex.Message}");
// 璺宠繃鏃犳硶鍔犺浇鐨勯厤缃枃浠?
continue;
}
}
}
return configs;
}
finally
{
_fileLock.Release();
}
}
///
/// 鍒犻櫎瀹炰緥閰嶇疆
///
public async Task DeleteInstanceConfigAsync(string instanceId)
{
if (string.IsNullOrWhiteSpace(instanceId))
throw new ArgumentException("瀹炰緥ID涓嶈兘涓虹┖", nameof(instanceId));
ValidateInstanceId(instanceId);
await _fileLock.WaitAsync();
try
{
var instanceDir = GetInstanceDirectory(instanceId);
if (Directory.Exists(instanceDir))
{
Directory.Delete(instanceDir, recursive: true);
}
}
catch (Exception ex)
{
Console.WriteLine($"Error deleting instance '{instanceId}': {ex.Message}");
throw;
}
finally
{
_fileLock.Release();
}
}
///
/// 淇濆瓨鍐呭瓨鏁版嵁
///
public async Task SaveMemoryDataAsync(string instanceId, IMemoryStore memoryStore)
{
if (string.IsNullOrWhiteSpace(instanceId))
throw new ArgumentException("瀹炰緥ID涓嶈兘涓虹┖", nameof(instanceId));
ValidateInstanceId(instanceId);
if (memoryStore == null)
throw new ArgumentNullException(nameof(memoryStore));
await _fileLock.WaitAsync();
try
{
var instanceDir = GetInstanceDirectory(instanceId);
if (!Directory.Exists(instanceDir))
{
Directory.CreateDirectory(instanceDir);
}
var memoryPath = Path.Combine(instanceDir, "memory.json");
var exportedData = memoryStore.Export();
// 灏嗗瓧鑺傛暟缁勮浆鎹负Base64瀛楃涓蹭互渚縅SON搴忓垪鍖?
var memoryDataModel = new MemoryDataModel
{
MemoryData = exportedData.ToDictionary(
kvp => kvp.Key,
kvp => Convert.ToBase64String(kvp.Value)
)
};
var json = JsonSerializer.Serialize(memoryDataModel, _jsonOptions);
await WriteFileAtomicAsync(memoryPath, json);
}
catch (Exception ex)
{
Console.WriteLine($"Error saving memory data for instance '{instanceId}': {ex.Message}");
throw;
}
finally
{
_fileLock.Release();
}
}
///
/// 鍔犺浇鍐呭瓨鏁版嵁
///
public async Task LoadMemoryDataAsync(string instanceId, IMemoryStore memoryStore)
{
if (string.IsNullOrWhiteSpace(instanceId))
throw new ArgumentException("瀹炰緥ID涓嶈兘涓虹┖", nameof(instanceId));
ValidateInstanceId(instanceId);
if (memoryStore == null)
throw new ArgumentNullException(nameof(memoryStore));
await _fileLock.WaitAsync();
try
{
var memoryPath = Path.Combine(GetInstanceDirectory(instanceId), "memory.json");
if (!File.Exists(memoryPath))
return; // 鍐呭瓨鏂囦欢涓嶅瓨鍦紝璺宠繃鍔犺浇
var json = await File.ReadAllTextAsync(memoryPath);
var memoryDataModel = JsonSerializer.Deserialize(json, _jsonOptions);
if (memoryDataModel?.MemoryData == null)
return;
// 灏咮ase64瀛楃涓茶浆鎹㈠洖瀛楄妭鏁扮粍
var importedData = new Dictionary();
foreach (var kvp in memoryDataModel.MemoryData)
{
try
{
importedData[kvp.Key] = Convert.FromBase64String(kvp.Value);
}
catch (FormatException ex)
{
Console.WriteLine($"Warning: Invalid Base64 data for memory region '{kvp.Key}' in instance '{instanceId}': {ex.Message}");
// 璺宠繃鏃犳晥鐨凚ase64鏁版嵁
continue;
}
}
memoryStore.Import(importedData);
}
catch (Exception ex) when (!(ex is FileNotFoundException))
{
Console.WriteLine($"Error loading memory data for instance '{instanceId}': {ex.Message}");
throw;
}
finally
{
_fileLock.Release();
}
}
///
/// 鑾峰彇瀹炰緥鐩綍璺緞
///
private string GetInstanceDirectory(string instanceId)
{
return Path.Combine(_dataPath, $"instance-{instanceId}");
}
///
/// 楠岃瘉瀹炰緥ID锛堥槻姝㈣矾寰勯亶鍘嗘敾鍑伙級
///
private void ValidateInstanceId(string instanceId)
{
if (string.IsNullOrWhiteSpace(instanceId))
throw new ArgumentException("瀹炰緥ID涓嶈兘涓虹┖", nameof(instanceId));
// 妫€鏌ヨ矾寰勯亶鍘嗗瓧绗?
if (instanceId.Contains("..") || instanceId.Contains("/") || instanceId.Contains("\\"))
throw new ArgumentException("瀹炰緥ID鍖呭惈闈炴硶瀛楃", nameof(instanceId));
// 妫€鏌ユ棤鏁堣矾寰勫瓧绗?
var invalidChars = Path.GetInvalidFileNameChars();
if (instanceId.IndexOfAny(invalidChars) >= 0)
throw new ArgumentException("瀹炰緥ID鍖呭惈闈炴硶瀛楃", nameof(instanceId));
}
///
/// 鍐欏叆鏂囦欢锛堢洿鎺ュ啓鍏ワ紝绠€鍖栫増鏈級
///
private async Task WriteFileAtomicAsync(string filePath, string content)
{
// 纭繚鐩爣鏂囦欢鐨勭埗鐩綍瀛樺湪
var directory = Path.GetDirectoryName(filePath);
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
// 鐩存帴鍐欏叆鏂囦欢
await File.WriteAllTextAsync(filePath, content, Encoding.UTF8);
}
///
/// 灏嗗疄浣撹浆鎹负鏁版嵁妯″瀷
///
private InstanceDataModel ToDataModel(InstanceConfig config)
{
return new InstanceDataModel
{
Id = config.Id,
Name = config.Name,
PlcType = config.PLCType.ToString(),
Port = config.Port,
ActivationKey = config.ActivationKey,
AutoStart = config.AutoStart,
ProtocolTemplateId = config.ProtocolTemplateId,
MemoryConfig = new MemoryRegionConfigModel
{
MRegionSize = config.MemoryConfig.MRegionSize,
DBBlockCount = config.MemoryConfig.DBBlockCount,
DBBlockNumbers = config.MemoryConfig.DBBlockNumbers.ToList(),
DBBlockSize = config.MemoryConfig.DBBlockSize,
IRegionSize = config.MemoryConfig.IRegionSize,
QRegionSize = config.MemoryConfig.QRegionSize,
TRegionCount = config.MemoryConfig.TRegionCount,
CRegionCount = config.MemoryConfig.CRegionCount
}
};
}
///
/// 灏嗘暟鎹ā鍨嬭浆鎹负瀹炰綋
///
private InstanceConfig ToEntity(InstanceDataModel model)
{
return new InstanceConfig
{
Id = model.Id,
Name = model.Name,
PLCType = Enum.Parse(model.PlcType),
Port = model.Port,
ActivationKey = model.ActivationKey,
AutoStart = model.AutoStart,
ProtocolTemplateId = model.ProtocolTemplateId,
MemoryConfig = new MemoryRegionConfig
{
MRegionSize = model.MemoryConfig.MRegionSize,
DBBlockCount = model.MemoryConfig.DBBlockCount,
DBBlockNumbers = model.MemoryConfig.DBBlockNumbers?.ToList() ?? new List(),
DBBlockSize = model.MemoryConfig.DBBlockSize,
IRegionSize = model.MemoryConfig.IRegionSize,
QRegionSize = model.MemoryConfig.QRegionSize,
TRegionCount = model.MemoryConfig.TRegionCount,
CRegionCount = model.MemoryConfig.CRegionCount
}
};
}
}
}