170 lines
5.7 KiB
C#
170 lines
5.7 KiB
C#
using System.Security;
|
||
using System.Text.Json;
|
||
using System.Windows.Input;
|
||
|
||
namespace Zcbot.WindowsNode;
|
||
|
||
internal sealed class AnsysAcceptanceViewModel : ObservableObject, IDisposable
|
||
{
|
||
private readonly IAnsysAcceptanceService service;
|
||
private readonly AsyncCommand runCommand;
|
||
private readonly RelayCommand cancelCommand;
|
||
private readonly RelayCommand enableGateCommand;
|
||
private CancellationTokenSource? stop;
|
||
private string? passedReportPath;
|
||
private string message;
|
||
private string tone;
|
||
|
||
internal AnsysAcceptanceViewModel(IAnsysAcceptanceService service)
|
||
{
|
||
this.service = service;
|
||
(message, tone) = GateStatus();
|
||
runCommand = new AsyncCommand(RunAsync, () => IsAvailable && !IsRunning);
|
||
cancelCommand = new RelayCommand(Cancel, () => IsRunning);
|
||
enableGateCommand = new RelayCommand(EnableGate, () => CanEnableGate);
|
||
RunCommand = runCommand;
|
||
CancelCommand = cancelCommand;
|
||
EnableGateCommand = enableGateCommand;
|
||
}
|
||
|
||
internal event Func<string?>? ChooseReportDirectoryRequested;
|
||
internal event Func<bool>? ConfirmRunRequested;
|
||
internal event Func<bool>? ConfirmEnableGateRequested;
|
||
|
||
public bool IsAvailable => service.IsAvailable;
|
||
public bool IsRunning => stop is not null;
|
||
public bool IsGateEnabled => service.IsGateEnabled;
|
||
public bool CanEnableGate => passedReportPath is not null && !IsGateEnabled && !IsRunning;
|
||
|
||
public string Message
|
||
{
|
||
get => message;
|
||
private set => SetProperty(ref message, value);
|
||
}
|
||
|
||
public string Tone
|
||
{
|
||
get => tone;
|
||
private set => SetProperty(ref tone, value);
|
||
}
|
||
|
||
public ICommand RunCommand { get; }
|
||
public ICommand CancelCommand { get; }
|
||
public ICommand EnableGateCommand { get; }
|
||
|
||
internal void RefreshStatus()
|
||
{
|
||
if (IsRunning) return;
|
||
var status = GateStatus();
|
||
Message = status.Message;
|
||
Tone = status.Tone;
|
||
RaiseStateChanged();
|
||
}
|
||
|
||
internal void Cancel() => stop?.Cancel();
|
||
|
||
public void Dispose()
|
||
{
|
||
stop?.Cancel();
|
||
stop?.Dispose();
|
||
stop = null;
|
||
}
|
||
|
||
private async Task RunAsync()
|
||
{
|
||
if (!IsAvailable)
|
||
{
|
||
Message = "当前安装包没有 ANSYS 验收工具。请先安装新版完整 Node 包。";
|
||
Tone = "error";
|
||
return;
|
||
}
|
||
var parent = ChooseReportDirectoryRequested?.Invoke();
|
||
if (string.IsNullOrWhiteSpace(parent) || ConfirmRunRequested?.Invoke() != true)
|
||
{
|
||
return;
|
||
}
|
||
|
||
var workRoot = Path.Combine(
|
||
parent,
|
||
$"zcbot-ansys-acceptance-{DateTime.Now:yyyyMMdd-HHmmss}");
|
||
stop?.Dispose();
|
||
stop = new CancellationTokenSource();
|
||
passedReportPath = null;
|
||
Message = "正在启动真机验收…";
|
||
Tone = "warning";
|
||
RaiseStateChanged();
|
||
var progress = new Progress<string>(line => Message = line);
|
||
try
|
||
{
|
||
var result = await service.RunAsync(workRoot, progress, stop.Token);
|
||
passedReportPath = result.Passed ? result.ReportPath : null;
|
||
Tone = result.Passed ? "success" : "error";
|
||
Message = result.Passed
|
||
? $"验收通过。报告:{result.ReportPath}\n请确认许可证管理端席位已释放,再开启执行门。"
|
||
: $"验收未通过:{result.Detail}\n报告:{result.ReportPath}";
|
||
}
|
||
catch (OperationCanceledException)
|
||
{
|
||
passedReportPath = null;
|
||
Tone = "neutral";
|
||
Message = "验收已停止,执行门保持关闭。";
|
||
}
|
||
catch (Exception exception) when (
|
||
exception is IOException
|
||
or InvalidDataException
|
||
or InvalidOperationException
|
||
or JsonException
|
||
or UnauthorizedAccessException)
|
||
{
|
||
passedReportPath = null;
|
||
Tone = "error";
|
||
Message = $"验收启动或执行失败:{exception.Message}";
|
||
}
|
||
finally
|
||
{
|
||
stop.Dispose();
|
||
stop = null;
|
||
RaiseStateChanged();
|
||
}
|
||
}
|
||
|
||
private void EnableGate()
|
||
{
|
||
if (passedReportPath is null || ConfirmEnableGateRequested?.Invoke() != true)
|
||
{
|
||
return;
|
||
}
|
||
try
|
||
{
|
||
service.EnableGate(passedReportPath);
|
||
Tone = "success";
|
||
Message = "机器级执行门已写入。请从托盘退出 Node 后重新启动;重启后 ANSYS 应显示可用。";
|
||
}
|
||
catch (Exception exception) when (
|
||
exception is InvalidDataException
|
||
or SecurityException
|
||
or UnauthorizedAccessException)
|
||
{
|
||
Tone = "error";
|
||
Message = exception is SecurityException or UnauthorizedAccessException
|
||
? $"写入机器级环境变量需要管理员权限:{exception.Message}\n请退出 Node,以管理员身份启动后再次点击。"
|
||
: exception.Message;
|
||
}
|
||
RaiseStateChanged();
|
||
}
|
||
|
||
private (string Message, string Tone) GateStatus() => IsGateEnabled
|
||
? ("当前执行门:已开启。Node 重启并且 probe 正常后可接收 ANSYS 任务。", "success")
|
||
: ("当前执行门:关闭。安装成功不代表验收通过,请先运行本页验收。", "neutral");
|
||
|
||
private void RaiseStateChanged()
|
||
{
|
||
OnPropertyChanged(nameof(IsRunning));
|
||
OnPropertyChanged(nameof(IsGateEnabled));
|
||
OnPropertyChanged(nameof(CanEnableGate));
|
||
runCommand.RaiseCanExecuteChanged();
|
||
cancelCommand.RaiseCanExecuteChanged();
|
||
enableGateCommand.RaiseCanExecuteChanged();
|
||
}
|
||
}
|