File: Commands\Workload\Install\FileBasedManifestInstaller.cs
Web Access
Project: src\sdk\src\Cli\dotnet\dotnet.csproj (dotnet)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.DotNet.Cli.NuGetPackageDownloader;
using Microsoft.DotNet.Cli.ToolPackage;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.Extensions.EnvironmentAbstractions;
using Microsoft.NET.Sdk.WorkloadManifestReader;

namespace Microsoft.DotNet.Cli.Commands.Workload.Install;

/// <summary>
///  The <see cref="IWorkloadManifestInstaller"/> behavior for file-based (non-MSI) workload installs:
///  computing the NuGet package ID that carries a manifest/workload-set, and extracting the "data"
///  directory of a downloaded manifest package to the advertising/installed manifest location.
///
///  <para>
///  Extracted out of <see cref="FileBasedInstaller"/> so this narrow, dependency-light behavior can be
///  shared by the full file-based installer and by lightweight consumers - such as the background
///  advertising-manifest updater - that only need to resolve/extract manifest packages and must not
///  pull in the rest of <see cref="FileBasedInstaller"/> (pack installation, transactions, workload
///  records, etc.).
///  </para>
/// </summary>
internal class FileBasedManifestInstaller(INuGetPackageDownloader nugetPackageDownloader, DirectoryPath tempPackagesDir) : IWorkloadManifestInstaller
{
    public PackageId GetManifestPackageId(ManifestId manifestId, SdkFeatureBand featureBand)
    {
        if (manifestId.ToString().Equals(WorkloadManifestUpdater.WorkloadSetManifestId, StringComparison.OrdinalIgnoreCase))
        {
            return new PackageId($"{manifestId}.{featureBand}");
        }
        else
        {
            return new PackageId($"{manifestId}.Manifest-{featureBand}");
        }
    }

    public async Task ExtractManifestAsync(string nupkgPath, string targetPath)
    {
        var extractionPath = Path.Combine(tempPackagesDir.Value, "dotnet-sdk-advertising-temp", $"{Path.GetFileName(nupkgPath)}-extracted");
        if (Directory.Exists(extractionPath))
        {
            Directory.Delete(extractionPath, true);
        }

        try
        {
            Directory.CreateDirectory(extractionPath);
            await nugetPackageDownloader.ExtractPackageAsync(nupkgPath, new DirectoryPath(extractionPath));
            if (Directory.Exists(targetPath))
            {
                Directory.Delete(targetPath, true);
            }
            Directory.CreateDirectory(Path.GetDirectoryName(targetPath)!);
            FileAccessRetrier.RetryOnMoveAccessFailure(() => DirectoryPath.MoveDirectory(Path.Combine(extractionPath, "data"), targetPath));
        }
        finally
        {
            if (!string.IsNullOrEmpty(extractionPath) && Directory.Exists(extractionPath))
            {
                Directory.Delete(extractionPath, true);
            }
        }
    }
}