File: Commands\Workload\GlobalJsonWorkloadSetFile.cs
Web Access
Project: src\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.

#nullable disable

using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.NET.Sdk.WorkloadManifestReader;

namespace Microsoft.DotNet.Cli.Commands.Workload;

internal class GlobalJsonWorkloadSetsFile(SdkFeatureBand sdkFeatureBand, string dotnetDir)
{
    public string Path { get; } = System.IO.Path.Combine(WorkloadInstallType.GetInstallStateFolder(sdkFeatureBand, dotnetDir), "globaljsonworkloadsets.json");

    public void RecordWorkloadSetInGlobalJson(string globalJsonPath, string workloadSetVersion)
    {
        //  Create install state folder if needed
        Directory.CreateDirectory(System.IO.Path.GetDirectoryName(Path));

        using (var fileStream = File.Open(Path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None))
        {
            Dictionary<string, string> globalJsonWorkloadSetVersions;
            if (fileStream.Length > 0)
            {
                globalJsonWorkloadSetVersions = JsonSerializer.Deserialize(fileStream, GlobalJsonWorkloadSetsJsonSerializerContext.Default.DictionaryStringString);
            }
            else
            {
                globalJsonWorkloadSetVersions = [];
            }
            globalJsonWorkloadSetVersions[globalJsonPath] = workloadSetVersion;
            fileStream.Seek(0, SeekOrigin.Begin);
            JsonSerializer.Serialize(fileStream, globalJsonWorkloadSetVersions, GlobalJsonWorkloadSetsJsonSerializerContext.Default.DictionaryStringString);
        }
    }

    public Dictionary<string, string> GetGlobalJsonWorkloadSetVersions()
    {
        //  If the file doesn't exist, we don't need to create it
        FileStream OpenFile()
        {
            try
            {
                return File.Open(Path, FileMode.Open, FileAccess.ReadWrite, FileShare.None);
            }
            catch (FileNotFoundException)
            {
                return null;
            }
            catch (DirectoryNotFoundException)
            {
                return null;
            }
        }

        using (var fileStream = OpenFile())
        {
            if (fileStream == null)
            {
                return [];
            }

            var globalJsonWorkloadSetVersions = JsonSerializer.Deserialize(fileStream, GlobalJsonWorkloadSetsJsonSerializerContext.Default.DictionaryStringString);
            bool updated = false;

            //  Create copy of dictionary for iteration so we can modify the original in the loop
            foreach (var kvp in new Dictionary<string, string>(globalJsonWorkloadSetVersions))
            {
                var updatedVersion = GetWorkloadVersionFromGlobalJson(kvp.Key);
                //  If the version in global.json has changed, we remove it from the GC root file rather than updating it.
                //  This avoids having workload sets in the GC Root file which may not be installed, which makes
                //  garbage collection easier
                if (updatedVersion == null || updatedVersion != kvp.Value)
                {
                    updated = true;
                    globalJsonWorkloadSetVersions.Remove(kvp.Key);
                }
            }

            if (updated)
            {
                fileStream.Seek(0, SeekOrigin.Begin);
                JsonSerializer.Serialize(fileStream, globalJsonWorkloadSetVersions, GlobalJsonWorkloadSetsJsonSerializerContext.Default.DictionaryStringString);
            }

            return globalJsonWorkloadSetVersions;
        }
    }

    private static string GetWorkloadVersionFromGlobalJson(string globalJsonPath)
    {
        try
        {
            return SdkDirectoryWorkloadManifestProvider.GlobalJsonReader.GetWorkloadVersionFromGlobalJson(globalJsonPath, out _);
        }
        catch (SdkDirectoryWorkloadManifestProvider.JsonFormatException)
        {
            //  If Json file is malformed then treat it as if it doesn't specify a workload set version
            return null;
        }
    }
}

[JsonSourceGenerationOptions(
    WriteIndented = true,
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
[JsonSerializable(typeof(Dictionary<string, string>))]
internal partial class GlobalJsonWorkloadSetsJsonSerializerContext : JsonSerializerContext;