fix: 修复进度条的问题

This commit is contained in:
SikongJueluo 2025-08-17 13:33:11 +08:00
parent 97b86acfa8
commit 55edfd771e
No known key found for this signature in database
13 changed files with 512 additions and 584 deletions

View File

@ -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) =>

View File

@ -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}");
} }
} }
}); });

View File

@ -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();
} }
} }

View File

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

View File

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

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

View File

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

View File

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

View File

@ -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
View 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,
};
});

View File

@ -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);
}
} }

View File

@ -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 = {

View File

@ -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 */