From 48774795283a4083a302433e3bcfea9b699d4cba Mon Sep 17 00:00:00 2001 From: Nick Blakely Date: Mon, 31 Aug 2020 22:46:07 -0700 Subject: [PATCH] Add basic crash report uploader/processor. --- GPatch/CopyBinaries.bat | 12 ++++ GPatch/GPatch.nsi | 2 +- Giants.Services/Core/ServicesModule.cs | 1 + Giants.Services/Giants.Services.csproj | 1 + .../Services/CrashReportService.cs | 48 +++++++++++++ .../Services/ICrashReportService.cs | 10 +++ ...rdController.cs => CommunityController.cs} | 0 .../Controllers/CrashReportsController.cs | 67 +++++++++++++++++++ Giants.WebApi/appsettings.json | 4 +- 9 files changed, 143 insertions(+), 2 deletions(-) create mode 100644 GPatch/CopyBinaries.bat create mode 100644 Giants.Services/Services/CrashReportService.cs create mode 100644 Giants.Services/Services/ICrashReportService.cs rename Giants.WebApi/Controllers/{DiscordController.cs => CommunityController.cs} (100%) create mode 100644 Giants.WebApi/Controllers/CrashReportsController.cs diff --git a/GPatch/CopyBinaries.bat b/GPatch/CopyBinaries.bat new file mode 100644 index 0000000..86d3d3f --- /dev/null +++ b/GPatch/CopyBinaries.bat @@ -0,0 +1,12 @@ +xcopy "%GIANTS_PATH%\gg_dx7r.dll" "Files\" /Y +xcopy "%GIANTS_PATH%\gg_dx9r.dll" "Files\" /Y +xcopy "%GIANTS_PATH%\Giants.exe" "Files\" /Y +xcopy "%GIANTS_PATH%\GiantsMain.exe" "Files\" /Y +xcopy "%GIANTS_PATH%\GiantsDedicated.exe" "Files\" /Y +xcopy "%GIANTS_PATH%\gs_ds.dll" "Files\" /Y +xcopy "%GIANTS_PATH%\Giants.WebApi.Clients.dll" "Files\" /Y +xcopy "%GIANTS_PATH%\fmt.dll" "Files\" /Y +xcopy "%GIANTS_PATH%\BugTrap.dll" "Files\" /Y +xcopy "%GIANTS_PATH%\cpprest_2_10.dll" "Files\" /Y +xcopy "%GIANTS_PATH%\Newtonsoft.Json.dll" "Files\" /Y +xcopy "%GIANTS_PATH%\zlib1.dll" "Files\" /Y \ No newline at end of file diff --git a/GPatch/GPatch.nsi b/GPatch/GPatch.nsi index d5d4d1f..3f12d3c 100644 --- a/GPatch/GPatch.nsi +++ b/GPatch/GPatch.nsi @@ -42,7 +42,7 @@ SetCompressor /SOLID lzma ; MUI end ------ Name "${PRODUCT_NAME} ${PRODUCT_VERSION}" -OutFile "Output\GPatch1_498_201_0.exe" +OutFile "Output\GPatch1_498_204_0.exe" InstallDir "$PROGRAMFILES\Giants\" InstallDirRegKey HKCU "SOFTWARE\PlanetMoon\Giants" "DestDir" ShowInstDetails hide diff --git a/Giants.Services/Core/ServicesModule.cs b/Giants.Services/Core/ServicesModule.cs index 4800364..a9332ed 100644 --- a/Giants.Services/Core/ServicesModule.cs +++ b/Giants.Services/Core/ServicesModule.cs @@ -18,6 +18,7 @@ services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddHostedService(); services.AddHostedService(); diff --git a/Giants.Services/Giants.Services.csproj b/Giants.Services/Giants.Services.csproj index 89b0664..d80cc51 100644 --- a/Giants.Services/Giants.Services.csproj +++ b/Giants.Services/Giants.Services.csproj @@ -6,6 +6,7 @@ + diff --git a/Giants.Services/Services/CrashReportService.cs b/Giants.Services/Services/CrashReportService.cs new file mode 100644 index 0000000..f3b412d --- /dev/null +++ b/Giants.Services/Services/CrashReportService.cs @@ -0,0 +1,48 @@ +namespace Giants.Services.Services +{ + using System; + using System.IO; + using System.Threading.Tasks; + using Azure.Storage.Blobs; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.Logging; + + public class CrashReportService : ICrashReportService + { + private readonly BlobServiceClient blobServiceClient; + private readonly IConfiguration configuration; + private readonly ILogger logger; + + public CrashReportService( + IConfiguration configuration, + ILogger logger) + { + this.configuration = configuration; + this.logger = logger; + + string blobConnectionString = configuration["BlobConnectionString"]; + this.blobServiceClient = new BlobServiceClient(blobConnectionString); + } + + public async Task ProcessReport(string fileName, string senderIpAddress, Stream stream) + { + this.logger.LogInformation("Processing crash report file {FileName} from {IP}", fileName, senderIpAddress); + + var containerClient = this.blobServiceClient.GetBlobContainerClient( + this.configuration["CrashBlobContainerName"]); + + string blobPath = this.GetBlobPath(fileName); + var blobClient = containerClient.GetBlobClient(blobPath); + + this.logger.LogInformation("Saving {FileName} to path: {Path}", fileName, blobPath); + + await blobClient.UploadAsync(stream).ConfigureAwait(false); + } + + private string GetBlobPath(string fileName) + { + DateTime dateTime = DateTime.Now; + return $"{dateTime.Year}/{dateTime.Month}/{dateTime.Day}/{fileName}"; + } + } +} diff --git a/Giants.Services/Services/ICrashReportService.cs b/Giants.Services/Services/ICrashReportService.cs new file mode 100644 index 0000000..86d4130 --- /dev/null +++ b/Giants.Services/Services/ICrashReportService.cs @@ -0,0 +1,10 @@ +namespace Giants.Services.Services +{ + using System.IO; + using System.Threading.Tasks; + + public interface ICrashReportService + { + Task ProcessReport(string fileName, string senderIpAddress, Stream stream); + } +} diff --git a/Giants.WebApi/Controllers/DiscordController.cs b/Giants.WebApi/Controllers/CommunityController.cs similarity index 100% rename from Giants.WebApi/Controllers/DiscordController.cs rename to Giants.WebApi/Controllers/CommunityController.cs diff --git a/Giants.WebApi/Controllers/CrashReportsController.cs b/Giants.WebApi/Controllers/CrashReportsController.cs new file mode 100644 index 0000000..eb491ef --- /dev/null +++ b/Giants.WebApi/Controllers/CrashReportsController.cs @@ -0,0 +1,67 @@ +namespace Giants.WebApi.Controllers +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Threading.Tasks; + using Giants.Services.Services; + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Mvc; + + [ApiController] + [Route("api/[controller]")] + public class CrashReportsController : ControllerBase + { + private readonly ICrashReportService crashReportService; + private readonly IHttpContextAccessor httpContextAccessor; + + private const long MaximumSizeInBytes = 5242880; // 5MB + + public CrashReportsController( + ICrashReportService crashReportService, + IHttpContextAccessor httpContextAccessor) + { + this.crashReportService = crashReportService; + this.httpContextAccessor = httpContextAccessor; + } + + [HttpPost] + public async Task Upload() + { + this.ValidateFiles(this.Request.Form.Files); + + var file = this.Request.Form.Files.First(); + using (var stream = file.OpenReadStream()) + { + await this.crashReportService.ProcessReport(file.FileName, this.GetRequestIpAddress(), stream).ConfigureAwait(false); + } + } + + private void ValidateFiles(IEnumerable formFiles) + { + if (formFiles.Count() != 1) + { + // We only expect one .zip file + throw new ArgumentException("Only one file is accepted.", nameof(formFiles)); + } + + var file = this.Request.Form.Files.First(); + if (file.Length > MaximumSizeInBytes) + { + throw new ArgumentException("File too large.", nameof(formFiles)); + } + + string fileName = Path.GetFileNameWithoutExtension(file.FileName); + if (!Guid.TryParse(fileName, out Guid _) || file.Name != "crashrpt") + { + throw new ArgumentException("Unexpected file name.", nameof(formFiles)); + } + } + + private string GetRequestIpAddress() + { + return this.httpContextAccessor.HttpContext.Connection.RemoteIpAddress.MapToIPv4().ToString(); + } + } +} diff --git a/Giants.WebApi/appsettings.json b/Giants.WebApi/appsettings.json index 2dfca16..b3e3b1e 100644 --- a/Giants.WebApi/appsettings.json +++ b/Giants.WebApi/appsettings.json @@ -12,5 +12,7 @@ "ServerTimeoutPeriodInMinutes": "7", "ServerCleanupIntervalInMinutes": "1", "MaxServerCount": 1000, - "DiscordUri": "https://discord.gg/Avj4azU" + "DiscordUri": "https://discord.gg/Avj4azU", + "BlobConnectionString": "", + "CrashBlobContainerName": "crashes" }