using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using PortingAssistant.Client.Common.Utils; using PortingAssistant.Client.Model; namespace PortingAssistant.Client.NuGet { public class PortingAssistantNuGetHandler : IPortingAssistantNuGetHandler { private readonly ILogger _logger; private readonly IEnumerable _compatibilityCheckers; private readonly ConcurrentDictionary> _compatibilityTaskCompletionSources; public PortingAssistantNuGetHandler( ILogger logger, IEnumerable compatibilityCheckers) { _logger = logger; _compatibilityCheckers = compatibilityCheckers.OrderBy((c) => c.CompatibilityCheckerType); _compatibilityTaskCompletionSources = new ConcurrentDictionary>(); } public Dictionary> GetNugetPackages(List packageVersions, string pathToSolution, bool isIncremental = false, bool incrementalRefresh = false ) { _logger.LogInformation("Memory usage before GetNugetPackages: "); MemoryUtils.LogMemoryConsumption(_logger); var packageVersionsToQuery = new List(); var tasks = packageVersions.Select(packageVersion => { var isNewCompatibilityTask = _compatibilityTaskCompletionSources.TryAdd(packageVersion, new TaskCompletionSource()); if (isNewCompatibilityTask) { packageVersionsToQuery.Add(packageVersion); } var packageVersionPairResult = _compatibilityTaskCompletionSources[packageVersion]; return new Tuple>(packageVersion, packageVersionPairResult.Task); }).ToDictionary(t => t.Item1, t => t.Item2); _logger.LogInformation("Checking compatibility for {0} packages", packageVersionsToQuery.Count); Process(packageVersionsToQuery, pathToSolution, isIncremental, incrementalRefresh); _logger.LogInformation("Memory usage after GetNugetPackages: "); MemoryUtils.LogMemoryConsumption(_logger); return tasks; } private async void Process(List packageVersions, string pathToSolution, bool isIncremental = false, bool incrementalRefresh = false) { if (!packageVersions.Any()) { _logger.LogInformation("No package version compatibilities to process."); return; } var packageVersionsGroupedByPackageIdDict = packageVersions.ToDictionary(t => t.ToString(), t => t); ConcurrentDictionary packageVersionsGroupedByPackageIdConcurrent = new ConcurrentDictionary(packageVersionsGroupedByPackageIdDict); var distinctPackageVersions = packageVersions.Distinct().ToList(); var exceptions = new ConcurrentDictionary(); foreach (var compatibilityChecker in _compatibilityCheckers) { try { var compatibilityResults = compatibilityChecker.Check(distinctPackageVersions, pathToSolution, isIncremental, incrementalRefresh); await Task.WhenAll(compatibilityResults.Select(result => { return result.Value.ContinueWith(task => { if (task.IsCompletedSuccessfully) { packageVersionsGroupedByPackageIdConcurrent.TryRemove(result.Key.ToString(), out _); if (_compatibilityTaskCompletionSources.TryGetValue(result.Key, out var packageVersionPairResult)) { packageVersionPairResult.TrySetResult(task.Result); } else { throw new ArgumentNullException($"Package version {result.Key} not found in compatibility tasks."); } } else { exceptions.TryAdd(result.Key, task.Exception); } }); }).ToList()); } catch (Exception ex) { _logger.LogError("Package compatibility processing failed with error: {0}", ex); } } foreach (var packageVersion in packageVersionsGroupedByPackageIdConcurrent.Select(packageVersionGroup => packageVersionGroup.Value)) { if (packageVersion != null && _compatibilityTaskCompletionSources.TryGetValue(packageVersion, out var packageVersionPairResult)) { var defaultErrorMessage = $"Could not find package {packageVersion} in all sources. Compatibility task status: {packageVersionPairResult.Task.Status}."; if (exceptions.TryGetValue(packageVersion, out var exception)) { var newException = new PortingAssistantClientException(defaultErrorMessage, (exception.InnerException is PortingAssistantClientException) ? null: exception.InnerException); packageVersionPairResult.TrySetException(newException); } else { var defaultException = new PortingAssistantClientException(ExceptionMessage.PackageNotFound(packageVersion), new PackageNotFoundException(defaultErrorMessage)); packageVersionPairResult.TrySetException(defaultException); } } else { _logger.LogInformation($"Attempted to get package {packageVersion} from compatibility tasks but it was not found."); } } } } }