61 lines
5.0 KiB
C#
61 lines
5.0 KiB
C#
using Launcher.Next.Core;
|
|
|
|
namespace Launcher.Next.Application;
|
|
|
|
public sealed class PackageResolutionUseCase
|
|
{
|
|
private readonly IPackageCatalogPackageAcquirer catalog; private readonly IPackageSynchronizer synchronizer; private readonly IPackageRequirementCacheBinder binder; private readonly ILastKnownGoodPackageResolver cache; private readonly ILauncherTelemetry telemetry; private readonly IPackageMaterializer? materializer;
|
|
public PackageResolutionUseCase(IPackageCatalogPackageAcquirer c, IPackageSynchronizer s, IPackageRequirementCacheBinder b, ILastKnownGoodPackageResolver l, ILauncherTelemetry t) { catalog=c; synchronizer=s; binder=b; cache=l; telemetry=t; }
|
|
public PackageResolutionUseCase(IPackageCatalogPackageAcquirer c, IPackageSynchronizer s, IPackageRequirementCacheBinder b, ILastKnownGoodPackageResolver l, IPackageMaterializer m, ILauncherTelemetry t) : this(c,s,b,l,t) { materializer=m; }
|
|
public async Task<PackageResolutionResult> ResolveAsync(PackageRequirement requirement, CancellationToken cancellationToken)
|
|
{
|
|
var evidence = new List<PackageResolutionEvidence> { new("PACKAGE_REQUIREMENT_RECEIVED", requirement.PackageId) };
|
|
telemetry.Write("PackageResolutionStarted", new Dictionary<string, object?> { ["packageId"] = requirement.PackageId, ["product"] = requirement.Product, ["channel"] = requirement.Channel, ["offlineAllowed"] = requirement.AllowOfflineFallback });
|
|
try
|
|
{
|
|
var online = await catalog.AcquireAsync(requirement, cancellationToken);
|
|
if (online is not null && Matches(requirement, online.Manifest))
|
|
{
|
|
if (materializer is not null)
|
|
{
|
|
var committed = await materializer.MaterializeAsync(online.Manifest, online.SourceRoot, cancellationToken);
|
|
await binder.BindAsync(requirement, committed.Version, cancellationToken);
|
|
evidence.Add(new("ONLINE_PACKAGE_MATERIALIZED_NOT_ACTIVATED", committed.Version));
|
|
return new(new CachedPackageResolution(committed.PackageId, committed.Version, committed.LocalPackageRoot, false, "MATERIALIZED_NOT_ACTIVATED"), true, evidence);
|
|
}
|
|
await synchronizer.SynchronizeAsync(online.Manifest, online.SourceRoot, cancellationToken);
|
|
await binder.BindAsync(requirement, online.Manifest.Version, cancellationToken);
|
|
var selected = await cache.ResolveAsync(requirement, cancellationToken);
|
|
if (selected is not null && !selected.IsOfflineFallback)
|
|
{
|
|
evidence.Add(new("ONLINE_PACKAGE_SELECTED", online.Manifest.Version));
|
|
telemetry.Write("PackageResolutionSelected", new Dictionary<string, object?> { ["code"] = "ONLINE_PACKAGE_SELECTED", ["packageId"] = selected.PackageId, ["version"] = selected.Version });
|
|
return new(selected, true, evidence);
|
|
}
|
|
evidence.Add(new("ONLINE_PACKAGE_REJECTED", "LOCAL_REVALIDATION_FAILED"));
|
|
}
|
|
else evidence.Add(new("CATALOG_UNAVAILABLE", online is null ? "NO_CATALOG_PACKAGE" : "REQUIREMENT_MISMATCH"));
|
|
}
|
|
catch (Exception ex) when (ex is not OperationCanceledException) { evidence.Add(new("CATALOG_UNAVAILABLE", "ACQUISITION_OR_STAGING_FAILED")); }
|
|
if (!requirement.AllowOfflineFallback)
|
|
{
|
|
evidence.Add(new("OFFLINE_FALLBACK_DISABLED", "POLICY"));
|
|
evidence.Add(new("NO_SAFE_PACKAGE_AVAILABLE", "BLOCKED"));
|
|
telemetry.Write("PackageResolutionBlocked", new Dictionary<string, object?> { ["code"] = "OFFLINE_FALLBACK_DISABLED", ["packageId"] = requirement.PackageId });
|
|
return new(null, false, evidence);
|
|
}
|
|
var cached = await cache.ResolveAsync(requirement, cancellationToken);
|
|
if (cached is null)
|
|
{
|
|
evidence.Add(new("NO_SAFE_PACKAGE_AVAILABLE", "CACHE_REJECTED"));
|
|
telemetry.Write("PackageResolutionBlocked", new Dictionary<string, object?> { ["code"] = "NO_SAFE_PACKAGE_AVAILABLE", ["packageId"] = requirement.PackageId });
|
|
return new(null, false, evidence);
|
|
}
|
|
var code = cached.IsOfflineFallback ? "PREVIOUS_LKG_SELECTED" : "VALID_CURRENT_CACHE_SELECTED";
|
|
evidence.Add(new(code, cached.Version));
|
|
telemetry.Write("PackageResolutionSelected", new Dictionary<string, object?> { ["code"] = code, ["packageId"] = cached.PackageId, ["version"] = cached.Version });
|
|
return new(cached, false, evidence);
|
|
}
|
|
private static bool Matches(PackageRequirement r, PackageManifest m) => string.Equals(r.PackageId, m.PackageId, StringComparison.Ordinal) && string.Equals(r.Product, m.Product, StringComparison.Ordinal) && r.ProductYear == m.ProductYear && string.Equals(r.Architecture, m.Architecture, StringComparison.OrdinalIgnoreCase) && string.Equals(r.Channel, m.RolloutChannel, StringComparison.OrdinalIgnoreCase) && string.Equals(r.RequiredVersion, m.Version, StringComparison.Ordinal);
|
|
}
|