using AIK.Common.AIKProtocol; using AIK.Common.Enum; using AIK.Common.Event; using AIK.Common.Language; using AIK.Common.SysCommon; using AIK.Models.HidModels; using AIK.Service.Interop; using AIK.Service.IService; using HidSharp; using HidSharp.Reports; using HidSharp.Reports.Input; using Microsoft.Extensions.Logging; using SharpCompress.Archives; using SharpCompress.Common; using SharpCompress.Readers; using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.Eventing.Reader; using System.IO; using System.IO.Compression; using System.Linq; using System.Management; using System.Security; using System.Security.Cryptography; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows; namespace AIK.Service.Service { public class HidCommunicationService : IHidCommunicationService, IDisposable { #region 常量定义 private const int REPORT_LENGTH = 65; private int _reconnectAttempts = 0; private const int MAX_RECONNECT_ATTEMPTS = 5; private const int RECONNECT_INTERVAL_MS = 100; private const int DEVICE_MONITOR_INTERVAL_MS = 300; private const int DEVICE_MONITOR_ERROR_DELAY_MS = 500; private const int KEEP_ALIVE_INTERVAL_MS = 50000; private const int HID_RESPONSE_WARNING_MS = 90000; private const int COPY_BUFFER_SIZE = 32 * 1024; private const int COPY_CHUNK_DELAY_MS = 5; private const int COPY_FILE_DELAY_MS = 50; private const int FILE_FLUSH_INTERVAL_BYTES = 4 * 1024 * 1024; #endregion #region 公共属性 public BigTransferPhase CurrentBigPhase => _currentBigPhase; public int CompletedBigPhases => _bigPhaseTasks.Count(t => t.IsCompleted); public int TotalBigPhases => _bigPhaseTasks.Count; public bool IsConnected => _stream != null && _isRunning; public TransferState State { get; } = new TransferState(); #endregion #region 数据传输字段 //写入文件(本地文件) private byte[]? _filelocalData; private string? _fileName; //key钥匙bin文件 private byte[]? _fileBinData; private string? _fileBinName; #endregion #region 设备配置 //private readonly int _vendorId, _productId; //设备的硬件Id private readonly int _vendorId = 0x28E9; private readonly int _productId = 0xFFF0; private readonly string usbdiskDir = ComStringHelper.K3ToolDirectory; #endregion #region 核心状态字段 private HidDevice? _currentDevice; private HidStream? _stream; private volatile bool _isRunning; private IProgress? _currentProgress; private double _currentBigPhaseProgress; private int _currentDataPacketIndex = 0; private List resNotity = new List(); // 自动重连相关字段 private bool _isMonitoring = false; private bool _autoReconnectEnabled = true; private volatile bool _isProcessingResponse = false; private volatile bool _isReconnecting = false; private bool _hasSentConnectRequest = false; // 👈 关键标志 //单开线程接收设备响应(CPU占用高/备用方法适应老版本库) //private Thread? _receiveThread; private HidDeviceInputReceiver? _inputReceiver; private byte[] _inputBuffer = Array.Empty(); private DateTime _lastHidResponseUtc = DateTime.MinValue; private int _consecutiveKeepAliveFailures = 0; private HidOperationTypeEnum _hidOperationType; #endregion #region 异步控制 private CancellationTokenSource? _cts; private Thread? _monitorThread; private CancellationToken _currentCancellationToken; //用于针对大阶段任务进度 private readonly Dictionary> _phaseTcsDict = new Dictionary>(); private BigTransferPhase _currentBigPhase = BigTransferPhase.None; // 当前大阶段 private List _bigPhaseTasks = new(); // 大阶段任务队列 //第一次连接 private TaskCompletionSource? _connectTcs; // (用于传输进度) private TaskCompletionSource? _transferTcs; // 全局大阶段任务完成源 private TaskCompletionSource? _allBigPhasesTcs; // 标记是否所有大阶段已完成 private volatile bool _allBigPhasesCompleted = false; #region 锁 private readonly object _streamLock = new(); private readonly object _responseLock = new object(); private readonly object _reconnectLock = new object(); // 加锁保护TCS字典 private readonly object _phaseTcsLock = new object(); private readonly object _bigPhaseLock = new(); // 大阶段锁 #endregion #endregion #region 事件定义 // 新增:大阶段变更事件 public event EventHandler? BigPhaseChanged; // 新增:所有大阶段完成事件 public event EventHandler? AllBigPhasesCompleted; public event EventHandler? ResponseReceived; public event EventHandler? LogMessage; // 自动重连相关事件 public event EventHandler? DeviceConnected; public event EventHandler? DeviceDisconnected; public event EventHandler? ReconnectAttempt; public event EventHandler? ConnectionStateChanged; #endregion private readonly ILogger _logger; public HidCommunicationService(ILogger logger) { _logger = logger; HidStart(); } private void HidStart() { try { if (_isMonitoring || _isReconnecting) { _logger.LogInformation("设备监视器已在运行或正在重连中,跳过启动。"); } else { _logger.LogInformation("启动设备监视器..."); StartDeviceMonitoring(); // 启动时立即尝试连接,不等轮询 _ = TryConnectIfDevicePresentAsync(); // 启用事件驱动监听(设备插入/拔出即时响应) StartHidDeviceMonitoring(); } } catch (Exception ex) { _logger.LogError(ex, "启动 HID 设备监控失败"); } } #region 连接状态事件 private void OnConnectionStateChanged(bool isConnected, string message = "") { ConnectionStateChanged?.Invoke(this, new ConnectionStateChangedEventArgs(isConnected, message)); } #endregion #region 自动重连实现 #region 新单开线程监听设备状态 (轮询方式CPU占用稍高) /// /// 轮询模式 /// private void StartDeviceMonitoring() { if (_isMonitoring) return; _isMonitoring = true; _monitorThread = new Thread(DeviceMonitorLoop) { IsBackground = true, Name = "HID-DeviceMonitor" }; _monitorThread.Start(); OnLog("🔍 开始设备监视..."); } private void StopDeviceMonitoring() { _isMonitoring = false; if (_monitorThread != null && _monitorThread.IsAlive) { _monitorThread.Join(DEVICE_MONITOR_INTERVAL_MS); } } private void DeviceMonitorLoop() { while (_isMonitoring) { try { // 检查设备连接状态 CheckDeviceConnection(); // 报告实际连接状态(IsConnected = 物理连接,IsDataConnected = 数据交互已建立) OnConnectionStateChanged(State.IsDataConnected, State.IsDataConnected ? "设备已就绪" : State.IsConnected ? "设备已连接,等待应答..." : "设备未连接"); // 每秒检查一次 Thread.Sleep(DEVICE_MONITOR_INTERVAL_MS); } catch (Exception ex) { _logger.LogWarning($"设备监视异常: {ex.Message}"); Thread.Sleep(DEVICE_MONITOR_ERROR_DELAY_MS); // 异常时等待更久 } } } private void CheckDeviceConnection() { lock (_reconnectLock) { var isDevicePresent = CheckIfDevicePresent(); // 去掉慢速WMI查询,HidSharp枚举已足够可靠 if (isDevicePresent) { _autoReconnectEnabled = true; } if ((State.IsConnected || State.IsDataConnected) && !isDevicePresent) { // 设备意外断开(IsConnected 物理连接 或 IsDataConnected 数据连接,任一成立都应触发) OnLog("⚠️ 检测到设备断开", true); HandleUnexpectedDisconnection(); } else if (!State.IsConnected && !State.IsDataConnected && isDevicePresent && _autoReconnectEnabled) { // 设备重新插入,尝试重连 _ = AttemptReconnect(isDevicePresent); } else if (!State.IsConnected && !State.IsDataConnected && !isDevicePresent && _autoReconnectEnabled) { // 设备不存在,但允许重连尝试 if (_reconnectAttempts < MAX_RECONNECT_ATTEMPTS) { _ = AttemptReconnect(isDevicePresent); } } } } // 新增:通过WMI检测USB设备(绕过HidSharp缓存,更精准) private bool CheckDeviceByManagementScope() { try { using var searcher = new ManagementObjectSearcher( "root\\CIMV2", $"SELECT * FROM Win32_USBHub WHERE PNPDeviceID LIKE '%VID_{_vendorId:X4}&PID_{_productId:X4}%'"); foreach (var queryObj in searcher.Get()) { return true; } return false; } catch (Exception ex) { _logger.LogWarning($"WMI设备检测失败: {ex.Message}"); return CheckIfDevicePresent(); // 降级使用HidSharp检测 } } private bool CheckIfDevicePresent() { try { var devices = DeviceList.Local.GetHidDevices(_vendorId, _productId); return devices.Any(); } catch (Exception ex) { OnLog($"检查设备存在性失败: {ex.Message}", true); return false; } } private void HandleUnexpectedDisconnection() { _isRunning = false; State.IsDataConnected = false; State.IsConnected = false; State.IsTransferring = false; _hasSentConnectRequest = false; _lastHidResponseUtc = DateTime.MinValue; _consecutiveKeepAliveFailures = 0; lock (_streamLock) { _stream?.Dispose(); _stream = null; } DeviceDisconnected?.Invoke(this, EventArgs.Empty); _reconnectAttempts = 0; // 重置重连计数 _inputReceiver = null; OnLog("设备连接已断开", true); CleanupTransfer(_currentBigPhase); } private async Task AttemptReconnect(bool isDevicePresent) { if (_reconnectAttempts >= MAX_RECONNECT_ATTEMPTS && !isDevicePresent) { OnLog($"❌ 已达到最大重连次数({MAX_RECONNECT_ATTEMPTS}),停止重连", true); _autoReconnectEnabled = false; // 禁用自动重连 return; } if (State.IsDataConnected && State.IsConnected) return; _reconnectAttempts++; var currentAttempt = _reconnectAttempts; ReconnectAttempt?.Invoke(this, new HidReconnectEventArgs(currentAttempt, MAX_RECONNECT_ATTEMPTS)); //OnLog($"🔄 尝试重连... ({currentAttempt}/{MAX_RECONNECT_ATTEMPTS})"); try { // 等待一段时间再重连 await Task.Delay(RECONNECT_INTERVAL_MS); // 指数退避策略 //int delayMs = Math.Min(500 * _reconnectAttempts, RECONNECT_INTERVAL_MS); //await Task.Delay(delayMs); var success = await ConnectAsync(_vendorId, _productId); if (success) { OnLog("✅ 重连成功"); _reconnectAttempts = 0; // 重置计数 DeviceConnected?.Invoke(this, EventArgs.Empty); } else { OnLog($"❌ 重连失败 ({currentAttempt}/{MAX_RECONNECT_ATTEMPTS})", true); if (State.IsDataConnected == false) { _ = DisconnectAsync(); } } } catch (Exception ex) { OnLog($"重连异常: {ex.Message}", true); _ = DisconnectAsync(); } } #endregion #region HidSharp 热拔插监听机制(设备只响应变更,每次打开页面需要重新执行连接方法,单例模式不适合) /// /// 事件驱动模式 /// private void StartHidDeviceMonitoring() { DeviceList.Local.Changed += OnDeviceListChanged; } public void StopHidDeviceMonitoring() { DeviceList.Local.Changed -= OnDeviceListChanged; } private async void OnDeviceListChanged(object? sender, EventArgs e) { try { // 检查目标设备是否存在 var deviceExists = DeviceList.Local.GetHidDevices(_vendorId, _productId).Any(); bool currentlyConnected = IsConnected; // 如果当前未连接,且设备出现了 → 尝试重连 if (!currentlyConnected && deviceExists) { OnLog("🔌 检测到设备插入,尝试自动重连..."); await TryReconnectAsync(); } // 如果已连接,但设备消失了 → 触发断开 else if (currentlyConnected && !deviceExists) { OnLog("⚠️ 检测到设备拔出", true); // 立即断开并清理状态 HandleUnexpectedDisconnection(); } OnConnectionStateChanged(IsConnected, $"设备连接状态:{IsConnected}"); } catch (Exception ex) { OnLog($"设备监听回调异常: {ex.Message}", true); } } private async Task TryReconnectAsync() { if (_isReconnecting) return; // 防止并发重连 _isReconnecting = true; try { await ConnectAsync(_vendorId, _productId); } finally { _isReconnecting = false; } } private async Task TryConnectIfDevicePresentAsync() { // 防止重复连接 if (IsConnected) return; var devices = DeviceList.Local.GetHidDevices(_vendorId, _productId); if (devices.Any()) { OnLog("🔌 检测到启动时设备已插入,尝试连接..."); await ConnectAsync(_vendorId, _productId); } else { OnLog("📭 启动时未检测到设备"); } } #endregion #endregion #region 新增:大阶段状态通知 private void OnBigPhaseChanged(BigTransferPhase newPhase) { var previous = _currentBigPhase; _currentBigPhase = newPhase; BigPhaseChanged?.Invoke(this, new BigPhaseChangedEventArgs( newPhase, previous, CompletedBigPhases, TotalBigPhases)); OnLog($"📌 大阶段切换:{previous} → {newPhase}"); } private void OnAllBigPhasesCompleted() { OnLog($"🎉 所有大阶段执行完成(共{TotalBigPhases}个)"); AllBigPhasesCompleted?.Invoke(this, EventArgs.Empty); // 标记全局完成 _allBigPhasesCompleted = true; // 双重保障:触发全局TCS _allBigPhasesTcs?.TrySetResult(true); // 重置大阶段状态 lock (_bigPhaseLock) { _bigPhaseTasks.Clear(); _currentBigPhase = BigTransferPhase.None; } } #endregion #region 新增的自动重连控制方法 /// /// 启用自动重连 /// public void EnableAutoReconnect() { _autoReconnectEnabled = true; _reconnectAttempts = 0; OnLog("✅ 自动重连已启用"); } /// /// 禁用自动重连 /// public void DisableAutoReconnect() { _autoReconnectEnabled = false; OnLog("❌ 自动重连已禁用"); } /// /// 立即尝试重连 /// public async Task ReconnectNowAsync() { _reconnectAttempts = 0; // 重置计数 _hasSentConnectRequest = false; return await ConnectAsync(_vendorId, _productId); } /// /// 获取重连状态 /// public ReconnectStatus GetReconnectStatus() { return new ReconnectStatus { IsAutoReconnectEnabled = _autoReconnectEnabled, ReconnectAttempts = _reconnectAttempts, MaxReconnectAttempts = MAX_RECONNECT_ATTEMPTS, IsMonitoring = _isMonitoring }; } #endregion #region 基础发送及响应 public async Task ConnectAsync(int vendorId = 0x28E9, int productId = 0xFFF0) { var devices = DeviceList.Local.GetHidDevices(vendorId, productId); if (!devices.Any()) { OnLog("未找到设备", true); return false; } try { var device = devices.First(); if (!device.TryOpen(out _stream)) { OnLog("无法打开设备流", true); return false; } Debug.Write($"设备已打开,输出报告长度: {device.GetMaxOutputReportLength()},输入报告长度: {device.GetMaxInputReportLength()}"); _isRunning = true; _cts = new CancellationTokenSource(); _lastHidResponseUtc = DateTime.UtcNow; _consecutiveKeepAliveFailures = 0; #region 事件异步接收 // 初始化接收器 var descriptor = device.GetReportDescriptor(); _inputReceiver = descriptor.CreateHidDeviceInputReceiver(); _inputBuffer = new byte[device.GetMaxInputReportLength()]; // 订阅异步接收事件(自动在线程池触发) _inputReceiver.Received += OnInputReceived; // 启动接收(非阻塞) _inputReceiver.Start(_stream); #endregion State.IsConnected = true; _reconnectAttempts = 0; if (_hasSentConnectRequest == false) { _hasSentConnectRequest = true; await SendAsync(K3ToolUSBProtocol.ConnectData); } // ✅ 适配 .NET Framework 4.6.1 的超时等待机制 var localConnectTcs = new TaskCompletionSource(); using (var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(_cts.Token)) { timeoutCts.CancelAfter(2500); // 2.5秒超时 // 临时保存到字段,供 ProcessIncomingResponse 使用 _connectTcs = localConnectTcs; try { // 经典超时等待模式:将 TCS 任务与超时取消令牌结合 var completedTask = await Task.WhenAny(localConnectTcs.Task, Task.Delay(Timeout.Infinite, timeoutCts.Token)); await completedTask; // 如果超时,此行会抛出 OperationCanceledException return true; } catch (OperationCanceledException) { if (timeoutCts.IsCancellationRequested) OnLog("连接超时:未收到设备应答(设备可能未就绪)", false); return false; } //finally //{ // _connectTcs = null; // 清理 //} } // using 块结束,自动释放 timeoutCts } catch (Exception ex) { OnLog($"连接失败: {ex.Message}", true); await DisconnectAsync(); Dispose(); return false; } } /// /// 轮询等待复合设备(U盘+HID)完全加载完成 /// /// 厂商ID /// 产品ID /// 最大等待时间(毫秒) /// 轮询间隔(毫秒) /// true 表示设备已就绪;false 表示超时 public async Task WaitForDeviceReadyAsync( int vendorId = 0x28E9, int productId = 0xFFF0, int maxWaitMs = 6000, int pollIntervalMs = 300) { var startTime = DateTime.UtcNow; var timeout = TimeSpan.FromMilliseconds(maxWaitMs); while ((DateTime.UtcNow - startTime) < timeout) { try { // Step 1: 枚举设备 var devices = DeviceList.Local.GetHidDevices(vendorId, productId); if (!devices.Any()) { await Task.Delay(pollIntervalMs); continue; } var device = devices.First(); // Step 2: 尝试打开设备(可选,但建议) if (!device.TryOpen(out var stream)) { await Task.Delay(pollIntervalMs); continue; } // Step 3: 【核心】读取报告描述符 var descriptor = null as ReportDescriptor; try { descriptor = device.GetReportDescriptor(); // HidSharp API } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"GetReportDescriptor failed: {ex.Message}"); } finally { stream?.Dispose(); // 立即释放流 } // ✅ 描述符有效 = 设备 ready! if (descriptor != null && descriptor.DeviceItems.Count > 0) { OnLog("✅ HID 报告描述符读取成功,设备已就绪", false); return true; } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"轮询异常: {ex.Message}"); } await Task.Delay(pollIntervalMs); } OnLog($"⚠️ 设备未在 {maxWaitMs}ms 内就绪", true); return false; } #region 接收线程(兼容 .NET Framework 4.6.2,同步 Read 方式) private void ReceiveLoop() { var buffer = new byte[REPORT_LENGTH]; while (_isRunning && _stream != null) { try { _stream.ReadTimeout = 0; int n = _stream.Read(buffer, 0, REPORT_LENGTH); if (n > 0) { var copy = new byte[n]; Array.Copy(buffer, copy, n); // 触发响应事件 ResponseReceived?.Invoke(this, new HidResponseReceivedEventArgs(copy)); // 处理协议响应 ProcessIncomingResponse(copy); } } catch (TimeoutException) { // 正常:无数据,继续循环 } catch (ObjectDisposedException) { break; } catch (Exception ex) { OnLog($"接收异常: {ex.Message}", true); break; } } // 自动断开 if (_isRunning) { OnLog("设备断开", true); _isRunning = false; State.IsDataConnected = false; State.IsConnected = false; _transferTcs?.TrySetResult(false); } } #endregion #region HidSharp 接收设备响应事件驱动 private void OnInputReceived(object? sender, EventArgs e) { if (_inputReceiver == null || _inputBuffer.Length == 0) return; Report report; while (_inputReceiver.TryRead(_inputBuffer, 0, out report)) { if (report.Length > 0) { var copy = new byte[report.Length]; Array.Copy(_inputBuffer, copy, report.Length); _lastHidResponseUtc = DateTime.UtcNow; _consecutiveKeepAliveFailures = 0; // 触发响应事件(注意:此方法在后台线程调用!) ResponseReceived?.Invoke(this, new HidResponseReceivedEventArgs(copy)); //var s = BitConverter.ToString(copy); //if (copy[0] == 0xA5 && copy[0] == 0x5A) //{ // ProcessSpecialFrame(copy); //} // 处理协议(如果 ProcessIncomingResponse 涉及 UI,需调度) ProcessIncomingResponse(copy); } } } #endregion public async Task SendAsync(byte[] data) { if (_stream == null || !_isRunning) throw new InvalidOperationException(GetLanguageValueOrDefault("DeviceDisconnected") ?? string.Empty); try { if (data.Length == 0) return; // 分包发送 for (int i = 0; i < data.Length; i += REPORT_LENGTH - 1) { int chunkSize = Math.Min(REPORT_LENGTH - 1, data.Length - i); var packet = new byte[REPORT_LENGTH]; packet[0] = 0x00; // Report ID Array.Copy(data, i, packet, 1, chunkSize); lock (_streamLock) { _stream?.Write(packet); } OnLog($"📤 发送包: {BitConverter.ToString(packet)}"); //await Task.Delay(1); // 避免总线过载 } } catch (Exception ex) { throw new InvalidOperationException($"{GetLanguageValueOrDefault("HidSendPacketError") ?? string.Empty}:{ex.Message}"); } } public async Task DisconnectAsync() { _isRunning = false; _cts?.Cancel(); #region (CPU占用高/备用方法适应老版本库) //if (_receiveThread != null && _receiveThread.IsAlive) //{ // _receiveThread.Join(DEVICE_MONITOR_INTERVAL_MS); //} #endregion _hasSentConnectRequest = false; _inputReceiver = null; lock (_streamLock) { _stream?.Dispose(); _stream = null; } _lastHidResponseUtc = DateTime.MinValue; _consecutiveKeepAliveFailures = 0; State.IsDataConnected = false; State.IsConnected = false; OnLog("🔌 设备已断开"); } // 响应处理方法 public void ProcessIncomingResponse(byte[] data) { lock (_responseLock) { if (_isProcessingResponse) return; _isProcessingResponse = true; try { if (resNotity.Count < 195) { resNotity.AddRange(data); } if (resNotity.Count != 195) return; var responseType = ParseResponseType(resNotity); if (responseType == ResponseType.ReadyForData) { if (State.IsDataConnected == false) { State.IsDataConnected = true; _connectTcs.TrySetResult(true); } else { //只有写入钥匙Bin文件时才走下面逻辑 if (_currentBigPhase == BigTransferPhase.Phase3_Write) { switch (State.CurrentPhase) { case TransferPhase.Phase2_WaitingForHeaderAck: HandleHeaderAck(); break; case TransferPhase.Phase4_WaitingForDataFrameAck: // 中间确认 HandleDataFrameAck(); break; case TransferPhase.Phase5_WaitingForFinalDataAck: // 最终确认 HandleFinalDataAck(); break; case TransferPhase.Phase11_WaitingForComplete: HandleFinalComplete(); break; default: State.StatusMessage = $"⚠️ 阶段 {State.CurrentPhase} 收到意外的ACK"; CleanupTransfer(_currentBigPhase); break; } } else if (_currentBigPhase == BigTransferPhase.Phase1_Connect || _currentBigPhase == BigTransferPhase.Phase2_Generate) { HandleFinalComplete(); } else { //未知情况的ACK回复 State.StatusMessage = $"⚠️ 大阶段{_currentBigPhase},阶段 {State.CurrentPhase} 收到意外的ACK"; CleanupTransfer(_currentBigPhase); } } } else if (responseType == ResponseType.Nak) { if (State.CurrentPhase == TransferPhase.Phase7_WaitingForNak) { HandleNakResponse(); } else { State.StatusMessage = $"⚠️ 阶段 {State.CurrentPhase} 收到意外的 NAK"; CleanupTransfer(_currentBigPhase); } } else if (responseType == ResponseType.Ackc) { if (State.CurrentPhase == TransferPhase.Phase9_WaitingForAckc) { HandleAckcResponse(); } else { State.StatusMessage = $"⚠️ 阶段 {State.CurrentPhase} 收到意外的 ACKC"; CleanupTransfer(_currentBigPhase); } } else { State.StatusMessage = $"⚠️ 阶段 {State.CurrentPhase}未知的错误,{BitConverter.ToString(resNotity.ToArray())}"; CleanupTransfer(_currentBigPhase); } resNotity.Clear(); } finally { _isProcessingResponse = false; } } } private ResponseType ParseResponseType(List buffer) { if (buffer.Count == 195) { // 检查 ReadyForData(ACK) if (K3ToolUSBProtocol.IsReadyForDataFrameResponse(buffer)) { return ResponseType.ReadyForData; } // 检查 NAK else if (K3ToolUSBProtocol.IsNakResponse(buffer)) { return ResponseType.Nak; } // 检查 ACKC else if (K3ToolUSBProtocol.IsAckcResponse(buffer)) { return ResponseType.Ackc; } } return ResponseType.Unknown; } /// /// 处理特殊帧(协议待定) /// private void ProcessSpecialFrame(byte[] frame) { if (frame.Length < 2) return; byte header = frame[0]; byte type = frame[1]; if (header != 0xA5) { return; } switch (type) { case 0x5A: // 固件版本 if (frame.Length >= 4) { string version = $"{frame[2]}.{frame[3]}.0"; } break; case 0x5B: // 电池电量 break; case 0x5C: // 序列号 if (frame.Length >= 10) { string serialNumber = BitConverter.ToString(frame, 2, 8).Replace("-", ""); } break; default: break; } } private async Task HandleHeaderAck() { OnLog("收到头帧确认,开始发送数据帧"); State.StatusMessage = "开始发送数据帧"; State.CurrentPhase = TransferPhase.Phase3_SendingDataFrames; _currentDataPacketIndex = 0; if (_fileBinData == null || State.TotalPackets <= 0) return; await SendNextDataFrameAsync(); } private async Task SendNextDataFrameAsync() { if (_currentDataPacketIndex >= State.TotalPackets || !IsConnected) return; var frame = K3ToolUSBProtocol.GetSendDataFrame(_currentDataPacketIndex, _fileBinData!).ToArray(); await SendAsync(frame); _currentBigPhaseProgress += (60.0 / State.TotalPackets); _currentProgress?.Report(_currentBigPhaseProgress); OnLog($"数据帧 {_currentDataPacketIndex + 1}/{State.TotalPackets} 已发送"); if (_currentDataPacketIndex == State.TotalPackets - 1) { State.CurrentPhase = TransferPhase.Phase5_WaitingForFinalDataAck; } else { State.CurrentPhase = TransferPhase.Phase4_WaitingForDataFrameAck; } _currentDataPacketIndex++; } private async Task HandleDataFrameAck() { OnLog($"收到数据帧确认 {_currentDataPacketIndex - 1}"); if (_currentDataPacketIndex < State.TotalPackets) { State.CurrentPhase = TransferPhase.Phase3_SendingDataFrames; await SendNextDataFrameAsync(); } else { OnLog("所有数据帧发送完成,等待最终确认"); State.CurrentPhase = TransferPhase.Phase5_WaitingForFinalDataAck; } } private async Task HandleFinalDataAck() { OnLog("收到最终数据帧确认,准备发送EOT"); State.CurrentPhase = TransferPhase.Phase6_SendingEOT; _currentBigPhaseProgress += (5.0) / 3; _currentProgress?.Report(_currentBigPhaseProgress); await SendAsync(K3ToolUSBProtocol.EndEOTData); State.CurrentPhase = TransferPhase.Phase7_WaitingForNak; } private async Task HandleNakResponse() { OnLog("收到NAK确认,发送最终EOT"); State.CurrentPhase = TransferPhase.Phase8_SendingFinalEOT; _currentBigPhaseProgress += (5.0) / 3; _currentProgress?.Report(_currentBigPhaseProgress); await SendAsync(K3ToolUSBProtocol.EndEOTData); State.CurrentPhase = TransferPhase.Phase9_WaitingForAckc; } private async Task HandleAckcResponse() { OnLog("收到ACKC确认,发送结束帧"); State.CurrentPhase = TransferPhase.Phase10_SendEndFrame; _currentBigPhaseProgress += (5.0) / 3; _currentProgress?.Report(_currentBigPhaseProgress); await SendAsync(K3ToolUSBProtocol.GetEndDataFrame()); State.CurrentPhase = TransferPhase.Phase11_WaitingForComplete; if (_hidOperationType == HidOperationTypeEnum.DeviceUpdate) { await HandleFinalComplete(); } } private async Task HandleFinalComplete() { OnLog("文件传输完成!"); State.CurrentPhase = TransferPhase.Completed; State.IsTransferring = false; State.StatusMessage = "传输完成"; // 标记当前大阶段任务完成 lock (_bigPhaseLock) { var currentTask = _bigPhaseTasks.FirstOrDefault(t => t.Phase == _currentBigPhase); if (currentTask != null) { currentTask.IsCompleted = true; //if (_currentBigPhase == BigTransferPhase.Phase3_Write) //{ // // ✅ 报告 100% // _currentProgress?.Report(100.0); //} } // ========== 触发当前阶段的独立TCS完成 ========== lock (_phaseTcsLock) { if (_phaseTcsDict.TryGetValue(_currentBigPhase, out var currentPhaseTcs) && !currentPhaseTcs.Task.IsCompleted) { currentPhaseTcs.TrySetResult(true); } } // ========== 关键:检查是否所有大阶段都已完成 ========== _allBigPhasesCompleted = _bigPhaseTasks.All(t => t.IsCompleted); if (_allBigPhasesCompleted) { OnAllBigPhasesCompleted(); // 触发全局TCS完成(只有所有阶段都完成才会执行这行) _allBigPhasesTcs?.TrySetResult(true); return; } } } #endregion private void OnLog(string message, bool isError = false) { LogMessage?.Invoke(this, new HidLogMessageEventArgs(message, isError)); } #region 进度条拓展(烧录文件) /// /// 用于测试(本地路径文件),线上采用读取文件方式 /// /// /// /// /// [Obsolete] public async Task<(bool, string)> SendFileAsync(string filePath, IProgress progress, CancellationToken ct) { _currentProgress?.Report(0.0); if (!IsConnected) { return (false, "设备未找到"); } try { if (!File.Exists(filePath)) { OnLog("文件不存在", true); return (false, $"烧录的文件不存在"); } _filelocalData = File.ReadAllBytes(filePath); K3ToolUSBProtocol.SetFileData(_filelocalData); var totalPackets = (int)Math.Ceiling(_filelocalData.Length / 1024.0); State.TotalPackets = totalPackets; State.IsTransferring = true; // 初始化传输上下文 _currentProgress = progress; _currentCancellationToken = ct; _transferTcs = new TaskCompletionSource(); // 注册取消监听 ct.Register(() => { if (!_transferTcs.Task.IsCompleted) { OnLog("文件传输已取消", true); _transferTcs.TrySetCanceled(); CleanupTransfer(_currentBigPhase); } }); // 报告初始进度(比如 5%) progress?.Report(5.0); // 发送头帧 var fileName = Path.GetFileName(filePath); var header = K3ToolUSBProtocol.GetSendHeader(fileName); await SendAsync(header); State.CurrentPhase = TransferPhase.Phase2_WaitingForHeaderAck; State.StatusMessage = "等待头帧确认"; // 等待传输完成(通过 TCS) bool success = await _transferTcs.Task; if (success) { return (true, "✅ 文件传输完成!"); } else { // 可以从 State.StatusMessage 获取更具体的错误信息 var msg = State.StatusMessage ?? "传输失败"; if (IsConnected == false) { return (false, $"❌【设备断开连接,请勿在烧录中拔插设备】"); } else { return (false, $"❌【烧录失败】 {msg}"); } } } catch (Exception ex) { OnLog($"发送文件失败: {ex.Message}", true); CleanupTransfer(_currentBigPhase); return (false, $"发送文件失败:{ex.Message}"); } } public async Task<(bool, string)> SendFileAsync(string fileName, byte[] filebin, IProgress progress, CancellationToken ct) { if (!IsConnected) { return (false, "设备未找到"); } try { if (!IsValidHasFile(filebin)) { OnLog("文件不存在", true); return (false, $"烧录的文件不存在"); } K3ToolUSBProtocol.SetFileData(filebin); var totalPackets = (int)Math.Ceiling(filebin.Length / 1024.0); State.TotalPackets = totalPackets; State.IsTransferring = true; // 初始化传输上下文 _currentProgress = progress; _currentCancellationToken = ct; _transferTcs = new TaskCompletionSource(); // 注册取消监听 ct.Register(() => { if (!_transferTcs.Task.IsCompleted) { OnLog("文件传输已取消", true); CleanupTransfer(_currentBigPhase); } }); // 报告初始进度(比如 5%) progress?.Report(5.0); // 发送头帧 var header = K3ToolUSBProtocol.GetSendHeader(fileName); await SendAsync(header); State.CurrentPhase = TransferPhase.Phase2_WaitingForHeaderAck; State.StatusMessage = "等待头帧确认"; // 等待传输完成(通过 TCS) bool success = await _transferTcs.Task; if (success) { return (true, "✅ 烧录成功!"); } else { // 可以从 State.StatusMessage 获取更具体的错误信息 var msg = State.StatusMessage ?? "传输失败"; if (IsConnected == false) { return (false, $"❌【设备断开连接,请勿在烧录中拔插设备】"); } else { return (false, $"❌【烧录失败】 {msg}"); } } } catch (Exception ex) { OnLog($"发送文件失败: {ex.Message}", true); CleanupTransfer(_currentBigPhase); return (false, $"发送文件失败:{ex.Message}"); } } #region 阶段性烧录(连接/生成/写入) /// /// 固定单命令(如连接/生成 等命令) /// /// /// /// /// /// private async Task<(bool, string)> SendSingleCommandAsync(string fileName, byte[] filebin, IProgress progress, CancellationToken ct) { if (!IsConnected) { return (false, "设备未找到"); } try { // ========== 关键:为当前大阶段创建独立TCS ========== TaskCompletionSource currentPhaseTcs; lock (_phaseTcsLock) { // 确保每个阶段只有一个TCS if (!_phaseTcsDict.TryGetValue(_currentBigPhase, out currentPhaseTcs) || currentPhaseTcs.Task.IsCompleted) { currentPhaseTcs = new TaskCompletionSource(); _phaseTcsDict[_currentBigPhase] = currentPhaseTcs; } } if (!IsValidHasFile(filebin)) { OnLog("文件不存在", true); return (false, $"烧录的文件不存在"); } State.IsTransferring = true; State.CurrentPhase = TransferPhase.Phase11_WaitingForComplete; await SendAsync(filebin); // 等待传输完成(通过 TCS) bool success = await currentPhaseTcs.Task; if (success) { GetCurrentPhaseStartProgress(); _currentProgress?.Report(_currentBigPhaseProgress); return (true, $"基础命令:{_currentBigPhase}完成"); } else { // 可以从 State.StatusMessage 获取更具体的错误信息 var msg = State.StatusMessage ?? "传输失败"; if (IsConnected == false) { return (false, $"❌【设备断开连接,请勿在烧录中拔插设备】"); } else { return (false, $"❌【烧录失败】 {msg}"); } } } catch (Exception ex) { OnLog($"发送文件失败: {ex.Message}", true); lock (_phaseTcsLock) { if (_phaseTcsDict.TryGetValue(_currentBigPhase, out var tcs)) { tcs.TrySetCanceled(); } } CleanupTransfer(_currentBigPhase); return (false, $"发送文件失败:{ex.Message}"); } } private async Task<(bool, string)> SendSingleFileAsync(string fileName, byte[] filebin, IProgress progress, CancellationToken ct) { if (!IsConnected) { return (false, "设备未找到"); } // ========== 关键:为当前大阶段创建独立TCS ========== TaskCompletionSource currentPhaseTcs; lock (_phaseTcsLock) { if (!_phaseTcsDict.TryGetValue(_currentBigPhase, out currentPhaseTcs) || currentPhaseTcs.Task.IsCompleted) { currentPhaseTcs = new TaskCompletionSource(); _phaseTcsDict[_currentBigPhase] = currentPhaseTcs; } } try { if (!IsValidHasFile(filebin)) { OnLog("文件不存在", true); return (false, $"烧录的文件不存在"); } K3ToolUSBProtocol.SetFileData(filebin); var totalPackets = (int)Math.Ceiling(filebin.Length / 1024.0); State.TotalPackets = totalPackets; State.IsTransferring = true; // 发送头帧 var header = K3ToolUSBProtocol.GetSendHeader(fileName); await SendAsync(header); GetCurrentPhaseStartProgress(); State.CurrentPhase = TransferPhase.Phase2_WaitingForHeaderAck; State.StatusMessage = "等待头帧确认"; // 等待传输完成(通过 TCS) bool success = await currentPhaseTcs.Task; if (success) { double scopedProgressVal = _currentBigPhaseProgress + 5.0; _currentProgress?.Report(scopedProgressVal); return (true, "✅ 烧录成功!"); } else { // 可以从 State.StatusMessage 获取更具体的错误信息 var msg = State.StatusMessage ?? "传输失败"; if (IsConnected == false) { return (false, $"❌【设备断开连接,请勿在烧录中拔插设备】"); } else { return (false, $"❌【烧录失败】 {msg}"); } } } catch (Exception ex) { OnLog($"发送文件失败: {ex.Message}", true); CleanupTransfer(_currentBigPhase); return (false, $"发送文件失败:{ex.Message}"); } } public async Task<(bool, string)> SendFileProcessAsync(string fileName, byte[] filebin, IProgress progress, CancellationToken ct) { _currentProgress?.Report(0.0); if (!IsConnected) { return (false, "设备未找到"); } try { if (!IsValidHasFile(filebin)) { OnLog("文件不存在", true); return (false, $"烧录的文件不存在"); } //========== 关键:初始化全局大阶段任务完成源 ========== _allBigPhasesTcs = new TaskCompletionSource(); _allBigPhasesCompleted = false; // 初始化大阶段任务队列 lock (_bigPhaseLock) { _bigPhaseTasks.Clear(); _bigPhaseTasks.Add(new BigPhaseTask { Phase = BigTransferPhase.Phase1_Connect, IsCompleted = false }); _bigPhaseTasks.Add(new BigPhaseTask { Phase = BigTransferPhase.Phase2_Generate, IsCompleted = false }); _bigPhaseTasks.Add(new BigPhaseTask { Phase = BigTransferPhase.Phase3_Write, IsCompleted = false }); } _fileBinData = filebin; _fileBinName = fileName; // 初始化传输上下文 _currentProgress = progress; _currentCancellationToken = ct; // ========== 关键:取消监听改为触发全局TCS ========== ct.Register(() => { if (_allBigPhasesTcs != null && !_allBigPhasesTcs.Task.IsCompleted) { OnLog("多阶段传输已取消", true); _allBigPhasesTcs.TrySetCanceled(); CleanupTransfer(); } }); // 启动第一个大阶段 await SwitchToNextBigPhaseAsync(); // 等待所有大阶段完成 // ========== 关键:等待所有大阶段完成(直到HandleFinalComplete触发所有阶段完成) ========== bool allSuccess = false; try { // 等待全局TCS完成(只有所有大阶段执行HandleFinalComplete并标记完成后才会触发) allSuccess = await _allBigPhasesTcs.Task; } catch (OperationCanceledException) { OnLog("多阶段传输被取消", true); return (false, "传输已取消"); } // 收集结果 string errorMsg = string.Empty; lock (_bigPhaseLock) { errorMsg = string.Join("; ", _bigPhaseTasks.Where(t => !string.IsNullOrEmpty(t.ErrorMessage)).Select(t => $"{t.Phase}: {t.ErrorMessage}")); } if (allSuccess) { _currentProgress.Report(100.00); return (true, GetLanguageValueOrDefault("BurnSuccess")); } else { return (false, string.IsNullOrEmpty(errorMsg) ? GetLanguageValueOrDefault("BurnError") : errorMsg); } } catch (Exception ex) { OnLog($"发送文件失败: {ex.Message}", true); CleanupTransfer(); return (false, $"发送文件失败:{ex.Message}"); } } private async Task SwitchToNextBigPhaseAsync() { BigPhaseTask? nextTask = null; // 先在锁内获取下一个任务(避免锁内执行异步逻辑) lock (_bigPhaseLock) { nextTask = _bigPhaseTasks.FirstOrDefault(t => !t.IsCompleted); if (nextTask == null) { OnAllBigPhasesCompleted(); return; } // 切换大阶段状态(提前切换,避免重复执行) OnBigPhaseChanged(nextTask.Phase); } // 锁外执行异步逻辑(避免阻塞其他线程) try { bool phaseSuccess = false; string phaseError = string.Empty; switch (nextTask.Phase) { case BigTransferPhase.Phase1_Connect: (phaseSuccess, phaseError) = await SendSingleCommandAsync( K3ToolUSBProtocol.ConnectFileName, K3ToolUSBProtocol.ConnectData, _currentProgress!, _currentCancellationToken); if (phaseSuccess) { // 延迟,避免设备未就绪(可选,根据设备特性调整) await Task.Delay(1000); } else { } break; case BigTransferPhase.Phase2_Generate: (phaseSuccess, phaseError) = await SendSingleCommandAsync( K3ToolUSBProtocol.CreateRemoteFileName, K3ToolUSBProtocol.CreateRemoteData, _currentProgress!, _currentCancellationToken); if (phaseSuccess) { // 延迟,避免设备未就绪(可选,根据设备特性调整) await Task.Delay(500); } break; case BigTransferPhase.Phase3_Write: (phaseSuccess, phaseError) = await SendSingleFileAsync( _fileBinName, _fileBinData!, _currentProgress!, _currentCancellationToken); if (phaseSuccess) { // 延迟,避免设备未就绪(可选,根据设备特性调整) //await Task.Delay(500); } break; } // 再次加锁更新任务状态(关键:确保状态更新原子性) lock (_bigPhaseLock) { nextTask.IsCompleted = true; nextTask.ErrorMessage = phaseError; } if (!phaseSuccess) { OnLog($"❌ {nextTask.Phase} 执行失败: {phaseError}", true); // ========== 触发当前阶段TCS失败 + 全局TCS失败 ========== lock (_phaseTcsLock) { if (_phaseTcsDict.TryGetValue(nextTask.Phase, out var tcs)) { tcs.TrySetResult(false); } } _allBigPhasesTcs?.TrySetResult(false); return; } OnLog($"✅ {nextTask.Phase} 执行完成,准备进入下一阶段"); // 递归调用,进入下一个大阶段 await SwitchToNextBigPhaseAsync(); } catch (Exception ex) { lock (_bigPhaseLock) { nextTask.IsCompleted = true; nextTask.ErrorMessage = ex.Message; } OnLog($"❌ {nextTask.Phase} 执行异常: {ex.Message}", true); _allBigPhasesTcs?.TrySetResult(false); } } #endregion #region 辅助方法 public void Dispose() { _isRunning = false; _cts?.Cancel(); #region (CPU占用高/备用方法适应老版本库) //if (_receiveThread != null && _receiveThread.IsAlive) //{ // _receiveThread.Join(DEVICE_MONITOR_INTERVAL_MS); //} #endregion _inputReceiver = null; lock (_streamLock) { _stream?.Dispose(); _stream = null; } CleanupTransfer(); _cts?.Dispose(); } private void CleanupTransfer(BigTransferPhase? phase = null) { State.IsTransferring = false; // 清理指定阶段的TCS if (phase.HasValue) { lock (_phaseTcsLock) { if (_phaseTcsDict.ContainsKey(phase.Value)) { _phaseTcsDict[phase.Value].TrySetCanceled(); _phaseTcsDict.Remove(phase.Value); } } } else { // 清理所有阶段的TCS lock (_phaseTcsLock) { foreach (var tcs in _phaseTcsDict.Values) { tcs.TrySetCanceled(); } _phaseTcsDict.Clear(); } // 清理全局TCS _allBigPhasesTcs?.TrySetCanceled(); _allBigPhasesTcs = null; _allBigPhasesCompleted = false; } _currentProgress = null; _fileBinData = null; _fileBinName = null; } private bool IsValidHasFile(byte[] filebin) { if (filebin == null || filebin.Length == 0) { return false; } return true; } #region 阶段进度条数据 private double GetCurrentPhaseStartProgress() { switch (_currentBigPhase) { case BigTransferPhase.Phase1_Connect: //连接指令较快 _currentBigPhaseProgress = 10; break; case BigTransferPhase.Phase2_Generate: //指令较快 _currentBigPhaseProgress += 10; break; case BigTransferPhase.Phase3_Write: _currentBigPhaseProgress += 10; break; default: return 0.0; } return _currentBigPhaseProgress; } #endregion #endregion #endregion #region 设备固件升级 public async Task<(bool, string)> SendFileProcessAsync(string fileName, byte[] filebin, HidOperationTypeEnum hidOperationType, IProgress progress, CancellationToken ct) { _hidOperationType = hidOperationType; _currentProgress?.Report(0.0); if (!IsConnected) { return (false, "设备未找到"); } try { if (!IsValidHasFile(filebin)) { OnLog("文件不存在", true); return (false, $"烧录的文件不存在"); } //========== 关键:初始化全局大阶段任务完成源 ========== _allBigPhasesTcs = new TaskCompletionSource(); _allBigPhasesCompleted = false; // 初始化大阶段任务队列 lock (_bigPhaseLock) { _bigPhaseTasks.Clear(); _bigPhaseTasks.Add(new BigPhaseTask { Phase = BigTransferPhase.Phase1_Connect, IsCompleted = false }); _bigPhaseTasks.Add(new BigPhaseTask { Phase = BigTransferPhase.Phase2_Generate, IsCompleted = false }); _bigPhaseTasks.Add(new BigPhaseTask { Phase = BigTransferPhase.Phase3_Write, IsCompleted = false }); } _fileBinData = filebin; _fileBinName = fileName; // 初始化传输上下文 _currentProgress = progress; _currentCancellationToken = ct; // ========== 关键:取消监听改为触发全局TCS ========== ct.Register(() => { if (_allBigPhasesTcs != null && !_allBigPhasesTcs.Task.IsCompleted) { OnLog("多阶段传输已取消", true); _allBigPhasesTcs.TrySetCanceled(); CleanupTransfer(); } }); // 启动第一个大阶段 await SwitchToNextBigPhaseAsync(); // 等待所有大阶段完成 // ========== 关键:等待所有大阶段完成(直到HandleFinalComplete触发所有阶段完成) ========== bool allSuccess = false; try { // 等待全局TCS完成(只有所有大阶段执行HandleFinalComplete并标记完成后才会触发) allSuccess = await _allBigPhasesTcs.Task; } catch (OperationCanceledException) { OnLog("多阶段传输被取消", true); return (false, "传输已取消"); } // 收集结果 string errorMsg = string.Empty; lock (_bigPhaseLock) { errorMsg = string.Join("; ", _bigPhaseTasks.Where(t => !string.IsNullOrEmpty(t.ErrorMessage)).Select(t => $"{t.Phase}: {t.ErrorMessage}")); } if (allSuccess) { _currentProgress.Report(100.00); return (true, GetLanguageValueOrDefault("BurnSuccess")); } else { return (false, string.IsNullOrEmpty(errorMsg) ? GetLanguageValueOrDefault("BurnError") : errorMsg); } } catch (Exception ex) { OnLog($"发送文件失败: {ex.Message}", true); CleanupTransfer(); return (false, $"发送文件失败:{ex.Message}"); } } #endregion #region 获取设备版本 public async Task<(bool, string)> GetDeviceVersion(HidOperationTypeEnum hidOperationType) { if (!IsConnected) { return (false, "设备未找到"); } await Task.Delay(5000); await SendAsync(K3ToolUSBProtocol.VersionQuery()); return (true, "获取设备版本指令已发送"); } #endregion #region 获取K3ToolUSB设备对应的U盘盘符 public async Task FindAssociatedUsbDisk(int vid, int pid) { string model = "K3 TOOL"; try { // 步骤1:获取所有可移动磁盘盘符(确保设备已挂载) var removableDrives = DriveInfo.GetDrives() .Where(d => d.DriveType == DriveType.Removable && d.IsReady) .ToList(); if (!removableDrives.Any()) return null; // 步骤2:查询所有 USB 磁盘的 Model 和 Index using var searcher = new ManagementObjectSearcher( "SELECT Index, Model FROM Win32_DiskDrive WHERE InterfaceType='USB'"); var usbDisks = new List<(uint Index, string Model)>(); foreach (ManagementObject disk in searcher.Get()) { if (disk["Model"] is string diskModel && diskModel.IndexOf(model, StringComparison.OrdinalIgnoreCase) >= 0) { uint index = (uint)(disk["Index"] ?? 0); usbDisks.Add((index, diskModel)); } } // 步骤3:根据匹配数量决定策略 if (usbDisks.Count == 0) return null; // 无匹配设备 if (usbDisks.Count == 1 && removableDrives.Count == 1) { // 唯一匹配 → 返回唯一盘符 return removableDrives[0].RootDirectory.FullName; } // 多个匹配?返回第一个(或扩展为用户选择) return removableDrives.FirstOrDefault()?.RootDirectory.FullName; } catch (ManagementException ex) when (ex.Message.Contains("Invalid class")) { // WMI 损坏,降级处理 // 如果只有一个可移动磁盘,且您确定它是目标设备,可直接返回 var drives = DriveInfo.GetDrives() .Where(d => d.DriveType == DriveType.Removable && d.IsReady) .ToList(); if (drives.Count == 1) return drives[0].RootDirectory.FullName; return null; } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"FindUsbDiskByModel error: {ex}"); return null; } } #endregion #region 复制文件到HID U盘 public async Task<(bool, string)> CopyToUdiskAsync(string sourcePath, bool isDirectory, IProgress progress, CancellationToken cancellationToken = default) { await OpenUDiskAndDetectDriveAsync(); var targetDrive = await FindAssociatedUsbDisk(_vendorId, _productId); if (string.IsNullOrEmpty(targetDrive)) throw new InvalidOperationException("未检测到 HID U盘设备"); // 目标路径改为 {targetDrive}\received_data string receivedDataDir = Path.Combine(targetDrive, usbdiskDir); // 自动创建 received_data 目录(如果不存在) Directory.CreateDirectory(receivedDataDir); // 启动保活任务(每60秒发送一次 ConnectData) using var keepAliveCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); var keepAliveTask = KeepDeviceAliveAsync(keepAliveCts.Token); try { if (isDirectory) { //文件夹复制 string sourceFolderName = Path.GetFileName(sourcePath); string destDir; if (string.Equals(sourceFolderName, usbdiskDir, StringComparison.OrdinalIgnoreCase)) { // 源文件夹名 == usbdiskDir(如 "received_data") // → 直接复制其内部内容到 receivedDataDir,不嵌套 destDir = receivedDataDir; } else { // 正常:在 received_data 下创建子文件夹 destDir = Path.Combine(receivedDataDir, sourceFolderName); } int skipped = await CopyDirectoryWithProgressAsync(sourcePath, destDir, progress, cancellationToken); progress?.Report(100.0); return skipped > 0 ? (true, $"数据同步完成({skipped} 个文件因损坏被跳过,建议对U盘执行 chkdsk 修复)") : (true, "数据同步完成"); } else { //单文件复制 string destFile = Path.Combine(receivedDataDir, Path.GetFileName(sourcePath)); long totalSize = new FileInfo(sourcePath).Length; var context = new CopyProgressContext(totalSize, progress); await CopyFileWithProgressAsync(sourcePath, destFile, context, cancellationToken); progress?.Report(100.0); return (true, "数据同步完成"); } } catch (Exception ex) { var failureCategory = UsbCopyGuard.ClassifyCopyException(ex); var failureLabel = UsbCopyGuard.GetFailureLabel(failureCategory); _logger.LogWarning(ex, "复制到K3Tool设备失败。类别:{FailureCategory},目标盘符:{TargetDrive},源路径:{SourcePath},连接状态: IsConnected={IsConnected}, IsDataConnected={IsDataConnected}", failureLabel, targetDrive, sourcePath, State.IsConnected, State.IsDataConnected); return (false, $"错误信息:[{failureLabel}] {ex.Message}"); } finally { // 复制完成后,停止保活 keepAliveCts.Cancel(); //try //{ // // 无论成功与否,都先尝试弹出卷;即使失败也继续发送关闭U盘指令。 // await WindowsVolumeEjector.EjectVolumeAsync(targetDrive); //} //catch (OperationCanceledException) { /* 忽略 */ } //catch (Exception ex) //{ // _logger.LogWarning(ex, "弹出K3Tool卷失败,继续执行关闭U盘指令。目标盘符:{TargetDrive}", targetDrive); //} try { await CloseUDisk(); } catch (OperationCanceledException) { /* 忽略 */ } finally { try { await keepAliveTask; // 等待保活任务结束 } catch (OperationCanceledException) { /* 忽略 */ } } // 同步完成后执行 devcon remove+rescan(应急方案):把设备从 U 盘模式"软件拔插"恢复为 HID 模式, // 清理 Win7 残留的设备状态,避免下次切换再次 Code 10。失败静默降级,不影响返回结果。 try { bool restoreOk = await UsbDevconHelper.RemoveAndRescanAsync(_vendorId, _productId); OnLog(restoreOk ? "✅ 同步完成,devcon remove+rescan 已执行,设备已恢复为 HID 模式" : "⚠️ 同步完成但 devcon remove+rescan 未执行(需要管理员权限,且 devcon.exe 需随程序分发到运行目录)"); } catch (Exception ex) { _logger?.LogWarning(ex, "同步完成后 devcon remove+rescan 执行失败"); } } } #region 普通复制方法 //private async Task CopyFileWithCancellation(string src, string dest, CancellationToken ct) //{ // const int bufferSize = 64 * 1024; // 64KB // var buffer = new byte[bufferSize]; // Directory.CreateDirectory(Path.GetDirectoryName(dest)!); // using var sourceStream = File.OpenRead(src); // using var destStream = File.Create(dest); // int bytesRead; // while ((bytesRead = await sourceStream.ReadAsync(buffer, 0, buffer.Length, ct)) > 0) // { // await destStream.WriteAsync(buffer, 0, bytesRead, ct); // // 可选:定期检查取消(ReadAsync 已支持) // } //} //private async Task CopyDirectoryRecursive(string src, string dest, CancellationToken ct) //{ // Directory.CreateDirectory(dest); // foreach (var file in Directory.GetFiles(src)) // { // ct.ThrowIfCancellationRequested(); // 关键:检查取消 // var destFile = Path.Combine(dest, Path.GetFileName(file)); // await CopyFileWithCancellation(file, destFile, ct); // } // foreach (var dir in Directory.GetDirectories(src)) // { // ct.ThrowIfCancellationRequested(); // await CopyDirectoryRecursive(dir, Path.Combine(dest, Path.GetFileName(dir)), ct); // } //} #endregion #region 带进度的复制方法 private async Task CopyFileWithProgressAsync( string src, string dest, CopyProgressContext context, CancellationToken ct) { var throttle = UsbCopyGuard.GetCopyThrottleSettings(); int bufferSize = throttle.BufferSize; try { UsbCopyGuard.ProbeCopyTargetHealth(dest); } catch (IOException ex) { _logger?.LogError("目录损坏或无法访问:{Dest}, {Message}", dest, ex.Message); throw; } if (File.Exists(dest)) { FileInfo fi = new FileInfo(dest); // 长度读取失败(损坏条目)按损坏文件处理,走下面的清理逻辑 long existingLength; try { existingLength = fi.Length; } catch { existingLength = 0; } // 正常文件 → 直接跳过 if (existingLength > 0) { context.ReportCopied(existingLength); //_logger?.LogInformation("文件已存在,跳过:{Dest}", dest); return; } // =============== 0KB/损坏文件:删除 → 重命名 → 强制覆盖 =============== if (UsbCopyGuard.TryClearCorruptedTarget(_logger, dest, out var clearDetail)) { // 给设备一点点刷新时间(非常短,不影响速度) await Task.Delay(20, ct); } else { _logger?.LogError("损坏文件清理失败,跳过:{Dest}, 错误:{Detail}", dest, clearDetail); return; } } byte[] buffer = new byte[bufferSize]; Directory.CreateDirectory(Path.GetDirectoryName(dest)!); using var sourceStream = new FileStream(src, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, true); using var destStream = new FileStream(dest, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize, true); int bytesRead; int chunkIndex = 0; try { while ((bytesRead = await sourceStream.ReadAsync(buffer, 0, buffer.Length, ct)) > 0) { chunkIndex++; ct.ThrowIfCancellationRequested(); UsbCopyGuard.ThrowIfCopyTargetUnavailable(_logger, State, Path.GetPathRoot(dest), dest, chunkIndex, context.CopiedBytes); await UsbCopyGuard.WriteChunkWithTimeoutAsync(destStream, buffer, bytesRead, ct, dest, chunkIndex, context.CopiedBytes); context.ReportCopied(bytesRead); // ====================== 加速关键:5ms 超短延迟 ====================== await Task.Delay(throttle.ChunkDelayMs, ct); } // 整个文件写完只刷 1 次 await destStream.FlushAsync(CancellationToken.None); } finally { // 文件间隔 50ms 防掉盘 //await Task.Delay(throttle.FileDelayMs, ct); } } /// /// 检测目录健康状态,确保目标路径可访问且没有损坏。 /// /// /// public static void ProbeCopyTargetHealth(string destinationPath) { string? dir = Path.GetDirectoryName(destinationPath); if (string.IsNullOrEmpty(dir)) return; try { // 仅检查目录是否存在,最轻量操作 bool exists = Directory.Exists(dir); if (!exists) Directory.CreateDirectory(dir); } catch { // 出错代表目录损坏 throw new IOException("目标目录已损坏,请修复U盘后重试"); } } private async Task CopyDirectoryWithProgressAsync( string src, string dest, IProgress progress, CancellationToken ct) { long totalBytes = CalculateTotalSize(src); var context = new CopyProgressContext(totalBytes, progress); int skipped = await CopyDirectoryRecursiveInternal(src, dest, context, ct); // 确保最终进度为 100% progress?.Report(100.0); return skipped; } private async Task CopyDirectoryRecursiveInternal( string src, string dest, CopyProgressContext context, CancellationToken ct) { int skipped = 0; // 目标目录本身损坏/不可创建 → 跳过整个子目录,不影响其他文件 try { Directory.CreateDirectory(dest); } catch (Exception ex) { _logger?.LogError("目标目录损坏或无法创建,跳过该目录:{Dest}, 错误:{Msg}", dest, ex.Message); return 1; } string[] files; try { files = Directory.GetFiles(src); } catch (Exception ex) { _logger?.LogError("源目录枚举失败,跳过该目录:{Src}, 错误:{Msg}", src, ex.Message); return 1; } foreach (var file in files) { ct.ThrowIfCancellationRequested(); var destFile = Path.Combine(dest, Path.GetFileName(file)); try { await CopyFileWithProgressAsync(file, destFile, context, ct); } catch (OperationCanceledException) { throw; } catch (Exception ex) { var category = UsbCopyGuard.ClassifyCopyException(ex); // 设备级问题(断联/写入超时):重试无意义,中断整个同步 if (category == UsbCopyGuard.CopyFailureCategory.DeviceDisconnected || category == UsbCopyGuard.CopyFailureCategory.WriteTimeout) { throw; } _logger?.LogWarning("文件复制失败,跳过继续:{Src} → {Dest}, 类别:{Label}, 错误:{Msg}", file, destFile, UsbCopyGuard.GetFailureLabel(category), ex.Message); skipped++; } } string[] dirs; try { dirs = Directory.GetDirectories(src); } catch (Exception ex) { _logger?.LogError("源子目录枚举失败,跳过该目录:{Src}, 错误:{Msg}", src, ex.Message); return skipped + 1; } foreach (var dir in dirs) { ct.ThrowIfCancellationRequested(); var destSubDir = Path.Combine(dest, Path.GetFileName(dir)); skipped += await CopyDirectoryRecursiveInternal(dir, destSubDir, context, ct); } return skipped; } #endregion /// /// 递归计算目录中所有文件的总字节数。 /// private long CalculateTotalSize(string path) { long total = 0; try { // 文件 if (File.Exists(path)) return new FileInfo(path).Length; // 目录 foreach (var file in Directory.GetFiles(path, "*", SearchOption.AllDirectories)) { total += new FileInfo(file).Length; } } catch (Exception ex) { // 可选:记录日志 System.Diagnostics.Debug.WriteLine($"计算大小失败: {ex.Message}"); } return total; } #endregion #region 打开/关闭Hid MSC U盘 private async Task OpenUDisk() { await SendAsync(K3ToolUSBProtocol.OpentheUdiskData); } private async Task CloseUDisk() { await SendAsync(K3ToolUSBProtocol.ClosetheUdiskData); _logger?.LogWarning($"{DateTime.Now}关闭U盘指令"); } public async Task OpenUDiskAndDetectDriveAsync(int maxWaitSeconds = 50, int pollIntervalMs = 500, CancellationToken cancellationToken = default) { // 1. 发送打开U盘指令 await OpenUDisk(); // 这个方法负责发 HID 指令 // 2. 设备固件收到指令后会重新枚举 USB(HID 模式 → U 盘模式)。 // Win7 的 USB 栈对热切换支持差(典型表现为 Code 10),模式切换与重新枚举需要时间, // 先固定等待 2 秒让 USB 栈完成切换,再开始轮询,避免过早轮询全部落空。 try { await Task.Delay(2000, cancellationToken); } catch (OperationCanceledException) { throw; } // 3. 轮询检测盘符,直到出现或超时 var stopwatch = Stopwatch.StartNew(); var lastProgressLogUtc = DateTime.UtcNow; bool removeRescanAttempted = false; while (stopwatch.Elapsed.TotalSeconds < maxWaitSeconds) { cancellationToken.ThrowIfCancellationRequested(); string drive = await FindAssociatedUsbDisk(_vendorId, _productId); if (!string.IsNullOrEmpty(drive)) { // 可选:再验证该盘是否可写(防“假挂载”) try { var testFile = Path.Combine(drive, ".k3tool_test"); File.WriteAllText(testFile, "ok"); File.Delete(testFile); return drive; // 成功 } catch { // 盘符存在但不可写(可能还在初始化),继续等待 } } // 每 5 秒输出一次等待进度,便于用户判断设备是否仍在切换中 if (DateTime.UtcNow - lastProgressLogUtc >= TimeSpan.FromSeconds(5)) { lastProgressLogUtc = DateTime.UtcNow; OnLog($"⏳ 正在等待 U 盘出现(已等待 {stopwatch.Elapsed.TotalSeconds:F0}s / {maxWaitSeconds}s)..." + "若长时间无盘符,请尝试拔插设备或重启设备"); } // devcon 兜底(Win7 Code 10 场景):10 秒仍无盘符时直接执行 // remove + rescan(删除设备实例后重新扫描,等价于软件拔插,最彻底)。 // devcon.exe 未分发或非管理员时 UsbDevconHelper 返回 false,静默降级不影响原流程。 if (!removeRescanAttempted && stopwatch.Elapsed.TotalSeconds >= 10) { removeRescanAttempted = true; OnLog("🔄 尚未检测到 U 盘,直接执行 devcon remove + rescan(软件拔插)..."); bool removeOk = await UsbDevconHelper.RemoveAndRescanAsync(_vendorId, _productId); OnLog(removeOk ? "✅ devcon remove+rescan 已完成,等待设备重新枚举..." : "⚠️ devcon remove+rescan 未执行(需要管理员权限,且 devcon.exe 需随程序分发到运行目录)"); } await Task.Delay(pollIntervalMs, cancellationToken); } throw new TimeoutException( $"在 {maxWaitSeconds} 秒内未检测到 U 盘设备(VID={_vendorId}, PID={_productId})。" + "若设备管理器中该设备显示黄色感叹号(Code 10),请拔插设备或重启设备后重试。"); } public async Task CloseUDiskSafeDriveAsync(string targetDrive="") { //无论成功与否,都尝试关闭U盘并发送关闭指令 //await WindowsVolumeEjector.EjectVolumeAsync(targetDrive); // 按固定时序执行:发指令 → 关闭 HID 句柄 → 等待切换 → 重试连接。 // 注:发送"关闭U盘"指令依赖 HID 流,故先发指令、再断开句柄(与物理拔插同理)。 // Step 1: 发送"关闭U盘"指令给 HID await CloseUDisk(); // Step 2: 关闭所有 HID 句柄(释放设备流,让 Windows 能干净地重新枚举) try { await DisconnectAsync(); } catch (Exception ex) { _logger?.LogWarning(ex, "关闭U盘后断开 HID 失败"); } // Step 3: 等待固件完成切换 + Win7 反应 await Task.Delay(600); // Step 4: devcon remove+rescan 应急清理(Win7 残留幽灵节点,等价于软件拔插) try { bool ok = await UsbDevconHelper.RemoveAndRescanAsync(_vendorId, _productId); OnLog(ok ? "✅ devcon remove+rescan 已执行,等待设备重新枚举..." : "⚠️ devcon remove+rescan 未执行(需要管理员权限,且 devcon.exe 需随程序分发到运行目录)"); } catch (Exception ex) { _logger?.LogWarning(ex, "关闭U盘后 devcon remove+rescan 执行失败"); } // Step 5: 重试打开 HID 设备(等待就绪 + 重连) for (int i = 0; i < 10; i++) { await Task.Delay(500); try { bool deviceExists = DeviceList.Local.GetHidDevices(_vendorId, _productId).Any(); if (deviceExists && IsConnected) { OnLog("✅ 设备已恢复为 HID 模式并就绪"); return; } if (deviceExists && !IsConnected) { OnLog("🔌 设备已出现,尝试重新连接..."); await TryReconnectAsync(); } } catch (Exception ex) { _logger?.LogWarning(ex, "重试连接 HID 设备异常"); } } OnLog("⚠️ 等待设备就绪超时,将依赖自动重连机制"); } #endregion #region Hid设备保活 private async Task KeepDeviceAliveAsync(CancellationToken cancellationToken) { try { while (!cancellationToken.IsCancellationRequested) { try { //if (_lastHidResponseUtc != DateTime.MinValue) //{ // var silenceDuration = DateTime.UtcNow - _lastHidResponseUtc; // if (silenceDuration.TotalMilliseconds >= HID_RESPONSE_WARNING_MS) // { // var warning = $"⚠️ HID 已 {silenceDuration.TotalSeconds:F0} 秒未收到设备响应,设备可能忙碌或即将断联"; // _logger.LogWarning(warning); // OnLog(warning, true); // } //} // 发送连接心跳命令 await SendAsync(K3ToolUSBProtocol.ConnectData); _consecutiveKeepAliveFailures = 0; } catch (Exception ex) { //_consecutiveKeepAliveFailures++; //if (UsbCopyGuard.ShouldEmitRepeatedWarning(_consecutiveKeepAliveFailures)) //{ // var warning = $"⚠️ 保活命令发送失败,第{_consecutiveKeepAliveFailures}次。{ex.Message}"; // _logger.LogWarning(ex, warning); // OnLog(warning, true); //} } // 等待 50 秒,或被取消 await Task.Delay(KEEP_ALIVE_INTERVAL_MS, cancellationToken); } } catch (OperationCanceledException) { // 正常取消,不处理 } } public async Task KeepDeviceAliveAsync() { CancellationToken cancellationToken = default; // 启动保活任务(每50秒发送一次 ConnectData) using var keepAliveCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); var keepAliveTask = KeepDeviceAliveAsync(keepAliveCts.Token); } #endregion #region 文件复制进度上下文 private class CopyProgressContext { public long CopiedBytes { get; set; } public long TotalBytes { get; } public IProgress Progress { get; } public CopyProgressContext(long totalBytes, IProgress progress) { TotalBytes = totalBytes; Progress = progress; } /// /// 更新已复制字节数并上报进度 /// public void ReportCopied(long additionalBytes) { CopiedBytes += additionalBytes; if (TotalBytes > 0) { double percentage = Math.Min(100.0, (double)CopiedBytes / TotalBytes * 100 * 0.79) + 20; Progress?.Report(percentage); } } } public static class UsbCopyGuard { private const int WriteTimeoutMs = 30000; public readonly record struct CopyThrottleSettings( int BufferSize, int ChunkDelayMs, int FileDelayMs, int FlushIntervalBytes); public enum CopyFailureCategory { Unknown, DeviceDisconnected, WriteTimeout, MscFileSystemCorruption, IoFailure } public static void ThrowIfCopyTargetUnavailable( ILogger logger, TransferState state, string? driveRoot, string destinationPath, int chunkIndex, long copiedBytes) { if (state.IsConnected && state.IsDataConnected) return; var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"); var driveText = string.IsNullOrWhiteSpace(driveRoot) ? "未知盘符" : driveRoot; var message = $"[{timestamp}] 设备断联,停止复制。目标盘符:{driveText},文件:{destinationPath},chunk:{chunkIndex},已复制:{copiedBytes}字节"; logger.LogWarning(message); throw new IOException(message); } public static void ProbeCopyTargetHealth(string destinationPath) { var directoryPath = Path.GetDirectoryName(destinationPath); if (string.IsNullOrWhiteSpace(directoryPath)) { throw new IOException($"无法确定目标目录。文件:{destinationPath}"); } var directoryInfo = new DirectoryInfo(directoryPath); _ = directoryInfo.Exists; _ = directoryInfo.GetFiles().Length; } /// /// 清理MSC上损坏的目标文件,按“删除 → 重命名 → 强制覆盖”逐级降级。 /// 返回 true 表示目标已清理(或原本不存在),可以继续复制;false 表示清理失败,应跳过该文件。 /// public static bool TryClearCorruptedTarget(ILogger logger, string destPath, out string detail) { detail = string.Empty; // 1. 直接删除(文件不存在时 File.Delete 不会抛异常) try { if (File.Exists(destPath)) { File.Delete(destPath); return true; } return true; // 目标不存在,无需清理 } catch (Exception ex) { logger?.LogWarning("损坏目标文件直接删除失败:{Dest},错误:{Msg}", destPath, ex.Message); } // 2. 重命名 → 再删除(FAT 目录项重写通常比直接删除更宽容) string newName = destPath + ".corrupt_" + Guid.NewGuid().ToString("N").Substring(0, 8); try { File.Move(destPath, newName); logger?.LogWarning("损坏目标文件已重命名:{Dest} → {NewName}", destPath, newName); try { File.Delete(newName); } catch { } return true; } catch (Exception ex) { logger?.LogWarning("损坏目标文件重命名失败:{Dest} → {NewName},错误:{Msg}", destPath, newName, ex.Message); } // 3. 强制覆盖:FileMode.Create 重建文件(可能跳过损坏的目录项) try { using (var fs = new FileStream(destPath, FileMode.Create, FileAccess.Write, FileShare.None, 4096)) { fs.SetLength(0); } return true; } catch (Exception ex) { detail = ex.Message; logger?.LogWarning("损坏目标文件强制覆盖失败:{Dest},错误:{Msg}", destPath, ex.Message); return false; } } public static CopyFailureCategory ClassifyCopyException(Exception ex) { if (ex is IOException ioEx) { var message = ioEx.Message ?? string.Empty; if (message.IndexOf("设备断联", StringComparison.OrdinalIgnoreCase) >= 0) { return CopyFailureCategory.DeviceDisconnected; } if (message.IndexOf("设备写入超时", StringComparison.OrdinalIgnoreCase) >= 0) { return CopyFailureCategory.WriteTimeout; } if (message.IndexOf("文件或目录损坏且无法读取", StringComparison.OrdinalIgnoreCase) >= 0) { return CopyFailureCategory.MscFileSystemCorruption; } return CopyFailureCategory.IoFailure; } return CopyFailureCategory.Unknown; } public static string GetFailureLabel(CopyFailureCategory category) { return category switch { CopyFailureCategory.DeviceDisconnected => "HID断联", CopyFailureCategory.WriteTimeout => "写入超时", CopyFailureCategory.MscFileSystemCorruption => "MSC文件系统损坏", CopyFailureCategory.IoFailure => "MSC写入异常", _ => "未知异常" }; } public static CopyThrottleSettings GetCopyThrottleSettings() { return new CopyThrottleSettings( COPY_BUFFER_SIZE, COPY_CHUNK_DELAY_MS, COPY_FILE_DELAY_MS, FILE_FLUSH_INTERVAL_BYTES); } public static bool ShouldEmitRepeatedWarning(int occurrence) { return occurrence <= 1 || occurrence % 3 == 0; } public static async Task WriteChunkWithTimeoutAsync( FileStream destStream, byte[] buffer, int bytesToWrite, CancellationToken cancellationToken, string destinationPath, int chunkIndex, long copiedBytes) { var writeTask = destStream.WriteAsync(buffer, 0, bytesToWrite, cancellationToken); var delayTask = Task.Delay(WriteTimeoutMs); var completedTask = await Task.WhenAny(writeTask, delayTask); if (completedTask == delayTask) { var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"); var message = $"[{timestamp}] 设备写入超时,停止复制。文件:{destinationPath},chunk:{chunkIndex},已复制:{copiedBytes}字节"; throw new IOException(message); } // 确保写入任务完成,避免数据丢失 await writeTask; } } #endregion #region 压缩包操作 public async Task CopyDeltaDirectoryToUdiskAsync( string localRootDir, IProgress progress, CancellationToken ct) { // 内部打开U盘 await OpenUDiskAndDetectDriveAsync(); string? targetDrive = await FindAssociatedUsbDisk(_vendorId, _productId); if (string.IsNullOrEmpty(targetDrive)) throw new InvalidOperationException("未检测到U盘设备"); // 保活机制(防止断开) using var keepAliveCts = CancellationTokenSource.CreateLinkedTokenSource(ct); var keepAliveTask = KeepDeviceAliveAsync(keepAliveCts.Token); try { string destRoot = Path.Combine(targetDrive, usbdiskDir); Directory.CreateDirectory(destRoot); // ============================== // 差异对比 // ============================== var localFiles = ScanLocalFiles(localRootDir); var diskFiles = await ScanUdiskFilesAsync(ct); var changedFiles = GetChangedFiles(localFiles, diskFiles); if (changedFiles.Count == 0) { progress.Report(100); return; } // 计算总大小(精准进度) long totalBytes = 0; foreach (var rel in changedFiles) { string file = Path.Combine(localRootDir, rel); if (File.Exists(file)) totalBytes += new FileInfo(file).Length; } // ============================== // 只复制差异文件 → 直写U盘(超快) // ============================== long processed = 0; int skippedFiles = 0; foreach (var relPath in changedFiles) { ct.ThrowIfCancellationRequested(); string srcFile = Path.Combine(localRootDir, relPath); string destFile = Path.Combine(destRoot, relPath); var (ok, copied) = await CopyDeltaFileWithRecoveryAsync(srcFile, destFile, ct); if (!ok) skippedFiles++; processed += copied; progress.Report(totalBytes > 0 ? (double)processed / totalBytes * 100 : 100); } progress.Report(100); if (skippedFiles > 0) { OnLog($"⚠️ 云数据同步完成,但 {skippedFiles} 个文件因损坏被跳过,建议对U盘执行 chkdsk 修复"); } } finally { keepAliveCts.Cancel(); try { await keepAliveTask; } catch { } } } /// /// 复制单个差异文件;若目标文件损坏(删除/重命名/覆盖失败等)则清理后重试一次。 /// 返回 (是否成功, 复制的字节数)。设备断联/写入超时等设备级异常会原样抛出,由上层中断整个同步。 /// private async Task<(bool ok, long copied)> CopyDeltaFileWithRecoveryAsync( string srcFile, string destFile, CancellationToken ct) { try { long copied = await CopyDeltaFileCoreAsync(srcFile, destFile, ct); return (true, copied); } catch (OperationCanceledException) { throw; } catch (Exception ex) { var category = UsbCopyGuard.ClassifyCopyException(ex); if (category == UsbCopyGuard.CopyFailureCategory.DeviceDisconnected || category == UsbCopyGuard.CopyFailureCategory.WriteTimeout) { throw; // 设备级问题:重试无意义,中断整个同步 } _logger?.LogWarning("差异文件复制失败({Label}),尝试清理损坏目标后重试:{Dest},错误:{Msg}", UsbCopyGuard.GetFailureLabel(category), destFile, ex.Message); if (UsbCopyGuard.TryClearCorruptedTarget(_logger, destFile, out var clearDetail)) { try { long copied = await CopyDeltaFileCoreAsync(srcFile, destFile, ct); return (true, copied); } catch (OperationCanceledException) { throw; } catch (Exception retryEx) { var retryCategory = UsbCopyGuard.ClassifyCopyException(retryEx); if (retryCategory == UsbCopyGuard.CopyFailureCategory.DeviceDisconnected || retryCategory == UsbCopyGuard.CopyFailureCategory.WriteTimeout) { throw; } _logger?.LogWarning("清理后重试仍失败,跳过该文件:{Dest},错误:{Msg}", destFile, retryEx.Message); return (false, 0); } } _logger?.LogError("损坏目标文件无法清理,跳过:{Dest},错误:{Detail}", destFile, clearDetail); return (false, 0); } } /// /// 差异文件直写U盘核心逻辑(不含容错)。 /// private async Task CopyDeltaFileCoreAsync(string srcFile, string destFile, CancellationToken ct) { string destDir = Path.GetDirectoryName(destFile)!; Directory.CreateDirectory(destDir); using var fsRead = new FileStream(srcFile, FileMode.Open, FileAccess.Read, FileShare.Read, 32768, true); using var fsWrite = new FileStream(destFile, FileMode.Create, FileAccess.Write, FileShare.None, 32768, true); byte[] buffer = new byte[32768]; long copied = 0; int read; while ((read = await fsRead.ReadAsync(buffer, 0, buffer.Length, ct)) > 0) { await fsWrite.WriteAsync(buffer, 0, read, ct); copied += read; } return copied; } // 扫描U盘中所有文件 (相对路径 + 文件信息) public async Task> ScanUdiskFilesAsync(CancellationToken ct) { var dict = new Dictionary(StringComparer.OrdinalIgnoreCase); string? drive = await FindAssociatedUsbDisk(_vendorId, _productId); if (string.IsNullOrEmpty(drive)) return dict; string root = Path.Combine(drive, usbdiskDir); if (!Directory.Exists(root)) return dict; await Task.Run(() => { // 逐目录容错递归:单个目录损坏时跳过该目录,不影响其余文件 ScanUdiskDirectoryRecursive(root, root, dict, ct); }, ct); return dict; } // 逐目录容错递归扫描:某个目录损坏/无法枚举时跳过该目录,保证整体扫描不中断 private void ScanUdiskDirectoryRecursive( string dir, string root, Dictionary dict, CancellationToken ct) { ct.ThrowIfCancellationRequested(); string[] files; try { files = Directory.GetFiles(dir); } catch (Exception ex) { _logger?.LogWarning("U盘目录枚举失败,跳过该目录:{Dir},错误:{Msg}", dir, ex.Message); return; } foreach (var file in files) { ct.ThrowIfCancellationRequested(); try { var fi = new FileInfo(file); string rel = GetRelativePath(root, file); if (rel == ".DS_Store") continue; dict[rel] = fi; } catch (Exception ex) { _logger?.LogWarning("U盘文件信息读取失败,跳过:{File},错误:{Msg}", file, ex.Message); } } string[] subDirs; try { subDirs = Directory.GetDirectories(dir); } catch (Exception ex) { _logger?.LogWarning("U盘子目录枚举失败,跳过该目录:{Dir},错误:{Msg}", dir, ex.Message); return; } foreach (var subDir in subDirs) { ScanUdiskDirectoryRecursive(subDir, root, dict, ct); } } // 扫描本地文件 public Dictionary ScanLocalFiles(string localRootDir) { var dict = new Dictionary(StringComparer.OrdinalIgnoreCase); if (!Directory.Exists(localRootDir)) return dict; foreach (var file in Directory.EnumerateFiles(localRootDir, "*.*", SearchOption.AllDirectories)) { var fi = new FileInfo(file); string rel = GetRelativePath(localRootDir, file); if (rel == ".DS_Store") continue; dict[rel] = fi; } return dict; } // 对比 → 得到需要更新的文件列表 public List GetChangedFiles( Dictionary localFiles, Dictionary udiskFiles) { var changed = new List(); foreach (var kv in localFiles) { string rel = kv.Key; var local = kv.Value; if (!udiskFiles.TryGetValue(rel, out var disk)) { changed.Add(rel); // 新增 continue; } if (local.Length != disk.Length) { changed.Add(rel); // 大小不一样 continue; } //if (local.LastWriteTimeUtc > disk.LastWriteTimeUtc.AddSeconds(5)) //{ // changed.Add(rel); // 本地更新 //} } return changed; } public async Task<(string zipPath, int count)> CreateDeltaZipAsync( string localRootDir, List changedRelativePaths, CancellationToken ct) { string zipPath = Path.Combine(Path.GetTempPath(), $"delta_{Guid.NewGuid():N}.zip"); await Task.Run(() => { using var zip = ZipFile.Open(zipPath, ZipArchiveMode.Create); foreach (var rel in changedRelativePaths) { ct.ThrowIfCancellationRequested(); string src = Path.Combine(localRootDir, rel); if (!File.Exists(src)) continue; zip.CreateEntryFromFile(src, rel, CompressionLevel.Fastest); } }, ct); return (zipPath, changedRelativePaths.Count); } #endregion /// /// 从语言字典中获取值,兼容 .NET Framework 4.8(替代 Dictionary.GetValueOrDefault) /// private static string GetLanguageValueOrDefault(string key) { return AiKLanguage.LanguageKey.TryGetValue(key, out var value) ? value : null; } /// /// 计算相对路径,兼容 .NET Framework 4.8(替代 Path.GetRelativePath) /// private static string GetRelativePath(string relativeTo, string path) { if (!relativeTo.EndsWith(Path.DirectorySeparatorChar.ToString())) relativeTo += Path.DirectorySeparatorChar; var relUri = new Uri(relativeTo).MakeRelativeUri(new Uri(path)); var relPath = Uri.UnescapeDataString(relUri.ToString()); return relPath.Replace('/', Path.DirectorySeparatorChar); } } }