feat: 添加下载进度条
This commit is contained in:
parent
e0ac21d141
commit
aff9da2a60
|
@ -303,11 +303,8 @@ async function generateApiClient(): Promise<void> {
|
||||||
async function generateSignalRClient(): Promise<void> {
|
async function generateSignalRClient(): Promise<void> {
|
||||||
console.log("Generating SignalR TypeScript client...");
|
console.log("Generating SignalR TypeScript client...");
|
||||||
try {
|
try {
|
||||||
// TypedSignalR.Client.TypeScript.Analyzer 会在编译时自动生成客户端
|
|
||||||
// 我们只需要确保服务器项目构建一次即可生成 TypeScript 客户端
|
|
||||||
const { stdout, stderr } = await execAsync(
|
const { stdout, stderr } = await execAsync(
|
||||||
"dotnet build --configuration Release",
|
"dotnet tsrts --project ./server/server.csproj --output ./src/",
|
||||||
{ cwd: "./server" }
|
|
||||||
);
|
);
|
||||||
if (stdout) console.log(stdout);
|
if (stdout) console.log(stdout);
|
||||||
if (stderr) console.error(stderr);
|
if (stderr) console.error(stderr);
|
||||||
|
|
|
@ -283,4 +283,28 @@ public class NumberTest
|
||||||
var reversed2 = Number.ReverseBits(new byte[0]);
|
var reversed2 = Number.ReverseBits(new byte[0]);
|
||||||
Assert.Empty(reversed2);
|
Assert.Empty(reversed2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 测试 GetLength
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void Test_GetLength()
|
||||||
|
{
|
||||||
|
Assert.Equal(5, Number.GetLength(12345));
|
||||||
|
Assert.Equal(4, Number.GetLength(-123));
|
||||||
|
Assert.Equal(1, Number.GetLength(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 测试 IntPow
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void Test_IntPow()
|
||||||
|
{
|
||||||
|
Assert.Equal(8, Number.IntPow(2, 3));
|
||||||
|
Assert.Equal(1, Number.IntPow(5, 0));
|
||||||
|
Assert.Equal(0, Number.IntPow(0, 5));
|
||||||
|
Assert.Equal(7, Number.IntPow(7, 1));
|
||||||
|
Assert.Equal(81, Number.IntPow(3, 4));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
using Moq;
|
||||||
|
using server.Hubs;
|
||||||
|
using server.Services;
|
||||||
|
|
||||||
|
public class ProgressTrackerTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task Test_ProgressReporter_Basic()
|
||||||
|
{
|
||||||
|
int reportedValue = -1;
|
||||||
|
var reporter = new ProgressReporter(async v => { reportedValue = v; await Task.CompletedTask; }, 0, 100, 10);
|
||||||
|
|
||||||
|
// Report
|
||||||
|
reporter.Report(50);
|
||||||
|
Assert.Equal(50, reporter.Progress);
|
||||||
|
Assert.Equal(ProgressStatus.InProgress, reporter.Status);
|
||||||
|
Assert.Equal(50, reportedValue);
|
||||||
|
|
||||||
|
// Increase by step
|
||||||
|
reporter.Increase();
|
||||||
|
Assert.Equal(60, reporter.Progress);
|
||||||
|
|
||||||
|
// Increase by value
|
||||||
|
reporter.Increase(20);
|
||||||
|
Assert.Equal(80, reporter.Progress);
|
||||||
|
|
||||||
|
// Finish
|
||||||
|
reporter.Finish();
|
||||||
|
Assert.Equal(ProgressStatus.Completed, reporter.Status);
|
||||||
|
Assert.Equal(100, reporter.Progress);
|
||||||
|
|
||||||
|
// Cancel
|
||||||
|
reporter = new ProgressReporter(async v => { reportedValue = v; await Task.CompletedTask; }, 0, 100, 10);
|
||||||
|
reporter.Cancel();
|
||||||
|
Assert.Equal(ProgressStatus.Canceled, reporter.Status);
|
||||||
|
Assert.Equal("User Cancelled", reporter.ErrorMessage);
|
||||||
|
|
||||||
|
// Error
|
||||||
|
reporter = new ProgressReporter(async v => { reportedValue = v; await Task.CompletedTask; }, 0, 100, 10);
|
||||||
|
reporter.Error("Test Error");
|
||||||
|
Assert.Equal(ProgressStatus.Failed, reporter.Status);
|
||||||
|
Assert.Equal("Test Error", reporter.ErrorMessage);
|
||||||
|
|
||||||
|
// CreateChild
|
||||||
|
var parent = new ProgressReporter(async v => { await Task.CompletedTask; }, 10, 100, 5);
|
||||||
|
var child = parent.CreateChild(50, 5);
|
||||||
|
Assert.Equal(ProgressStatus.Pending, child.Status);
|
||||||
|
Assert.NotNull(child);
|
||||||
|
|
||||||
|
// Child Increase
|
||||||
|
child.Increase();
|
||||||
|
Assert.Equal(ProgressStatus.InProgress, child.Status);
|
||||||
|
Assert.Equal(20, child.ProgressPercent);
|
||||||
|
Assert.Equal(20, parent.Progress);
|
||||||
|
|
||||||
|
// Child Complete
|
||||||
|
child.Finish();
|
||||||
|
Assert.Equal(ProgressStatus.Completed, child.Status);
|
||||||
|
Assert.Equal(100, child.ProgressPercent);
|
||||||
|
Assert.Equal(60, parent.Progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Test_ProgressTrackerService_Basic()
|
||||||
|
{
|
||||||
|
// Mock SignalR HubContext
|
||||||
|
var mockHubContext = new Mock<IHubContext<ProgressHub, IProgressReceiver>>();
|
||||||
|
var service = new ProgressTrackerService(mockHubContext.Object);
|
||||||
|
|
||||||
|
// CreateTask
|
||||||
|
var (taskId, reporter) = service.CreateTask();
|
||||||
|
Assert.NotNull(taskId);
|
||||||
|
Assert.NotNull(reporter);
|
||||||
|
|
||||||
|
// GetReporter
|
||||||
|
var optReporter = service.GetReporter(taskId);
|
||||||
|
Assert.True(optReporter.HasValue);
|
||||||
|
Assert.Equal(reporter, optReporter.Value);
|
||||||
|
|
||||||
|
// GetProgressStatus
|
||||||
|
var optStatus = service.GetProgressStatus(taskId);
|
||||||
|
Assert.True(optStatus.HasValue);
|
||||||
|
Assert.Equal(ProgressStatus.Pending, optStatus.Value);
|
||||||
|
|
||||||
|
// BindTask
|
||||||
|
var bindResult = service.BindTask(taskId, "conn1");
|
||||||
|
Assert.True(bindResult);
|
||||||
|
|
||||||
|
// CancelTask
|
||||||
|
var cancelResult = service.CancelTask(taskId);
|
||||||
|
Assert.True(cancelResult);
|
||||||
|
|
||||||
|
// After cancel, status should be Cancelled
|
||||||
|
var optStatus2 = service.GetProgressStatus(taskId);
|
||||||
|
Assert.True(optStatus2.HasValue);
|
||||||
|
Assert.Equal(ProgressStatus.Canceled, optStatus2.Value);
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,6 +11,7 @@
|
||||||
<PackageReference Include="coverlet.collector" Version="6.0.2" />
|
<PackageReference Include="coverlet.collector" Version="6.0.2" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.4" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.4" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||||
|
<PackageReference Include="Moq" Version="4.20.72" />
|
||||||
<PackageReference Include="xunit" Version="2.9.2" />
|
<PackageReference Include="xunit" Version="2.9.2" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
@ -148,6 +148,10 @@ try
|
||||||
builder.Services.AddSingleton<HttpHdmiVideoStreamService>();
|
builder.Services.AddSingleton<HttpHdmiVideoStreamService>();
|
||||||
builder.Services.AddHostedService(provider => provider.GetRequiredService<HttpHdmiVideoStreamService>());
|
builder.Services.AddHostedService(provider => provider.GetRequiredService<HttpHdmiVideoStreamService>());
|
||||||
|
|
||||||
|
// 添加进度跟踪服务
|
||||||
|
builder.Services.AddSingleton<ProgressTrackerService>();
|
||||||
|
builder.Services.AddHostedService(provider => provider.GetRequiredService<ProgressTrackerService>());
|
||||||
|
|
||||||
// Application Settings
|
// Application Settings
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
// Configure the HTTP request pipeline.
|
// Configure the HTTP request pipeline.
|
||||||
|
@ -217,7 +221,8 @@ try
|
||||||
|
|
||||||
// Router
|
// Router
|
||||||
app.MapControllers();
|
app.MapControllers();
|
||||||
app.MapHub<server.Hubs.JtagHub.JtagHub>("hubs/JtagHub");
|
app.MapHub<server.Hubs.JtagHub>("hubs/JtagHub");
|
||||||
|
app.MapHub<server.Hubs.ProgressHub>("hubs/ProgressHub");
|
||||||
|
|
||||||
// Setup Program
|
// Setup Program
|
||||||
MsgBus.Init();
|
MsgBus.Init();
|
||||||
|
|
|
@ -348,4 +348,37 @@ public class Number
|
||||||
}
|
}
|
||||||
return dstBytes;
|
return dstBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取数字的长度
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="number">数字</param>
|
||||||
|
/// <returns>数字的长度</returns>
|
||||||
|
public static int GetLength(int number)
|
||||||
|
{
|
||||||
|
// 将整数转换为字符串
|
||||||
|
string numberString = number.ToString();
|
||||||
|
|
||||||
|
// 返回字符串的长度
|
||||||
|
return numberString.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 计算整形的幂
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="x">底数</param>
|
||||||
|
/// <param name="pow">幂</param>
|
||||||
|
/// <returns>计算结果</returns>
|
||||||
|
public static int IntPow(int x, int pow)
|
||||||
|
{
|
||||||
|
int ret = 1;
|
||||||
|
while (pow != 0)
|
||||||
|
{
|
||||||
|
if ((pow & 1) == 1)
|
||||||
|
ret *= x;
|
||||||
|
x *= x;
|
||||||
|
pow >>= 1;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Cors;
|
using Microsoft.AspNetCore.Cors;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Database;
|
using Database;
|
||||||
|
using server.Services;
|
||||||
|
|
||||||
namespace server.Controllers;
|
namespace server.Controllers;
|
||||||
|
|
||||||
|
@ -15,6 +16,15 @@ 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 const string BITSTREAM_PATH = "bitstream/Jtag";
|
||||||
|
|
||||||
|
public JtagController(ProgressTrackerService tracker)
|
||||||
|
{
|
||||||
|
_tracker = tracker;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 控制器首页信息
|
/// 控制器首页信息
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -117,14 +127,14 @@ public class JtagController : ControllerBase
|
||||||
/// <param name="address">JTAG 设备地址</param>
|
/// <param name="address">JTAG 设备地址</param>
|
||||||
/// <param name="port">JTAG 设备端口</param>
|
/// <param name="port">JTAG 设备端口</param>
|
||||||
/// <param name="bitstreamId">比特流ID</param>
|
/// <param name="bitstreamId">比特流ID</param>
|
||||||
/// <returns>下载结果</returns>
|
/// <returns>进度跟踪TaskID</returns>
|
||||||
[HttpPost("DownloadBitstream")]
|
[HttpPost("DownloadBitstream")]
|
||||||
[EnableCors("Users")]
|
[EnableCors("Users")]
|
||||||
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(string), StatusCodes.Status200OK)]
|
||||||
[ProducesResponseType(typeof(string), StatusCodes.Status400BadRequest)]
|
[ProducesResponseType(typeof(string), StatusCodes.Status400BadRequest)]
|
||||||
[ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
|
[ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
|
||||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||||
public async ValueTask<IResult> DownloadBitstream(string address, int port, int bitstreamId)
|
public async ValueTask<IResult> DownloadBitstream(string address, int port, int bitstreamId, CancellationToken cancelToken)
|
||||||
{
|
{
|
||||||
logger.Info($"User {User.Identity?.Name} initiating bitstream download to device {address}:{port} using bitstream ID: {bitstreamId}");
|
logger.Info($"User {User.Identity?.Name} initiating bitstream download to device {address}:{port} using bitstream ID: {bitstreamId}");
|
||||||
|
|
||||||
|
@ -176,55 +186,67 @@ 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");
|
||||||
|
|
||||||
// 定义缓冲区大小: 32KB
|
// 定义进度跟踪
|
||||||
byte[] buffer = new byte[32 * 1024];
|
var (taskId, progress) = _tracker.CreateTask(cancelToken);
|
||||||
byte[] revBuffer = new byte[32 * 1024];
|
progress.Report(10);
|
||||||
long totalBytesProcessed = 0;
|
|
||||||
|
|
||||||
// 使用内存流处理文件
|
_ = Task.Run(async () =>
|
||||||
using (var inputStream = new MemoryStream(fileBytes))
|
|
||||||
using (var outputStream = new MemoryStream())
|
|
||||||
{
|
{
|
||||||
int bytesRead;
|
// 定义缓冲区大小: 32KB
|
||||||
while ((bytesRead = await inputStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
|
byte[] buffer = new byte[32 * 1024];
|
||||||
{
|
byte[] revBuffer = new byte[32 * 1024];
|
||||||
// 反转 32bits
|
long totalBytesProcessed = 0;
|
||||||
var retBuffer = Common.Number.ReverseBytes(buffer, 4);
|
|
||||||
if (!retBuffer.IsSuccessful)
|
|
||||||
{
|
|
||||||
logger.Error($"User {username} failed to reverse bytes: {retBuffer.Error}");
|
|
||||||
return TypedResults.InternalServerError(retBuffer.Error);
|
|
||||||
}
|
|
||||||
revBuffer = retBuffer.Value;
|
|
||||||
|
|
||||||
for (int i = 0; i < revBuffer.Length; i++)
|
// 使用内存流处理文件
|
||||||
|
using (var inputStream = new MemoryStream(fileBytes))
|
||||||
|
using (var outputStream = new MemoryStream())
|
||||||
|
{
|
||||||
|
int bytesRead;
|
||||||
|
while ((bytesRead = await inputStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
|
||||||
{
|
{
|
||||||
revBuffer[i] = Common.Number.ReverseBits(revBuffer[i]);
|
// 反转 32bits
|
||||||
|
var retBuffer = Common.Number.ReverseBytes(buffer, 4);
|
||||||
|
if (!retBuffer.IsSuccessful)
|
||||||
|
{
|
||||||
|
logger.Error($"User {username} failed to reverse bytes: {retBuffer.Error}");
|
||||||
|
progress.Error($"User {username} failed to reverse bytes: {retBuffer.Error}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
revBuffer = retBuffer.Value;
|
||||||
|
|
||||||
|
for (int i = 0; i < revBuffer.Length; i++)
|
||||||
|
{
|
||||||
|
revBuffer[i] = Common.Number.ReverseBits(revBuffer[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
await outputStream.WriteAsync(revBuffer, 0, bytesRead);
|
||||||
|
totalBytesProcessed += bytesRead;
|
||||||
}
|
}
|
||||||
|
|
||||||
await outputStream.WriteAsync(revBuffer, 0, bytesRead);
|
// 获取处理后的数据
|
||||||
totalBytesProcessed += bytesRead;
|
var processedBytes = outputStream.ToArray();
|
||||||
}
|
logger.Info($"User {username} processed {totalBytesProcessed} bytes for device {address}");
|
||||||
|
|
||||||
// 获取处理后的数据
|
progress.Report(20);
|
||||||
var processedBytes = outputStream.ToArray();
|
|
||||||
logger.Info($"User {username} processed {totalBytesProcessed} bytes for device {address}");
|
|
||||||
|
|
||||||
// 下载比特流
|
// 下载比特流
|
||||||
var jtagCtrl = new Peripherals.JtagClient.Jtag(address, port);
|
var jtagCtrl = new Peripherals.JtagClient.Jtag(address, port);
|
||||||
var ret = await jtagCtrl.DownloadBitstream(processedBytes);
|
var ret = await jtagCtrl.DownloadBitstream(processedBytes);
|
||||||
|
|
||||||
if (ret.IsSuccessful)
|
if (ret.IsSuccessful)
|
||||||
{
|
{
|
||||||
logger.Info($"User {username} successfully downloaded bitstream '{bitstream.ResourceName}' to device {address}");
|
logger.Info($"User {username} successfully downloaded bitstream '{bitstream.ResourceName}' to device {address}");
|
||||||
return TypedResults.Ok(ret.Value);
|
progress.Finish();
|
||||||
|
}
|
||||||
|
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}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
});
|
||||||
{
|
|
||||||
logger.Error($"User {username} failed to download bitstream to device {address}: {ret.Error}");
|
return TypedResults.Ok(taskId);
|
||||||
return TypedResults.InternalServerError(ret.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Cors;
|
using Microsoft.AspNetCore.Cors;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using Microsoft.AspNetCore.SignalR;
|
using Microsoft.AspNetCore.SignalR;
|
||||||
using DotNext;
|
using DotNext;
|
||||||
|
@ -8,7 +7,7 @@ using System.Collections.Concurrent;
|
||||||
using TypedSignalR.Client;
|
using TypedSignalR.Client;
|
||||||
using Tapper;
|
using Tapper;
|
||||||
|
|
||||||
namespace server.Hubs.JtagHub;
|
namespace server.Hubs;
|
||||||
|
|
||||||
[Hub]
|
[Hub]
|
||||||
public interface IJtagHub
|
public interface IJtagHub
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
using Microsoft.AspNetCore.Cors;
|
||||||
|
using TypedSignalR.Client;
|
||||||
|
using Tapper;
|
||||||
|
using server.Services;
|
||||||
|
|
||||||
|
namespace server.Hubs;
|
||||||
|
|
||||||
|
[Hub]
|
||||||
|
public interface IProgressHub
|
||||||
|
{
|
||||||
|
Task<bool> Join(string taskId);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Receiver]
|
||||||
|
public interface IProgressReceiver
|
||||||
|
{
|
||||||
|
Task OnReceiveProgress(ProgressInfo message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TranspilationSource]
|
||||||
|
public enum ProgressStatus
|
||||||
|
{
|
||||||
|
Pending,
|
||||||
|
InProgress,
|
||||||
|
Completed,
|
||||||
|
Canceled,
|
||||||
|
Failed
|
||||||
|
}
|
||||||
|
|
||||||
|
[TranspilationSource]
|
||||||
|
public class ProgressInfo
|
||||||
|
{
|
||||||
|
public string TaskId { get; }
|
||||||
|
public ProgressStatus Status { get; }
|
||||||
|
public int ProgressPercent { get; }
|
||||||
|
public string ErrorMessage { get; }
|
||||||
|
};
|
||||||
|
|
||||||
|
[Authorize]
|
||||||
|
[EnableCors("SignalR")]
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> Join(string taskId)
|
||||||
|
{
|
||||||
|
return _tracker.BindTask(taskId, Context.ConnectionId);
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,7 @@ using System.Collections;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using DotNext;
|
using DotNext;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using server;
|
using server.Services;
|
||||||
using WebProtocol;
|
using WebProtocol;
|
||||||
|
|
||||||
namespace Peripherals.JtagClient;
|
namespace Peripherals.JtagClient;
|
||||||
|
@ -442,11 +442,12 @@ public class Jtag
|
||||||
return Convert.ToUInt32(Common.Number.BytesToUInt32(retPackOpts.Data).Value);
|
return Convert.ToUInt32(Common.Number.BytesToUInt32(retPackOpts.Data).Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
async ValueTask<Result<bool>> WriteFIFO
|
async ValueTask<Result<bool>> WriteFIFO(
|
||||||
(UInt32 devAddr, UInt32 data, UInt32 result, UInt32 resultMask = 0xFF_FF_FF_FF, UInt32 delayMilliseconds = 0)
|
UInt32 devAddr, UInt32 data, UInt32 result,
|
||||||
|
UInt32 resultMask = 0xFF_FF_FF_FF, UInt32 delayMilliseconds = 0, ProgressReporter? progress = null)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
var ret = await UDPClientPool.WriteAddr(this.ep, 0, devAddr, data, this.timeout);
|
var ret = await UDPClientPool.WriteAddr(this.ep, 0, devAddr, data, this.timeout, progress?.CreateChild(80));
|
||||||
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"));
|
||||||
}
|
}
|
||||||
|
@ -457,15 +458,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();
|
||||||
return ret.Value;
|
return ret.Value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async ValueTask<Result<bool>> WriteFIFO
|
async ValueTask<Result<bool>> WriteFIFO(
|
||||||
(UInt32 devAddr, byte[] data, UInt32 result, UInt32 resultMask = 0xFF_FF_FF_FF, UInt32 delayMilliseconds = 0)
|
UInt32 devAddr, byte[] data, UInt32 result,
|
||||||
|
UInt32 resultMask = 0xFF_FF_FF_FF, UInt32 delayMilliseconds = 0, ProgressReporter? progress = null)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
var ret = await UDPClientPool.WriteAddr(this.ep, 0, devAddr, data, this.timeout);
|
var ret = await UDPClientPool.WriteAddr(this.ep, 0, devAddr, data, this.timeout, progress?.CreateChild(80));
|
||||||
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"));
|
||||||
}
|
}
|
||||||
|
@ -476,6 +479,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();
|
||||||
return ret.Value;
|
return ret.Value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -559,7 +563,8 @@ public class Jtag
|
||||||
return await ClearWriteDataReg();
|
return await ClearWriteDataReg();
|
||||||
}
|
}
|
||||||
|
|
||||||
async ValueTask<Result<bool>> LoadDRCareInput(byte[] bytesArray, UInt32 timeout = 10_000, UInt32 cycle = 500)
|
async ValueTask<Result<bool>> LoadDRCareInput(
|
||||||
|
byte[] bytesArray, UInt32 timeout = 10_000, UInt32 cycle = 500, ProgressReporter? progress = null)
|
||||||
{
|
{
|
||||||
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)"));
|
||||||
|
@ -574,11 +579,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);
|
||||||
|
|
||||||
{
|
{
|
||||||
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)
|
||||||
|
);
|
||||||
|
|
||||||
if (!ret.IsSuccessful) return new(ret.Error);
|
if (!ret.IsSuccessful) return new(ret.Error);
|
||||||
return ret.Value;
|
return ret.Value;
|
||||||
|
@ -701,44 +710,55 @@ public class Jtag
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="bitstream">比特流数据</param>
|
/// <param name="bitstream">比特流数据</param>
|
||||||
/// <returns>指示下载是否成功的异步结果</returns>
|
/// <returns>指示下载是否成功的异步结果</returns>
|
||||||
public async ValueTask<Result<bool>> DownloadBitstream(byte[] bitstream)
|
public async ValueTask<Result<bool>> DownloadBitstream(byte[] bitstream, ProgressReporter? progress = null)
|
||||||
{
|
{
|
||||||
// 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();
|
||||||
|
|
||||||
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();
|
||||||
|
|
||||||
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();
|
||||||
|
|
||||||
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();
|
||||||
|
|
||||||
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();
|
||||||
|
|
||||||
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();
|
||||||
|
|
||||||
ret = await LoadDRCareInput(bitstream);
|
ret = await LoadDRCareInput(bitstream, progress: progress?.CreateChild(50));
|
||||||
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"));
|
||||||
|
|
||||||
|
@ -747,32 +767,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();
|
||||||
|
|
||||||
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();
|
||||||
|
|
||||||
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();
|
||||||
|
|
||||||
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();
|
||||||
|
|
||||||
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();
|
||||||
|
|
||||||
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();
|
||||||
|
|
||||||
|
// Finish
|
||||||
|
progress?.Finish();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,288 @@
|
||||||
|
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;
|
||||||
|
|
||||||
|
public string TaskId { get; set; } = new Guid().ToString();
|
||||||
|
public int ProgressPercent => _progress * 100 / MaxProgress;
|
||||||
|
public ProgressStatus Status => _status;
|
||||||
|
public 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 async 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.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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
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))
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ using System.Net.Sockets;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using DotNext;
|
using DotNext;
|
||||||
using WebProtocol;
|
using WebProtocol;
|
||||||
|
using server.Services;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// UDP客户端发送池
|
/// UDP客户端发送池
|
||||||
|
@ -465,7 +466,7 @@ public class UDPClientPool
|
||||||
CommandID = Convert.ToByte(taskID),
|
CommandID = Convert.ToByte(taskID),
|
||||||
IsWrite = false,
|
IsWrite = false,
|
||||||
BurstLength = (byte)(currentSegmentSize - 1),
|
BurstLength = (byte)(currentSegmentSize - 1),
|
||||||
Address = (burstType == BurstType.ExtendBurst)?(devAddr + (uint)(i * max4BytesPerRead)):(devAddr),
|
Address = (burstType == BurstType.ExtendBurst) ? (devAddr + (uint)(i * max4BytesPerRead)) : (devAddr),
|
||||||
// Address = devAddr + (uint)(i * max4BytesPerRead),
|
// Address = devAddr + (uint)(i * max4BytesPerRead),
|
||||||
};
|
};
|
||||||
pkgList.Add(new SendAddrPackage(opts));
|
pkgList.Add(new SendAddrPackage(opts));
|
||||||
|
@ -586,7 +587,8 @@ public class UDPClientPool
|
||||||
/// <param name="timeout">超时时间(毫秒)</param>
|
/// <param name="timeout">超时时间(毫秒)</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, UInt32 data, int timeout = 1000)
|
IPEndPoint endPoint, int taskID, UInt32 devAddr,
|
||||||
|
UInt32 data, int timeout = 1000, ProgressReporter? progress = null)
|
||||||
{
|
{
|
||||||
var ret = false;
|
var ret = false;
|
||||||
var opts = new SendAddrPackOptions()
|
var opts = new SendAddrPackOptions()
|
||||||
|
@ -597,14 +599,17 @@ public class UDPClientPool
|
||||||
Address = devAddr,
|
Address = devAddr,
|
||||||
IsWrite = true,
|
IsWrite = true,
|
||||||
};
|
};
|
||||||
|
progress?.Report(20);
|
||||||
|
|
||||||
// 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);
|
||||||
// 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);
|
||||||
|
|
||||||
// Check Msg Bus
|
// Check Msg Bus
|
||||||
if (!MsgBus.IsRunning)
|
if (!MsgBus.IsRunning)
|
||||||
|
@ -614,6 +619,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();
|
||||||
|
|
||||||
return udpWriteAck.Value.IsSuccessful;
|
return udpWriteAck.Value.IsSuccessful;
|
||||||
}
|
}
|
||||||
|
@ -628,7 +634,8 @@ public class UDPClientPool
|
||||||
/// <param name="timeout">超时时间(毫秒)</param>
|
/// <param name="timeout">超时时间(毫秒)</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, byte[] dataArray, int timeout = 1000)
|
IPEndPoint endPoint, int taskID, UInt32 devAddr,
|
||||||
|
byte[] dataArray, int timeout = 1000, ProgressReporter? progress = null)
|
||||||
{
|
{
|
||||||
var ret = false;
|
var ret = false;
|
||||||
var opts = new SendAddrPackOptions()
|
var opts = new SendAddrPackOptions()
|
||||||
|
@ -650,6 +657,8 @@ 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
|
||||||
|
@ -678,8 +687,11 @@ public class UDPClientPool
|
||||||
|
|
||||||
if (!udpWriteAck.Value.IsSuccessful)
|
if (!udpWriteAck.Value.IsSuccessful)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
progress?.Increase();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
progress?.Finish();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3331,9 +3331,9 @@ export class JtagClient {
|
||||||
* @param address (optional) JTAG 设备地址
|
* @param address (optional) JTAG 设备地址
|
||||||
* @param port (optional) JTAG 设备端口
|
* @param port (optional) JTAG 设备端口
|
||||||
* @param bitstreamId (optional) 比特流ID
|
* @param bitstreamId (optional) 比特流ID
|
||||||
* @return 下载结果
|
* @return 进度跟踪TaskID
|
||||||
*/
|
*/
|
||||||
downloadBitstream(address: string | undefined, port: number | undefined, bitstreamId: number | undefined, cancelToken?: CancelToken): Promise<boolean> {
|
downloadBitstream(address: string | undefined, port: number | undefined, bitstreamId: number | undefined, cancelToken?: CancelToken): Promise<string> {
|
||||||
let url_ = this.baseUrl + "/api/Jtag/DownloadBitstream?";
|
let url_ = this.baseUrl + "/api/Jtag/DownloadBitstream?";
|
||||||
if (address === null)
|
if (address === null)
|
||||||
throw new Error("The parameter 'address' cannot be null.");
|
throw new Error("The parameter 'address' cannot be null.");
|
||||||
|
@ -3369,7 +3369,7 @@ export class JtagClient {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected processDownloadBitstream(response: AxiosResponse): Promise<boolean> {
|
protected processDownloadBitstream(response: AxiosResponse): Promise<string> {
|
||||||
const status = response.status;
|
const status = response.status;
|
||||||
let _headers: any = {};
|
let _headers: any = {};
|
||||||
if (response.headers && typeof response.headers === "object") {
|
if (response.headers && typeof response.headers === "object") {
|
||||||
|
@ -3385,7 +3385,7 @@ export class JtagClient {
|
||||||
let resultData200 = _responseText;
|
let resultData200 = _responseText;
|
||||||
result200 = resultData200 !== undefined ? resultData200 : <any>null;
|
result200 = resultData200 !== undefined ? resultData200 : <any>null;
|
||||||
|
|
||||||
return Promise.resolve<boolean>(result200);
|
return Promise.resolve<string>(result200);
|
||||||
|
|
||||||
} else if (status === 400) {
|
} else if (status === 400) {
|
||||||
const _responseText = response.data;
|
const _responseText = response.data;
|
||||||
|
@ -3413,7 +3413,7 @@ export class JtagClient {
|
||||||
const _responseText = response.data;
|
const _responseText = response.data;
|
||||||
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
||||||
}
|
}
|
||||||
return Promise.resolve<boolean>(null as any);
|
return Promise.resolve<string>(null as any);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -3,7 +3,8 @@
|
||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import type { HubConnection, IStreamResult, Subject } from '@microsoft/signalr';
|
import type { HubConnection, IStreamResult, Subject } from '@microsoft/signalr';
|
||||||
import type { IJtagHub, IJtagReceiver } from './server.Hubs.JtagHub';
|
import type { IJtagHub, IProgressHub, IJtagReceiver, IProgressReceiver } from './server.Hubs';
|
||||||
|
import type { ProgressInfo } from '../server.Hubs';
|
||||||
|
|
||||||
|
|
||||||
// components
|
// components
|
||||||
|
@ -43,22 +44,30 @@ class ReceiverMethodSubscription implements Disposable {
|
||||||
|
|
||||||
export type HubProxyFactoryProvider = {
|
export type HubProxyFactoryProvider = {
|
||||||
(hubType: "IJtagHub"): HubProxyFactory<IJtagHub>;
|
(hubType: "IJtagHub"): HubProxyFactory<IJtagHub>;
|
||||||
|
(hubType: "IProgressHub"): HubProxyFactory<IProgressHub>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getHubProxyFactory = ((hubType: string) => {
|
export const getHubProxyFactory = ((hubType: string) => {
|
||||||
if(hubType === "IJtagHub") {
|
if(hubType === "IJtagHub") {
|
||||||
return IJtagHub_HubProxyFactory.Instance;
|
return IJtagHub_HubProxyFactory.Instance;
|
||||||
}
|
}
|
||||||
|
if(hubType === "IProgressHub") {
|
||||||
|
return IProgressHub_HubProxyFactory.Instance;
|
||||||
|
}
|
||||||
}) as HubProxyFactoryProvider;
|
}) as HubProxyFactoryProvider;
|
||||||
|
|
||||||
export type ReceiverRegisterProvider = {
|
export type ReceiverRegisterProvider = {
|
||||||
(receiverType: "IJtagReceiver"): ReceiverRegister<IJtagReceiver>;
|
(receiverType: "IJtagReceiver"): ReceiverRegister<IJtagReceiver>;
|
||||||
|
(receiverType: "IProgressReceiver"): ReceiverRegister<IProgressReceiver>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getReceiverRegister = ((receiverType: string) => {
|
export const getReceiverRegister = ((receiverType: string) => {
|
||||||
if(receiverType === "IJtagReceiver") {
|
if(receiverType === "IJtagReceiver") {
|
||||||
return IJtagReceiver_Binder.Instance;
|
return IJtagReceiver_Binder.Instance;
|
||||||
}
|
}
|
||||||
|
if(receiverType === "IProgressReceiver") {
|
||||||
|
return IProgressReceiver_Binder.Instance;
|
||||||
|
}
|
||||||
}) as ReceiverRegisterProvider;
|
}) as ReceiverRegisterProvider;
|
||||||
|
|
||||||
// HubProxy
|
// HubProxy
|
||||||
|
@ -92,6 +101,27 @@ class IJtagHub_HubProxy implements IJtagHub {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class IProgressHub_HubProxyFactory implements HubProxyFactory<IProgressHub> {
|
||||||
|
public static Instance = new IProgressHub_HubProxyFactory();
|
||||||
|
|
||||||
|
private constructor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly createHubProxy = (connection: HubConnection): IProgressHub => {
|
||||||
|
return new IProgressHub_HubProxy(connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class IProgressHub_HubProxy implements IProgressHub {
|
||||||
|
|
||||||
|
public constructor(private connection: HubConnection) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly join = async (taskId: string): Promise<boolean> => {
|
||||||
|
return await this.connection.invoke("Join", taskId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Receiver
|
// Receiver
|
||||||
|
|
||||||
|
@ -116,3 +146,24 @@ class IJtagReceiver_Binder implements ReceiverRegister<IJtagReceiver> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class IProgressReceiver_Binder implements ReceiverRegister<IProgressReceiver> {
|
||||||
|
|
||||||
|
public static Instance = new IProgressReceiver_Binder();
|
||||||
|
|
||||||
|
private constructor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly register = (connection: HubConnection, receiver: IProgressReceiver): Disposable => {
|
||||||
|
|
||||||
|
const __onReceiveProgress = (...args: [ProgressInfo]) => receiver.onReceiveProgress(...args);
|
||||||
|
|
||||||
|
connection.on("OnReceiveProgress", __onReceiveProgress);
|
||||||
|
|
||||||
|
const methodList: ReceiverMethod[] = [
|
||||||
|
{ methodName: "OnReceiveProgress", method: __onReceiveProgress }
|
||||||
|
]
|
||||||
|
|
||||||
|
return new ReceiverMethodSubscription(connection, methodList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import type { IStreamResult, Subject } from '@microsoft/signalr';
|
import type { IStreamResult, Subject } from '@microsoft/signalr';
|
||||||
|
import type { ProgressInfo } from '../server.Hubs';
|
||||||
|
|
||||||
export type IJtagHub = {
|
export type IJtagHub = {
|
||||||
/**
|
/**
|
||||||
|
@ -21,6 +22,14 @@ export type IJtagHub = {
|
||||||
stopBoundaryScan(): Promise<boolean>;
|
stopBoundaryScan(): Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type IProgressHub = {
|
||||||
|
/**
|
||||||
|
* @param taskId Transpiled from string
|
||||||
|
* @returns Transpiled from System.Threading.Tasks.Task<bool>
|
||||||
|
*/
|
||||||
|
join(taskId: string): Promise<boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
export type IJtagReceiver = {
|
export type IJtagReceiver = {
|
||||||
/**
|
/**
|
||||||
* @param msg Transpiled from System.Collections.Generic.Dictionary<string, bool>
|
* @param msg Transpiled from System.Collections.Generic.Dictionary<string, bool>
|
||||||
|
@ -29,3 +38,11 @@ export type IJtagReceiver = {
|
||||||
onReceiveBoundaryScanData(msg: Partial<Record<string, boolean>>): Promise<void>;
|
onReceiveBoundaryScanData(msg: Partial<Record<string, boolean>>): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type IProgressReceiver = {
|
||||||
|
/**
|
||||||
|
* @param message Transpiled from server.Hubs.ProgressInfo
|
||||||
|
* @returns Transpiled from System.Threading.Tasks.Task
|
||||||
|
*/
|
||||||
|
onReceiveProgress(message: ProgressInfo): Promise<void>;
|
||||||
|
}
|
||||||
|
|
|
@ -1,276 +1,314 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col bg-base-100 justify-center items-center gap-4">
|
<div class="flex flex-col bg-base-100 justify-center items-center gap-4">
|
||||||
<!-- Title -->
|
<!-- Title -->
|
||||||
<h1 class="font-bold text-2xl">比特流文件</h1>
|
<h1 class="font-bold text-2xl">比特流文件</h1>
|
||||||
|
|
||||||
<!-- 示例比特流下载区域 (仅在有examId时显示) -->
|
<!-- 示例比特流下载区域 (仅在有examId时显示) -->
|
||||||
<div v-if="examId && availableBitstreams.length > 0" class="w-full">
|
<div v-if="examId && availableBitstreams.length > 0" class="w-full">
|
||||||
<fieldset class="fieldset w-full">
|
<fieldset class="fieldset w-full">
|
||||||
<legend class="fieldset-legend text-sm">示例比特流文件</legend>
|
<legend class="fieldset-legend text-sm">示例比特流文件</legend>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<div v-for="bitstream in availableBitstreams" :key="bitstream.id" class="flex items-center justify-between p-2 border-2 border-base-300 rounded-lg">
|
<div
|
||||||
<span class="text-sm">{{ bitstream.name }}</span>
|
v-for="bitstream in availableBitstreams"
|
||||||
<div class="flex gap-2">
|
:key="bitstream.id"
|
||||||
<button
|
class="flex items-center justify-between p-2 border-2 border-base-300 rounded-lg"
|
||||||
@click="downloadExampleBitstream(bitstream)"
|
>
|
||||||
class="btn btn-sm btn-secondary"
|
<span class="text-sm">{{ bitstream.name }}</span>
|
||||||
:disabled="isDownloading || isProgramming"
|
<div class="flex gap-2">
|
||||||
>
|
<button
|
||||||
<div v-if="isDownloading">
|
@click="downloadExampleBitstream(bitstream)"
|
||||||
<span class="loading loading-spinner loading-xs"></span>
|
class="btn btn-sm btn-secondary"
|
||||||
下载中...
|
:disabled="isDownloading || isProgramming"
|
||||||
</div>
|
>
|
||||||
<div v-else>
|
<div v-if="isDownloading">
|
||||||
下载示例
|
<span class="loading loading-spinner loading-xs"></span>
|
||||||
</div>
|
{{ downloadProgress }}%
|
||||||
</button>
|
</div>
|
||||||
<button
|
<div v-else>下载示例</div>
|
||||||
@click="programExampleBitstream(bitstream)"
|
</button>
|
||||||
class="btn btn-sm btn-primary"
|
<button
|
||||||
:disabled="isDownloading || isProgramming || !uploadEvent"
|
@click="programExampleBitstream(bitstream)"
|
||||||
>
|
class="btn btn-sm btn-primary"
|
||||||
<div v-if="isProgramming">
|
:disabled="isDownloading || isProgramming"
|
||||||
<span class="loading loading-spinner loading-xs"></span>
|
>
|
||||||
烧录中...
|
<div v-if="isProgramming">
|
||||||
</div>
|
<span class="loading loading-spinner loading-xs"></span>
|
||||||
<div v-else>
|
烧录中...
|
||||||
直接烧录
|
</div>
|
||||||
</div>
|
<div v-else>直接烧录</div>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 分割线 -->
|
<!-- 分割线 -->
|
||||||
<div v-if="examId && availableBitstreams.length > 0" class="divider">或</div>
|
<div v-if="examId && availableBitstreams.length > 0" class="divider">
|
||||||
|
或
|
||||||
<!-- Input File -->
|
</div>
|
||||||
<fieldset class="fieldset w-full">
|
|
||||||
<legend class="fieldset-legend text-sm">上传自定义比特流文件</legend>
|
<!-- Input File -->
|
||||||
<input type="file" ref="fileInput" class="file-input w-full" @change="handleFileChange" />
|
<fieldset class="fieldset w-full">
|
||||||
<label class="fieldset-label">文件最大容量: {{ maxMemory }}MB</label>
|
<legend class="fieldset-legend text-sm">上传自定义比特流文件</legend>
|
||||||
</fieldset>
|
<input
|
||||||
|
type="file"
|
||||||
<!-- Upload Button -->
|
ref="fileInput"
|
||||||
<div class="card-actions w-full">
|
class="file-input w-full"
|
||||||
<button @click="handleClick" class="btn btn-primary grow" :disabled="isUploading || isProgramming">
|
@change="handleFileChange"
|
||||||
<div v-if="isUploading">
|
/>
|
||||||
<span class="loading loading-spinner"></span>
|
<label class="fieldset-label">文件最大容量: {{ maxMemory }}MB</label>
|
||||||
上传中...
|
</fieldset>
|
||||||
</div>
|
|
||||||
<div v-else>
|
<!-- Upload Button -->
|
||||||
{{ buttonText }}
|
<div class="card-actions w-full">
|
||||||
</div>
|
<button
|
||||||
</button>
|
@click="handleClick"
|
||||||
</div>
|
class="btn btn-primary grow"
|
||||||
</div>
|
:disabled="isUploading || isProgramming"
|
||||||
</template>
|
>
|
||||||
|
<div v-if="isUploading">
|
||||||
<script lang="ts" setup>
|
<span class="loading loading-spinner"></span>
|
||||||
import { computed, ref, useTemplateRef, onMounted } from "vue";
|
上传中...
|
||||||
import { AuthManager } from "@/utils/AuthManager"; // Adjust the path based on your project structure
|
</div>
|
||||||
import { useDialogStore } from "@/stores/dialog";
|
<div v-else>上传并下载</div>
|
||||||
import { isNull, isUndefined } from "lodash";
|
</button>
|
||||||
|
</div>
|
||||||
interface Props {
|
</div>
|
||||||
uploadEvent?: (file: File, examId: string) => Promise<number | null>;
|
</template>
|
||||||
downloadEvent?: (bitstreamId: number) => Promise<boolean>;
|
|
||||||
maxMemory?: number;
|
<script lang="ts" setup>
|
||||||
examId?: string; // 新增examId属性
|
import { computed, ref, useTemplateRef, onMounted } from "vue";
|
||||||
}
|
import { AuthManager } from "@/utils/AuthManager"; // Adjust the path based on your project structure
|
||||||
|
import { useDialogStore } from "@/stores/dialog";
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
import { isNull, isUndefined } from "lodash";
|
||||||
maxMemory: 4,
|
import { useEquipments } from "@/stores/equipments";
|
||||||
examId: '',
|
import type { HubConnection } from "@microsoft/signalr";
|
||||||
});
|
import type {
|
||||||
|
IProgressHub,
|
||||||
const emits = defineEmits<{
|
IProgressReceiver,
|
||||||
finishedUpload: [file: File];
|
} from "@/TypedSignalR.Client/server.Hubs";
|
||||||
}>();
|
import { getHubProxyFactory, getReceiverRegister } from "@/TypedSignalR.Client";
|
||||||
|
import { ProgressStatus } from "@/server.Hubs";
|
||||||
const dialog = useDialogStore();
|
import { useRequiredInjection } from "@/utils/Common";
|
||||||
|
import { useAlertStore } from "./Alert";
|
||||||
const isUploading = ref(false);
|
|
||||||
const isDownloading = ref(false);
|
interface Props {
|
||||||
const isProgramming = ref(false);
|
maxMemory?: number;
|
||||||
const availableBitstreams = ref<{id: number, name: string}[]>([]);
|
examId?: string; // 新增examId属性
|
||||||
|
}
|
||||||
const buttonText = computed(() => {
|
|
||||||
return isUndefined(props.downloadEvent) ? "上传" : "上传并下载";
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
});
|
maxMemory: 4,
|
||||||
|
examId: "",
|
||||||
const fileInput = useTemplateRef("fileInput");
|
});
|
||||||
const bitstream = defineModel("bitstreamFile", {
|
|
||||||
type: File,
|
const emits = defineEmits<{
|
||||||
default: undefined,
|
finishedUpload: [file: File];
|
||||||
});
|
}>();
|
||||||
|
|
||||||
// 初始化时加载示例比特流
|
const alert = useRequiredInjection(useAlertStore);
|
||||||
onMounted(async () => {
|
const dialog = useDialogStore();
|
||||||
if (!isUndefined(bitstream.value) && !isNull(fileInput.value)) {
|
const eqps = useEquipments();
|
||||||
let fileList = new DataTransfer();
|
|
||||||
fileList.items.add(bitstream.value);
|
const isUploading = ref(false);
|
||||||
fileInput.value.files = fileList.files;
|
const isDownloading = ref(false);
|
||||||
}
|
const isProgramming = ref(false);
|
||||||
|
const availableBitstreams = ref<{ id: number; name: string }[]>([]);
|
||||||
await loadAvailableBitstreams();
|
|
||||||
});
|
// Progress
|
||||||
|
const downloadTaskId = ref("");
|
||||||
// 加载可用的比特流文件列表
|
const downloadProgress = ref(0);
|
||||||
async function loadAvailableBitstreams() {
|
const progressHubConnection = ref<HubConnection>();
|
||||||
console.log('加载可用比特流文件,examId:', props.examId);
|
const progressHubProxy = ref<IProgressHub>();
|
||||||
if (!props.examId) {
|
const progressHubReceiver: IProgressReceiver = {
|
||||||
availableBitstreams.value = [];
|
onReceiveProgress: async (msg) => {
|
||||||
return;
|
if (msg.taskId == downloadTaskId.value) {
|
||||||
}
|
if (msg.status == ProgressStatus.InProgress) {
|
||||||
|
downloadProgress.value = msg.progressPercent;
|
||||||
try {
|
} else if (msg.status == ProgressStatus.Failed) {
|
||||||
const resourceClient = AuthManager.createAuthenticatedResourceClient();
|
dialog.error(msg.errorMessage);
|
||||||
// 使用新的ResourceClient API获取比特流模板资源列表
|
} else if (msg.status == ProgressStatus.Completed) {
|
||||||
const resources = await resourceClient.getResourceList(props.examId, 'bitstream', 'template');
|
alert.info("比特流下载成功");
|
||||||
availableBitstreams.value = resources.map(r => ({ id: r.id, name: r.name })) || [];
|
}
|
||||||
} catch (error) {
|
}
|
||||||
console.error('加载比特流列表失败:', error);
|
},
|
||||||
availableBitstreams.value = [];
|
};
|
||||||
}
|
onMounted(async () => {
|
||||||
}
|
progressHubConnection.value =
|
||||||
|
AuthManager.createAuthenticatedProgressHubConnection();
|
||||||
// 下载示例比特流
|
progressHubProxy.value = getHubProxyFactory("IProgressHub").createHubProxy(
|
||||||
async function downloadExampleBitstream(bitstream: {id: number, name: string}) {
|
progressHubConnection.value,
|
||||||
if (isDownloading.value) return;
|
);
|
||||||
|
getReceiverRegister("IProgressReceiver").register(
|
||||||
isDownloading.value = true;
|
progressHubConnection.value,
|
||||||
try {
|
progressHubReceiver,
|
||||||
const resourceClient = AuthManager.createAuthenticatedResourceClient();
|
);
|
||||||
|
});
|
||||||
// 使用新的ResourceClient API获取资源文件
|
|
||||||
const response = await resourceClient.getResourceById(bitstream.id);
|
const fileInput = useTemplateRef("fileInput");
|
||||||
|
const bitstream = defineModel("bitstreamFile", {
|
||||||
if (response && response.data) {
|
type: File,
|
||||||
// 创建下载链接
|
default: undefined,
|
||||||
const url = URL.createObjectURL(response.data);
|
});
|
||||||
const link = document.createElement('a');
|
|
||||||
link.href = url;
|
// 初始化时加载示例比特流
|
||||||
link.download = response.fileName || bitstream.name;
|
onMounted(async () => {
|
||||||
document.body.appendChild(link);
|
if (!isUndefined(bitstream.value) && !isNull(fileInput.value)) {
|
||||||
link.click();
|
let fileList = new DataTransfer();
|
||||||
document.body.removeChild(link);
|
fileList.items.add(bitstream.value);
|
||||||
URL.revokeObjectURL(url);
|
fileInput.value.files = fileList.files;
|
||||||
|
}
|
||||||
dialog.info("示例比特流下载成功");
|
|
||||||
} else {
|
await loadAvailableBitstreams();
|
||||||
dialog.error("下载失败:响应数据为空");
|
});
|
||||||
}
|
|
||||||
} catch (error) {
|
// 加载可用的比特流文件列表
|
||||||
console.error('下载示例比特流失败:', error);
|
async function loadAvailableBitstreams() {
|
||||||
dialog.error("下载示例比特流失败");
|
console.log("加载可用比特流文件,examId:", props.examId);
|
||||||
} finally {
|
if (!props.examId) {
|
||||||
isDownloading.value = false;
|
availableBitstreams.value = [];
|
||||||
}
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 直接烧录示例比特流
|
try {
|
||||||
async function programExampleBitstream(bitstream: {id: number, name: string}) {
|
const resourceClient = AuthManager.createAuthenticatedResourceClient();
|
||||||
if (isProgramming.value) return;
|
// 使用新的ResourceClient API获取比特流模板资源列表
|
||||||
|
const resources = await resourceClient.getResourceList(
|
||||||
isProgramming.value = true;
|
props.examId,
|
||||||
try {
|
"bitstream",
|
||||||
const resourceClient = AuthManager.createAuthenticatedResourceClient();
|
"template",
|
||||||
|
);
|
||||||
if (props.downloadEvent) {
|
availableBitstreams.value =
|
||||||
const downloadSuccess = await props.downloadEvent(bitstream.id);
|
resources.map((r) => ({ id: r.id, name: r.name })) || [];
|
||||||
if (downloadSuccess) {
|
} catch (error) {
|
||||||
dialog.info("示例比特流烧录成功");
|
console.error("加载比特流列表失败:", error);
|
||||||
} else {
|
availableBitstreams.value = [];
|
||||||
dialog.error("烧录失败");
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
dialog.info("示例比特流props.downloadEvent未定义 无法烧录");
|
// 下载示例比特流
|
||||||
}
|
async function downloadExampleBitstream(bitstream: {
|
||||||
} catch (error) {
|
id: number;
|
||||||
console.error('烧录示例比特流失败:', error);
|
name: string;
|
||||||
dialog.error("烧录示例比特流失败");
|
}) {
|
||||||
} finally {
|
if (isDownloading.value) return;
|
||||||
isProgramming.value = false;
|
|
||||||
}
|
isDownloading.value = true;
|
||||||
}
|
try {
|
||||||
|
const resourceClient = AuthManager.createAuthenticatedResourceClient();
|
||||||
function handleFileChange(event: Event): void {
|
|
||||||
const target = event.target as HTMLInputElement;
|
// 使用新的ResourceClient API获取资源文件
|
||||||
const file = target.files?.[0]; // 获取选中的第一个文件
|
const response = await resourceClient.getResourceById(bitstream.id);
|
||||||
|
|
||||||
if (!file) {
|
if (response && response.data) {
|
||||||
return;
|
// 创建下载链接
|
||||||
}
|
const url = URL.createObjectURL(response.data);
|
||||||
|
const link = document.createElement("a");
|
||||||
bitstream.value = file;
|
link.href = url;
|
||||||
}
|
link.download = response.fileName || bitstream.name;
|
||||||
|
document.body.appendChild(link);
|
||||||
function checkFile(file: File): boolean {
|
link.click();
|
||||||
const maxBytes = props.maxMemory! * 1024 * 1024; // 将最大容量从 MB 转换为字节
|
document.body.removeChild(link);
|
||||||
if (file.size > maxBytes) {
|
URL.revokeObjectURL(url);
|
||||||
dialog.error(`文件大小超过最大限制: ${props.maxMemory}MB`);
|
|
||||||
return false;
|
dialog.info("示例比特流下载成功");
|
||||||
}
|
} else {
|
||||||
return true;
|
dialog.error("下载失败:响应数据为空");
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
async function handleClick(event: Event): Promise<void> {
|
console.error("下载示例比特流失败:", error);
|
||||||
console.log("上传按钮被点击");
|
dialog.error("下载示例比特流失败");
|
||||||
if (isNull(bitstream.value) || isUndefined(bitstream.value)) {
|
} finally {
|
||||||
dialog.error(`未选择文件`);
|
isDownloading.value = false;
|
||||||
return;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!checkFile(bitstream.value)) return;
|
// 直接烧录示例比特流
|
||||||
if (isUndefined(props.uploadEvent)) {
|
async function programExampleBitstream(bitstream: {
|
||||||
dialog.error("无法上传");
|
id: number;
|
||||||
return;
|
name: string;
|
||||||
}
|
}) {
|
||||||
|
if (isProgramming.value) return;
|
||||||
isUploading.value = true;
|
|
||||||
let uploadedBitstreamId: number | null = null;
|
isProgramming.value = true;
|
||||||
try {
|
try {
|
||||||
console.log("开始上传比特流文件:", bitstream.value.name);
|
const downloadTaskId = await eqps.jtagDownloadBitstream(bitstream.id);
|
||||||
const bitstreamId = await props.uploadEvent(bitstream.value, props.examId || '');
|
} catch (error) {
|
||||||
console.log("上传结果,ID:", bitstreamId);
|
console.error("烧录示例比特流失败:", error);
|
||||||
if (isUndefined(props.downloadEvent)) {
|
dialog.error("烧录示例比特流失败");
|
||||||
console.log("上传成功,下载未定义");
|
} finally {
|
||||||
isUploading.value = false;
|
isProgramming.value = false;
|
||||||
return;
|
}
|
||||||
}
|
}
|
||||||
if (bitstreamId === null || bitstreamId === undefined) {
|
|
||||||
isUploading.value = false;
|
function handleFileChange(event: Event): void {
|
||||||
return;
|
const target = event.target as HTMLInputElement;
|
||||||
}
|
const file = target.files?.[0]; // 获取选中的第一个文件
|
||||||
uploadedBitstreamId = bitstreamId;
|
|
||||||
} catch (e) {
|
if (!file) {
|
||||||
dialog.error("上传失败");
|
return;
|
||||||
console.error(e);
|
}
|
||||||
return;
|
|
||||||
}
|
bitstream.value = file;
|
||||||
|
}
|
||||||
// Download
|
|
||||||
try {
|
function checkFile(file: File): boolean {
|
||||||
console.log("开始下载比特流,ID:", uploadedBitstreamId);
|
const maxBytes = props.maxMemory! * 1024 * 1024; // 将最大容量从 MB 转换为字节
|
||||||
if (uploadedBitstreamId === null || uploadedBitstreamId === undefined) {
|
if (file.size > maxBytes) {
|
||||||
dialog.error("uploadedBitstreamId is null or undefined");
|
dialog.error(`文件大小超过最大限制: ${props.maxMemory}MB`);
|
||||||
} else {
|
return false;
|
||||||
const ret = await props.downloadEvent(uploadedBitstreamId);
|
}
|
||||||
if (ret) dialog.info("下载成功");
|
return true;
|
||||||
else dialog.error("下载失败");
|
}
|
||||||
}
|
|
||||||
} catch (e) {
|
async function handleClick(event: Event): Promise<void> {
|
||||||
dialog.error("下载失败");
|
console.log("上传按钮被点击");
|
||||||
console.error(e);
|
if (isNull(bitstream.value) || isUndefined(bitstream.value)) {
|
||||||
}
|
dialog.error(`未选择文件`);
|
||||||
|
return;
|
||||||
isUploading.value = false;
|
}
|
||||||
}
|
|
||||||
</script>
|
if (!checkFile(bitstream.value)) return;
|
||||||
|
|
||||||
<style scoped lang="postcss">
|
isUploading.value = true;
|
||||||
@import "../assets/main.css";
|
let uploadedBitstreamId: number | null = null;
|
||||||
</style>
|
try {
|
||||||
|
console.log("开始上传比特流文件:", bitstream.value.name);
|
||||||
|
const bitstreamId = await eqps.jtagUploadBitstream(
|
||||||
|
bitstream.value,
|
||||||
|
props.examId || "",
|
||||||
|
);
|
||||||
|
console.log("上传结果,ID:", bitstreamId);
|
||||||
|
if (bitstreamId === null || bitstreamId === undefined) {
|
||||||
|
isUploading.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uploadedBitstreamId = bitstreamId;
|
||||||
|
} catch (e) {
|
||||||
|
dialog.error("上传失败");
|
||||||
|
console.error(e);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
dialog.error("下载失败");
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="postcss">
|
||||||
|
@import "../assets/main.css";
|
||||||
|
</style>
|
||||||
|
|
|
@ -24,7 +24,6 @@
|
||||||
<UploadCard
|
<UploadCard
|
||||||
:exam-id="props.examId"
|
:exam-id="props.examId"
|
||||||
:upload-event="eqps.jtagUploadBitstream"
|
:upload-event="eqps.jtagUploadBitstream"
|
||||||
:download-event="handleDownloadBitstream"
|
|
||||||
:bitstream-file="eqps.jtagBitstream"
|
:bitstream-file="eqps.jtagBitstream"
|
||||||
@update:bitstream-file="handleBitstreamChange"
|
@update:bitstream-file="handleBitstreamChange"
|
||||||
>
|
>
|
||||||
|
@ -128,11 +127,6 @@ function handleBitstreamChange(file: File | undefined) {
|
||||||
eqps.jtagBitstream = file;
|
eqps.jtagBitstream = file;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleDownloadBitstream(bitstreamId: number): Promise<boolean> {
|
|
||||||
console.log("开始下载比特流,ID:", bitstreamId);
|
|
||||||
return await eqps.jtagDownloadBitstream(bitstreamId);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleSelectJtagSpeed(event: Event) {
|
function handleSelectJtagSpeed(event: Event) {
|
||||||
const target = event.target as HTMLSelectElement;
|
const target = event.target as HTMLSelectElement;
|
||||||
eqps.jtagSetSpeed(target.selectedIndex);
|
eqps.jtagSetSpeed(target.selectedIndex);
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
/* THIS (.ts) FILE IS GENERATED BY Tapper */
|
||||||
|
/* eslint-disable */
|
||||||
|
/* tslint:disable */
|
||||||
|
|
||||||
|
/** Transpiled from server.Hubs.ProgressStatus */
|
||||||
|
export enum ProgressStatus {
|
||||||
|
Pending = 0,
|
||||||
|
InProgress = 1,
|
||||||
|
Completed = 2,
|
||||||
|
Canceled = 3,
|
||||||
|
Failed = 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Transpiled from server.Hubs.ProgressInfo */
|
||||||
|
export type ProgressInfo = {
|
||||||
|
/** Transpiled from string */
|
||||||
|
taskId: string;
|
||||||
|
/** Transpiled from server.Hubs.ProgressStatus */
|
||||||
|
status: ProgressStatus;
|
||||||
|
/** Transpiled from int */
|
||||||
|
progressPercent: number;
|
||||||
|
/** Transpiled from string */
|
||||||
|
errorMessage: string;
|
||||||
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { AuthManager } from "@/utils/AuthManager";
|
||||||
import { HubConnection, HubConnectionBuilder } from "@microsoft/signalr";
|
import { HubConnection, HubConnectionBuilder } from "@microsoft/signalr";
|
||||||
import { getHubProxyFactory, getReceiverRegister } from "@/TypedSignalR.Client";
|
import { getHubProxyFactory, getReceiverRegister } from "@/TypedSignalR.Client";
|
||||||
import type { ResourceInfo } from "@/APIClient";
|
import type { ResourceInfo } from "@/APIClient";
|
||||||
import type { IJtagHub } from "@/TypedSignalR.Client/server.Hubs.JtagHub";
|
import type { IJtagHub } from "@/TypedSignalR.Client/server.Hubs";
|
||||||
|
|
||||||
export const useEquipments = defineStore("equipments", () => {
|
export const useEquipments = defineStore("equipments", () => {
|
||||||
// Global Stores
|
// Global Stores
|
||||||
|
@ -123,24 +123,27 @@ export const useEquipments = defineStore("equipments", () => {
|
||||||
enableJtagBoundaryScan.value = enable;
|
enableJtagBoundaryScan.value = enable;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function jtagUploadBitstream(bitstream: File, examId?: string): Promise<number | null> {
|
async function jtagUploadBitstream(
|
||||||
|
bitstream: File,
|
||||||
|
examId?: string,
|
||||||
|
): Promise<number | null> {
|
||||||
try {
|
try {
|
||||||
// 自动开启电源
|
// 自动开启电源
|
||||||
await powerSetOnOff(true);
|
await powerSetOnOff(true);
|
||||||
|
|
||||||
const resourceClient = AuthManager.createAuthenticatedResourceClient();
|
const resourceClient = AuthManager.createAuthenticatedResourceClient();
|
||||||
const resp = await resourceClient.addResource(
|
const resp = await resourceClient.addResource(
|
||||||
'bitstream',
|
"bitstream",
|
||||||
'user',
|
"user",
|
||||||
examId || null,
|
examId || null,
|
||||||
toFileParameterOrUndefined(bitstream),
|
toFileParameterOrUndefined(bitstream),
|
||||||
);
|
);
|
||||||
|
|
||||||
// 如果上传成功,设置为当前选中的比特流
|
// 如果上传成功,设置为当前选中的比特流
|
||||||
if (resp && resp.id !== undefined && resp.id !== null) {
|
if (resp && resp.id !== undefined && resp.id !== null) {
|
||||||
return resp.id;
|
return resp.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
dialog.error("上传错误");
|
dialog.error("上传错误");
|
||||||
|
@ -149,10 +152,10 @@ export const useEquipments = defineStore("equipments", () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function jtagDownloadBitstream(bitstreamId?: number): Promise<boolean> {
|
async function jtagDownloadBitstream(bitstreamId?: number): Promise<string> {
|
||||||
if (bitstreamId === null || bitstreamId === undefined) {
|
if (bitstreamId === null || bitstreamId === undefined) {
|
||||||
dialog.error("请先选择要下载的比特流");
|
dialog.error("请先选择要下载的比特流");
|
||||||
return false;
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
const release = await jtagClientMutex.acquire();
|
const release = await jtagClientMutex.acquire();
|
||||||
|
@ -170,7 +173,7 @@ export const useEquipments = defineStore("equipments", () => {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
dialog.error("下载错误");
|
dialog.error("下载错误");
|
||||||
console.error(e);
|
console.error(e);
|
||||||
return false;
|
throw e;
|
||||||
} finally {
|
} finally {
|
||||||
release();
|
release();
|
||||||
}
|
}
|
||||||
|
|
|
@ -210,7 +210,7 @@ export class AuthManager {
|
||||||
public static createAuthenticatedHdmiVideoStreamClient(): HdmiVideoStreamClient {
|
public static createAuthenticatedHdmiVideoStreamClient(): HdmiVideoStreamClient {
|
||||||
return AuthManager.createAuthenticatedClient(HdmiVideoStreamClient);
|
return AuthManager.createAuthenticatedClient(HdmiVideoStreamClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static createAuthenticatedJtagHubConnection() {
|
public static createAuthenticatedJtagHubConnection() {
|
||||||
const token = this.getToken();
|
const token = this.getToken();
|
||||||
if (isNull(token)) {
|
if (isNull(token)) {
|
||||||
|
@ -226,6 +226,21 @@ export class AuthManager {
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static createAuthenticatedProgressHubConnection() {
|
||||||
|
const token = this.getToken();
|
||||||
|
if (isNull(token)) {
|
||||||
|
router.push("/login");
|
||||||
|
throw Error("Token Null!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new HubConnectionBuilder()
|
||||||
|
.withUrl("http://127.0.0.1:5000/hubs/ProgressHub", {
|
||||||
|
accessTokenFactory: () => token,
|
||||||
|
})
|
||||||
|
.withAutomaticReconnect()
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
// 登录函数
|
// 登录函数
|
||||||
public static async login(
|
public static async login(
|
||||||
username: string,
|
username: string,
|
||||||
|
|
Loading…
Reference in New Issue