File: TemplateDiscoveryMetadata\LegacySearchCacheReader.cs
Web Access
Project: src\src\sdk\src\TemplateEngine\Microsoft.TemplateSearch.Common\Microsoft.TemplateSearch.Common.csproj (Microsoft.TemplateSearch.Common)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections;
using System.Text.Json;
using System.Text.Json.Nodes;
using Microsoft.Extensions.Logging;
using Microsoft.TemplateEngine;
using Microsoft.TemplateEngine.Abstractions;

namespace Microsoft.TemplateSearch.Common
{
    [Obsolete("Use TemplateSearchCache instead.")]
    internal static class LegacySearchCacheReader
    {
        internal static TemplateSearchCache ConvertTemplateDiscoveryMetadata(TemplateDiscoveryMetadata discoveryMetadata, IReadOnlyDictionary<string, Func<object, object>>? additionalDataReaders)
        {
            List<TemplatePackageSearchData> packageData = new List<TemplatePackageSearchData>();
            foreach (var package in discoveryMetadata.PackToTemplateMap)
            {
                List<TemplateSearchData> templateData = new List<TemplateSearchData>();
                foreach (var template in package.Value.TemplateIdentificationEntry)
                {
                    var foundTemplate = discoveryMetadata.TemplateCache.FirstOrDefault(t => t.Identity.Equals(template.Identity, StringComparison.OrdinalIgnoreCase));
                    if (foundTemplate == null)
                    {
                        continue;
                    }
                    if (additionalDataReaders != null)
                    {
                        Dictionary<string, object> data = new Dictionary<string, object>();
                        foreach (var reader in additionalDataReaders)
                        {
                            if (discoveryMetadata.AdditionalData.TryGetValue(reader.Key, out object? legacyData))
                            {
                                IDictionary? dict = legacyData as IDictionary;
                                if (dict?.Contains(template.Identity) ?? false)
                                {
                                    data[reader.Key] = dict[template.Identity];
                                }
                            }
                        }
                        templateData.Add(new TemplateSearchData(foundTemplate, data));
                    }
                    else
                    {
                        templateData.Add(new TemplateSearchData(foundTemplate));
                    }
                }
                packageData.Add(new TemplatePackageSearchData(new PackInfo(package.Key, package.Value.Version, package.Value.TotalDownloads, package.Value.Owners, package.Value.Reserved), templateData));
            }
            return new TemplateSearchCache(packageData);
        }

        internal static bool TryReadDiscoveryMetadata(IEngineEnvironmentSettings environmentSettings, string filePath, IReadOnlyDictionary<string, Func<object, object>>? additionalDataReaders, out TemplateDiscoveryMetadata? discoveryMetadata)
        {
            string pathToConfig = Path.Combine(environmentSettings.Paths.HostVersionSettingsDir, filePath);
            environmentSettings.Host.Logger.LogDebug($"Reading cache file {pathToConfig}");
            string cacheText = environmentSettings.Host.FileSystem.ReadAllText(pathToConfig);

            JsonObject cacheObject = JExtensions.ParseJsonObject(cacheText);

            return TryReadDiscoveryMetadata(cacheObject, environmentSettings.Host.Logger, additionalDataReaders, out discoveryMetadata);
        }

        internal static bool TryReadDiscoveryMetadata(JsonObject cacheObject, ILogger logger, IReadOnlyDictionary<string, Func<object, object>>? additionalDataReaders, out TemplateDiscoveryMetadata? discoveryMetadata)
        {
            // add the reader calls, build the model objects
            if (TryReadVersion(logger, cacheObject, out string? version)
                && TryReadTemplateList(logger, cacheObject, out IReadOnlyList<ITemplateInfo>? templateList)
                && TryReadPackToTemplateMap(logger, cacheObject, out IReadOnlyDictionary<string, PackToTemplateEntry>? packToTemplateMap)
                && TryReadAdditionalData(logger, cacheObject, additionalDataReaders, out IReadOnlyDictionary<string, object>? additionalDta))
            {
                discoveryMetadata = new TemplateDiscoveryMetadata(version!, templateList!, packToTemplateMap!, additionalDta!);
                return true;
            }
            discoveryMetadata = null;
            return false;
        }

        private static bool TryReadVersion(ILogger logger, JsonObject cacheObject, out string? version)
        {
            logger.LogDebug($"Reading template metadata version");
            if (cacheObject.TryGetValueCaseInsensitive(nameof(TemplateDiscoveryMetadata.Version), out JsonNode? value))
            {
                version = value?.ToString();
                logger.LogDebug($"Version: {version}.");
                return true;
            }
            logger.LogDebug($"Failed to read template metadata version.");
            version = null;
            return false;
        }

        private static bool TryReadTemplateList(
            ILogger logger,
            JsonObject cacheObject,
            out IReadOnlyList<ITemplateInfo>? templateList)
        {
            logger.LogDebug($"Reading template list");
            try
            {
                // This is lifted from TemplateCache.ParseCacheContent - almost identical
                if (cacheObject.TryGetValueCaseInsensitive(nameof(TemplateDiscoveryMetadata.TemplateCache), out JsonNode? templateInfoToken))
                {
                    List<ITemplateInfo> buildingTemplateList = new List<ITemplateInfo>();

                    if (templateInfoToken is JsonArray arr)
                    {
                        foreach (JsonNode? entry in arr)
                        {
                            if (entry is JsonObject entryObj)
                            {
                                try
                                {
                                    buildingTemplateList.Add(BlobStorageTemplateInfo.FromJObject(entryObj));
                                }
                                catch (ArgumentException ex)
                                {
                                    logger.LogDebug($"Failed to read template info entry, missing mandatory fields. Details: {ex}");
                                }
                            }
                        }
                    }

                    logger.LogDebug($"Successfully read {buildingTemplateList.Count} templates.");
                    templateList = buildingTemplateList;
                    return true;
                }

                logger.LogDebug($"Failed to read template info entries. Details: no TemplateCache property found.");
                templateList = null;
                return false;
            }
            catch (Exception ex)
            {
                logger.LogDebug($"Failed to read template info entries. Details: {ex}");
                templateList = null;
                return false;
            }
        }

