zcbot/windows-node/Zcbot.WindowsNode/Presentation/Services/SoftwareManagementService.cs

147 lines
5.3 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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