File: Donor\ProjectDataDonorIndex.Reader.cs
Web Access
Project: Microsoft.NET.ProjectData.csproj (Microsoft.NET.ProjectData)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Concurrent;

namespace Microsoft.NET.ProjectData;

public static partial class ProjectDataDonorIndex
{
	private const string CacheFileExtension = ".lscache";
	private const int MaximumCachedContexts = 64;
	private const int GitMetadataCandidateLimit = 5;
	private static readonly ConcurrentDictionary<string, SelectionCacheEntry> SelectionCache = new(PathComparer);
	private static readonly ConcurrentDictionary<string, WorkspaceMetadataCacheEntry> WorkspaceMetadataCache = new(PathComparer);

	internal static int SelectionCacheCount => SelectionCache.Count;
	internal static int WorkspaceMetadataCacheCount => WorkspaceMetadataCache.Count;

	public static IEnumerable<ProjectDataDonorCandidate> EnumerateDonorCandidates(
		string recipientProjectFilePath,
		ProjectDataDonorOptions? options,
		CancellationToken cancellationToken)
	{
		cancellationToken.ThrowIfCancellationRequested();
		GitQueryContext gitQueryContext = new(cancellationToken);
		foreach (ProjectDataDonorCandidate candidate in EnumerateDonorCandidatesCore(recipientProjectFilePath, options, gitQueryContext))
		{
			cancellationToken.ThrowIfCancellationRequested();
			yield return candidate;
		}

		cancellationToken.ThrowIfCancellationRequested();
	}

	internal static IEnumerable<ProjectDataDonorCandidate> EnumerateDonorCandidatesCore(
		string recipientProjectFilePath,
		ProjectDataDonorOptions? options,
		GitQueryContext gitQueryContext)
	{
		if (string.IsNullOrEmpty(recipientProjectFilePath))
		{
			yield break;
		}

		options ??= ProjectDataDonorOptions.Default;
		if (!options.Enabled)
		{
			yield break;
		}

		if (!TryResolveIndexContext(
			recipientProjectFilePath,
			options.WorkspaceRoot,
			options.IndexPath,
			out string? workspaceRoot,
			out string? indexPath))
		{
			yield break;
		}

		if (!TryGetRelativePath(workspaceRoot, recipientProjectFilePath, out string? relativeProjectPath))
		{
			yield break;
		}

		bool probeUserFolderCache = true;
		ProjectDataDonorSelection selection = GetSelection(indexPath, workspaceRoot, options, gitQueryContext);
		foreach (ProjectDataDonorIndexEntry entry in selection.RankedEntries)
		{
			if (PathsEqual(entry.Path, workspaceRoot))
			{
				continue;
			}

			string donorProjectFilePath = Path.GetFullPath(Path.Combine(entry.Path, relativeProjectPath));
			string donorProjectFolderCachePath = donorProjectFilePath + CacheFileExtension;
			if (File.Exists(donorProjectFolderCachePath))
			{
				yield return new ProjectDataDonorCandidate(donorProjectFolderCachePath, entry.Path);
			}

			if (probeUserFolderCache)
			{
				if (!UserFolderCachePath.TryCompute(donorProjectFilePath, out string donorUserFolderCachePath))
				{
					options.TraceWarning(
						"[donor] User-folder cache root is unavailable; continuing with project-folder donors for recipient {0}.",
						recipientProjectFilePath);
					probeUserFolderCache = false;
				}
				else if (File.Exists(donorUserFolderCachePath))
				{
					yield return new ProjectDataDonorCandidate(donorUserFolderCachePath, entry.Path);
				}
			}
		}
	}

	static partial void OnIndexUpdated()
		=> SelectionCache.Clear();
}