兼容win7版应用
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2984 lines
115 KiB

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<double>? _currentProgress;
private double _currentBigPhaseProgress;
private int _currentDataPacketIndex = 0;
private List<byte> resNotity = new List<byte>();
// 自动重连相关字段
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<byte>();
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<BigTransferPhase, TaskCompletionSource<bool>> _phaseTcsDict = new Dictionary<BigTransferPhase, TaskCompletionSource<bool>>();
private BigTransferPhase _currentBigPhase = BigTransferPhase.None; // 当前大阶段
private List<BigPhaseTask> _bigPhaseTasks = new(); // 大阶段任务队列
//第一次连接
private TaskCompletionSource<bool>? _connectTcs;
// (用于传输进度)
private TaskCompletionSource<bool>? _transferTcs;
// 全局大阶段任务完成源
private TaskCompletionSource<bool>? _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<BigPhaseChangedEventArgs>? BigPhaseChanged;
// 新增:所有大阶段完成事件
public event EventHandler? AllBigPhasesCompleted;
public event EventHandler<HidResponseReceivedEventArgs>? ResponseReceived;
public event EventHandler<HidLogMessageEventArgs>? LogMessage;
// 自动重连相关事件
public event EventHandler? DeviceConnected;
public event EventHandler? DeviceDisconnected;
public event EventHandler<HidReconnectEventArgs>? ReconnectAttempt;
public event EventHandler<ConnectionStateChangedEventArgs>? ConnectionStateChanged;
#endregion
private readonly ILogger<HidCommunicationService> _logger;
public HidCommunicationService(ILogger<HidCommunicationService> 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占用稍高)
/// <summary>
/// 轮询模式
/// </summary>
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 热拔插监听机制(设备只响应变更,每次打开页面需要重新执行连接方法,单例模式不适合)
/// <summary>
/// 事件驱动模式
/// </summary>
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 新增的自动重连控制方法
/// <summary>
/// 启用自动重连
/// </summary>
public void EnableAutoReconnect()
{
_autoReconnectEnabled = true;
_reconnectAttempts = 0;
OnLog("✅ 自动重连已启用");
}
/// <summary>
/// 禁用自动重连
/// </summary>
public void DisableAutoReconnect()
{
_autoReconnectEnabled = false;
OnLog("❌ 自动重连已禁用");
}
/// <summary>
/// 立即尝试重连
/// </summary>
public async Task<bool> ReconnectNowAsync()
{
_reconnectAttempts = 0; // 重置计数
_hasSentConnectRequest = false;
return await ConnectAsync(_vendorId, _productId);
}
/// <summary>
/// 获取重连状态
/// </summary>
public ReconnectStatus GetReconnectStatus()
{
return new ReconnectStatus
{
IsAutoReconnectEnabled = _autoReconnectEnabled,
ReconnectAttempts = _reconnectAttempts,
MaxReconnectAttempts = MAX_RECONNECT_ATTEMPTS,
IsMonitoring = _isMonitoring
};
}
#endregion
#region 基础发送及响应
public async Task<bool> 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<bool>();
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;
}
}
/// <summary>
/// 轮询等待复合设备(U盘+HID)完全加载完成
/// </summary>
/// <param name="vendorId">厂商ID</param>
/// <param name="productId">产品ID</param>
/// <param name="maxWaitMs">最大等待时间(毫秒)</param>
/// <param name="pollIntervalMs">轮询间隔(毫秒)</param>
/// <returns>true 表示设备已就绪;false 表示超时</returns>
public async Task<bool> 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<byte> 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;
}
/// <summary>
/// 处理特殊帧(协议待定)
/// </summary>
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 进度条拓展(烧录文件)
/// <summary>
/// 用于测试(本地路径文件),线上采用读取文件方式
/// </summary>
/// <param name="filePath"></param>
/// <param name="progress"></param>
/// <param name="ct"></param>
/// <returns></returns>
[Obsolete]
public async Task<(bool, string)> SendFileAsync(string filePath, IProgress<double> 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<bool>();
// 注册取消监听
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<double> 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<bool>();
// 注册取消监听
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 阶段性烧录(连接/生成/写入)
/// <summary>
/// 固定单命令(如连接/生成 等命令)
/// </summary>
/// <param name="fileName"></param>
/// <param name="filebin"></param>
/// <param name="progress"></param>
/// <param name="ct"></param>
/// <returns></returns>
private async Task<(bool, string)> SendSingleCommandAsync(string fileName, byte[] filebin, IProgress<double> progress, CancellationToken ct)
{
if (!IsConnected)
{
return (false, "设备未找到");
}
try
{
// ========== 关键:为当前大阶段创建独立TCS ==========
TaskCompletionSource<bool> currentPhaseTcs;
lock (_phaseTcsLock)
{
// 确保每个阶段只有一个TCS
if (!_phaseTcsDict.TryGetValue(_currentBigPhase, out currentPhaseTcs) || currentPhaseTcs.Task.IsCompleted)
{
currentPhaseTcs = new TaskCompletionSource<bool>();
_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<double> progress, CancellationToken ct)
{
if (!IsConnected)
{
return (false, "设备未找到");
}
// ========== 关键:为当前大阶段创建独立TCS ==========
TaskCompletionSource<bool> currentPhaseTcs;
lock (_phaseTcsLock)
{
if (!_phaseTcsDict.TryGetValue(_currentBigPhase, out currentPhaseTcs) || currentPhaseTcs.Task.IsCompleted)
{
currentPhaseTcs = new TaskCompletionSource<bool>();
_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<double> progress, CancellationToken ct)
{
_currentProgress?.Report(0.0);
if (!IsConnected)
{
return (false, "设备未找到");
}
try
{
if (!IsValidHasFile(filebin))
{
OnLog("文件不存在", true);
return (false, $"烧录的文件不存在");
}
//========== 关键:初始化全局大阶段任务完成源 ==========
_allBigPhasesTcs = new TaskCompletionSource<bool>();
_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<double> progress, CancellationToken ct)
{
_hidOperationType = hidOperationType;
_currentProgress?.Report(0.0);
if (!IsConnected)
{
return (false, "设备未找到");
}
try
{
if (!IsValidHasFile(filebin))
{
OnLog("文件不存在", true);
return (false, $"烧录的文件不存在");
}
//========== 关键:初始化全局大阶段任务完成源 ==========
_allBigPhasesTcs = new TaskCompletionSource<bool>();
_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<string> 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<double> 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);
}
}
/// <summary>
/// 检测目录健康状态,确保目标路径可访问且没有损坏。
/// </summary>
/// <param name="destinationPath"></param>
/// <exception cref="IOException"></exception>
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<int> CopyDirectoryWithProgressAsync(
string src,
string dest,
IProgress<double> 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<int> 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
/// <summary>
/// 递归计算目录中所有文件的总字节数。
/// </summary>
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<string> 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<double> Progress { get; }
public CopyProgressContext(long totalBytes, IProgress<double> progress)
{
TotalBytes = totalBytes;
Progress = progress;
}
/// <summary>
/// 更新已复制字节数并上报进度
/// </summary>
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;
}
/// <summary>
/// 清理MSC上损坏的目标文件,按“删除 → 重命名 → 强制覆盖”逐级降级。
/// 返回 true 表示目标已清理(或原本不存在),可以继续复制;false 表示清理失败,应跳过该文件。
/// </summary>
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<double> 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 { }
}
}
/// <summary>
/// 复制单个差异文件;若目标文件损坏(删除/重命名/覆盖失败等)则清理后重试一次。
/// 返回 (是否成功, 复制的字节数)。设备断联/写入超时等设备级异常会原样抛出,由上层中断整个同步。
/// </summary>
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);
}
}
/// <summary>
/// 差异文件直写U盘核心逻辑(不含容错)。
/// </summary>
private async Task<long> 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<Dictionary<string, FileInfo>> ScanUdiskFilesAsync(CancellationToken ct)
{
var dict = new Dictionary<string, FileInfo>(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<string, FileInfo> 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<string, FileInfo> ScanLocalFiles(string localRootDir)
{
var dict = new Dictionary<string, FileInfo>(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<string> GetChangedFiles(
Dictionary<string, FileInfo> localFiles,
Dictionary<string, FileInfo> udiskFiles)
{
var changed = new List<string>();
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<string> 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
/// <summary>
/// 从语言字典中获取值,兼容 .NET Framework 4.8(替代 Dictionary.GetValueOrDefault)
/// </summary>
private static string GetLanguageValueOrDefault(string key)
{
return AiKLanguage.LanguageKey.TryGetValue(key, out var value) ? value : null;
}
/// <summary>
/// 计算相对路径,兼容 .NET Framework 4.8(替代 Path.GetRelativePath)
/// </summary>
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);
}
}
}