using System; using System.Collections.Generic; using System.Diagnostics; using ElectronCgi.DotNet; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using PortingAssistant.Client.Model; using PortingAssistant.Client.NuGet.Interfaces; using PortingAssistant.Common.Model; using PortingAssistant.Common.Services; using PortingAssistant.Common.Utils; using PortingAssistant.VisualStudio; using Serilog.Context; namespace PortingAssistant.Api { public class Application { private readonly ServiceProvider _services; private readonly Connection _connection; private readonly ILogger _logger; public Application(IServiceCollection serviceCollection) { _services = serviceCollection.BuildServiceProvider(); _logger = _services.GetRequiredService>(); _connection = BuildConnection(); } private Connection BuildConnection() { _logger.LogInformation(nameof(BuildConnection)); var serialiser = new PortingAssistantJsonSerializer(); var channelMessageFactory = new ChannelMessageFactory(serialiser); Console.OutputEncoding = System.Text.Encoding.UTF8; return new Connection( new Channel(new TabSeparatedInputStreamParser(), serialiser), new MessageDispatcher(), new RequestExecutor(serialiser, channelMessageFactory), new ResponseHandlerExecutor(serialiser), new System.Threading.Tasks.Dataflow.BufferBlock(), channelMessageFactory) { LogFilePath = "electron-cgi.log", MinimumLogLevel = LogLevel.Warning, IsLoggingEnabled = false }; } public void SetupConnection(bool console = false) { _connection.On>("analyzeSolution", request => { var logContext = CreateLogContextJson(request); using var _ = LogContext.PushProperty("context", logContext); try { _logger.LogInformation($"Begin On{nameof(AnalyzeSolutionRequest)}"); var assessmentService = _services.GetRequiredService(); assessmentService.AddApiAnalysisListener((response) => { _connection.Send("onApiAnalysisUpdate", response); }); assessmentService.AddNugetPackageListener((response) => { _connection.Send("onNugetPackageUpdate", response); }); request.settings.UseGenerator = true; AssessmentManager._logger = _logger; AssessmentManager.addSolution(request.solutionFilePath); AssessmentManager.setState(request.solutionFilePath, Status.InProgress); var analysisResult = assessmentService.AnalyzeSolution(request).Result; if (analysisResult.Status.Status == Response.ResponseStatus.StatusCode.Failure) { throw analysisResult.Status.Error; } return analysisResult; } catch (Exception ex) { _logger.LogError(ex, "Failed to analyze solution" + logContext); return new Response { Status = Response.Failed(ex), ErrorValue = ex.Message }; } finally { _logger.LogInformation($"End On{nameof(AnalyzeSolutionRequest)}"); } }); _connection.On>("copyDirectory", request => { try { _logger.LogInformation($"Begin On{nameof(CopyDirectoryRequest)}"); PortingAssistantUtils.CopyDirectory(request.solutionPath, request.destinationPath); return new Response { Status = Response.Success() }; } catch (Exception ex) { _logger.LogError(ex, "Failed to copy the solution to new location"); return new Response { Status = Response.Failed(ex), ErrorValue = ex.Message }; } finally { _logger.LogInformation($"End On{nameof(CopyDirectoryRequest)}"); } }); _connection.On, List>>("applyPortingProjectFileChanges", request => { var logContext = CreateLogContextJson(request); using var _ = LogContext.PushProperty("context", logContext); try { _logger.LogInformation($"Begin On{nameof(ProjectFilePortingRequest)}"); var portingService = _services.GetRequiredService(); var portingResult = portingService.ApplyPortingChanges(request); if (portingResult.Status.Status == Response, List>.ResponseStatus.StatusCode.Failure) { throw portingResult.Status.Error; } return portingService.ApplyPortingChanges(request); } catch (Exception ex) { _logger.LogError(ex, "Failed to apply porting project file changes"); return new Response, List> { Status = Response, List>.Failed(ex) }; } finally { _logger.LogInformation($"End On{nameof(ProjectFilePortingRequest)}"); } }); _connection.On>("openSolutionInIDE", request => { try { _logger.LogInformation("Begin OnOpenSolutionInIDE"); var vsfinder = _services.GetRequiredService(); var vsPath = vsfinder.GetLatestVisualStudioPath(); var vsexe = PortingAssistantUtils.FindFiles(vsPath, "devenv.exe"); if (vsexe == null) { return new Response { Status = Response.Failed(new Exception("No Visual Studio")), ErrorValue = "A valid installation of Visual Studio was not found" }; } Process.Start(vsexe, $"\"{request}\""); return new Response { Status = Response.Success() }; } catch (Exception ex) { return new Response { Status = Response.Failed(ex), ErrorValue = ex.Message }; } finally { _logger.LogInformation("End OnOpenSolutionInIDE"); } }); _connection.On("checkInternetAccess", _ => { try { _logger.LogInformation("Begin OnCheckInternetAccess"); var httpService = _services.GetRequiredService(); string[] files = { "newtonsoft.json.json.gz", "github.json.gz", "giger.json.gz", }; return HttpServiceUtils.CheckInternetAccess(httpService, files); } finally { _logger.LogInformation("End OnCheckInternetAccess"); } }); _connection.On("cancelAssessment", solutionPath => { try { _logger.LogInformation("Begin OnCancelAssessment"); AssessmentManager.solutionToAssessmentState[solutionPath].cancellationTokenSource.Cancel(); } finally { _logger.LogInformation("End OnCancelAssessment"); } }); _connection.On, string>>("getSupportedVersion", _ => { try { _logger.LogInformation("Begin OnGetSupportedVersion"); // Note that Console.WriteLine() would somehow mess up with the communication channel. // The output message will be captured by the channel and fail the parsing, // resulting to crash the return result of this request. var defaultConfiguration = SupportedVersionConfiguration.GetDefaultConfiguration(); return new Response, string> { Value = defaultConfiguration.Versions, ErrorValue = string.Empty, Status = Response, string>.Success(), }; } finally { _logger.LogInformation("End OnGetSupportedVersion"); } }); } public void Start() { try { _connection.Listen(); } catch (Exception ex) { _logger.LogError(ex, "Connection ended"); } } private string CreateLogContextJson(object request) { try { return Environment.NewLine + JsonConvert.SerializeObject( new { RequestPayload = request, RequestType = request.GetType().Name, TraceId = Guid.NewGuid(), TimeStamp = DateTime.UtcNow.ToString("u") }, Formatting.Indented); } catch (Exception e) { return Environment.NewLine + $"Exception building log context: {e.Message}"; } } } }