fix: 修复进度条的问题
This commit is contained in:
		@@ -180,8 +180,7 @@ try
 | 
				
			|||||||
    builder.Services.AddHostedService(provider => provider.GetRequiredService<HttpHdmiVideoStreamService>());
 | 
					    builder.Services.AddHostedService(provider => provider.GetRequiredService<HttpHdmiVideoStreamService>());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 添加进度跟踪服务
 | 
					    // 添加进度跟踪服务
 | 
				
			||||||
    builder.Services.AddSingleton<ProgressTrackerService>();
 | 
					    builder.Services.AddSingleton<ProgressTracker>();
 | 
				
			||||||
    builder.Services.AddHostedService(provider => provider.GetRequiredService<ProgressTrackerService>());
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Application Settings
 | 
					    // Application Settings
 | 
				
			||||||
    var app = builder.Build();
 | 
					    var app = builder.Build();
 | 
				
			||||||
@@ -258,6 +257,8 @@ try
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    // Setup Program
 | 
					    // Setup Program
 | 
				
			||||||
    MsgBus.Init();
 | 
					    MsgBus.Init();
 | 
				
			||||||
 | 
					    var progressTracker = app.Services.GetRequiredService<ProgressTracker>();
 | 
				
			||||||
 | 
					    MsgBus.SetProgressTracker(progressTracker);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Generate API Client
 | 
					    // Generate API Client
 | 
				
			||||||
    app.MapGet("GetAPIClientCode", async (HttpContext context) =>
 | 
					    app.MapGet("GetAPIClientCode", async (HttpContext context) =>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,17 +16,12 @@ public class JtagController : ControllerBase
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
 | 
					    private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private readonly ProgressTrackerService _tracker;
 | 
					    private readonly ProgressTracker _tracker = MsgBus.ProgressTracker;
 | 
				
			||||||
    private readonly UserManager _userManager = new();
 | 
					    private readonly UserManager _userManager = new();
 | 
				
			||||||
    private readonly ResourceManager _resourceManager = new();
 | 
					    private readonly ResourceManager _resourceManager = new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private const string BITSTREAM_PATH = "bitstream/Jtag";
 | 
					    private const string BITSTREAM_PATH = "bitstream/Jtag";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public JtagController(ProgressTrackerService tracker)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        _tracker = tracker;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 控制器首页信息
 | 
					    /// 控制器首页信息
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
@@ -188,8 +183,8 @@ public class JtagController : ControllerBase
 | 
				
			|||||||
            logger.Info($"User {username} processing bitstream file of size: {fileBytes.Length} bytes");
 | 
					            logger.Info($"User {username} processing bitstream file of size: {fileBytes.Length} bytes");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // 定义进度跟踪
 | 
					            // 定义进度跟踪
 | 
				
			||||||
            var (taskId, progress) = _tracker.CreateTask(cancelToken);
 | 
					            var taskId = _tracker.CreateTask(1000);
 | 
				
			||||||
            progress.Report(10);
 | 
					            _tracker.AdvanceProgress(taskId, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            _ = Task.Run(async () =>
 | 
					            _ = Task.Run(async () =>
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@@ -210,7 +205,8 @@ public class JtagController : ControllerBase
 | 
				
			|||||||
                        if (!retBuffer.IsSuccessful)
 | 
					                        if (!retBuffer.IsSuccessful)
 | 
				
			||||||
                        {
 | 
					                        {
 | 
				
			||||||
                            logger.Error($"User {username} failed to reverse bytes: {retBuffer.Error}");
 | 
					                            logger.Error($"User {username} failed to reverse bytes: {retBuffer.Error}");
 | 
				
			||||||
                            progress.Error($"User {username} failed to reverse bytes: {retBuffer.Error}");
 | 
					                            _tracker.FailProgress(taskId,
 | 
				
			||||||
 | 
					                                $"User {username} failed to reverse bytes: {retBuffer.Error}");
 | 
				
			||||||
                            return;
 | 
					                            return;
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        revBuffer = retBuffer.Value;
 | 
					                        revBuffer = retBuffer.Value;
 | 
				
			||||||
@@ -228,7 +224,7 @@ public class JtagController : ControllerBase
 | 
				
			|||||||
                    var processedBytes = outputStream.ToArray();
 | 
					                    var processedBytes = outputStream.ToArray();
 | 
				
			||||||
                    logger.Info($"User {username} processed {totalBytesProcessed} bytes for device {address}");
 | 
					                    logger.Info($"User {username} processed {totalBytesProcessed} bytes for device {address}");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    progress.Report(20);
 | 
					                    _tracker.AdvanceProgress(taskId, 20);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    // 下载比特流
 | 
					                    // 下载比特流
 | 
				
			||||||
                    var jtagCtrl = new Peripherals.JtagClient.Jtag(address, port);
 | 
					                    var jtagCtrl = new Peripherals.JtagClient.Jtag(address, port);
 | 
				
			||||||
@@ -237,12 +233,13 @@ public class JtagController : ControllerBase
 | 
				
			|||||||
                    if (ret.IsSuccessful)
 | 
					                    if (ret.IsSuccessful)
 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
                        logger.Info($"User {username} successfully downloaded bitstream '{resource.ResourceName}' to device {address}");
 | 
					                        logger.Info($"User {username} successfully downloaded bitstream '{resource.ResourceName}' to device {address}");
 | 
				
			||||||
                        progress.Finish();
 | 
					                        _tracker.CompleteProgress(taskId);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    else
 | 
					                    else
 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
                        logger.Error($"User {username} failed to download bitstream to device {address}: {ret.Error}");
 | 
					                        logger.Error($"User {username} failed to download bitstream to device {address}: {ret.Error}");
 | 
				
			||||||
                        progress.Error($"User {username} failed to download bitstream to device {address}: {ret.Error}");
 | 
					                        _tracker.FailProgress(taskId,
 | 
				
			||||||
 | 
					                            $"User {username} failed to download bitstream to device {address}: {ret.Error}");
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,17 +1,20 @@
 | 
				
			|||||||
using Microsoft.AspNetCore.Authorization;
 | 
					using Microsoft.AspNetCore.Authorization;
 | 
				
			||||||
using System.Security.Claims;
 | 
					 | 
				
			||||||
using Microsoft.AspNetCore.SignalR;
 | 
					using Microsoft.AspNetCore.SignalR;
 | 
				
			||||||
using Microsoft.AspNetCore.Cors;
 | 
					using Microsoft.AspNetCore.Cors;
 | 
				
			||||||
using TypedSignalR.Client;
 | 
					using TypedSignalR.Client;
 | 
				
			||||||
using Tapper;
 | 
					using Tapper;
 | 
				
			||||||
using server.Services;
 | 
					using server.Services;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#pragma warning disable 1998
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace server.Hubs;
 | 
					namespace server.Hubs;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[Hub]
 | 
					[Hub]
 | 
				
			||||||
public interface IProgressHub
 | 
					public interface IProgressHub
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    Task<bool> Join(string taskId);
 | 
					    Task<bool> Join(string taskId);
 | 
				
			||||||
 | 
					    Task<bool> Leave(string taskId);
 | 
				
			||||||
 | 
					    Task<ProgressInfo?> GetProgress(string taskId);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[Receiver]
 | 
					[Receiver]
 | 
				
			||||||
@@ -23,8 +26,7 @@ public interface IProgressReceiver
 | 
				
			|||||||
[TranspilationSource]
 | 
					[TranspilationSource]
 | 
				
			||||||
public enum ProgressStatus
 | 
					public enum ProgressStatus
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    Pending,
 | 
					    Running,
 | 
				
			||||||
    InProgress,
 | 
					 | 
				
			||||||
    Completed,
 | 
					    Completed,
 | 
				
			||||||
    Canceled,
 | 
					    Canceled,
 | 
				
			||||||
    Failed
 | 
					    Failed
 | 
				
			||||||
@@ -33,10 +35,10 @@ public enum ProgressStatus
 | 
				
			|||||||
[TranspilationSource]
 | 
					[TranspilationSource]
 | 
				
			||||||
public class ProgressInfo
 | 
					public class ProgressInfo
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public virtual string TaskId { get; } = string.Empty;
 | 
					    public required string TaskId { get; set; }
 | 
				
			||||||
    public virtual ProgressStatus Status { get; }
 | 
					    public required ProgressStatus Status { get; set; }
 | 
				
			||||||
    public virtual int ProgressPercent { get; } = 0;
 | 
					    public required int ProgressPercent { get; set; }
 | 
				
			||||||
    public virtual string ErrorMessage { get; } = string.Empty;
 | 
					    public required string ErrorMessage { get; set; }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[Authorize]
 | 
					[Authorize]
 | 
				
			||||||
@@ -44,18 +46,32 @@ public class ProgressInfo
 | 
				
			|||||||
public class ProgressHub : Hub<IProgressReceiver>, IProgressHub
 | 
					public class ProgressHub : Hub<IProgressReceiver>, IProgressHub
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
 | 
					    private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
 | 
				
			||||||
 | 
					    private readonly ProgressTracker _progressTracker = MsgBus.ProgressTracker;
 | 
				
			||||||
    private readonly IHubContext<ProgressHub, IProgressReceiver> _hubContext;
 | 
					 | 
				
			||||||
    private readonly ProgressTrackerService _tracker;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public ProgressHub(IHubContext<ProgressHub, IProgressReceiver> hubContext, ProgressTrackerService tracker)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        _hubContext = hubContext;
 | 
					 | 
				
			||||||
        _tracker = tracker;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public async Task<bool> Join(string taskId)
 | 
					    public async Task<bool> Join(string taskId)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return await Task.Run(() => _tracker.BindTask(taskId, Context.ConnectionId));
 | 
					        await Groups.AddToGroupAsync(Context.ConnectionId, taskId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 发送当前状态(如果存在)
 | 
				
			||||||
 | 
					        var task = _progressTracker.GetTask(taskId);
 | 
				
			||||||
 | 
					        if (task != null)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            await Clients.Caller.OnReceiveProgress(task.Value.ToProgressInfo());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        logger.Info($"Client {Context.ConnectionId} joined task {taskId}");
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public async Task<bool> Leave(string taskId)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        await Groups.RemoveFromGroupAsync(Context.ConnectionId, taskId);
 | 
				
			||||||
 | 
					        logger.Info($"Client {Context.ConnectionId} left task {taskId}");
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public async Task<ProgressInfo?> GetProgress(string taskId)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return _progressTracker.GetTask(taskId)?.ToProgressInfo();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,8 @@
 | 
				
			|||||||
 | 
					using server.Services;
 | 
				
			||||||
/// <summary>
 | 
					/// <summary>
 | 
				
			||||||
/// 多线程通信总线
 | 
					/// 多线程通信总线
 | 
				
			||||||
/// </summary>
 | 
					/// </summary>
 | 
				
			||||||
public static class MsgBus
 | 
					public sealed class MsgBus
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
 | 
					    private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -11,12 +12,39 @@ public static class MsgBus
 | 
				
			|||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    public static UDPServer UDPServer { get { return udpServer; } }
 | 
					    public static UDPServer UDPServer { get { return udpServer; } }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 添加静态ProgressTracker引用
 | 
				
			||||||
 | 
					    private static ProgressTracker? _progressTracker;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// <summary>
 | 
				
			||||||
 | 
					    /// 设置全局ProgressTracker实例
 | 
				
			||||||
 | 
					    /// </summary>
 | 
				
			||||||
 | 
					    public static void SetProgressTracker(ProgressTracker progressTracker)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        _progressTracker = progressTracker;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static ProgressTracker ProgressTracker
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        get
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (_progressTracker == null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                throw new InvalidOperationException("ProgressTracker is not set.");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return _progressTracker;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static bool isRunning = false;
 | 
					    private static bool isRunning = false;
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 获取通信总线运行状态
 | 
					    /// 获取通信总线运行状态
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    public static bool IsRunning { get { return isRunning; } }
 | 
					    public static bool IsRunning { get { return isRunning; } }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private MsgBus() { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    static MsgBus() { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 通信总线初始化
 | 
					    /// 通信总线初始化
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -380,6 +380,7 @@ public class JtagStatusReg
 | 
				
			|||||||
public class Jtag
 | 
					public class Jtag
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
 | 
					    private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
 | 
				
			||||||
 | 
					    private readonly ProgressTracker? _progressTracker;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private const int CLOCK_FREQ = 50; // MHz
 | 
					    private const int CLOCK_FREQ = 50; // MHz
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -392,18 +393,21 @@ public class Jtag
 | 
				
			|||||||
    public readonly string address;
 | 
					    public readonly string address;
 | 
				
			||||||
    private IPEndPoint ep;
 | 
					    private IPEndPoint ep;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// Jtag 构造函数
 | 
					    /// Jtag 构造函数
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    /// <param name="address">目标 IP 地址</param>
 | 
					    /// <param name="address">目标 IP 地址</param>
 | 
				
			||||||
    /// <param name="port">目标 UDP 端口</param>
 | 
					    /// <param name="port">目标 UDP 端口</param>
 | 
				
			||||||
    /// <param name="timeout">超时时间(毫秒)</param>
 | 
					    /// <param name="timeout">超时时间(毫秒)</param>
 | 
				
			||||||
    public Jtag(string address, int port, int timeout = 2000)
 | 
					    /// <param name="progressTracker">进度追踪器</param>
 | 
				
			||||||
 | 
					    public Jtag(string address, int port, int timeout = 2000, ProgressTracker? progressTracker = null)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        this.address = address;
 | 
					        this.address = address;
 | 
				
			||||||
        this.port = port;
 | 
					        this.port = port;
 | 
				
			||||||
        this.ep = new IPEndPoint(IPAddress.Parse(address), port);
 | 
					        this.ep = new IPEndPoint(IPAddress.Parse(address), port);
 | 
				
			||||||
        this.timeout = timeout;
 | 
					        this.timeout = timeout;
 | 
				
			||||||
 | 
					        this._progressTracker = progressTracker;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async ValueTask<Result<uint>> ReadFIFO(uint devAddr)
 | 
					    async ValueTask<Result<uint>> ReadFIFO(uint devAddr)
 | 
				
			||||||
@@ -444,10 +448,10 @@ public class Jtag
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    async ValueTask<Result<bool>> WriteFIFO(
 | 
					    async ValueTask<Result<bool>> WriteFIFO(
 | 
				
			||||||
        UInt32 devAddr, UInt32 data, UInt32 result,
 | 
					        UInt32 devAddr, UInt32 data, UInt32 result,
 | 
				
			||||||
        UInt32 resultMask = 0xFF_FF_FF_FF, UInt32 delayMilliseconds = 0, ProgressReporter? progress = null)
 | 
					        UInt32 resultMask = 0xFF_FF_FF_FF, UInt32 delayMilliseconds = 0, string progressId = "")
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var ret = await UDPClientPool.WriteAddr(this.ep, 0, devAddr, data, this.timeout, progress?.CreateChild(80));
 | 
					            var ret = await UDPClientPool.WriteAddr(this.ep, 0, devAddr, data, this.timeout, progressId);
 | 
				
			||||||
            if (!ret.IsSuccessful) return new(ret.Error);
 | 
					            if (!ret.IsSuccessful) return new(ret.Error);
 | 
				
			||||||
            if (!ret.Value) return new(new Exception("Write FIFO failed"));
 | 
					            if (!ret.Value) return new(new Exception("Write FIFO failed"));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -458,17 +462,17 @@ public class Jtag
 | 
				
			|||||||
        {
 | 
					        {
 | 
				
			||||||
            var ret = await UDPClientPool.ReadAddrWithWait(this.ep, 0, JtagAddr.STATE, result, resultMask, 0, this.timeout);
 | 
					            var ret = await UDPClientPool.ReadAddrWithWait(this.ep, 0, JtagAddr.STATE, result, resultMask, 0, this.timeout);
 | 
				
			||||||
            if (!ret.IsSuccessful) return new(ret.Error);
 | 
					            if (!ret.IsSuccessful) return new(ret.Error);
 | 
				
			||||||
            progress?.Finish();
 | 
					            _progressTracker?.AdvanceProgress(progressId, 10);
 | 
				
			||||||
            return ret.Value;
 | 
					            return ret.Value;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async ValueTask<Result<bool>> WriteFIFO(
 | 
					    async ValueTask<Result<bool>> WriteFIFO(
 | 
				
			||||||
        UInt32 devAddr, byte[] data, UInt32 result,
 | 
					        UInt32 devAddr, byte[] data, UInt32 result,
 | 
				
			||||||
        UInt32 resultMask = 0xFF_FF_FF_FF, UInt32 delayMilliseconds = 0, ProgressReporter? progress = null)
 | 
					        UInt32 resultMask = 0xFF_FF_FF_FF, UInt32 delayMilliseconds = 0, string progressId = "")
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var ret = await UDPClientPool.WriteAddr(this.ep, 0, devAddr, data, this.timeout, progress?.CreateChild(80));
 | 
					            var ret = await UDPClientPool.WriteAddr(this.ep, 0, devAddr, data, this.timeout, progressId);
 | 
				
			||||||
            if (!ret.IsSuccessful) return new(ret.Error);
 | 
					            if (!ret.IsSuccessful) return new(ret.Error);
 | 
				
			||||||
            if (!ret.Value) return new(new Exception("Write FIFO failed"));
 | 
					            if (!ret.Value) return new(new Exception("Write FIFO failed"));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -479,7 +483,7 @@ public class Jtag
 | 
				
			|||||||
        {
 | 
					        {
 | 
				
			||||||
            var ret = await UDPClientPool.ReadAddrWithWait(this.ep, 0, JtagAddr.STATE, result, resultMask, 0, this.timeout);
 | 
					            var ret = await UDPClientPool.ReadAddrWithWait(this.ep, 0, JtagAddr.STATE, result, resultMask, 0, this.timeout);
 | 
				
			||||||
            if (!ret.IsSuccessful) return new(ret.Error);
 | 
					            if (!ret.IsSuccessful) return new(ret.Error);
 | 
				
			||||||
            progress?.Finish();
 | 
					            _progressTracker?.AdvanceProgress(progressId, 10);
 | 
				
			||||||
            return ret.Value;
 | 
					            return ret.Value;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -564,7 +568,7 @@ public class Jtag
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async ValueTask<Result<bool>> LoadDRCareInput(
 | 
					    async ValueTask<Result<bool>> LoadDRCareInput(
 | 
				
			||||||
        byte[] bytesArray, UInt32 timeout = 10_000, UInt32 cycle = 500, ProgressReporter? progress = null)
 | 
					        byte[] bytesArray, UInt32 timeout = 10_000, UInt32 cycle = 500, string progressId = "")
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var bytesLen = ((uint)(bytesArray.Length * 8));
 | 
					        var bytesLen = ((uint)(bytesArray.Length * 8));
 | 
				
			||||||
        if (bytesLen > Math.Pow(2, 28)) return new(new Exception("Length is over 2^(28 - 3)"));
 | 
					        if (bytesLen > Math.Pow(2, 28)) return new(new Exception("Length is over 2^(28 - 3)"));
 | 
				
			||||||
@@ -579,14 +583,15 @@ public class Jtag
 | 
				
			|||||||
            else if (!ret.Value) return new(new Exception("Write CMD_JTAG_LOAD_DR_CAREI Failed"));
 | 
					            else if (!ret.Value) return new(new Exception("Write CMD_JTAG_LOAD_DR_CAREI Failed"));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        progress?.Report(10);
 | 
					        _progressTracker?.AdvanceProgress(progressId, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var ret = await WriteFIFO(
 | 
					            var ret = await WriteFIFO(
 | 
				
			||||||
                JtagAddr.WRITE_DATA,
 | 
					                JtagAddr.WRITE_DATA,
 | 
				
			||||||
                bytesArray, 0x01_00_00_00,
 | 
					                bytesArray, 0x01_00_00_00,
 | 
				
			||||||
                JtagState.CMD_EXEC_FINISH,
 | 
					                JtagState.CMD_EXEC_FINISH,
 | 
				
			||||||
                progress: progress?.CreateChild(90)
 | 
					                0,
 | 
				
			||||||
 | 
					                progressId
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (!ret.IsSuccessful) return new(ret.Error);
 | 
					            if (!ret.IsSuccessful) return new(ret.Error);
 | 
				
			||||||
@@ -709,58 +714,53 @@ public class Jtag
 | 
				
			|||||||
    /// 下载比特流到 JTAG 设备
 | 
					    /// 下载比特流到 JTAG 设备
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    /// <param name="bitstream">比特流数据</param>
 | 
					    /// <param name="bitstream">比特流数据</param>
 | 
				
			||||||
    /// <param name="progress">进度报告器</param>
 | 
					    /// <param name="progressId">进度ID</param>
 | 
				
			||||||
    /// <returns>指示下载是否成功的异步结果</returns>
 | 
					    /// <returns>指示下载是否成功的异步结果</returns>
 | 
				
			||||||
    public async ValueTask<Result<bool>> DownloadBitstream(
 | 
					    public async ValueTask<Result<bool>> DownloadBitstream(
 | 
				
			||||||
        byte[] bitstream, ProgressReporter? progress = null)
 | 
					        byte[] bitstream, string progressId = "")
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        // Clear Data
 | 
					        // Clear Data
 | 
				
			||||||
        MsgBus.UDPServer.ClearUDPData(this.address, 0);
 | 
					        MsgBus.UDPServer.ClearUDPData(this.address, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        logger.Trace($"Clear up udp server {this.address,0} receive data");
 | 
					        logger.Trace($"Clear up udp server {this.address,0} receive data");
 | 
				
			||||||
        if (progress != null)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            progress.ExpectedSteps = 25;
 | 
					 | 
				
			||||||
            progress.Increase();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Result<bool> ret;
 | 
					        Result<bool> ret;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ret = await CloseTest();
 | 
					        ret = await CloseTest();
 | 
				
			||||||
        if (!ret.IsSuccessful) return new(ret.Error);
 | 
					        if (!ret.IsSuccessful) return new(ret.Error);
 | 
				
			||||||
        else if (!ret.Value) return new(new Exception("Jtag Close Test Failed"));
 | 
					        else if (!ret.Value) return new(new Exception("Jtag Close Test Failed"));
 | 
				
			||||||
        progress?.Increase();
 | 
					        _progressTracker?.AdvanceProgress(progressId, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ret = await RunTest();
 | 
					        ret = await RunTest();
 | 
				
			||||||
        if (!ret.IsSuccessful) return new(ret.Error);
 | 
					        if (!ret.IsSuccessful) return new(ret.Error);
 | 
				
			||||||
        else if (!ret.Value) return new(new Exception("Jtag Run Test Failed"));
 | 
					        else if (!ret.Value) return new(new Exception("Jtag Run Test Failed"));
 | 
				
			||||||
        progress?.Increase();
 | 
					        _progressTracker?.AdvanceProgress(progressId, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        logger.Trace("Jtag initialize");
 | 
					        logger.Trace("Jtag initialize");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ret = await ExecRDCmd(JtagCmd.JTAG_DR_JRST);
 | 
					        ret = await ExecRDCmd(JtagCmd.JTAG_DR_JRST);
 | 
				
			||||||
        if (!ret.IsSuccessful) return new(ret.Error);
 | 
					        if (!ret.IsSuccessful) return new(ret.Error);
 | 
				
			||||||
        else if (!ret.Value) return new(new Exception("Jtag Execute Command JTAG_DR_JRST Failed"));
 | 
					        else if (!ret.Value) return new(new Exception("Jtag Execute Command JTAG_DR_JRST Failed"));
 | 
				
			||||||
        progress?.Increase();
 | 
					        _progressTracker?.AdvanceProgress(progressId, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ret = await RunTest();
 | 
					        ret = await RunTest();
 | 
				
			||||||
        if (!ret.IsSuccessful) return new(ret.Error);
 | 
					        if (!ret.IsSuccessful) return new(ret.Error);
 | 
				
			||||||
        else if (!ret.Value) return new(new Exception("Jtag Run Test Failed"));
 | 
					        else if (!ret.Value) return new(new Exception("Jtag Run Test Failed"));
 | 
				
			||||||
        progress?.Increase();
 | 
					        _progressTracker?.AdvanceProgress(progressId, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ret = await ExecRDCmd(JtagCmd.JTAG_DR_CFGI);
 | 
					        ret = await ExecRDCmd(JtagCmd.JTAG_DR_CFGI);
 | 
				
			||||||
        if (!ret.IsSuccessful) return new(ret.Error);
 | 
					        if (!ret.IsSuccessful) return new(ret.Error);
 | 
				
			||||||
        else if (!ret.Value) return new(new Exception("Jtag Execute Command JTAG_DR_CFGI Failed"));
 | 
					        else if (!ret.Value) return new(new Exception("Jtag Execute Command JTAG_DR_CFGI Failed"));
 | 
				
			||||||
        progress?.Increase();
 | 
					        _progressTracker?.AdvanceProgress(progressId, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        logger.Trace("Jtag ready to write bitstream");
 | 
					        logger.Trace("Jtag ready to write bitstream");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ret = await IdleDelay(100000);
 | 
					        ret = await IdleDelay(100000);
 | 
				
			||||||
        if (!ret.IsSuccessful) return new(ret.Error);
 | 
					        if (!ret.IsSuccessful) return new(ret.Error);
 | 
				
			||||||
        else if (!ret.Value) return new(new Exception("Jtag IDLE Delay Failed"));
 | 
					        else if (!ret.Value) return new(new Exception("Jtag IDLE Delay Failed"));
 | 
				
			||||||
        progress?.Increase();
 | 
					        _progressTracker?.AdvanceProgress(progressId, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ret = await LoadDRCareInput(bitstream, progress: progress?.CreateChild(50));
 | 
					        ret = await LoadDRCareInput(bitstream, progressId: progressId);
 | 
				
			||||||
        if (!ret.IsSuccessful) return new(ret.Error);
 | 
					        if (!ret.IsSuccessful) return new(ret.Error);
 | 
				
			||||||
        else if (!ret.Value) return new(new Exception("Jtag Load Data Failed"));
 | 
					        else if (!ret.Value) return new(new Exception("Jtag Load Data Failed"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -769,40 +769,40 @@ public class Jtag
 | 
				
			|||||||
        ret = await CloseTest();
 | 
					        ret = await CloseTest();
 | 
				
			||||||
        if (!ret.IsSuccessful) return new(ret.Error);
 | 
					        if (!ret.IsSuccessful) return new(ret.Error);
 | 
				
			||||||
        else if (!ret.Value) return new(new Exception("Jtag Close Test Failed"));
 | 
					        else if (!ret.Value) return new(new Exception("Jtag Close Test Failed"));
 | 
				
			||||||
        progress?.Increase();
 | 
					        _progressTracker?.AdvanceProgress(progressId, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ret = await RunTest();
 | 
					        ret = await RunTest();
 | 
				
			||||||
        if (!ret.IsSuccessful) return new(ret.Error);
 | 
					        if (!ret.IsSuccessful) return new(ret.Error);
 | 
				
			||||||
        else if (!ret.Value) return new(new Exception("Jtag Run Test Failed"));
 | 
					        else if (!ret.Value) return new(new Exception("Jtag Run Test Failed"));
 | 
				
			||||||
        progress?.Increase();
 | 
					        _progressTracker?.AdvanceProgress(progressId, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ret = await ExecRDCmd(JtagCmd.JTAG_DR_JWAKEUP);
 | 
					        ret = await ExecRDCmd(JtagCmd.JTAG_DR_JWAKEUP);
 | 
				
			||||||
        if (!ret.IsSuccessful) return new(ret.Error);
 | 
					        if (!ret.IsSuccessful) return new(ret.Error);
 | 
				
			||||||
        else if (!ret.Value) return new(new Exception("Jtag Execute Command JTAG_DR_JWAKEUP Failed"));
 | 
					        else if (!ret.Value) return new(new Exception("Jtag Execute Command JTAG_DR_JWAKEUP Failed"));
 | 
				
			||||||
        progress?.Increase();
 | 
					        _progressTracker?.AdvanceProgress(progressId, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        logger.Trace("Jtag reset device");
 | 
					        logger.Trace("Jtag reset device");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ret = await IdleDelay(10000);
 | 
					        ret = await IdleDelay(10000);
 | 
				
			||||||
        if (!ret.IsSuccessful) return new(ret.Error);
 | 
					        if (!ret.IsSuccessful) return new(ret.Error);
 | 
				
			||||||
        else if (!ret.Value) return new(new Exception("Jtag IDLE Delay Failed"));
 | 
					        else if (!ret.Value) return new(new Exception("Jtag IDLE Delay Failed"));
 | 
				
			||||||
        progress?.Increase();
 | 
					        _progressTracker?.AdvanceProgress(progressId, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var retCode = await ReadStatusReg();
 | 
					        var retCode = await ReadStatusReg();
 | 
				
			||||||
        if (!retCode.IsSuccessful) return new(retCode.Error);
 | 
					        if (!retCode.IsSuccessful) return new(retCode.Error);
 | 
				
			||||||
        var jtagStatus = new JtagStatusReg(retCode.Value);
 | 
					        var jtagStatus = new JtagStatusReg(retCode.Value);
 | 
				
			||||||
        if (!(jtagStatus.done && jtagStatus.wakeup_over && jtagStatus.init_complete))
 | 
					        if (!(jtagStatus.done && jtagStatus.wakeup_over && jtagStatus.init_complete))
 | 
				
			||||||
            return new(new Exception("Jtag download bitstream failed"));
 | 
					            return new(new Exception("Jtag download bitstream failed"));
 | 
				
			||||||
        progress?.Increase();
 | 
					        _progressTracker?.AdvanceProgress(progressId, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ret = await CloseTest();
 | 
					        ret = await CloseTest();
 | 
				
			||||||
        if (!ret.IsSuccessful) return new(ret.Error);
 | 
					        if (!ret.IsSuccessful) return new(ret.Error);
 | 
				
			||||||
        else if (!ret.Value) return new(new Exception("Jtag Close Test Failed"));
 | 
					        else if (!ret.Value) return new(new Exception("Jtag Close Test Failed"));
 | 
				
			||||||
        logger.Trace("Jtag download bitstream successfully");
 | 
					        logger.Trace("Jtag download bitstream successfully");
 | 
				
			||||||
        progress?.Increase();
 | 
					        _progressTracker?.AdvanceProgress(progressId, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Finish
 | 
					        // Finish
 | 
				
			||||||
        progress?.Finish();
 | 
					        _progressTracker?.AdvanceProgress(progressId, 10);
 | 
				
			||||||
        return true;
 | 
					        return true;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										147
									
								
								server/src/Services/ProgressTracker.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								server/src/Services/ProgressTracker.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,147 @@
 | 
				
			|||||||
 | 
					using System.Collections.Concurrent;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.SignalR;
 | 
				
			||||||
 | 
					using server.Hubs;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace server.Services;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public enum TaskState { Running, Completed, Failed, Cancelled }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public readonly struct TaskProgress
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public string Id { get; }
 | 
				
			||||||
 | 
					    public int Current { get; }
 | 
				
			||||||
 | 
					    public int Total { get; }
 | 
				
			||||||
 | 
					    public TaskState State { get; }
 | 
				
			||||||
 | 
					    public long Timestamp { get; }
 | 
				
			||||||
 | 
					    public string? Error { get; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public TaskProgress(string id, int current, int total, TaskState state, long timestamp, string? error = null)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        Id = id;
 | 
				
			||||||
 | 
					        Current = current;
 | 
				
			||||||
 | 
					        Total = total;
 | 
				
			||||||
 | 
					        State = state;
 | 
				
			||||||
 | 
					        Timestamp = timestamp;
 | 
				
			||||||
 | 
					        Error = error;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public TaskProgress WithUpdate(int? current = null, TaskState? state = null, string? error = null)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return new TaskProgress(
 | 
				
			||||||
 | 
					            Id,
 | 
				
			||||||
 | 
					            current ?? Current,
 | 
				
			||||||
 | 
					            Total,
 | 
				
			||||||
 | 
					            state ?? State,
 | 
				
			||||||
 | 
					            DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
 | 
				
			||||||
 | 
					            error ?? Error
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public ProgressInfo ToProgressInfo()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return new ProgressInfo
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            TaskId = Id,
 | 
				
			||||||
 | 
					            Status = State switch
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                TaskState.Running => ProgressStatus.Running,
 | 
				
			||||||
 | 
					                TaskState.Completed => ProgressStatus.Completed,
 | 
				
			||||||
 | 
					                TaskState.Failed => ProgressStatus.Failed,
 | 
				
			||||||
 | 
					                TaskState.Cancelled => ProgressStatus.Canceled,
 | 
				
			||||||
 | 
					                _ => ProgressStatus.Failed
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            ProgressPercent = Total > 0 ? (Current * 100) / Total : 0,
 | 
				
			||||||
 | 
					            ErrorMessage = Error ?? string.Empty
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public sealed class ProgressTracker
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    private readonly ConcurrentDictionary<string, TaskProgress> _tasks = new();
 | 
				
			||||||
 | 
					    private readonly Timer _cleaner;
 | 
				
			||||||
 | 
					    private readonly IHubContext<ProgressHub, IProgressReceiver> _hubContext;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 构造器支持可选的Hub注入
 | 
				
			||||||
 | 
					    public ProgressTracker(IHubContext<ProgressHub, IProgressReceiver> hubContext)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        _hubContext = hubContext;
 | 
				
			||||||
 | 
					        _cleaner = new Timer(CleanExpiredTasks, null,
 | 
				
			||||||
 | 
					            TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public void CleanExpiredTasks(object? obj)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        var cutoff = DateTimeOffset.Now.AddMinutes(-3).ToUnixTimeSeconds();
 | 
				
			||||||
 | 
					        var expired = _tasks.Where(kvp => kvp.Value.Timestamp < cutoff).Select(kvp => kvp.Key).ToList();
 | 
				
			||||||
 | 
					        foreach (var id in expired)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            _tasks.TryRemove(id, out _);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public string CreateTask(int total)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        var id = Guid.NewGuid().ToString();
 | 
				
			||||||
 | 
					        var task = new TaskProgress(id, 0, total, TaskState.Running, DateTimeOffset.UtcNow.ToUnixTimeSeconds());
 | 
				
			||||||
 | 
					        _tasks[id] = task;
 | 
				
			||||||
 | 
					        NotifyIfNeeded(task);
 | 
				
			||||||
 | 
					        return id;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 核心更新方法,现在包含自动通知
 | 
				
			||||||
 | 
					    public bool UpdateTask(string id, Func<TaskProgress, TaskProgress> updater)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if (!_tasks.TryGetValue(id, out var current))
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var updated = updater(current);
 | 
				
			||||||
 | 
					        if (_tasks.TryUpdate(id, updated, current))
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            NotifyIfNeeded(updated);
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 自动通知逻辑 - 简单直接
 | 
				
			||||||
 | 
					    private void NotifyIfNeeded(TaskProgress task)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        _hubContext.Clients.Group(task.Id).OnReceiveProgress(task.ToProgressInfo());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public bool UpdateProgress(string id, int current)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return UpdateTask(id, p => p.WithUpdate(
 | 
				
			||||||
 | 
					            current: Math.Min(current, p.Total)));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public bool AdvanceProgress(string id, int steps)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return UpdateTask(id, p => p.WithUpdate(
 | 
				
			||||||
 | 
					            current: Math.Min(p.Current + steps, p.Total)));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public bool CancelProgress(string id)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return UpdateTask(id, p => p.WithUpdate(state: TaskState.Cancelled));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public bool CompleteProgress(string id)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return UpdateTask(id, p => p.WithUpdate(
 | 
				
			||||||
 | 
					            current: p.Total, state: TaskState.Completed));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public bool FailProgress(string id, string? error)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return UpdateTask(id, p => p.WithUpdate(
 | 
				
			||||||
 | 
					            state: TaskState.Failed, error: error));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public TaskProgress? GetTask(string id)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        _tasks.TryGetValue(id, out var task);
 | 
				
			||||||
 | 
					        return task.Id == null ? null : task;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,294 +0,0 @@
 | 
				
			|||||||
using Microsoft.AspNetCore.SignalR;
 | 
					 | 
				
			||||||
using System.Collections.Concurrent;
 | 
					 | 
				
			||||||
using DotNext;
 | 
					 | 
				
			||||||
using Common;
 | 
					 | 
				
			||||||
using server.Hubs;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace server.Services;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public class ProgressReporter : ProgressInfo, IProgress<int>
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    private int _progress = 0;
 | 
					 | 
				
			||||||
    private int _stepProgress = 1;
 | 
					 | 
				
			||||||
    private int _expectedSteps = 100;
 | 
					 | 
				
			||||||
    private int _parentProportion = 100;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public int Progress => _progress;
 | 
					 | 
				
			||||||
    public int MaxProgress { get; set; } = 100;
 | 
					 | 
				
			||||||
    public int StepProgress
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        get => _stepProgress;
 | 
					 | 
				
			||||||
        set
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            _stepProgress = value;
 | 
					 | 
				
			||||||
            _expectedSteps = MaxProgress / value;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    public int ExpectedSteps
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        get => _expectedSteps;
 | 
					 | 
				
			||||||
        set
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            _expectedSteps = value;
 | 
					 | 
				
			||||||
            MaxProgress = Number.IntPow(10, Number.GetLength(value));
 | 
					 | 
				
			||||||
            _stepProgress = MaxProgress / value;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    public Func<int, Task>? ReporterFunc { get; set; } = null;
 | 
					 | 
				
			||||||
    public ProgressReporter? Parent { get; set; }
 | 
					 | 
				
			||||||
    public ProgressReporter? Child { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private ProgressStatus _status = ProgressStatus.Pending;
 | 
					 | 
				
			||||||
    private string _errorMessage = string.Empty;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public override string TaskId { get; } = Guid.NewGuid().ToString();
 | 
					 | 
				
			||||||
    public override int ProgressPercent => _progress * 100 / MaxProgress;
 | 
					 | 
				
			||||||
    public override ProgressStatus Status => _status;
 | 
					 | 
				
			||||||
    public override string ErrorMessage => _errorMessage;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public ProgressReporter(Func<int, Task>? reporter = null, int initProgress = 0, int maxProgress = 100, int step = 1)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        _progress = initProgress;
 | 
					 | 
				
			||||||
        MaxProgress = maxProgress;
 | 
					 | 
				
			||||||
        StepProgress = step;
 | 
					 | 
				
			||||||
        ReporterFunc = reporter;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public ProgressReporter(int parentProportion, int expectedSteps = 100, Func<int, Task>? reporter = null)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        this._parentProportion = parentProportion;
 | 
					 | 
				
			||||||
        MaxProgress = Number.IntPow(10, Number.GetLength(expectedSteps));
 | 
					 | 
				
			||||||
        StepProgress = MaxProgress / expectedSteps;
 | 
					 | 
				
			||||||
        ReporterFunc = reporter;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private async void ForceReport(int value)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        try
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            if (ReporterFunc != null)
 | 
					 | 
				
			||||||
                await ReporterFunc(value);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (Parent != null)
 | 
					 | 
				
			||||||
                Parent.Increase((value - _progress) / StepProgress * _parentProportion / (MaxProgress / StepProgress));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            _progress = value;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        catch (OperationCanceledException ex)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            _errorMessage = ex.Message;
 | 
					 | 
				
			||||||
            this._status = ProgressStatus.Canceled;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        catch (Exception ex)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            _errorMessage = ex.Message;
 | 
					 | 
				
			||||||
            this._status = ProgressStatus.Failed;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public void Report(int value)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        if (this._status == ProgressStatus.Pending)
 | 
					 | 
				
			||||||
            this._status = ProgressStatus.InProgress;
 | 
					 | 
				
			||||||
        else if (this.Status != ProgressStatus.InProgress)
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (value > MaxProgress) return;
 | 
					 | 
				
			||||||
        ForceReport(value);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public void Increase(int? value = null)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        if (this._status == ProgressStatus.Pending)
 | 
					 | 
				
			||||||
            this._status = ProgressStatus.InProgress;
 | 
					 | 
				
			||||||
        else if (this.Status != ProgressStatus.InProgress)
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (value.HasValue)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            if (_progress + value.Value >= MaxProgress) return;
 | 
					 | 
				
			||||||
            this.Report(_progress + value.Value);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        else
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            if (_progress + StepProgress >= MaxProgress) return;
 | 
					 | 
				
			||||||
            this.Report(_progress + StepProgress);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public void Finish()
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        this._status = ProgressStatus.Completed;
 | 
					 | 
				
			||||||
        this.ForceReport(MaxProgress);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public void Cancel()
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        this._status = ProgressStatus.Canceled;
 | 
					 | 
				
			||||||
        this._errorMessage = "User Cancelled";
 | 
					 | 
				
			||||||
        this.ForceReport(_progress);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public void Error(string message)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        this._status = ProgressStatus.Failed;
 | 
					 | 
				
			||||||
        this._errorMessage = message;
 | 
					 | 
				
			||||||
        this.ForceReport(_progress);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public ProgressReporter CreateChild(int proportion, int expectedSteps = 100)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        var child = new ProgressReporter(proportion, expectedSteps);
 | 
					 | 
				
			||||||
        child.Parent = this;
 | 
					 | 
				
			||||||
        this.Child = child;
 | 
					 | 
				
			||||||
        return child;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public class ProgressTrackerService : BackgroundService
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
 | 
					 | 
				
			||||||
    private readonly ConcurrentDictionary<string, TaskProgressInfo> _taskMap = new();
 | 
					 | 
				
			||||||
    private readonly IHubContext<ProgressHub, IProgressReceiver> _hubContext;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private class TaskProgressInfo
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        public ProgressReporter? Reporter { get; set; }
 | 
					 | 
				
			||||||
        public string? ConnectionId { get; set; }
 | 
					 | 
				
			||||||
        public required CancellationToken CancellationToken { get; set; }
 | 
					 | 
				
			||||||
        public required CancellationTokenSource CancellationTokenSource { get; set; }
 | 
					 | 
				
			||||||
        public required DateTime UpdatedAt { get; set; }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public ProgressTrackerService(IHubContext<ProgressHub, IProgressReceiver> hubContext)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        _hubContext = hubContext;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        while (!stoppingToken.IsCancellationRequested)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            try
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                var now = DateTime.UtcNow;
 | 
					 | 
				
			||||||
                foreach (var kvp in _taskMap)
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    var info = kvp.Value;
 | 
					 | 
				
			||||||
                    // 超过 1 分钟且任务已完成/失败/取消
 | 
					 | 
				
			||||||
                    if (
 | 
					 | 
				
			||||||
                        (now - info.UpdatedAt).TotalMinutes > 1 &&
 | 
					 | 
				
			||||||
                        info.Reporter != null &&
 | 
					 | 
				
			||||||
                        (
 | 
					 | 
				
			||||||
                            info.Reporter.Status == ProgressStatus.Completed ||
 | 
					 | 
				
			||||||
                            info.Reporter.Status == ProgressStatus.Failed ||
 | 
					 | 
				
			||||||
                            info.Reporter.Status == ProgressStatus.Canceled
 | 
					 | 
				
			||||||
                        )
 | 
					 | 
				
			||||||
                    )
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        _taskMap.TryRemove(kvp.Key, out _);
 | 
					 | 
				
			||||||
                        logger.Info($"Cleaned up task {kvp.Key}");
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            catch (Exception ex)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                logger.Error(ex, "Error during ProgressTracker cleanup");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public (string, ProgressReporter) CreateTask(CancellationToken? cancellationToken = null)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        CancellationTokenSource? cancellationTokenSource;
 | 
					 | 
				
			||||||
        if (cancellationToken.HasValue)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken.Value);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        else
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            cancellationTokenSource = new CancellationTokenSource();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        var progressInfo = new TaskProgressInfo
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            ConnectionId = null,
 | 
					 | 
				
			||||||
            UpdatedAt = DateTime.UtcNow,
 | 
					 | 
				
			||||||
            CancellationToken = cancellationTokenSource.Token,
 | 
					 | 
				
			||||||
            CancellationTokenSource = cancellationTokenSource,
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        var progress = new ProgressReporter(async value =>
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            cancellationTokenSource.Token.ThrowIfCancellationRequested();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // 通过 SignalR 推送进度
 | 
					 | 
				
			||||||
            if (progressInfo.ConnectionId != null && progressInfo.Reporter != null)
 | 
					 | 
				
			||||||
                await _hubContext.Clients.Client(progressInfo.ConnectionId).OnReceiveProgress(progressInfo.Reporter);
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        progressInfo.Reporter = progress;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        _taskMap.TryAdd(progressInfo.Reporter.TaskId, progressInfo);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return (progressInfo.Reporter.TaskId, progress);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public Optional<ProgressReporter> GetReporter(string taskId)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        if (_taskMap.TryGetValue(taskId, out var info))
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return info.Reporter;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return Optional<ProgressReporter>.None;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public Optional<ProgressStatus> GetProgressStatus(string taskId)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        if (_taskMap.TryGetValue(taskId, out var info))
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            if (info.Reporter != null)
 | 
					 | 
				
			||||||
                return info.Reporter.Status;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return Optional<ProgressStatus>.None;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public bool BindTask(string taskId, string connectionId)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        if (_taskMap.TryGetValue(taskId, out var info) && info != null)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            lock (info)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                info.ConnectionId = connectionId;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            return true;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return false;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public bool CancelTask(string taskId)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        try
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            if (_taskMap.TryGetValue(taskId, out var info) && info != null && info.Reporter != null)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                lock (info)
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    info.CancellationTokenSource.Cancel();
 | 
					 | 
				
			||||||
                    info.Reporter.Cancel();
 | 
					 | 
				
			||||||
                    info.UpdatedAt = DateTime.UtcNow;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                return true;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return false;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        catch (Exception ex)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            logger.Error(ex, $"Failed to cancel task {taskId}");
 | 
					 | 
				
			||||||
            return false;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -8,11 +8,11 @@ using server.Services;
 | 
				
			|||||||
/// <summary>
 | 
					/// <summary>
 | 
				
			||||||
/// UDP客户端发送池
 | 
					/// UDP客户端发送池
 | 
				
			||||||
/// </summary>
 | 
					/// </summary>
 | 
				
			||||||
public class UDPClientPool
 | 
					public sealed class UDPClientPool
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
 | 
					    private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static IPAddress localhost = IPAddress.Parse("127.0.0.1");
 | 
					    private static ProgressTracker _progressTracker = MsgBus.ProgressTracker;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 发送字符串
 | 
					    /// 发送字符串
 | 
				
			||||||
@@ -183,40 +183,6 @@ public class UDPClientPool
 | 
				
			|||||||
        return await Task.Run(() => { return SendDataPack(endPoint, pkg); });
 | 
					        return await Task.Run(() => { return SendDataPack(endPoint, pkg); });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// <summary>
 | 
					 | 
				
			||||||
    /// 发送字符串到本地
 | 
					 | 
				
			||||||
    /// </summary>
 | 
					 | 
				
			||||||
    /// <param name="port">端口</param>
 | 
					 | 
				
			||||||
    /// <param name="stringArray">字符串数组</param>
 | 
					 | 
				
			||||||
    /// <returns>是否成功</returns>
 | 
					 | 
				
			||||||
    public static bool SendStringLocalHost(int port, string[] stringArray)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        return SendString(new IPEndPoint(localhost, port), stringArray);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// <summary>
 | 
					 | 
				
			||||||
    /// 循环发送字符串到本地
 | 
					 | 
				
			||||||
    /// </summary>
 | 
					 | 
				
			||||||
    /// <param name="times">发送总次数</param>
 | 
					 | 
				
			||||||
    /// <param name="sleepMilliSeconds">间隔时间</param>
 | 
					 | 
				
			||||||
    /// <param name="port">端口</param>
 | 
					 | 
				
			||||||
    /// <param name="stringArray">字符串数组</param>
 | 
					 | 
				
			||||||
    /// <returns>是否成功</returns>
 | 
					 | 
				
			||||||
    public static bool CycleSendStringLocalHost(int times, int sleepMilliSeconds, int port, string[] stringArray)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        var isSuccessful = true;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        while (times-- >= 0)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            isSuccessful = SendStringLocalHost(port, stringArray);
 | 
					 | 
				
			||||||
            if (!isSuccessful) break;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            Thread.Sleep(sleepMilliSeconds);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return isSuccessful;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// 读取设备地址数据
 | 
					    /// 读取设备地址数据
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
@@ -607,11 +573,11 @@ public class UDPClientPool
 | 
				
			|||||||
    /// <param name="devAddr">设备地址</param>
 | 
					    /// <param name="devAddr">设备地址</param>
 | 
				
			||||||
    /// <param name="data">要写入的32位数据</param>
 | 
					    /// <param name="data">要写入的32位数据</param>
 | 
				
			||||||
    /// <param name="timeout">超时时间(毫秒)</param>
 | 
					    /// <param name="timeout">超时时间(毫秒)</param>
 | 
				
			||||||
    /// <param name="progress">进度报告器</param>
 | 
					    /// <param name="progressId">进度报告器</param>
 | 
				
			||||||
    /// <returns>写入结果,true表示写入成功</returns>
 | 
					    /// <returns>写入结果,true表示写入成功</returns>
 | 
				
			||||||
    public static async ValueTask<Result<bool>> WriteAddr(
 | 
					    public static async ValueTask<Result<bool>> WriteAddr(
 | 
				
			||||||
            IPEndPoint endPoint, int taskID, UInt32 devAddr,
 | 
					            IPEndPoint endPoint, int taskID, UInt32 devAddr,
 | 
				
			||||||
            UInt32 data, int timeout = 1000, ProgressReporter? progress = null)
 | 
					            UInt32 data, int timeout = 1000, string progressId = "")
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var ret = false;
 | 
					        var ret = false;
 | 
				
			||||||
        var opts = new SendAddrPackOptions()
 | 
					        var opts = new SendAddrPackOptions()
 | 
				
			||||||
@@ -622,17 +588,18 @@ public class UDPClientPool
 | 
				
			|||||||
            Address = devAddr,
 | 
					            Address = devAddr,
 | 
				
			||||||
            IsWrite = true,
 | 
					            IsWrite = true,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        progress?.Report(20);
 | 
					        _progressTracker.AdvanceProgress(progressId, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Write Register
 | 
					        // Write Register
 | 
				
			||||||
        ret = await UDPClientPool.SendAddrPackAsync(endPoint, new SendAddrPackage(opts));
 | 
					        ret = await UDPClientPool.SendAddrPackAsync(endPoint, new SendAddrPackage(opts));
 | 
				
			||||||
        if (!ret) return new(new Exception("Send 1st address package failed!"));
 | 
					        if (!ret) return new(new Exception("Send 1st address package failed!"));
 | 
				
			||||||
        progress?.Report(40);
 | 
					        _progressTracker.AdvanceProgress(progressId, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Send Data Package
 | 
					        // Send Data Package
 | 
				
			||||||
        ret = await UDPClientPool.SendDataPackAsync(endPoint,
 | 
					        ret = await UDPClientPool.SendDataPackAsync(endPoint,
 | 
				
			||||||
                new SendDataPackage(Common.Number.NumberToBytes(data, 4).Value));
 | 
					                new SendDataPackage(Common.Number.NumberToBytes(data, 4).Value));
 | 
				
			||||||
        if (!ret) return new(new Exception("Send data package failed!"));
 | 
					        if (!ret) return new(new Exception("Send data package failed!"));
 | 
				
			||||||
        progress?.Report(60);
 | 
					        _progressTracker.AdvanceProgress(progressId, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Check Msg Bus
 | 
					        // Check Msg Bus
 | 
				
			||||||
        if (!MsgBus.IsRunning)
 | 
					        if (!MsgBus.IsRunning)
 | 
				
			||||||
@@ -642,7 +609,7 @@ public class UDPClientPool
 | 
				
			|||||||
        var udpWriteAck = await MsgBus.UDPServer.WaitForAckAsync(
 | 
					        var udpWriteAck = await MsgBus.UDPServer.WaitForAckAsync(
 | 
				
			||||||
                endPoint.Address.ToString(), taskID, endPoint.Port, timeout);
 | 
					                endPoint.Address.ToString(), taskID, endPoint.Port, timeout);
 | 
				
			||||||
        if (!udpWriteAck.IsSuccessful) return new(udpWriteAck.Error);
 | 
					        if (!udpWriteAck.IsSuccessful) return new(udpWriteAck.Error);
 | 
				
			||||||
        progress?.Finish();
 | 
					        _progressTracker.AdvanceProgress(progressId, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return udpWriteAck.Value.IsSuccessful;
 | 
					        return udpWriteAck.Value.IsSuccessful;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -655,11 +622,11 @@ public class UDPClientPool
 | 
				
			|||||||
    /// <param name="devAddr">设备地址</param>
 | 
					    /// <param name="devAddr">设备地址</param>
 | 
				
			||||||
    /// <param name="dataArray">要写入的字节数组</param>
 | 
					    /// <param name="dataArray">要写入的字节数组</param>
 | 
				
			||||||
    /// <param name="timeout">超时时间(毫秒)</param>
 | 
					    /// <param name="timeout">超时时间(毫秒)</param>
 | 
				
			||||||
    /// <param name="progress">进度报告器</param>
 | 
					    /// <param name="progressId">进度报告器</param>
 | 
				
			||||||
    /// <returns>写入结果,true表示写入成功</returns>
 | 
					    /// <returns>写入结果,true表示写入成功</returns>
 | 
				
			||||||
    public static async ValueTask<Result<bool>> WriteAddr(
 | 
					    public static async ValueTask<Result<bool>> WriteAddr(
 | 
				
			||||||
        IPEndPoint endPoint, int taskID, UInt32 devAddr,
 | 
					        IPEndPoint endPoint, int taskID, UInt32 devAddr,
 | 
				
			||||||
        byte[] dataArray, int timeout = 1000, ProgressReporter? progress = null)
 | 
					        byte[] dataArray, int timeout = 1000, string progressId = "")
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var ret = false;
 | 
					        var ret = false;
 | 
				
			||||||
        var opts = new SendAddrPackOptions()
 | 
					        var opts = new SendAddrPackOptions()
 | 
				
			||||||
@@ -681,8 +648,6 @@ public class UDPClientPool
 | 
				
			|||||||
        var writeTimes = hasRest ?
 | 
					        var writeTimes = hasRest ?
 | 
				
			||||||
            dataArray.Length / (max4BytesPerRead * (32 / 8)) + 1 :
 | 
					            dataArray.Length / (max4BytesPerRead * (32 / 8)) + 1 :
 | 
				
			||||||
            dataArray.Length / (max4BytesPerRead * (32 / 8));
 | 
					            dataArray.Length / (max4BytesPerRead * (32 / 8));
 | 
				
			||||||
        if (progress != null)
 | 
					 | 
				
			||||||
            progress.ExpectedSteps = writeTimes;
 | 
					 | 
				
			||||||
        for (var i = 0; i < writeTimes; i++)
 | 
					        for (var i = 0; i < writeTimes; i++)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            // Sperate Data Array
 | 
					            // Sperate Data Array
 | 
				
			||||||
@@ -712,10 +677,9 @@ public class UDPClientPool
 | 
				
			|||||||
            if (!udpWriteAck.Value.IsSuccessful)
 | 
					            if (!udpWriteAck.Value.IsSuccessful)
 | 
				
			||||||
                return false;
 | 
					                return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            progress?.Increase();
 | 
					            _progressTracker.AdvanceProgress(progressId, 10);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        progress?.Finish();
 | 
					 | 
				
			||||||
        return true;
 | 
					        return true;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,22 +16,32 @@
 | 
				
			|||||||
            <span class="text-sm">{{ bitstream.name }}</span>
 | 
					            <span class="text-sm">{{ bitstream.name }}</span>
 | 
				
			||||||
            <div class="flex gap-2">
 | 
					            <div class="flex gap-2">
 | 
				
			||||||
              <button
 | 
					              <button
 | 
				
			||||||
                @click="downloadExampleBitstream(bitstream)"
 | 
					                @click="handleExampleBitstream('download', bitstream)"
 | 
				
			||||||
                class="btn btn-sm btn-secondary"
 | 
					                class="btn btn-sm btn-secondary"
 | 
				
			||||||
                :disabled="isDownloading || isProgramming"
 | 
					                :disabled="currentTask !== 'none'"
 | 
				
			||||||
              >
 | 
					              >
 | 
				
			||||||
                <div v-if="isDownloading">
 | 
					                <div
 | 
				
			||||||
 | 
					                  v-if="
 | 
				
			||||||
 | 
					                    currentTask === 'downloading' &&
 | 
				
			||||||
 | 
					                    currentBitstreamId === bitstream.id
 | 
				
			||||||
 | 
					                  "
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
                  <span class="loading loading-spinner loading-xs"></span>
 | 
					                  <span class="loading loading-spinner loading-xs"></span>
 | 
				
			||||||
                  {{ downloadProgress }}%
 | 
					                  下载中...
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                <div v-else>下载示例</div>
 | 
					                <div v-else>下载示例</div>
 | 
				
			||||||
              </button>
 | 
					              </button>
 | 
				
			||||||
              <button
 | 
					              <button
 | 
				
			||||||
                @click="programExampleBitstream(bitstream)"
 | 
					                @click="handleExampleBitstream('program', bitstream)"
 | 
				
			||||||
                class="btn btn-sm btn-primary"
 | 
					                class="btn btn-sm btn-primary"
 | 
				
			||||||
                :disabled="isDownloading || isProgramming"
 | 
					                :disabled="currentTask !== 'none'"
 | 
				
			||||||
              >
 | 
					              >
 | 
				
			||||||
                <div v-if="isProgramming">
 | 
					                <div
 | 
				
			||||||
 | 
					                  v-if="
 | 
				
			||||||
 | 
					                    currentTask === 'programming' &&
 | 
				
			||||||
 | 
					                    currentBitstreamId === bitstream.id
 | 
				
			||||||
 | 
					                  "
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
                  <span class="loading loading-spinner loading-xs"></span>
 | 
					                  <span class="loading loading-spinner loading-xs"></span>
 | 
				
			||||||
                  烧录中...
 | 
					                  烧录中...
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
@@ -63,14 +73,18 @@
 | 
				
			|||||||
    <!-- Upload Button -->
 | 
					    <!-- Upload Button -->
 | 
				
			||||||
    <div class="card-actions w-full">
 | 
					    <div class="card-actions w-full">
 | 
				
			||||||
      <button
 | 
					      <button
 | 
				
			||||||
        @click="handleClick"
 | 
					        @click="handleUploadAndDownload"
 | 
				
			||||||
        class="btn btn-primary grow"
 | 
					        class="btn btn-primary grow"
 | 
				
			||||||
        :disabled="isUploading || isProgramming"
 | 
					        :disabled="currentTask !== 'none'"
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        <div v-if="isUploading">
 | 
					        <div v-if="currentTask === 'uploading'">
 | 
				
			||||||
          <span class="loading loading-spinner"></span>
 | 
					          <span class="loading loading-spinner"></span>
 | 
				
			||||||
          上传中...
 | 
					          上传中...
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div v-else-if="currentTask === 'programming'">
 | 
				
			||||||
 | 
					          <span class="loading loading-spinner"></span>
 | 
				
			||||||
 | 
					          {{ currentProgressPercent }}% ...
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
        <div v-else>上传并下载</div>
 | 
					        <div v-else>上传并下载</div>
 | 
				
			||||||
      </button>
 | 
					      </button>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
@@ -78,28 +92,19 @@
 | 
				
			|||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { computed, ref, useTemplateRef, onMounted } from "vue";
 | 
					import { ref, useTemplateRef, onMounted } from "vue";
 | 
				
			||||||
import { AuthManager } from "@/utils/AuthManager"; // Adjust the path based on your project structure
 | 
					import { AuthManager } from "@/utils/AuthManager";
 | 
				
			||||||
import { useDialogStore } from "@/stores/dialog";
 | 
					import { useDialogStore } from "@/stores/dialog";
 | 
				
			||||||
import { isNull, isUndefined } from "lodash";
 | 
					 | 
				
			||||||
import { useEquipments } from "@/stores/equipments";
 | 
					import { useEquipments } from "@/stores/equipments";
 | 
				
			||||||
import type { HubConnection } from "@microsoft/signalr";
 | 
					 | 
				
			||||||
import type {
 | 
					 | 
				
			||||||
  IProgressHub,
 | 
					 | 
				
			||||||
  IProgressReceiver,
 | 
					 | 
				
			||||||
} from "@/utils/signalR/TypedSignalR.Client/server.Hubs";
 | 
					 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
  getHubProxyFactory,
 | 
					 | 
				
			||||||
  getReceiverRegister,
 | 
					 | 
				
			||||||
} from "@/utils/signalR/TypedSignalR.Client";
 | 
					 | 
				
			||||||
import { ProgressStatus } from "@/utils/signalR/server.Hubs";
 | 
					 | 
				
			||||||
import { useRequiredInjection } from "@/utils/Common";
 | 
					import { useRequiredInjection } from "@/utils/Common";
 | 
				
			||||||
import { useAlertStore } from "./Alert";
 | 
					import { useAlertStore } from "./Alert";
 | 
				
			||||||
import { ResourceClient, ResourcePurpose } from "@/APIClient";
 | 
					import { ResourceClient, ResourcePurpose } from "@/APIClient";
 | 
				
			||||||
 | 
					import { useProgressStore } from "@/stores/progress";
 | 
				
			||||||
 | 
					import { ProgressStatus, type ProgressInfo } from "@/utils/signalR/server.Hubs";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface Props {
 | 
					interface Props {
 | 
				
			||||||
  maxMemory?: number;
 | 
					  maxMemory?: number;
 | 
				
			||||||
  examId?: string; // 新增examId属性
 | 
					  examId?: string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = withDefaults(defineProps<Props>(), {
 | 
					const props = withDefaults(defineProps<Props>(), {
 | 
				
			||||||
@@ -112,71 +117,82 @@ const emits = defineEmits<{
 | 
				
			|||||||
}>();
 | 
					}>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const alert = useRequiredInjection(useAlertStore);
 | 
					const alert = useRequiredInjection(useAlertStore);
 | 
				
			||||||
 | 
					const progressTracker = useProgressStore();
 | 
				
			||||||
const dialog = useDialogStore();
 | 
					const dialog = useDialogStore();
 | 
				
			||||||
const eqps = useEquipments();
 | 
					const eqps = useEquipments();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const isUploading = ref(false);
 | 
					 | 
				
			||||||
const isDownloading = ref(false);
 | 
					 | 
				
			||||||
const isProgramming = ref(false);
 | 
					 | 
				
			||||||
const availableBitstreams = ref<{ id: string; name: string }[]>([]);
 | 
					const availableBitstreams = ref<{ id: string; name: string }[]>([]);
 | 
				
			||||||
 | 
					 | 
				
			||||||
// Progress
 | 
					 | 
				
			||||||
const downloadTaskId = ref("");
 | 
					 | 
				
			||||||
const downloadProgress = ref(0);
 | 
					 | 
				
			||||||
const progressHubConnection = ref<HubConnection>();
 | 
					 | 
				
			||||||
const progressHubProxy = ref<IProgressHub>();
 | 
					 | 
				
			||||||
const progressHubReceiver: IProgressReceiver = {
 | 
					 | 
				
			||||||
  onReceiveProgress: async (msg) => {
 | 
					 | 
				
			||||||
    if (msg.taskId == downloadTaskId.value) {
 | 
					 | 
				
			||||||
      if (msg.status == ProgressStatus.InProgress) {
 | 
					 | 
				
			||||||
        downloadProgress.value = msg.progressPercent;
 | 
					 | 
				
			||||||
      } else if (msg.status == ProgressStatus.Failed) {
 | 
					 | 
				
			||||||
        dialog.error(msg.errorMessage);
 | 
					 | 
				
			||||||
      } else if (msg.status == ProgressStatus.Completed) {
 | 
					 | 
				
			||||||
        alert.info("比特流下载成功");
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
onMounted(async () => {
 | 
					 | 
				
			||||||
  progressHubConnection.value = AuthManager.createHubConnection("ProgressHub");
 | 
					 | 
				
			||||||
  progressHubProxy.value = getHubProxyFactory("IProgressHub").createHubProxy(
 | 
					 | 
				
			||||||
    progressHubConnection.value,
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
  getReceiverRegister("IProgressReceiver").register(
 | 
					 | 
				
			||||||
    progressHubConnection.value,
 | 
					 | 
				
			||||||
    progressHubReceiver,
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const fileInput = useTemplateRef("fileInput");
 | 
					const fileInput = useTemplateRef("fileInput");
 | 
				
			||||||
const bitstream = defineModel("bitstreamFile", {
 | 
					const bitstream = ref<File | undefined>(undefined);
 | 
				
			||||||
  type: File,
 | 
					
 | 
				
			||||||
  default: undefined,
 | 
					// 用一个状态变量替代多个
 | 
				
			||||||
});
 | 
					const currentTask = ref<"none" | "uploading" | "downloading" | "programming">(
 | 
				
			||||||
 | 
					  "none",
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					const currentBitstreamId = ref<string>("");
 | 
				
			||||||
 | 
					const currentProgressId = ref<string>("");
 | 
				
			||||||
 | 
					const currentProgressPercent = ref<number>(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 初始化时加载示例比特流
 | 
					 | 
				
			||||||
onMounted(async () => {
 | 
					onMounted(async () => {
 | 
				
			||||||
  if (!isUndefined(bitstream.value) && !isNull(fileInput.value)) {
 | 
					  if (bitstream.value && fileInput.value) {
 | 
				
			||||||
    let fileList = new DataTransfer();
 | 
					    let fileList = new DataTransfer();
 | 
				
			||||||
    fileList.items.add(bitstream.value);
 | 
					    fileList.items.add(bitstream.value);
 | 
				
			||||||
    fileInput.value.files = fileList.files;
 | 
					    fileInput.value.files = fileList.files;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					 | 
				
			||||||
  await loadAvailableBitstreams();
 | 
					  await loadAvailableBitstreams();
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 加载可用的比特流文件列表
 | 
					function handleFileChange(event: Event): void {
 | 
				
			||||||
 | 
					  const target = event.target as HTMLInputElement;
 | 
				
			||||||
 | 
					  const file = target.files?.[0];
 | 
				
			||||||
 | 
					  bitstream.value = file || undefined;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function checkFileInput(): boolean {
 | 
				
			||||||
 | 
					  if (!bitstream.value) {
 | 
				
			||||||
 | 
					    dialog.error(`未选择文件`);
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  const maxBytes = props.maxMemory! * 1024 * 1024;
 | 
				
			||||||
 | 
					  if (bitstream.value.size > maxBytes) {
 | 
				
			||||||
 | 
					    dialog.error(`文件大小超过最大限制: ${props.maxMemory}MB`);
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function downloadBitstream() {
 | 
				
			||||||
 | 
					  currentTask.value = "programming";
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    currentProgressId.value = await eqps.jtagDownloadBitstream(
 | 
				
			||||||
 | 
					      currentBitstreamId.value,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    progressTracker.register(
 | 
				
			||||||
 | 
					      currentProgressId.value,
 | 
				
			||||||
 | 
					      "programBitstream",
 | 
				
			||||||
 | 
					      handleProgressUpdate,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  } catch {
 | 
				
			||||||
 | 
					    dialog.error("比特流烧录失败");
 | 
				
			||||||
 | 
					    cleanProgressTracker();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function cleanProgressTracker() {
 | 
				
			||||||
 | 
					  currentTask.value = "none";
 | 
				
			||||||
 | 
					  currentProgressId.value = "";
 | 
				
			||||||
 | 
					  currentBitstreamId.value = "";
 | 
				
			||||||
 | 
					  currentProgressPercent.value = 0;
 | 
				
			||||||
 | 
					  progressTracker.unregister(currentProgressId.value, "programBitstream");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function loadAvailableBitstreams() {
 | 
					async function loadAvailableBitstreams() {
 | 
				
			||||||
  console.log("加载可用比特流文件,examId:", props.examId);
 | 
					 | 
				
			||||||
  if (!props.examId) {
 | 
					  if (!props.examId) {
 | 
				
			||||||
    availableBitstreams.value = [];
 | 
					    availableBitstreams.value = [];
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
    const resourceClient = AuthManager.createClient(ResourceClient);
 | 
					    const resourceClient = AuthManager.createClient(ResourceClient);
 | 
				
			||||||
    // 使用新的ResourceClient API获取比特流模板资源列表
 | 
					 | 
				
			||||||
    const resources = await resourceClient.getResourceList(
 | 
					    const resources = await resourceClient.getResourceList(
 | 
				
			||||||
      props.examId,
 | 
					      props.examId,
 | 
				
			||||||
      "bitstream",
 | 
					      "bitstream",
 | 
				
			||||||
@@ -185,129 +201,82 @@ async function loadAvailableBitstreams() {
 | 
				
			|||||||
    availableBitstreams.value =
 | 
					    availableBitstreams.value =
 | 
				
			||||||
      resources.map((r) => ({ id: r.id, name: r.name })) || [];
 | 
					      resources.map((r) => ({ id: r.id, name: r.name })) || [];
 | 
				
			||||||
  } catch (error) {
 | 
					  } catch (error) {
 | 
				
			||||||
    console.error("加载比特流列表失败:", error);
 | 
					 | 
				
			||||||
    availableBitstreams.value = [];
 | 
					    availableBitstreams.value = [];
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 下载示例比特流
 | 
					// 统一处理示例比特流的下载/烧录
 | 
				
			||||||
async function downloadExampleBitstream(bitstream: {
 | 
					async function handleExampleBitstream(
 | 
				
			||||||
  id: string;
 | 
					  action: "download" | "program",
 | 
				
			||||||
  name: string;
 | 
					  bitstreamObj: { id: string; name: string },
 | 
				
			||||||
}) {
 | 
					) {
 | 
				
			||||||
  if (isDownloading.value) return;
 | 
					  if (currentTask.value !== "none") return;
 | 
				
			||||||
 | 
					  currentBitstreamId.value = bitstreamObj.id;
 | 
				
			||||||
  isDownloading.value = true;
 | 
					  if (action === "download") {
 | 
				
			||||||
  try {
 | 
					    currentTask.value = "downloading";
 | 
				
			||||||
    const resourceClient = AuthManager.createClient(ResourceClient);
 | 
					    try {
 | 
				
			||||||
 | 
					      const resourceClient = AuthManager.createClient(ResourceClient);
 | 
				
			||||||
    // 使用新的ResourceClient API获取资源文件
 | 
					      const response = await resourceClient.getResourceById(bitstreamObj.id);
 | 
				
			||||||
    const response = await resourceClient.getResourceById(bitstream.id);
 | 
					      if (response && response.data) {
 | 
				
			||||||
 | 
					        const url = URL.createObjectURL(response.data);
 | 
				
			||||||
    if (response && response.data) {
 | 
					        const link = document.createElement("a");
 | 
				
			||||||
      // 创建下载链接
 | 
					        link.href = url;
 | 
				
			||||||
      const url = URL.createObjectURL(response.data);
 | 
					        link.download = response.fileName || bitstreamObj.name;
 | 
				
			||||||
      const link = document.createElement("a");
 | 
					        document.body.appendChild(link);
 | 
				
			||||||
      link.href = url;
 | 
					        link.click();
 | 
				
			||||||
      link.download = response.fileName || bitstream.name;
 | 
					        document.body.removeChild(link);
 | 
				
			||||||
      document.body.appendChild(link);
 | 
					        URL.revokeObjectURL(url);
 | 
				
			||||||
      link.click();
 | 
					        alert.info("示例比特流下载成功");
 | 
				
			||||||
      document.body.removeChild(link);
 | 
					      } else {
 | 
				
			||||||
      URL.revokeObjectURL(url);
 | 
					        alert.error("下载失败:响应数据为空");
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
      dialog.info("示例比特流下载成功");
 | 
					    } catch {
 | 
				
			||||||
    } else {
 | 
					      alert.error("下载示例比特流失败");
 | 
				
			||||||
      dialog.error("下载失败:响应数据为空");
 | 
					    } finally {
 | 
				
			||||||
 | 
					      currentTask.value = "none";
 | 
				
			||||||
 | 
					      currentBitstreamId.value = "";
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  } catch (error) {
 | 
					  } else if (action === "program") {
 | 
				
			||||||
    console.error("下载示例比特流失败:", error);
 | 
					    currentBitstreamId.value = bitstreamObj.id;
 | 
				
			||||||
    dialog.error("下载示例比特流失败");
 | 
					    await downloadBitstream();
 | 
				
			||||||
  } finally {
 | 
					 | 
				
			||||||
    isDownloading.value = false;
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 直接烧录示例比特流
 | 
					// 上传并下载
 | 
				
			||||||
async function programExampleBitstream(bitstream: {
 | 
					async function handleUploadAndDownload() {
 | 
				
			||||||
  id: string;
 | 
					  if (currentTask.value !== "none") return;
 | 
				
			||||||
  name: string;
 | 
					  if (!checkFileInput()) return;
 | 
				
			||||||
}) {
 | 
					 | 
				
			||||||
  if (isProgramming.value) return;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  isProgramming.value = true;
 | 
					  currentTask.value = "uploading";
 | 
				
			||||||
  try {
 | 
					 | 
				
			||||||
    const downloadTaskId = await eqps.jtagDownloadBitstream(bitstream.id);
 | 
					 | 
				
			||||||
  } catch (error) {
 | 
					 | 
				
			||||||
    console.error("烧录示例比特流失败:", error);
 | 
					 | 
				
			||||||
    dialog.error("烧录示例比特流失败");
 | 
					 | 
				
			||||||
  } finally {
 | 
					 | 
				
			||||||
    isProgramming.value = false;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function handleFileChange(event: Event): void {
 | 
					 | 
				
			||||||
  const target = event.target as HTMLInputElement;
 | 
					 | 
				
			||||||
  const file = target.files?.[0]; // 获取选中的第一个文件
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (!file) {
 | 
					 | 
				
			||||||
    return;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  bitstream.value = file;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function checkFile(file: File): boolean {
 | 
					 | 
				
			||||||
  const maxBytes = props.maxMemory! * 1024 * 1024; // 将最大容量从 MB 转换为字节
 | 
					 | 
				
			||||||
  if (file.size > maxBytes) {
 | 
					 | 
				
			||||||
    dialog.error(`文件大小超过最大限制: ${props.maxMemory}MB`);
 | 
					 | 
				
			||||||
    return false;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  return true;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async function handleClick(event: Event): Promise<void> {
 | 
					 | 
				
			||||||
  console.log("上传按钮被点击");
 | 
					 | 
				
			||||||
  if (isNull(bitstream.value) || isUndefined(bitstream.value)) {
 | 
					 | 
				
			||||||
    dialog.error(`未选择文件`);
 | 
					 | 
				
			||||||
    return;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (!checkFile(bitstream.value)) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  isUploading.value = true;
 | 
					 | 
				
			||||||
  let uploadedBitstreamId: string | null = null;
 | 
					  let uploadedBitstreamId: string | null = null;
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
    console.log("开始上传比特流文件:", bitstream.value.name);
 | 
					    uploadedBitstreamId = await eqps.jtagUploadBitstream(
 | 
				
			||||||
    const bitstreamId = await eqps.jtagUploadBitstream(
 | 
					      bitstream.value!,
 | 
				
			||||||
      bitstream.value,
 | 
					 | 
				
			||||||
      props.examId || "",
 | 
					      props.examId || "",
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    console.log("上传结果,ID:", bitstreamId);
 | 
					    if (!uploadedBitstreamId) throw new Error("上传失败");
 | 
				
			||||||
    if (bitstreamId === null || bitstreamId === undefined) {
 | 
					    emits("finishedUpload", bitstream.value!);
 | 
				
			||||||
      isUploading.value = false;
 | 
					  } catch {
 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    uploadedBitstreamId = bitstreamId;
 | 
					 | 
				
			||||||
  } catch (e) {
 | 
					 | 
				
			||||||
    dialog.error("上传失败");
 | 
					    dialog.error("上传失败");
 | 
				
			||||||
    console.error(e);
 | 
					    currentTask.value = "none";
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  isUploading.value = false;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Download
 | 
					  currentBitstreamId.value = uploadedBitstreamId;
 | 
				
			||||||
  try {
 | 
					
 | 
				
			||||||
    console.log("开始下载比特流,ID:", uploadedBitstreamId);
 | 
					  await downloadBitstream();
 | 
				
			||||||
    if (uploadedBitstreamId === null || uploadedBitstreamId === undefined) {
 | 
					}
 | 
				
			||||||
      dialog.error("uploadedBitstreamId is null or undefined");
 | 
					
 | 
				
			||||||
    } else {
 | 
					function handleProgressUpdate(msg: ProgressInfo) {
 | 
				
			||||||
      isDownloading.value = true;
 | 
					  // console.log(msg);
 | 
				
			||||||
      downloadTaskId.value =
 | 
					  if (msg.status === ProgressStatus.Running)
 | 
				
			||||||
        await eqps.jtagDownloadBitstream(uploadedBitstreamId);
 | 
					    currentProgressPercent.value = msg.progressPercent;
 | 
				
			||||||
    }
 | 
					  else if (msg.status === ProgressStatus.Failed) {
 | 
				
			||||||
  } catch (e) {
 | 
					    dialog.error(`比特流烧录失败: ${msg.errorMessage}`);
 | 
				
			||||||
    dialog.error("下载失败");
 | 
					    cleanProgressTracker();
 | 
				
			||||||
    console.error(e);
 | 
					  } else if (msg.status === ProgressStatus.Completed) {
 | 
				
			||||||
 | 
					    dialog.info("比特流烧录成功");
 | 
				
			||||||
 | 
					    cleanProgressTracker();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										83
									
								
								src/stores/progress.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								src/stores/progress.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,83 @@
 | 
				
			|||||||
 | 
					import type { HubConnection } from "@microsoft/signalr";
 | 
				
			||||||
 | 
					import type {
 | 
				
			||||||
 | 
					  IProgressHub,
 | 
				
			||||||
 | 
					  IProgressReceiver,
 | 
				
			||||||
 | 
					} from "@/utils/signalR/TypedSignalR.Client/server.Hubs";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  getHubProxyFactory,
 | 
				
			||||||
 | 
					  getReceiverRegister,
 | 
				
			||||||
 | 
					} from "@/utils/signalR/TypedSignalR.Client";
 | 
				
			||||||
 | 
					import { ProgressStatus, type ProgressInfo } from "@/utils/signalR/server.Hubs";
 | 
				
			||||||
 | 
					import { onMounted, onUnmounted, ref, shallowRef } from "vue";
 | 
				
			||||||
 | 
					import { defineStore } from "pinia";
 | 
				
			||||||
 | 
					import { AuthManager } from "@/utils/AuthManager";
 | 
				
			||||||
 | 
					import { forEach, isUndefined } from "lodash";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type ProgressCallback = (msg: ProgressInfo) => void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const useProgressStore = defineStore("progress", () => {
 | 
				
			||||||
 | 
					  // taskId -> name -> callback
 | 
				
			||||||
 | 
					  const progressCallbackFuncs = shallowRef<
 | 
				
			||||||
 | 
					    Map<string, Map<string, ProgressCallback>>
 | 
				
			||||||
 | 
					  >(new Map());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const progressHubConnection = shallowRef<HubConnection>();
 | 
				
			||||||
 | 
					  const progressHubProxy = shallowRef<IProgressHub>();
 | 
				
			||||||
 | 
					  const progressHubReceiver: IProgressReceiver = {
 | 
				
			||||||
 | 
					    onReceiveProgress: async (msg) => {
 | 
				
			||||||
 | 
					      const taskMap = progressCallbackFuncs.value.get(msg.taskId);
 | 
				
			||||||
 | 
					      if (taskMap) {
 | 
				
			||||||
 | 
					        for (const func of taskMap.values()) {
 | 
				
			||||||
 | 
					          func(msg);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  onMounted(async () => {
 | 
				
			||||||
 | 
					    progressHubConnection.value =
 | 
				
			||||||
 | 
					      AuthManager.createHubConnection("ProgressHub");
 | 
				
			||||||
 | 
					    progressHubProxy.value = getHubProxyFactory("IProgressHub").createHubProxy(
 | 
				
			||||||
 | 
					      progressHubConnection.value,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    getReceiverRegister("IProgressReceiver").register(
 | 
				
			||||||
 | 
					      progressHubConnection.value,
 | 
				
			||||||
 | 
					      progressHubReceiver,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    progressHubConnection.value.start();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  onUnmounted(() => {
 | 
				
			||||||
 | 
					    if (progressHubConnection.value) {
 | 
				
			||||||
 | 
					      progressHubConnection.value.stop();
 | 
				
			||||||
 | 
					      progressHubConnection.value = undefined;
 | 
				
			||||||
 | 
					      progressHubProxy.value = undefined;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function register(progressId: string, name: string, func: ProgressCallback) {
 | 
				
			||||||
 | 
					    progressHubProxy.value?.join(progressId);
 | 
				
			||||||
 | 
					    let taskMap = progressCallbackFuncs.value.get(progressId);
 | 
				
			||||||
 | 
					    if (!taskMap) {
 | 
				
			||||||
 | 
					      taskMap = new Map();
 | 
				
			||||||
 | 
					      progressCallbackFuncs.value?.set(progressId, taskMap);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    taskMap.set(name, func);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function unregister(taskId: string, name: string) {
 | 
				
			||||||
 | 
					    progressHubProxy.value?.leave(taskId);
 | 
				
			||||||
 | 
					    const taskMap = progressCallbackFuncs.value.get(taskId);
 | 
				
			||||||
 | 
					    if (taskMap) {
 | 
				
			||||||
 | 
					      taskMap.delete(name);
 | 
				
			||||||
 | 
					      if (taskMap.size === 0) {
 | 
				
			||||||
 | 
					        progressCallbackFuncs.value?.delete(taskId);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    register,
 | 
				
			||||||
 | 
					    unregister,
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
@@ -161,6 +161,14 @@ class IProgressHub_HubProxy implements IProgressHub {
 | 
				
			|||||||
    public readonly join = async (taskId: string): Promise<boolean> => {
 | 
					    public readonly join = async (taskId: string): Promise<boolean> => {
 | 
				
			||||||
        return await this.connection.invoke("Join", taskId);
 | 
					        return await this.connection.invoke("Join", taskId);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public readonly leave = async (taskId: string): Promise<boolean> => {
 | 
				
			||||||
 | 
					        return await this.connection.invoke("Leave", taskId);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public readonly getProgress = async (taskId: string): Promise<ProgressInfo> => {
 | 
				
			||||||
 | 
					        return await this.connection.invoke("GetProgress", taskId);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -48,6 +48,16 @@ export type IProgressHub = {
 | 
				
			|||||||
    * @returns Transpiled from System.Threading.Tasks.Task<bool>
 | 
					    * @returns Transpiled from System.Threading.Tasks.Task<bool>
 | 
				
			||||||
    */
 | 
					    */
 | 
				
			||||||
    join(taskId: string): Promise<boolean>;
 | 
					    join(taskId: string): Promise<boolean>;
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    * @param taskId Transpiled from string
 | 
				
			||||||
 | 
					    * @returns Transpiled from System.Threading.Tasks.Task<bool>
 | 
				
			||||||
 | 
					    */
 | 
				
			||||||
 | 
					    leave(taskId: string): Promise<boolean>;
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    * @param taskId Transpiled from string
 | 
				
			||||||
 | 
					    * @returns Transpiled from System.Threading.Tasks.Task<server.Hubs.ProgressInfo?>
 | 
				
			||||||
 | 
					    */
 | 
				
			||||||
 | 
					    getProgress(taskId: string): Promise<ProgressInfo>;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type IDigitalTubesReceiver = {
 | 
					export type IDigitalTubesReceiver = {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,11 +12,10 @@ export type DigitalTubeTaskStatus = {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
/** Transpiled from server.Hubs.ProgressStatus */
 | 
					/** Transpiled from server.Hubs.ProgressStatus */
 | 
				
			||||||
export enum ProgressStatus {
 | 
					export enum ProgressStatus {
 | 
				
			||||||
    Pending = 0,
 | 
					    Running = 0,
 | 
				
			||||||
    InProgress = 1,
 | 
					    Completed = 1,
 | 
				
			||||||
    Completed = 2,
 | 
					    Canceled = 2,
 | 
				
			||||||
    Canceled = 3,
 | 
					    Failed = 3,
 | 
				
			||||||
    Failed = 4,
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** Transpiled from server.Hubs.ProgressInfo */
 | 
					/** Transpiled from server.Hubs.ProgressInfo */
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user