        private static bool TryReadPackToTemplateMap(ILogger logger, JsonObject cacheObject, out IReadOnlyDictionary<string, PackToTemplateEntry>? packToTemplateMap)
        {
            logger.LogDebug($"Reading package information.");
            try
            {
                JsonNode? packToTemplateMapToken = JExtensions.GetPropertyCaseInsensitive(cacheObject, nameof(TemplateDiscoveryMetadata.PackToTemplateMap));
                if (packToTemplateMapToken is not JsonObject packToTemplateMapObject)
                {
                    logger.LogDebug($"Failed to read package info entries. Details: no PackToTemplateMap property found.");
                    packToTemplateMap = null;
                    return false;
                }

                Dictionary<string, PackToTemplateEntry> workingPackToTemplateMap = new();

                foreach (var packEntry in packToTemplateMapObject)
                {
                    if (packEntry.Value != null)
                    {
                        string packName = packEntry.Key;
                        JsonObject entryValue = (JsonObject)packEntry.Value;

                        JsonNode? versionNode = JExtensions.GetPropertyCaseInsensitive(entryValue, nameof(PackToTemplateEntry.Version));
                        JsonNode? identificationNode = JExtensions.GetPropertyCaseInsensitive(entryValue, nameof(PackToTemplateEntry.TemplateIdentificationEntry));
                        if (versionNode is JsonValue versionVal && versionVal.GetValueKind() == JsonValueKind.String
                            && identificationNode is JsonArray identificationArray)
                        {
                            string? version = versionNode.ToString() ?? throw new Exception("Version value is null.");
                            List<TemplateIdentificationEntry> templatesInPack = new List<TemplateIdentificationEntry>();

                            foreach (JsonNode? templateIdentityInfo in identificationArray)
                            {
                                string? identity = templateIdentityInfo?.ToString(nameof(TemplateIdentificationEntry.Identity));
                                string? groupIdentity = templateIdentityInfo?.ToString(nameof(TemplateIdentificationEntry.GroupIdentity));

                                if (identity == null)
                                {
                                    throw new Exception("Identity value is null.");
                                }
                                TemplateIdentificationEntry deserializedEntry = new TemplateIdentificationEntry(identity, groupIdentity);
                                templatesInPack.Add(deserializedEntry);
                            }

                            workingPackToTemplateMap[packName] = new PackToTemplateEntry(version, templatesInPack);
                            if (entryValue.TryGetValueCaseInsensitive(nameof(PackToTemplateEntry.TotalDownloads), out JsonNode? totalDownloadsNode)
                                && long.TryParse(totalDownloadsNode?.ToString(), out long totalDownloads))
                            {
                                workingPackToTemplateMap[packName].TotalDownloads = totalDownloads;
                            }
                        }
                    }
                }

                logger.LogDebug($"Successfully read {workingPackToTemplateMap.Count} packages.");
                packToTemplateMap = workingPackToTemplateMap;
                return true;
            }
            catch (Exception ex)
            {
                logger.LogDebug($"Failed to read package info entries. Details: {ex}");
                packToTemplateMap = null;
                return false;
            }
        }

        private static bool TryReadAdditionalData(ILogger logger, JsonObject cacheObject, IReadOnlyDictionary<string, Func<object, object>>? additionalDataReaders, out IReadOnlyDictionary<string, object>? additionalData)
        {
            if (additionalDataReaders == null)
            {
                additionalData = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
                return true;
            }
            logger.LogDebug($"Reading additional information.");
            // get the additional data section
            JsonNode? additionalDataToken = JExtensions.GetPropertyCaseInsensitive(cacheObject, nameof(TemplateDiscoveryMetadata.AdditionalData));
            if (additionalDataToken is not JsonObject additionalDataObject)
            {
                logger.LogDebug($"Failed to read package info entries. Details: no AdditionalData property found.");
                additionalData = null;
                return false;
            }

            Dictionary<string, object> workingAdditionalData = new(StringComparer.OrdinalIgnoreCase);

            foreach (KeyValuePair<string, Func<object, object>> dataReadInfo in additionalDataReaders)
            {
                try
                {
                    // get the entry for this piece of additional data
                    JsonNode? dataNode = JExtensions.GetPropertyCaseInsensitive(additionalDataObject, dataReadInfo.Key);
                    if (dataNode is not JsonObject dataObject)
                    {
                        // this piece of data wasn't found, or wasn't valid. Ignore it.
                        continue;
                    }

                    workingAdditionalData[dataReadInfo.Key] = dataReadInfo.Value(dataObject);
                }
                catch (Exception ex)
                {
                    logger.LogDebug($"Failed to read additional info entries. Details: {ex}");
                    // Do nothing.
                    // This piece of data failed to read, but isn't strictly necessary.
                }
            }

            logger.LogDebug($"Successfully read {workingAdditionalData.Count} additional information entries.");
            additionalData = workingAdditionalData;
            return true;
        }
    }
}