fix: 修复进度条的问题
This commit is contained in:
parent
97b86acfa8
commit
55edfd771e
|
@ -180,8 +180,7 @@ try
|
|||
builder.Services.AddHostedService(provider => provider.GetRequiredService<HttpHdmiVideoStreamService>());
|
||||
|
||||
// 添加进度跟踪服务
|
||||
builder.Services.AddSingleton<ProgressTrackerService>();
|
||||
builder.Services.AddHostedService(provider => provider.GetRequiredService<ProgressTrackerService>());
|
||||
builder.Services.AddSingleton<ProgressTracker>();
|
||||
|
||||
// Application Settings
|
||||
var app = builder.Build();
|
||||
|
@ -258,6 +257,8 @@ try
|
|||
|
||||
// Setup Program
|
||||
MsgBus.Init();
|
||||
var progressTracker = app.Services.GetRequiredService<ProgressTracker>();
|
||||
MsgBus.SetProgressTracker(progressTracker);
|
||||
|
||||
// Generate API Client
|
||||
app.MapGet("GetAPIClientCode", async (HttpContext context) =>
|
||||
|
|
|
@ -16,17 +16,12 @@ public class JtagController : ControllerBase
|
|||
{
|
||||
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 ResourceManager _resourceManager = new();
|
||||
|
||||
private const string BITSTREAM_PATH = "bitstream/Jtag";
|
||||
|
||||
public JtagController(ProgressTrackerService tracker)
|
||||
{
|
||||
_tracker = tracker;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 控制器首页信息
|
||||
/// </summary>
|
||||
|
@ -188,8 +183,8 @@ public class JtagController : ControllerBase
|
|||
logger.Info($"User {username} processing bitstream file of size: {fileBytes.Length} bytes");
|
||||
|
||||
// 定义进度跟踪
|
||||
var (taskId, progress) = _tracker.CreateTask(cancelToken);
|
||||
progress.Report(10);
|
||||
var taskId = _tracker.CreateTask(1000);
|
||||
_tracker.AdvanceProgress(taskId, 10);
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
|
@ -210,7 +205,8 @@ public class JtagController : ControllerBase
|
|||
if (!retBuffer.IsSuccessful)
|
||||
{
|
||||
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;
|
||||
}
|
||||
revBuffer = retBuffer.Value;
|
||||
|
@ -228,7 +224,7 @@ public class JtagController : ControllerBase
|
|||
var processedBytes = outputStream.ToArray();
|
||||
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);
|
||||
|
@ -237,12 +233,13 @@ public class JtagController : ControllerBase
|
|||
if (ret.IsSuccessful)
|
||||
{
|
||||
logger.Info($"User {username} successfully downloaded bitstream '{resource.ResourceName}' to device {address}");
|
||||
progress.Finish();
|
||||
_tracker.CompleteProgress(taskId);
|
||||
}
|
||||
else
|
||||
{
|
||||
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 System.Security.Claims;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using TypedSignalR.Client;
|
||||
using Tapper;
|
||||
using server.Services;
|
||||
|
||||
#pragma warning disable 1998
|
||||
|
||||
namespace server.Hubs;
|
||||
|
||||
[Hub]
|
||||
public interface IProgressHub
|
||||
{
|
||||
Task<bool> Join(string taskId);
|
||||
Task<bool> Leave(string taskId);
|
||||
Task<ProgressInfo?> GetProgress(string taskId);
|
||||
}
|
||||
|
||||
[Receiver]
|
||||
|
@ -23,8 +26,7 @@ public interface IProgressReceiver
|
|||
[TranspilationSource]
|
||||
public enum ProgressStatus
|
||||
{
|
||||
Pending,
|
||||
InProgress,
|
||||
Running,
|
||||
Completed,
|
||||
Canceled,
|
||||
Failed
|
||||
|
@ -33,10 +35,10 @@ public enum ProgressStatus
|
|||
[TranspilationSource]
|
||||
public class ProgressInfo
|
||||
{
|
||||
public virtual string TaskId { get; } = string.Empty;
|
||||
public virtual ProgressStatus Status { get; }
|
||||
public virtual int ProgressPercent { get; } = 0;
|
||||
public virtual string ErrorMessage { get; } = string.Empty;
|
||||
public required string TaskId { get; set; }
|
||||
public required ProgressStatus Status { get; set; }
|
||||
public required int ProgressPercent { get; set; }
|
||||
public required string ErrorMessage { get; set; }
|
||||
};
|
||||
|
||||
[Authorize]
|
||||
|
@ -44,18 +46,32 @@ public class ProgressInfo
|
|||
public class ProgressHub : Hub<IProgressReceiver>, IProgressHub
|
||||
{
|
||||
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
|
||||
|
||||
private readonly IHubContext<ProgressHub, IProgressReceiver> _hubContext;
|
||||
private readonly ProgressTrackerService _tracker;
|
||||
|
||||
public ProgressHub(IHubContext<ProgressHub, IProgressReceiver> hubContext, ProgressTrackerService tracker)
|
||||
{
|
||||
_hubContext = hubContext;
|
||||
_tracker = tracker;
|
||||
}
|
||||
private readonly ProgressTracker _progressTracker = MsgBus.ProgressTracker;
|
||||
|
||||
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>
|
||||
public static class MsgBus
|
||||
public sealed class MsgBus
|
||||
{
|
||||
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
|
||||
|
||||
|
@ -11,12 +12,39 @@ public static class MsgBus
|
|||
/// </summary>
|
||||
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;
|
||||
/// <summary>
|
||||
/// 获取通信总线运行状态
|
||||
/// </summary>
|
||||
public static bool IsRunning { get { return isRunning; } }
|
||||
|
||||
private MsgBus() { }
|
||||
|
||||
static MsgBus() { }
|
||||
|
||||
/// <summary>
|
||||
/// 通信总线初始化
|
||||
/// </summary>
|
||||
|
|
|
@ -380,6 +380,7 @@ public class JtagStatusReg
|
|||
public class Jtag
|
||||
{
|
||||
private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
|
||||
private readonly ProgressTracker? _progressTracker;
|
||||
|
||||
private const int CLOCK_FREQ = 50; // MHz
|
||||
|
||||
|
@ -392,18 +393,21 @@ public class Jtag
|
|||
public readonly string address;
|
||||
private IPEndPoint ep;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Jtag 构造函数
|
||||
/// </summary>
|
||||
/// <param name="address">目标 IP 地址</param>
|
||||
/// <param name="port">目标 UDP 端口</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.port = port;
|
||||
this.ep = new IPEndPoint(IPAddress.Parse(address), port);
|
||||
this.timeout = timeout;
|
||||
this._progressTracker = progressTracker;
|
||||
}
|
||||
|
||||
async ValueTask<Result<uint>> ReadFIFO(uint devAddr)
|
||||
|
@ -444,10 +448,10 @@ public class Jtag
|
|||
|
||||
async ValueTask<Result<bool>> WriteFIFO(
|
||||
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.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);
|
||||
if (!ret.IsSuccessful) return new(ret.Error);
|
||||
progress?.Finish();
|
||||
_progressTracker?.AdvanceProgress(progressId, 10);
|
||||
return ret.Value;
|
||||
}
|
||||
}
|
||||
|
||||
async ValueTask<Result<bool>> WriteFIFO(
|
||||
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.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);
|
||||
if (!ret.IsSuccessful) return new(ret.Error);
|
||||
progress?.Finish();
|
||||
_progressTracker?.AdvanceProgress(progressId, 10);
|
||||
return ret.Value;
|
||||
}
|
||||
}
|
||||
|
@ -564,7 +568,7 @@ public class Jtag
|
|||
}
|
||||
|
||||
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));
|
||||
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"));
|
||||
}
|
||||
|
||||
progress?.Report(10);
|
||||
_progressTracker?.AdvanceProgress(progressId, 10);
|
||||
|
||||
{
|
||||
var ret = await WriteFIFO(
|
||||
JtagAddr.WRITE_DATA,
|
||||
bytesArray, 0x01_00_00_00,
|
||||
JtagState.CMD_EXEC_FINISH,
|
||||
progress: progress?.CreateChild(90)
|
||||
0,
|
||||
progressId
|
||||
);
|
||||
|
||||
if (!ret.IsSuccessful) return new(ret.Error);
|
||||
|
@ -709,58 +714,53 @@ public class Jtag
|
|||
/// 下载比特流到 JTAG 设备
|
||||
/// </summary>
|
||||
/// <param name="bitstream">比特流数据</param>
|
||||
/// <param name="progress">进度报告器</param>
|
||||
/// <param name="progressId">进度ID</param>
|
||||
/// <returns>指示下载是否成功的异步结果</returns>
|
||||
public async ValueTask<Result<bool>> DownloadBitstream(
|
||||
byte[] bitstream, ProgressReporter? progress = null)
|
||||
byte[] bitstream, string progressId = "")
|
||||
{
|
||||
// Clear Data
|
||||
MsgBus.UDPServer.ClearUDPData(this.address, 0);
|
||||
|
||||
logger.Trace($"Clear up udp server {this.address,0} receive data");
|
||||
if (progress != null)
|
||||
{
|
||||
progress.ExpectedSteps = 25;
|
||||
progress.Increase();
|
||||
}
|
||||
|
||||
Result<bool> ret;
|
||||
|
||||
ret = await CloseTest();
|
||||
if (!ret.IsSuccessful) return new(ret.Error);
|
||||
else if (!ret.Value) return new(new Exception("Jtag Close Test Failed"));
|
||||
progress?.Increase();
|
||||
_progressTracker?.AdvanceProgress(progressId, 10);
|
||||
|
||||
ret = await RunTest();
|
||||
if (!ret.IsSuccessful) return new(ret.Error);
|
||||
else if (!ret.Value) return new(new Exception("Jtag Run Test Failed"));
|
||||
progress?.Increase();
|
||||
_progressTracker?.AdvanceProgress(progressId, 10);
|
||||
|
||||
logger.Trace("Jtag initialize");
|
||||
|
||||
ret = await ExecRDCmd(JtagCmd.JTAG_DR_JRST);
|
||||
if (!ret.IsSuccessful) return new(ret.Error);
|
||||
else if (!ret.Value) return new(new Exception("Jtag Execute Command JTAG_DR_JRST Failed"));
|
||||
progress?.Increase();
|
||||
_progressTracker?.AdvanceProgress(progressId, 10);
|
||||
|
||||
ret = await RunTest();
|
||||
if (!ret.IsSuccessful) return new(ret.Error);
|
||||
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);
|
||||
if (!ret.IsSuccessful) return new(ret.Error);
|
||||
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");
|
||||
|
||||
ret = await IdleDelay(100000);
|
||||
if (!ret.IsSuccessful) return new(ret.Error);
|
||||
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);
|
||||
else if (!ret.Value) return new(new Exception("Jtag Load Data Failed"));
|
||||
|
||||
|
@ -769,40 +769,40 @@ public class Jtag
|
|||
ret = await CloseTest();
|
||||
if (!ret.IsSuccessful) return new(ret.Error);
|
||||
else if (!ret.Value) return new(new Exception("Jtag Close Test Failed"));
|
||||
progress?.Increase();
|
||||
_progressTracker?.AdvanceProgress(progressId, 10);
|
||||
|
||||
ret = await RunTest();
|
||||
if (!ret.IsSuccessful) return new(ret.Error);
|
||||
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);
|
||||
if (!ret.IsSuccessful) return new(ret.Error);
|
||||
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");
|
||||
|
||||
ret = await IdleDelay(10000);
|
||||
if (!ret.IsSuccessful) return new(ret.Error);
|
||||
else if (!ret.Value) return new(new Exception("Jtag IDLE Delay Failed"));
|
||||
progress?.Increase();
|
||||
_progressTracker?.AdvanceProgress(progressId, 10);
|
||||
|
||||
var retCode = await ReadStatusReg();
|
||||
if (!retCode.IsSuccessful) return new(retCode.Error);
|
||||
var jtagStatus = new JtagStatusReg(retCode.Value);
|
||||
if (!(jtagStatus.done && jtagStatus.wakeup_over && jtagStatus.init_complete))
|
||||
return new(new Exception("Jtag download bitstream failed"));
|
||||
progress?.Increase();
|
||||
_progressTracker?.AdvanceProgress(progressId, 10);
|
||||
|
||||
ret = await CloseTest();
|
||||
if (!ret.IsSuccessful) return new(ret.Error);
|
||||
else if (!ret.Value) return new(new Exception("Jtag Close Test Failed"));
|
||||
logger.Trace("Jtag download bitstream successfully");
|
||||
progress?.Increase();
|
||||
_progressTracker?.AdvanceProgress(progressId, 10);
|
||||
|
||||
// Finish
|
||||
progress?.Finish();
|
||||
_progressTracker?.AdvanceProgress(progressId, 10);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
/// UDP客户端发送池
|
||||
/// </summary>
|
||||
public class UDPClientPool
|
||||
public sealed class UDPClientPool
|
||||
{
|
||||
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>
|
||||
/// 发送字符串
|
||||
|
@ -183,40 +183,6 @@ public class UDPClientPool
|
|||
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>
|
||||
|
@ -607,11 +573,11 @@ public class UDPClientPool
|
|||
/// <param name="devAddr">设备地址</param>
|
||||
/// <param name="data">要写入的32位数据</param>
|
||||
/// <param name="timeout">超时时间(毫秒)</param>
|
||||
/// <param name="progress">进度报告器</param>
|
||||
/// <param name="progressId">进度报告器</param>
|
||||
/// <returns>写入结果,true表示写入成功</returns>
|
||||
public static async ValueTask<Result<bool>> WriteAddr(
|
||||
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 opts = new SendAddrPackOptions()
|
||||
|
@ -622,17 +588,18 @@ public class UDPClientPool
|
|||
Address = devAddr,
|
||||
IsWrite = true,
|
||||
};
|
||||
progress?.Report(20);
|
||||
_progressTracker.AdvanceProgress(progressId, 10);
|
||||
|
||||
// Write Register
|
||||
ret = await UDPClientPool.SendAddrPackAsync(endPoint, new SendAddrPackage(opts));
|
||||
if (!ret) return new(new Exception("Send 1st address package failed!"));
|
||||
progress?.Report(40);
|
||||
_progressTracker.AdvanceProgress(progressId, 10);
|
||||
|
||||
// Send Data Package
|
||||
ret = await UDPClientPool.SendDataPackAsync(endPoint,
|
||||
new SendDataPackage(Common.Number.NumberToBytes(data, 4).Value));
|
||||
if (!ret) return new(new Exception("Send data package failed!"));
|
||||
progress?.Report(60);
|
||||
_progressTracker.AdvanceProgress(progressId, 10);
|
||||
|
||||
// Check Msg Bus
|
||||
if (!MsgBus.IsRunning)
|
||||
|
@ -642,7 +609,7 @@ public class UDPClientPool
|
|||
var udpWriteAck = await MsgBus.UDPServer.WaitForAckAsync(
|
||||
endPoint.Address.ToString(), taskID, endPoint.Port, timeout);
|
||||
if (!udpWriteAck.IsSuccessful) return new(udpWriteAck.Error);
|
||||
progress?.Finish();
|
||||
_progressTracker.AdvanceProgress(progressId, 10);
|
||||
|
||||
return udpWriteAck.Value.IsSuccessful;
|
||||
}
|
||||
|
@ -655,11 +622,11 @@ public class UDPClientPool
|
|||
/// <param name="devAddr">设备地址</param>
|
||||
/// <param name="dataArray">要写入的字节数组</param>
|
||||
/// <param name="timeout">超时时间(毫秒)</param>
|
||||
/// <param name="progress">进度报告器</param>
|
||||
/// <param name="progressId">进度报告器</param>
|
||||
/// <returns>写入结果,true表示写入成功</returns>
|
||||
public static async ValueTask<Result<bool>> WriteAddr(
|
||||
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 opts = new SendAddrPackOptions()
|
||||
|
@ -681,8 +648,6 @@ public class UDPClientPool
|
|||
var writeTimes = hasRest ?
|
||||
dataArray.Length / (max4BytesPerRead * (32 / 8)) + 1 :
|
||||
dataArray.Length / (max4BytesPerRead * (32 / 8));
|
||||
if (progress != null)
|
||||
progress.ExpectedSteps = writeTimes;
|
||||
for (var i = 0; i < writeTimes; i++)
|
||||
{
|
||||
// Sperate Data Array
|
||||
|
@ -712,10 +677,9 @@ public class UDPClientPool
|
|||
if (!udpWriteAck.Value.IsSuccessful)
|
||||
return false;
|
||||
|
||||
progress?.Increase();
|
||||
_progressTracker.AdvanceProgress(progressId, 10);
|
||||
}
|
||||
|
||||
progress?.Finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -16,22 +16,32 @@
|
|||
<span class="text-sm">{{ bitstream.name }}</span>
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
@click="downloadExampleBitstream(bitstream)"
|
||||
@click="handleExampleBitstream('download', bitstream)"
|
||||
class="btn btn-sm btn-secondary"
|
||||
:disabled="isDownloading || isProgramming"
|
||||
:disabled="currentTask !== 'none'"
|
||||
>
|
||||
<div
|
||||
v-if="
|
||||
currentTask === 'downloading' &&
|
||||
currentBitstreamId === bitstream.id
|
||||
"
|
||||
>
|
||||
<div v-if="isDownloading">
|
||||
<span class="loading loading-spinner loading-xs"></span>
|
||||
{{ downloadProgress }}%
|
||||
下载中...
|
||||
</div>
|
||||
<div v-else>下载示例</div>
|
||||
</button>
|
||||
<button
|
||||
@click="programExampleBitstream(bitstream)"
|
||||
@click="handleExampleBitstream('program', bitstream)"
|
||||
class="btn btn-sm btn-primary"
|
||||
:disabled="isDownloading || isProgramming"
|
||||
:disabled="currentTask !== 'none'"
|
||||
>
|
||||
<div
|
||||
v-if="
|
||||
currentTask === 'programming' &&
|
||||
currentBitstreamId === bitstream.id
|
||||
"
|
||||
>
|
||||
<div v-if="isProgramming">
|
||||
<span class="loading loading-spinner loading-xs"></span>
|
||||
烧录中...
|
||||
</div>
|
||||
|
@ -63,14 +73,18 @@
|
|||
<!-- Upload Button -->
|
||||
<div class="card-actions w-full">
|
||||
<button
|
||||
@click="handleClick"
|
||||
@click="handleUploadAndDownload"
|
||||
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>
|
||||
上传中...
|
||||
</div>
|
||||
<div v-else-if="currentTask === 'programming'">
|
||||
<span class="loading loading-spinner"></span>
|
||||
{{ currentProgressPercent }}% ...
|
||||
</div>
|
||||
<div v-else>上传并下载</div>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -78,28 +92,19 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, useTemplateRef, onMounted } from "vue";
|
||||
import { AuthManager } from "@/utils/AuthManager"; // Adjust the path based on your project structure
|
||||
import { ref, useTemplateRef, onMounted } from "vue";
|
||||
import { AuthManager } from "@/utils/AuthManager";
|
||||
import { useDialogStore } from "@/stores/dialog";
|
||||
import { isNull, isUndefined } from "lodash";
|
||||
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 { useAlertStore } from "./Alert";
|
||||
import { ResourceClient, ResourcePurpose } from "@/APIClient";
|
||||
import { useProgressStore } from "@/stores/progress";
|
||||
import { ProgressStatus, type ProgressInfo } from "@/utils/signalR/server.Hubs";
|
||||
|
||||
interface Props {
|
||||
maxMemory?: number;
|
||||
examId?: string; // 新增examId属性
|
||||
examId?: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
|
@ -112,71 +117,82 @@ const emits = defineEmits<{
|
|||
}>();
|
||||
|
||||
const alert = useRequiredInjection(useAlertStore);
|
||||
const progressTracker = useProgressStore();
|
||||
const dialog = useDialogStore();
|
||||
const eqps = useEquipments();
|
||||
|
||||
const isUploading = ref(false);
|
||||
const isDownloading = ref(false);
|
||||
const isProgramming = ref(false);
|
||||
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 bitstream = defineModel("bitstreamFile", {
|
||||
type: File,
|
||||
default: undefined,
|
||||
});
|
||||
const bitstream = ref<File | undefined>(undefined);
|
||||
|
||||
// 用一个状态变量替代多个
|
||||
const currentTask = ref<"none" | "uploading" | "downloading" | "programming">(
|
||||
"none",
|
||||
);
|
||||
const currentBitstreamId = ref<string>("");
|
||||
const currentProgressId = ref<string>("");
|
||||
const currentProgressPercent = ref<number>(0);
|
||||
|
||||
// 初始化时加载示例比特流
|
||||
onMounted(async () => {
|
||||
if (!isUndefined(bitstream.value) && !isNull(fileInput.value)) {
|
||||
if (bitstream.value && fileInput.value) {
|
||||
let fileList = new DataTransfer();
|
||||
fileList.items.add(bitstream.value);
|
||||
fileInput.value.files = fileList.files;
|
||||
}
|
||||
|
||||
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() {
|
||||
console.log("加载可用比特流文件,examId:", props.examId);
|
||||
if (!props.examId) {
|
||||
availableBitstreams.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const resourceClient = AuthManager.createClient(ResourceClient);
|
||||
// 使用新的ResourceClient API获取比特流模板资源列表
|
||||
const resources = await resourceClient.getResourceList(
|
||||
props.examId,
|
||||
"bitstream",
|
||||
|
@ -185,129 +201,82 @@ async function loadAvailableBitstreams() {
|
|||
availableBitstreams.value =
|
||||
resources.map((r) => ({ id: r.id, name: r.name })) || [];
|
||||
} catch (error) {
|
||||
console.error("加载比特流列表失败:", error);
|
||||
availableBitstreams.value = [];
|
||||
}
|
||||
}
|
||||
|
||||
// 下载示例比特流
|
||||
async function downloadExampleBitstream(bitstream: {
|
||||
id: string;
|
||||
name: string;
|
||||
}) {
|
||||
if (isDownloading.value) return;
|
||||
|
||||
isDownloading.value = true;
|
||||
// 统一处理示例比特流的下载/烧录
|
||||
async function handleExampleBitstream(
|
||||
action: "download" | "program",
|
||||
bitstreamObj: { id: string; name: string },
|
||||
) {
|
||||
if (currentTask.value !== "none") return;
|
||||
currentBitstreamId.value = bitstreamObj.id;
|
||||
if (action === "download") {
|
||||
currentTask.value = "downloading";
|
||||
try {
|
||||
const resourceClient = AuthManager.createClient(ResourceClient);
|
||||
|
||||
// 使用新的ResourceClient API获取资源文件
|
||||
const response = await resourceClient.getResourceById(bitstream.id);
|
||||
|
||||
const response = await resourceClient.getResourceById(bitstreamObj.id);
|
||||
if (response && response.data) {
|
||||
// 创建下载链接
|
||||
const url = URL.createObjectURL(response.data);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = response.fileName || bitstream.name;
|
||||
link.download = response.fileName || bitstreamObj.name;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
dialog.info("示例比特流下载成功");
|
||||
alert.info("示例比特流下载成功");
|
||||
} else {
|
||||
dialog.error("下载失败:响应数据为空");
|
||||
alert.error("下载失败:响应数据为空");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("下载示例比特流失败:", error);
|
||||
dialog.error("下载示例比特流失败");
|
||||
} catch {
|
||||
alert.error("下载示例比特流失败");
|
||||
} finally {
|
||||
isDownloading.value = false;
|
||||
currentTask.value = "none";
|
||||
currentBitstreamId.value = "";
|
||||
}
|
||||
} else if (action === "program") {
|
||||
currentBitstreamId.value = bitstreamObj.id;
|
||||
await downloadBitstream();
|
||||
}
|
||||
}
|
||||
|
||||
// 直接烧录示例比特流
|
||||
async function programExampleBitstream(bitstream: {
|
||||
id: string;
|
||||
name: string;
|
||||
}) {
|
||||
if (isProgramming.value) return;
|
||||
// 上传并下载
|
||||
async function handleUploadAndDownload() {
|
||||
if (currentTask.value !== "none") return;
|
||||
if (!checkFileInput()) return;
|
||||
|
||||
isProgramming.value = true;
|
||||
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;
|
||||
currentTask.value = "uploading";
|
||||
let uploadedBitstreamId: string | null = null;
|
||||
try {
|
||||
console.log("开始上传比特流文件:", bitstream.value.name);
|
||||
const bitstreamId = await eqps.jtagUploadBitstream(
|
||||
bitstream.value,
|
||||
uploadedBitstreamId = await eqps.jtagUploadBitstream(
|
||||
bitstream.value!,
|
||||
props.examId || "",
|
||||
);
|
||||
console.log("上传结果,ID:", bitstreamId);
|
||||
if (bitstreamId === null || bitstreamId === undefined) {
|
||||
isUploading.value = false;
|
||||
return;
|
||||
}
|
||||
uploadedBitstreamId = bitstreamId;
|
||||
} catch (e) {
|
||||
if (!uploadedBitstreamId) throw new Error("上传失败");
|
||||
emits("finishedUpload", bitstream.value!);
|
||||
} catch {
|
||||
dialog.error("上传失败");
|
||||
console.error(e);
|
||||
currentTask.value = "none";
|
||||
return;
|
||||
}
|
||||
isUploading.value = false;
|
||||
|
||||
// Download
|
||||
try {
|
||||
console.log("开始下载比特流,ID:", uploadedBitstreamId);
|
||||
if (uploadedBitstreamId === null || uploadedBitstreamId === undefined) {
|
||||
dialog.error("uploadedBitstreamId is null or undefined");
|
||||
} else {
|
||||
isDownloading.value = true;
|
||||
downloadTaskId.value =
|
||||
await eqps.jtagDownloadBitstream(uploadedBitstreamId);
|
||||
currentBitstreamId.value = uploadedBitstreamId;
|
||||
|
||||
await downloadBitstream();
|
||||
}
|
||||
} catch (e) {
|
||||
dialog.error("下载失败");
|
||||
console.error(e);
|
||||
|
||||
function handleProgressUpdate(msg: ProgressInfo) {
|
||||
// console.log(msg);
|
||||
if (msg.status === ProgressStatus.Running)
|
||||
currentProgressPercent.value = msg.progressPercent;
|
||||
else if (msg.status === ProgressStatus.Failed) {
|
||||
dialog.error(`比特流烧录失败: ${msg.errorMessage}`);
|
||||
cleanProgressTracker();
|
||||
} else if (msg.status === ProgressStatus.Completed) {
|
||||
dialog.info("比特流烧录成功");
|
||||
cleanProgressTracker();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -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> => {
|
||||
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>
|
||||
*/
|
||||
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 = {
|
||||
|
|
|
@ -12,11 +12,10 @@ export type DigitalTubeTaskStatus = {
|
|||
|
||||
/** Transpiled from server.Hubs.ProgressStatus */
|
||||
export enum ProgressStatus {
|
||||
Pending = 0,
|
||||
InProgress = 1,
|
||||
Completed = 2,
|
||||
Canceled = 3,
|
||||
Failed = 4,
|
||||
Running = 0,
|
||||
Completed = 1,
|
||||
Canceled = 2,
|
||||
Failed = 3,
|
||||
}
|
||||
|
||||
/** Transpiled from server.Hubs.ProgressInfo */
|
||||
|
|
Loading…
Reference in New Issue