using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using PortingAssistant.Client.Model; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using System.IO; using PortingAssistant.Client.NuGet.Interfaces; using Newtonsoft.Json.Linq; namespace PortingAssistant.Client.NuGet { public class PortingAssistantRecommendationHandler : IPortingAssistantRecommendationHandler { private readonly ILogger _logger; private readonly IHttpService _httpService; private static readonly int _maxProcessConcurrency = 3; private static readonly SemaphoreSlim _semaphore = new SemaphoreSlim(_maxProcessConcurrency); private const string RecommendationLookupFile = "namespaces.recommendation.lookup.json"; private Dictionary _manifest; public PackageSourceType CompatibilityCheckerType => PackageSourceType.RECOMMENDATION; public PortingAssistantRecommendationHandler( IHttpService httpService, ILogger logger ) { _logger = logger; _httpService = httpService; _manifest = null; } public Dictionary> GetApiRecommendation(IEnumerable namespaces) { var recommendationTaskCompletionSources = new Dictionary>(); try { if (_manifest == null) { var manifestTask = GetManifestAsync(); manifestTask.Wait(); _manifest = manifestTask.Result; } var foundPackages = new Dictionary>(); namespaces.ToList().ForEach(p => { var value = _manifest.GetValueOrDefault(p, null); if (value != null) { recommendationTaskCompletionSources.Add(p, new TaskCompletionSource()); if (!foundPackages.ContainsKey(value)) { foundPackages.Add(value, new List()); } foundPackages[value].Add(p); } }); _logger.LogInformation("Checking Github files {0} for recommendations", foundPackages.Count); if (foundPackages.Any()) { Task.Run(() => { _semaphore.Wait(); try { ProcessCompatibility(namespaces, foundPackages, recommendationTaskCompletionSources); } finally { _semaphore.Release(); } }); } return recommendationTaskCompletionSources.ToDictionary(t => t.Key, t => t.Value.Task); } catch (Exception ex) { foreach (var @namespace in namespaces) { if (recommendationTaskCompletionSources.TryGetValue(@namespace, out var taskCompletionSource)) { taskCompletionSource.TrySetException( new PortingAssistantClientException(ExceptionMessage.NamespaceNotFound(@namespace), ex)); } } return recommendationTaskCompletionSources.ToDictionary(t => t.Key, t => t.Value.Task); } } private async void ProcessCompatibility(IEnumerable namespaces, Dictionary> foundPackages, Dictionary> recommendationTaskCompletionSources) { var namespacesFound = new HashSet(); var namespacesWithErrors = new HashSet(); try { foreach (var url in foundPackages) { try { _logger.LogInformation("Downloading {0} from {1}", url.Key, CompatibilityCheckerType); using var stream = await _httpService.DownloadGitHubFileAsync("recommendation/" + url.Key); using var streamReader = new StreamReader(stream); var packageFromGithub = JsonConvert.DeserializeObject(streamReader.ReadToEnd()); foreach (var @namespace in url.Value) { if (recommendationTaskCompletionSources.TryGetValue(@namespace, out var taskCompletionSource)) { taskCompletionSource.SetResult(packageFromGithub); namespacesFound.Add(@namespace); } } } catch (Exception ex) { _logger.LogError("Failed when downloading recommendation and parsing {0} from {1}, {2}", url.Key, CompatibilityCheckerType, ex); foreach (var @namespace in url.Value) { if (recommendationTaskCompletionSources.TryGetValue(@namespace, out var taskCompletionSource)) { taskCompletionSource.SetException(new PortingAssistantClientException(ExceptionMessage.NamespaceFailedToProcess(@namespace), ex)); namespacesWithErrors.Add(@namespace); } } } } foreach (var @namespace in namespaces) { if (namespacesFound.Contains(@namespace) || namespacesWithErrors.Contains(@namespace)) { continue; } if (recommendationTaskCompletionSources.TryGetValue(@namespace, out var taskCompletionSource)) { var errorMessage = $"Could not find {@namespace} recommendation in external source; discarding this namespace."; _logger.LogInformation(errorMessage); var innerException = new NamespaceNotFoundException(errorMessage); taskCompletionSource.TrySetException(new PortingAssistantClientException(ExceptionMessage.NamespaceNotFound(@namespace), innerException)); } } } catch (Exception ex) { foreach (var @namespace in namespaces) { if (recommendationTaskCompletionSources.TryGetValue(@namespace, out var taskCompletionSource)) { taskCompletionSource.TrySetException( new PortingAssistantClientException(ExceptionMessage.NamespaceFailedToProcess(@namespace), ex)); } } _logger.LogError("Error encountered while processing recommendations: {0}", ex); } } public PackageSourceType GetCompatibilityCheckerType() { return PackageSourceType.RECOMMENDATION; } private async Task> GetManifestAsync() { using var stream = await _httpService.DownloadGitHubFileAsync("data/" + RecommendationLookupFile); using var streamReader = new StreamReader(stream); return JsonConvert.DeserializeObject(streamReader.ReadToEnd()).ToObject>(); } } }