147 lines
5.3 KiB
C#
147 lines
5.3 KiB
C#
namespace Zcbot.WindowsNode;
|
||
|
||
internal sealed record SoftwareManagementSnapshot(
|
||
string Id,
|
||
string DisplayName,
|
||
SoftwarePathKind PathKind,
|
||
string RequiredRelativePath,
|
||
bool LocationAvailable,
|
||
string LocationText,
|
||
string StatusText,
|
||
bool RuntimeInstalled,
|
||
bool HasActiveJobs);
|
||
|
||
internal interface ISoftwareManagementService
|
||
{
|
||
Task<IReadOnlyList<SoftwareManagementSnapshot>> RefreshAsync(
|
||
CancellationToken cancellationToken);
|
||
|
||
void SaveLocation(string softwareId, string path);
|
||
void ClearLocation(string softwareId);
|
||
|
||
Task<RuntimeInstallResult> InstallRuntimeAsync(
|
||
string softwareId,
|
||
IProgress<string> progress,
|
||
CancellationToken cancellationToken);
|
||
}
|
||
|
||
internal sealed class SoftwareManagementService : ISoftwareManagementService
|
||
{
|
||
private readonly NodePaths paths;
|
||
private readonly AdapterCatalog adapters;
|
||
|
||
internal SoftwareManagementService(NodePaths paths)
|
||
{
|
||
this.paths = paths;
|
||
adapters = AdapterCatalog.CreateDefault(
|
||
new JobRepository(paths.JobsDirectory),
|
||
paths);
|
||
}
|
||
|
||
public Task<IReadOnlyList<SoftwareManagementSnapshot>> RefreshAsync(
|
||
CancellationToken cancellationToken) =>
|
||
Task.Run<IReadOnlyList<SoftwareManagementSnapshot>>(
|
||
CreateSnapshots,
|
||
cancellationToken);
|
||
|
||
public void SaveLocation(string softwareId, string path)
|
||
{
|
||
var definition = Definition(softwareId);
|
||
SoftwareLocationService.SaveConfiguredPath(definition, path);
|
||
adapters.InvalidateRuntime(definition.RuntimeId);
|
||
}
|
||
|
||
public void ClearLocation(string softwareId)
|
||
{
|
||
var definition = Definition(softwareId);
|
||
SoftwareLocationService.ClearConfiguredPath(definition);
|
||
adapters.InvalidateRuntime(definition.RuntimeId);
|
||
}
|
||
|
||
public async Task<RuntimeInstallResult> InstallRuntimeAsync(
|
||
string softwareId,
|
||
IProgress<string> progress,
|
||
CancellationToken cancellationToken)
|
||
{
|
||
var definition = Definition(softwareId);
|
||
var result = await ManagedRuntimeInstaller.InstallAsync(
|
||
definition,
|
||
progress,
|
||
cancellationToken);
|
||
adapters.InvalidateRuntime(definition.RuntimeId);
|
||
return result;
|
||
}
|
||
|
||
private IReadOnlyList<SoftwareManagementSnapshot> CreateSnapshots()
|
||
{
|
||
return SoftwareCatalog.Definitions.Select(definition =>
|
||
{
|
||
var location = SoftwareLocationService.ResolveLocation(definition);
|
||
var runtimePath = Path.Combine(
|
||
paths.RootDirectory,
|
||
"runtimes",
|
||
definition.RuntimeId,
|
||
"Scripts",
|
||
"python.exe");
|
||
var runtimeInstalled = File.Exists(runtimePath);
|
||
var matchingAdapters = adapters.All.Where(item =>
|
||
definition.RuntimeId.Equals(item.RuntimeId, StringComparison.Ordinal)).ToArray();
|
||
var hasActiveJobs = AdapterCatalog.HasActiveExecution
|
||
|| matchingAdapters.Any(item => item.HasActiveJobs);
|
||
var status = BuildStatus(
|
||
location,
|
||
runtimeInstalled,
|
||
matchingAdapters);
|
||
return new SoftwareManagementSnapshot(
|
||
definition.Id,
|
||
definition.DisplayName,
|
||
definition.PathKind,
|
||
definition.RequiredRelativePath ?? string.Empty,
|
||
location.Available,
|
||
location.Path ?? "未检测到应用位置",
|
||
status,
|
||
runtimeInstalled,
|
||
hasActiveJobs);
|
||
}).ToArray();
|
||
}
|
||
|
||
private static string BuildStatus(
|
||
SoftwareLocation location,
|
||
bool runtimeInstalled,
|
||
IReadOnlyList<INodeAdapter> matchingAdapters)
|
||
{
|
||
var runtime = runtimeInstalled ? "运行环境已安装" : "运行环境尚未安装";
|
||
if (!location.Available)
|
||
{
|
||
return $"{runtime} · {location.Detail}";
|
||
}
|
||
if (!runtimeInstalled || matchingAdapters.Count == 0)
|
||
{
|
||
return $"{runtime} · {location.Source}";
|
||
}
|
||
|
||
var probes = matchingAdapters.Select(item => item.DetectRuntime()).ToArray();
|
||
var health = probes.All(item => item.Health == "ready")
|
||
? "可用"
|
||
: probes.Any(item => item.Health == "ready") ? "部分可用" : "需配置";
|
||
var versions = probes
|
||
.Select(item => item.SoftwareVersion)
|
||
.Where(value => !string.IsNullOrWhiteSpace(value))
|
||
.Distinct(StringComparer.Ordinal)
|
||
.ToArray();
|
||
var version = versions.Length == 0 ? string.Empty : $" · {string.Join(" / ", versions)}";
|
||
var detail = string.Join(";", probes
|
||
.Where(item => item.Health != "ready")
|
||
.Select(item => item.Detail)
|
||
.Where(value => !string.IsNullOrWhiteSpace(value))
|
||
.Distinct(StringComparer.Ordinal));
|
||
return string.IsNullOrWhiteSpace(detail)
|
||
? $"{runtime} · {health}{version} · {location.Source}"
|
||
: $"{runtime} · {health}{version} · {detail}";
|
||
}
|
||
|
||
private static SoftwareDefinition Definition(string softwareId) =>
|
||
SoftwareCatalog.Definitions.Single(item =>
|
||
item.Id.Equals(softwareId, StringComparison.Ordinal));
|
||
}
|