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 } }; } } }