using Cognex.VisionPro; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; namespace TeamAAS_VP.Core { /// /// VisionPro 文件操作辅助类:提供安全保存、加载、备份、恢复与验证等功能。 /// 该类旨在通过临时文件、备份与验证机制减少文件损坏风险并支持自动恢复。 /// public static class VisionProFileHelper { #region 配置选项 /// /// VisionPro 文件处理的配置项。 /// - EnableBackup: 是否启用备份机制 /// - KeepBackupFiles: 是否保留版本化备份文件(否则仅保留 .bak 临时备份) /// - MaxBackupVersions: 保留的最大版本备份数量(超过则删除最旧) /// - BackupDirectory: 存放版本备份的目录名(相对于原文件所在目录) /// - ValidateAfterSave: 保存后是否验证文件完整性 /// public class VisionProOptions { public bool EnableBackup { get; set; } = true; public bool KeepBackupFiles { get; set; } = false; public int MaxBackupVersions { get; set; } = 3; public string BackupDirectory { get; set; } = "VisionProBackups"; public bool ValidateAfterSave { get; set; } = true; } private static VisionProOptions _options = new VisionProOptions(); /// /// 配置 VisionPro 文件处理选项。 /// /// 用于配置 VisionProOptions 的委托(可为 null) public static void Configure(Action configure) { configure?.Invoke(_options); } #endregion #region 安全保存方法 /// /// 安全保存 VisionPro 对象到指定文件路径。 /// 实现要点: /// - 支持在保存前根据配置创建版本化备份或 .bak 备份 /// - 先保存到临时文件,再进行原子替换(File.Replace 或 File.Move) /// - 可选的保存后验证,验证失败会抛出异常并尝试从备份恢复 /// /// 要保存的 VisionPro 对象(不能为空) /// 目标文件路径(不能为空或空白) /// 可选:覆盖全局备份配置 /// 成功返回 true,如发生异常会抛出 IOException public static bool SaveObjectToFileSafe(object obj, string filePath, bool? backupEnabled = null) { if (obj == null) throw new ArgumentNullException(nameof(obj)); if (string.IsNullOrWhiteSpace(filePath)) throw new ArgumentException("文件路径不能为空", nameof(filePath)); bool useBackup = backupEnabled ?? _options.EnableBackup; // 创建目录(若不存在) string directory = Path.GetDirectoryName(filePath); if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) { Directory.CreateDirectory(directory); } // 生成临时文件路径用于先写入 string tempFilePath = GetVisionProTempFilePath(filePath); try { // 如果启用备份且目标文件已存在,则创建备份(版本化或延续 .bak 机制) if (useBackup && File.Exists(filePath)) { CreateVisionProBackup(filePath); } // 将对象保存到临时文件 CogSerializer.SaveObjectToFile(obj, tempFilePath); // 可选:验证临时文件的完整性 if (_options.ValidateAfterSave && !ValidateVisionProFile(tempFilePath)) { throw new InvalidOperationException("保存的VisionPro文件验证失败"); } // 原子性替换:如果目标存在使用 File.Replace(可生成 .bak),否则直接移动临时文件 if (File.Exists(filePath)) { string backupPath = filePath + ".bak"; File.Replace(tempFilePath, filePath, backupPath, true); // 若配置不保留备份,删除 .bak 文件 if (!_options.KeepBackupFiles && File.Exists(backupPath)) { SafeDelete(backupPath); } } else { File.Move(tempFilePath, filePath); } Log($"成功保存VisionPro对象到: {filePath}"); return true; } catch (Exception ex) { Log($"保存VisionPro对象失败: {ex.Message}"); // 尝试从备份恢复(若启用了备份) if (useBackup) { Log("尝试从备份恢复..."); TryRestoreVisionProFile(filePath); } throw new IOException($"保存VisionPro文件失败: {ex.Message}", ex); } finally { // 无论成功与否,尝试清理临时文件 SafeDelete(tempFilePath); } } /// /// 安全加载 VisionPro 对象(带自动恢复机制)。 /// 尝试加载主文件,若失败并且允许自动恢复则尝试从 .bak 或版本备份恢复。 /// /// 目标对象类型 /// 文件路径 /// 是否启用自动恢复 /// 加载得到的对象(成功),否则抛出 FileNotFoundException public static T LoadObjectFromFileSafe(string filePath, bool autoRecover = true) where T : class { if (string.IsNullOrWhiteSpace(filePath)) throw new ArgumentException("文件路径不能为空", nameof(filePath)); // 清理可能残留的临时文件 CleanVisionProTempFile(filePath); // 记录最后一次异常用于抛出上下文 Exception lastException = null; try { if (File.Exists(filePath)) { var obj = CogSerializer.LoadObjectFromFile(filePath) as T; if (obj == null) throw new InvalidCastException($"文件 {filePath} 不是有效的 {typeof(T).Name} 类型"); return obj; } } catch (Exception ex) { lastException = ex; Log($"加载主文件失败: {ex.Message}"); if (autoRecover) { try { // 尝试从 .bak 或其他备份恢复并返回对象 T recovered = TryRecoverVisionProFile(filePath); if (recovered != null) return recovered; } catch (Exception recoveryEx) { Log($"恢复尝试失败: {recoveryEx.Message}"); } } } // 如果主文件不存在且允许恢复,尝试查找版本化备份进行恢复 if (autoRecover && !File.Exists(filePath)) { T recovered = TryFindAndRecoverVisionProFile(filePath); if (recovered != null) return recovered; } throw new FileNotFoundException($"VisionPro文件 {filePath} 不存在且无法恢复", lastException); } /// /// 简化版安全加载:遇到任何异常时返回提供的默认值(不抛出)。 /// public static T SafeLoadObjectFromFile(string filePath, T defaultValue = null) where T : class { try { return LoadObjectFromFileSafe(filePath, true); } catch { return defaultValue; } } #endregion #region 恢复和验证方法 /// /// 检查并修复单个 VisionPro 文件: /// - 检查并尝试从临时文件恢复 /// - 检查 .bak 备份并在合理时恢复 /// - 检查版本化备份并恢复最新的可用版本 /// /// 要检查的文件路径 /// 如果执行了恢复操作返回 true,否则返回 false public static bool CheckAndRepairVisionProFile(string filePath) { try { // 检查临时文件并尝试恢复 string tempPath = filePath + ".vp_tmp"; if (File.Exists(tempPath)) { TryRecoverFromVisionProTemp(tempPath); } // 检查 .bak 备份(File.Replace 可能产生) string backupPath = filePath + ".bak"; if (File.Exists(backupPath)) { // 验证备份文件并在主文件不存在或备份更新时恢复 if (ValidateVisionProFile(backupPath)) { if (!File.Exists(filePath) || File.GetLastWriteTime(backupPath) > File.GetLastWriteTime(filePath)) { File.Copy(backupPath, filePath, true); Log($"从备份恢复VisionPro文件: {filePath}"); return true; } } } // 查找并恢复版本化备份(BackupDirectory 或其他模式) var latestBackup = FindLatestVisionProBackup(filePath); if (latestBackup != null) { File.Copy(latestBackup, filePath, true); Log($"从版本备份恢复VisionPro文件: {filePath}"); return true; } return false; } catch (Exception ex) { Log($"修复VisionPro文件失败: {ex.Message}"); return false; } } /// /// 强制从备份恢复(优先 .bak,然后版本化备份)。 /// /// 目标文件路径 /// 恢复成功返回 true,否则 false public static bool ForceRecoverVisionProFile(string filePath) { try { // 优先使用 .bak 恢复(如存在且验证通过) string backupPath = filePath + ".bak"; if (File.Exists(backupPath) && ValidateVisionProFile(backupPath)) { File.Copy(backupPath, filePath, true); Log($"强制从备份恢复: {filePath}"); return true; } // 否则查找版本化备份并恢复最新可用的 var latestBackup = FindLatestVisionProBackup(filePath); if (latestBackup != null) { File.Copy(latestBackup, filePath, true); Log($"强制从版本备份恢复: {filePath}"); return true; } return false; } catch (Exception ex) { Log($"强制恢复失败: {ex.Message}"); return false; } } /// /// 尝试从已知备份恢复主文件(单参数便捷版本)。 /// 会检查 .bak 与版本化备份并在找到有效备份时覆盖主文件。 /// private static bool TryRestoreVisionProFile(string filePath) { try { string backupPath = filePath + ".bak"; if (File.Exists(backupPath) && ValidateVisionProFile(backupPath)) { File.Copy(backupPath, filePath, true); Log($"从备份恢复文件: {filePath}"); return true; } // 若 .bak 不可用,尝试版本化备份 var latestBackup = FindLatestVisionProBackup(filePath); if (latestBackup != null) { File.Copy(latestBackup, filePath, true); Log($"从版本备份恢复文件: {filePath}"); return true; } return false; } catch (Exception ex) { Log($"恢复文件失败: {ex.Message}"); return false; } } /// /// 批量检查并修复目录下所有 .vpp 文件。 /// /// 根目录路径 public static void CheckAndRepairDirectory(string directoryPath) { if (!Directory.Exists(directoryPath)) return; // 查找所有 .vpp 文件(包含子目录) var vppFiles = Directory.GetFiles(directoryPath, "*.vpp", SearchOption.AllDirectories); Log($"开始检查和修复 {vppFiles.Length} 个VisionPro文件..."); int repairedCount = 0; foreach (var file in vppFiles) { if (CheckAndRepairVisionProFile(file)) { repairedCount++; } } Log($"完成检查和修复,共修复 {repairedCount} 个文件"); } #endregion #region 私有实现方法 /// /// 根据配置创建备份: /// - 若 KeepBackupFiles 为 true,则在指定 BackupDirectory 中创建带时间戳的版本备份并清理旧版本; /// - 若为 false,则不在此方法中处理 .bak(.bak 由 File.Replace 在保存时产生)。 /// /// 原文件路径 private static void CreateVisionProBackup(string originalPath) { try { if (_options.KeepBackupFiles) { // 在原文件目录下创建 backupDir(如果不存在) string backupDir = Path.Combine(Path.GetDirectoryName(originalPath), _options.BackupDirectory); if (!Directory.Exists(backupDir)) Directory.CreateDirectory(backupDir); string fileName = Path.GetFileNameWithoutExtension(originalPath); string extension = Path.GetExtension(originalPath); string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss_fff"); string backupName = $"{fileName}_{timestamp}{extension}"; string backupPath = Path.Combine(backupDir, backupName); File.Copy(originalPath, backupPath, true); Log($"创建VisionPro版本备份: {backupPath}"); // 清理超过 MaxBackupVersions 的旧备份 CleanupOldVisionProBackups(backupDir, fileName); } } catch (Exception ex) { Log($"创建VisionPro备份失败: {ex.Message}"); } } /// /// 生成一个用于临时保存的唯一文件路径(位于系统临时目录)。 /// /// 与原文件关联的文件名(用于可读性) /// 临时文件路径 private static string GetVisionProTempFilePath(string originalPath) { return originalPath + ".tmp"; //string tempDir = Path.GetTempPath(); //string safeName = Path.GetFileName(originalPath) // .Replace(" ", "_") // .Replace(":", "_"); //return Path.Combine(tempDir, $"vp_{Guid.NewGuid():N}_{safeName}"); } /// /// 尝试通过加载文件来验证 VisionPro 文件的完整性(加载失败视为无效)。 /// /// 要验证的文件路径 /// 若能成功加载返回 true,否则 false private static bool ValidateVisionProFile(string filePath) { try { // 使用 Cognex 的序列化加载验证完整性 var obj = CogSerializer.LoadObjectFromFile(filePath); return obj != null; } catch { return false; } } /// /// 尝试从 .bak 备份恢复并返回加载的对象(泛型)。 /// 若恢复成功同时尝试将备份复制回主文件以保持一致性。 /// private static T TryRecoverVisionProFile(string originalPath) where T : class { string backupPath = originalPath + ".bak"; if (File.Exists(backupPath)) { try { var obj = CogSerializer.LoadObjectFromFile(backupPath) as T; if (obj != null) { // 当备份可用时,尝试恢复主文件 TryRestoreVisionProFile(originalPath); Log($"从备份恢复VisionPro对象: {originalPath}"); return obj; } } catch (Exception ex) { Log($"从备份恢复VisionPro对象失败: {ex.Message}"); } } return default; } /// /// 查找版本化备份并尝试恢复为指定类型,成功时会将备份复制为主文件并返回对象。 /// private static T TryFindAndRecoverVisionProFile(string originalPath) where T : class { var backup = FindLatestVisionProBackup(originalPath); if (backup != null) { try { var obj = CogSerializer.LoadObjectFromFile(backup) as T; if (obj != null) { File.Copy(backup, originalPath, true); Log($"从版本备份恢复VisionPro对象: {originalPath}"); return obj; } } catch (Exception ex) { Log($"从版本备份恢复VisionPro对象失败: {ex.Message}"); } } return default; } /// /// 在备份目录和当前目录下查找与原文件对应的最新备份文件路径(按写入时间排序)。 /// 支持多种命名模式以提高鲁棒性。 /// private static string FindLatestVisionProBackup(string originalPath) { string directory = Path.GetDirectoryName(originalPath); string fileName = Path.GetFileNameWithoutExtension(originalPath); string extension = Path.GetExtension(originalPath); if (!Directory.Exists(directory)) return null; // 优先检查专用备份目录 string backupDir = Path.Combine(directory, _options.BackupDirectory); if (Directory.Exists(backupDir)) { var backups = Directory.GetFiles(backupDir, $"{fileName}_*{extension}") .OrderByDescending(f => File.GetLastWriteTime(f)) .ToList(); if (backups.Any()) return backups.First(); } // 检查当前目录中常见的备份命名模式 var backupPatterns = new[] { $"{fileName}.*.bak", $"{fileName}_*{extension}", $"{fileName}.backup.*" }; foreach (var pattern in backupPatterns) { try { var backups = Directory.GetFiles(directory, pattern) .OrderByDescending(f => File.GetLastWriteTime(f)) .ToList(); if (backups.Any()) return backups.First(); } catch { continue; } } return null; } /// /// 恢复主文件的重载版本(从指定备份路径复制到原文件路径)。 /// private static void TryRestoreVisionProFile(string backupPath, string originalPath) { try { File.Copy(backupPath, originalPath, true); Log($"恢复VisionPro主文件: {originalPath}"); } catch (Exception ex) { Log($"恢复VisionPro主文件失败: {ex.Message}"); } } /// /// 清理与指定原文件可能残留的各种临时文件(本进程或其他异常中断时产生)。 /// 支持单个文件名与通配符模式(在系统临时目录下)。 /// /// 原文件路径 private static void CleanVisionProTempFile(string originalPath) { // 可能的临时文件模式集合 var tempPatterns = new[] { originalPath + ".vp_tmp", originalPath + ".tmp", Path.GetTempPath() + $"vp_*_{Path.GetFileName(originalPath)}" }; foreach (var pattern in tempPatterns) { try { if (File.Exists(pattern)) { File.Delete(pattern); Log($"清理VisionPro临时文件: {pattern}"); } else if (pattern.Contains("*")) { // 通配符处理:删除系统临时目录下匹配的文件 var files = Directory.GetFiles(Path.GetTempPath(), $"vp_*_{Path.GetFileName(originalPath)}"); foreach (var file in files) { File.Delete(file); Log($"清理VisionPro临时文件: {file}"); } } } catch (Exception ex) { Log($"清理VisionPro临时文件失败: {ex.Message}"); } } } /// /// 尝试从临时文件恢复到主文件(当临时文件完整且主文件缺失或临时文件更可靠时)。 /// 若临时文件无效则删除该临时文件。 /// /// 临时文件路径(通常以 .vp_tmp 结尾) private static void TryRecoverFromVisionProTemp(string tempPath) { try { string originalPath = tempPath.Replace(".vp_tmp", ""); // 当主文件不存在或者临时文件验证通过时,尝试恢复 if (!File.Exists(originalPath) || ValidateVisionProFile(tempPath)) { if (ValidateVisionProFile(tempPath)) { File.Move(tempPath, originalPath); Log($"从临时文件恢复VisionPro文件: {originalPath}"); } else { File.Delete(tempPath); } } } catch { SafeDelete(tempPath); } } /// /// 清理备份目录中过多的旧版本,保留最新的 _options.MaxBackupVersions 个版本。 /// private static void CleanupOldVisionProBackups(string backupDir, string baseName) { try { var files = Directory.GetFiles(backupDir) .Where(f => Path.GetFileName(f).StartsWith(baseName + "_")) .OrderByDescending(f => File.GetLastWriteTime(f)) .ToList(); for (int i = _options.MaxBackupVersions; i < files.Count; i++) { SafeDelete(files[i]); } } catch { // 忽略清理错误以避免影响主流程 } } /// /// 安全删除文件(吞掉异常),用于清理临时/备份文件时保证不抛出影响主逻辑的异常。 /// private static void SafeDelete(string filePath) { try { if (File.Exists(filePath)) File.Delete(filePath); } catch { // 忽略删除错误 } } /// /// 日志记录适配器(当前使用 LogHelper.WriteLogDebug)。 /// 可根据项目替换为其它日志实现。 /// /// 日志消息 private static void Log(string message) { // 这里可以使用你喜欢的日志系统 //Console.WriteLine($"[VisionProFileHelper] {DateTime.Now:HH:mm:ss} - {message}"); LogHelper.WriteLogDebug($"[VisionProFileHelper] {DateTime.Now:HH:mm:ss} - {message}"); // 或者使用:Debug.WriteLine(message); // 或者使用:Logger.Log(message); } #endregion } }