using MainShell.Filewritable; using System; using System.IO; using System.Linq; namespace MainShell.Recipe.Models { public class RecipeManager { public event EventHandler RecipesChanged; // 记录上次加载的基板配方名(放在根目录) private string LastLoadedFile => Path.Combine(Paths.RecipeBasePath, "last_loaded.txt"); public CarrierRecipe CurrentCarrierRecipe { get; private set; } public SubstrateRecipe CurrentSubstrateRecipe { get; private set; } public WaferRecipe CurrentWaferRecipe { get; private set; } public ProcessRecipe CurrentProcessRecipe { get; private set; } public RecipeManager() { var lastLoaded = LoadLastLoadedRecipe(); if (!string.IsNullOrWhiteSpace(lastLoaded)) { // 构造阶段不主动抛事件,避免初始化期无意义通知 SwitchSubstrateRecipeInternal(lastLoaded, cascade: true, notify: false); } } public void SaveLastLoadedRecipe(string recipeName) { var dir = Path.GetDirectoryName(LastLoadedFile); if (!string.IsNullOrWhiteSpace(dir) && !Directory.Exists(dir)) { Directory.CreateDirectory(dir); } File.WriteAllText(LastLoadedFile, recipeName ?? string.Empty); } public string LoadLastLoadedRecipe() { if (!File.Exists(LastLoadedFile)) return string.Empty; return File.ReadAllText(LastLoadedFile).Trim(); } public void SwitchCarrierRecipe(string carrierRecipeName) { SwitchCarrierRecipeInternal(carrierRecipeName, notify: true); } public void SwitchSubstrateRecipe(string substrateRecipeName) { // 需求:切换基板时自动切换芯片;切换芯片时自动切换工艺 SwitchSubstrateRecipeInternal(substrateRecipeName, cascade: true, notify: true); } public void SwitchWaferRecipe(string waferRecipeName) { // 需求:切换芯片时自动切换工艺 SwitchWaferRecipeInternal(waferRecipeName, cascade: true, notify: true); } public void SwitchProcessRecipe(string processRecipeName) { SwitchProcessRecipeInternal(processRecipeName, notify: true); } private void SwitchCarrierRecipeInternal(string carrierRecipeName, bool notify) { if (string.IsNullOrWhiteSpace(carrierRecipeName)) return; CurrentCarrierRecipe = CurrentCarrierRecipe ?? new CarrierRecipe(); CurrentCarrierRecipe.RecipeName = carrierRecipeName; var path = Path.Combine(Paths.CarrierRecipe, carrierRecipeName, CurrentCarrierRecipe.FileName); CurrentCarrierRecipe.Read(path); var substrateName = GetFirstSubstrateName(CurrentCarrierRecipe); if (!string.IsNullOrWhiteSpace(substrateName)) { // 向下级联,但由最外层统一通知 SwitchSubstrateRecipeInternal(substrateName, cascade: true, notify: false); } else { ClearSubstrateRecipeInternal(false); } if (notify) RaiseRecipesChanged(); } private void SwitchSubstrateRecipeInternal(string substrateRecipeName, bool cascade, bool notify) { if (string.IsNullOrWhiteSpace(substrateRecipeName)) return; CurrentSubstrateRecipe = CurrentSubstrateRecipe ?? new SubstrateRecipe(); CurrentSubstrateRecipe.RecipeName = substrateRecipeName; var path = Path.Combine(Paths.SubstrateRecipe, substrateRecipeName, CurrentSubstrateRecipe.FileName); CurrentSubstrateRecipe.Read(path); SaveLastLoadedRecipe(substrateRecipeName); if (cascade) { var waferName = GetFirstWaferName(CurrentSubstrateRecipe); if (!string.IsNullOrWhiteSpace(waferName)) { SwitchWaferRecipeInternal(waferName, cascade: true, notify: false); } else { // 没有关联芯片时,清空下游当前引用,避免残留旧状态 CurrentWaferRecipe = null; CurrentProcessRecipe = null; } } if (notify) RaiseRecipesChanged(); } private void SwitchWaferRecipeInternal(string waferRecipeName, bool cascade, bool notify) { if (string.IsNullOrWhiteSpace(waferRecipeName)) return; CurrentWaferRecipe = CurrentWaferRecipe ?? new WaferRecipe(); CurrentWaferRecipe.RecipeName = waferRecipeName; var path = Path.Combine(Paths.WaferRecipe, waferRecipeName, CurrentWaferRecipe.FileName); CurrentWaferRecipe.Read(path); if (cascade) { var processName = CurrentWaferRecipe.ProcessRecipeName; if (!string.IsNullOrWhiteSpace(processName)) { SwitchProcessRecipeInternal(processName, notify: false); } else { CurrentProcessRecipe = null; } } if (notify) RaiseRecipesChanged(); } private void SwitchProcessRecipeInternal(string processRecipeName, bool notify) { if (string.IsNullOrWhiteSpace(processRecipeName)) return; CurrentProcessRecipe = CurrentProcessRecipe ?? new ProcessRecipe(); CurrentProcessRecipe.RecipeName = processRecipeName; var path = Path.Combine(Paths.ProcessRecipe, processRecipeName, CurrentProcessRecipe.FileName); CurrentProcessRecipe.Read(path); if (notify) RaiseRecipesChanged(); } private static string GetFirstSubstrateName(CarrierRecipe carrierRecipe) { if (carrierRecipe == null || carrierRecipe.SubstrateSelectInfos == null || carrierRecipe.SubstrateSelectInfos.Count == 0) { return string.Empty; } return carrierRecipe.SubstrateSelectInfos[0].SubstrateName; } private static string GetFirstWaferName(SubstrateRecipe substrateRecipe) { if (substrateRecipe == null || substrateRecipe.WaferSelectInfos == null || substrateRecipe.WaferSelectInfos.Count == 0) { return string.Empty; } var inUse = substrateRecipe.WaferSelectInfos .FirstOrDefault(x => x != null && x.IsUse && !string.IsNullOrWhiteSpace(x.WaferRecipeName)); if (inUse != null) return inUse.WaferRecipeName; var firstValid = substrateRecipe.WaferSelectInfos .FirstOrDefault(x => x != null && !string.IsNullOrWhiteSpace(x.WaferRecipeName)); return firstValid != null ? firstValid.WaferRecipeName : string.Empty; } private void RaiseRecipesChanged() { RecipesChanged?.Invoke(this, EventArgs.Empty); } public void DeleteCarrierRecipe(string recipeName) { DeleteRecipeFolder(Paths.CarrierRecipe, recipeName, out _); } public bool DeleteCarrierRecipe(string recipeName, out string error) { return DeleteRecipeFolder(Paths.CarrierRecipe, recipeName, out error); } public void DeleteSubstrateRecipe(string recipeName) { DeleteRecipeFolder(Paths.SubstrateRecipe, recipeName, out _); } public bool DeleteSubstrateRecipe(string recipeName, out string error) { return DeleteRecipeFolder(Paths.SubstrateRecipe, recipeName, out error); } public void DeleteWaferRecipe(string recipeName) { DeleteRecipeFolder(Paths.WaferRecipe, recipeName, out _); } public bool DeleteWaferRecipe(string recipeName, out string error) { return DeleteRecipeFolder(Paths.WaferRecipe, recipeName, out error); } public void DeleteProcessRecipe(string recipeName) { DeleteRecipeFolder(Paths.ProcessRecipe, recipeName, out _); } public bool DeleteProcessRecipe(string recipeName, out string error) { return DeleteRecipeFolder(Paths.ProcessRecipe, recipeName, out error); } public bool DeleteRecipeFolder(string baseFolder, string recipeName, out string error) { error = null; if (string.IsNullOrWhiteSpace(baseFolder) || string.IsNullOrWhiteSpace(recipeName)) { error = "配方名称不能为空"; return false; } var path = Path.Combine(baseFolder, recipeName); if (!Directory.Exists(path)) { error = "配方目录不存在"; return false; } try { Directory.Delete(path, true); return true; } catch (Exception ex) { error = ex.Message; return false; } } public bool CopyRecipeFolder(string baseFolder, string sourceRecipeName, string targetRecipeName, out string error) { error = null; if (string.IsNullOrWhiteSpace(baseFolder) || string.IsNullOrWhiteSpace(sourceRecipeName) || string.IsNullOrWhiteSpace(targetRecipeName)) { error = "配方名称不能为空"; return false; } if (sourceRecipeName.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0 || targetRecipeName.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0) { error = "名称包含非法字符"; return false; } var sourcePath = Path.Combine(baseFolder, sourceRecipeName); var targetPath = Path.Combine(baseFolder, targetRecipeName); if (!Directory.Exists(sourcePath)) { error = "源文件夹不存在"; return false; } if (Directory.Exists(targetPath)) { error = "目标文件夹已存在"; return false; } try { CopyDirectory(sourcePath, targetPath); return true; } catch (Exception ex) { error = ex.Message; try { if (Directory.Exists(targetPath)) { Directory.Delete(targetPath, true); } } catch { } return false; } } public void ClearCarrierRecipe() { ClearCarrierRecipeInternal(true); } public void ClearSubstrateRecipe() { ClearSubstrateRecipeInternal(true); } public void ClearWaferRecipe() { ClearWaferRecipeInternal(true); } public void ClearProcessRecipe() { ClearProcessRecipeInternal(true); } private void ClearCarrierRecipeInternal(bool notify) { CurrentCarrierRecipe = null; if (notify) RaiseRecipesChanged(); } private void ClearSubstrateRecipeInternal(bool notify) { CurrentSubstrateRecipe = null; CurrentWaferRecipe = null; CurrentProcessRecipe = null; SaveLastLoadedRecipe(string.Empty); if (notify) RaiseRecipesChanged(); } private void ClearWaferRecipeInternal(bool notify) { CurrentWaferRecipe = null; CurrentProcessRecipe = null; if (notify) RaiseRecipesChanged(); } private void ClearProcessRecipeInternal(bool notify) { CurrentProcessRecipe = null; if (notify) RaiseRecipesChanged(); } private static void CopyDirectory(string sourcePath, string targetPath) { Directory.CreateDirectory(targetPath); foreach (var filePath in Directory.GetFiles(sourcePath)) { var fileName = Path.GetFileName(filePath); var targetFilePath = Path.Combine(targetPath, fileName); File.Copy(filePath, targetFilePath, false); } foreach (var directoryPath in Directory.GetDirectories(sourcePath)) { var directoryName = Path.GetFileName(directoryPath); var targetDirectoryPath = Path.Combine(targetPath, directoryName); CopyDirectory(directoryPath, targetDirectoryPath); } } public bool RenameFolder(string baseFolder, string oldName, string newName, out string error) { error = null; if (string.IsNullOrWhiteSpace(oldName) || string.IsNullOrWhiteSpace(newName)) { error = "名称不能为空"; return false; } if (oldName.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0 || newName.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0) { error = "名称包含非法字符"; return false; } var src = Path.Combine(baseFolder, oldName); var dst = Path.Combine(baseFolder, newName); if (!Directory.Exists(src)) { error = "源文件夹不存在"; return false; } if (Directory.Exists(dst)) { error = "目标文件夹已存在"; return false; } try { if (string.Equals(oldName, newName, StringComparison.OrdinalIgnoreCase) && !string.Equals(oldName, newName, StringComparison.Ordinal)) { var temp = Path.Combine(baseFolder, oldName + "_tmp_" + Guid.NewGuid().ToString("N")); Directory.Move(src, temp); Directory.Move(temp, dst); } else { Directory.Move(src, dst); } return true; } catch (Exception ex) { error = ex.Message; return false; } } } }