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