diff --git a/.gitignore b/.gitignore
index 871fd03..95d4a30 100644
--- a/.gitignore
+++ b/.gitignore
@@ -347,7 +347,4 @@ healthchecksdb
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
-.ionide/
-
-# Signing certificates
-*.snk
\ No newline at end of file
+.ionide/
\ No newline at end of file
diff --git a/SolutionResources/.gitignore b/SolutionResources/.gitignore
new file mode 100644
index 0000000..2c7ef37
--- /dev/null
+++ b/SolutionResources/.gitignore
@@ -0,0 +1,4 @@
+ValheimServerGUI.snk
+appsettings.secret.json
+appsettings.local.json
+Secrets.Values.cs
\ No newline at end of file
diff --git a/SolutionResources/README.md b/SolutionResources/README.md
new file mode 100644
index 0000000..cb3bcad
--- /dev/null
+++ b/SolutionResources/README.md
@@ -0,0 +1,6 @@
+# Solution Resources
+
+### Developer note
+
+You must include the files outlined in the .gitignore in order to properly build and publish this project.
+Contact Runeberry Software for more information.
\ No newline at end of file
diff --git a/SolutionResources/Secrets.cs b/SolutionResources/Secrets.cs
new file mode 100644
index 0000000..58b08b2
--- /dev/null
+++ b/SolutionResources/Secrets.cs
@@ -0,0 +1,26 @@
+using System.Collections.Generic;
+
+namespace ValheimServerGUI.Properties
+{
+ ///
+ /// The values in this class are populated by the static constructor in the corresponding
+ /// partial class, which is kept out of source control.
+ ///
+ public static partial class Secrets
+ {
+ ///
+ /// The HTTP header that will contain the API key for requests made to the Runeberry API.
+ ///
+ public static string RuneberryApiKeyHeader { get; } = string.Empty;
+
+ ///
+ /// The API key that will be attached to all VSG client requests to the Runeberry API.
+ ///
+ public static string RuneberryClientApiKey { get; }
+
+ ///
+ /// The Runeberry API keys that will be accepted by the server.
+ ///
+ public static HashSet RuneberryServerApiKeys { get; } = new();
+ }
+}
diff --git a/ValheimServerGUI.Controls/Controls/TextFormField.Designer.cs b/ValheimServerGUI.Controls/Controls/TextFormField.Designer.cs
index 97e63ec..bfadd50 100644
--- a/ValheimServerGUI.Controls/Controls/TextFormField.Designer.cs
+++ b/ValheimServerGUI.Controls/Controls/TextFormField.Designer.cs
@@ -36,7 +36,8 @@ private void InitializeComponent()
//
// TextBox
//
- this.TextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
+ this.TextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
+ | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.TextBox.Location = new System.Drawing.Point(9, 18);
this.TextBox.Name = "TextBox";
diff --git a/ValheimServerGUI.Controls/Controls/TextFormField.cs b/ValheimServerGUI.Controls/Controls/TextFormField.cs
index 10b2479..028d328 100644
--- a/ValheimServerGUI.Controls/Controls/TextFormField.cs
+++ b/ValheimServerGUI.Controls/Controls/TextFormField.cs
@@ -52,6 +52,12 @@ public int MaxLength
set => this.TextBox.MaxLength = value;
}
+ public bool Multiline
+ {
+ get => this.TextBox.Multiline;
+ set => this.TextBox.Multiline = value;
+ }
+
public TextFormField()
{
InitializeComponent();
diff --git a/ValheimServerGUI.Controls/ValheimServerGUI.Controls.csproj b/ValheimServerGUI.Controls/ValheimServerGUI.Controls.csproj
index 6108a98..27d3e64 100644
--- a/ValheimServerGUI.Controls/ValheimServerGUI.Controls.csproj
+++ b/ValheimServerGUI.Controls/ValheimServerGUI.Controls.csproj
@@ -4,8 +4,10 @@
net5.0-windows
true
ValheimServerGUI
+
+
true
- ValheimServerGUI.snk
+ ..\SolutionResources\ValheimServerGUI.snk
diff --git a/ValheimServerGUI.Serverless.Tests/Properties/launchSettings.json b/ValheimServerGUI.Serverless.Tests/Properties/launchSettings.json
new file mode 100644
index 0000000..d8d7436
--- /dev/null
+++ b/ValheimServerGUI.Serverless.Tests/Properties/launchSettings.json
@@ -0,0 +1,14 @@
+{
+ "profiles": {
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "ValheimServerGUI.Serverless.Tests": {
+ "commandName": "Project"
+ }
+ }
+}
\ No newline at end of file
diff --git a/ValheimServerGUI.Serverless.Tests/SampleRequests/ValuesController-Get.json b/ValheimServerGUI.Serverless.Tests/SampleRequests/ValuesController-Get.json
new file mode 100644
index 0000000..e624852
--- /dev/null
+++ b/ValheimServerGUI.Serverless.Tests/SampleRequests/ValuesController-Get.json
@@ -0,0 +1,34 @@
+{
+ "resource": "/{proxy+}",
+ "path": "/api/values",
+ "httpMethod": "GET",
+ "headers": null,
+ "queryStringParameters": null,
+ "pathParameters": {
+ "proxy": "api/values"
+ },
+ "stageVariables": null,
+ "requestContext": {
+ "accountId": "AAAAAAAAAAAA",
+ "resourceId": "5agfss",
+ "stage": "test-invoke-stage",
+ "requestId": "test-invoke-request",
+ "identity": {
+ "cognitoIdentityPoolId": null,
+ "accountId": "AAAAAAAAAAAA",
+ "cognitoIdentityId": null,
+ "caller": "BBBBBBBBBBBB",
+ "apiKey": "test-invoke-api-key",
+ "sourceIp": "test-invoke-source-ip",
+ "cognitoAuthenticationType": null,
+ "cognitoAuthenticationProvider": null,
+ "userArn": "arn:aws:iam::AAAAAAAAAAAA:root",
+ "userAgent": "Apache-HttpClient/4.5.x (Java/1.8.0_102)",
+ "user": "AAAAAAAAAAAA"
+ },
+ "resourcePath": "/{proxy+}",
+ "httpMethod": "GET",
+ "apiId": "t2yh6sjnmk"
+ },
+ "body": null
+}
\ No newline at end of file
diff --git a/ValheimServerGUI.Serverless.Tests/ValheimServerGUI.Serverless.Tests.csproj b/ValheimServerGUI.Serverless.Tests/ValheimServerGUI.Serverless.Tests.csproj
new file mode 100644
index 0000000..f29d117
--- /dev/null
+++ b/ValheimServerGUI.Serverless.Tests/ValheimServerGUI.Serverless.Tests.csproj
@@ -0,0 +1,32 @@
+
+
+ net5.0
+ False
+
+
+
+ PreserveNewest
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ValheimServerGUI.Serverless.Tests/ValuesControllerTests.cs b/ValheimServerGUI.Serverless.Tests/ValuesControllerTests.cs
new file mode 100644
index 0000000..3d849b6
--- /dev/null
+++ b/ValheimServerGUI.Serverless.Tests/ValuesControllerTests.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+
+using Xunit;
+using Amazon.Lambda.Core;
+using Amazon.Lambda.TestUtilities;
+using Amazon.Lambda.APIGatewayEvents;
+
+using Newtonsoft.Json;
+
+using ValheimServerGUI.Serverless;
+
+
+namespace ValheimServerGUI.Serverless.Tests
+{
+ public class ValuesControllerTests
+ {
+
+
+ [Fact]
+ public async Task TestGet()
+ {
+ var lambdaFunction = new LambdaEntryPoint();
+
+ var requestStr = File.ReadAllText("./SampleRequests/ValuesController-Get.json");
+ var request = JsonConvert.DeserializeObject(requestStr);
+ var context = new TestLambdaContext();
+ var response = await lambdaFunction.FunctionHandlerAsync(request, context);
+
+ Assert.Equal(200, response.StatusCode);
+ Assert.Equal("[\"value1\",\"value2\"]", response.Body);
+ Assert.True(response.MultiValueHeaders.ContainsKey("Content-Type"));
+ Assert.Equal("application/json; charset=utf-8", response.MultiValueHeaders["Content-Type"][0]);
+ }
+
+
+ }
+}
diff --git a/ValheimServerGUI.Serverless.Tests/appsettings.json b/ValheimServerGUI.Serverless.Tests/appsettings.json
new file mode 100644
index 0000000..2283363
--- /dev/null
+++ b/ValheimServerGUI.Serverless.Tests/appsettings.json
@@ -0,0 +1,14 @@
+{
+ "Lambda.Logging": {
+ "IncludeCategory": false,
+ "IncludeLogLevel": false,
+ "IncludeNewline": true,
+ "LogLevel": {
+ "Default": "Debug",
+ "Microsoft": "Information"
+ }
+ },
+ "AWS": {
+ "Region": "DefaultRegion"
+ }
+}
\ No newline at end of file
diff --git a/ValheimServerGUI.Serverless/Controllers/VsgController.cs b/ValheimServerGUI.Serverless/Controllers/VsgController.cs
new file mode 100644
index 0000000..02385b2
--- /dev/null
+++ b/ValheimServerGUI.Serverless/Controllers/VsgController.cs
@@ -0,0 +1,89 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Amazon;
+using Amazon.Lambda.Core;
+using Amazon.S3;
+using Amazon.S3.Model;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+using Newtonsoft.Json;
+using ValheimServerGUI.Tools;
+
+namespace ValheimServerGUI.Serverless.Controllers
+{
+ [ApiController]
+ public class VsgController : ControllerBase
+ {
+ private readonly ILogger Logger;
+ private readonly IConfiguration Configuration;
+
+ private ILambdaContext LambdaContext => this.HttpContext.Items["LambdaContext"] as ILambdaContext;
+
+ public VsgController(ILogger logger, IConfiguration configuration)
+ {
+ Logger = logger;
+ Configuration = configuration;
+ }
+
+ [HttpPost("crash-report")]
+ public async Task CreateCrashReport([FromBody] CrashReport request)
+ {
+ Logger.LogInformation("Receiving crash report (standard logger)");
+
+ Exception exception;
+ int statusCode;
+
+ try
+ {
+ var s3BucketName = Configuration.GetValue("S3BucketName");
+ var s3BucketRegion = Configuration.GetValue("S3BucketRegion");
+
+ var client = new AmazonS3Client(RegionEndpoint.GetBySystemName(s3BucketRegion));
+
+ // Ensure that each crash report has an ID
+ request.CrashReportId ??= Guid.NewGuid().ToString();
+ request.Source ??= "CrashReport";
+ request.Timestamp ??= DateTimeOffset.UtcNow;
+ var filename = $"{request.Source}-{request.Timestamp.Value.ToFileTime()}-{request.CrashReportId}.json";
+
+ var s3Request = new PutObjectRequest
+ {
+ BucketName = s3BucketName,
+ Key = $"crash-reports/{filename}",
+ ContentType = "application/json",
+ ContentBody = JsonConvert.SerializeObject(request),
+ };
+ var s3Response = await client.PutObjectAsync(s3Request);
+
+ Logger.LogInformation($"Crash report created: {request.CrashReportId}");
+
+ return Accepted(request);
+ }
+ catch (AmazonS3Exception e)
+ {
+ exception = e;
+ statusCode = (int)e.StatusCode;
+ }
+ catch (Exception e)
+ {
+ exception = e;
+ statusCode = 500;
+ }
+
+ Logger.LogException(exception, $"{exception.GetType().Name} occurred during S3 upload");
+ Logger.LogError(exception.Message);
+ Logger.LogError(exception.StackTrace);
+
+ return StatusCode(statusCode, new { message = exception.Message });
+ }
+
+ [HttpGet("player-steam-info")]
+ public async Task GetPlayerSteamInfo([FromQuery] string steamId)
+ {
+ return Ok("Player steam info");
+ }
+ }
+}
diff --git a/ValheimServerGUI.Serverless/Dockerfile b/ValheimServerGUI.Serverless/Dockerfile
new file mode 100644
index 0000000..68ec18f
--- /dev/null
+++ b/ValheimServerGUI.Serverless/Dockerfile
@@ -0,0 +1,13 @@
+FROM public.ecr.aws/lambda/dotnet:5.0
+
+WORKDIR /var/task
+
+# This COPY command copies the .NET Lambda project's build artifacts from the host machine into the image.
+# The source of the COPY should match where the .NET Lambda project publishes its build artifacts. If the Lambda function is being built
+# with the AWS .NET Lambda Tooling, the `--docker-host-build-output-dir` switch controls where the .NET Lambda project
+# will be built. The .NET Lambda project templates default to having `--docker-host-build-output-dir`
+# set in the aws-lambda-tools-defaults.json file to "bin/Release/net5.0/linux-x64/publish".
+#
+# Alternatively Docker multi-stage build could be used to build the .NET Lambda project inside the image.
+# For more information on this approach checkout the project's README.md file.
+COPY "bin/Release/net5.0/linux-x64/publish" .
diff --git a/ValheimServerGUI.Serverless/LambdaEntryPoint.cs b/ValheimServerGUI.Serverless/LambdaEntryPoint.cs
new file mode 100644
index 0000000..649c296
--- /dev/null
+++ b/ValheimServerGUI.Serverless/LambdaEntryPoint.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Hosting;
+
+namespace ValheimServerGUI.Serverless
+{
+ ///
+ /// This class extends from APIGatewayProxyFunction which contains the method FunctionHandlerAsync which is the
+ /// actual Lambda function entry point. The Lambda handler field should be set to
+ ///
+ /// ValheimServerGUI.Serverless::ValheimServerGUI.Serverless.LambdaEntryPoint::FunctionHandlerAsync
+ ///
+ public class LambdaEntryPoint :
+
+ // The base class must be set to match the AWS service invoking the Lambda function. If not Amazon.Lambda.AspNetCoreServer
+ // will fail to convert the incoming request correctly into a valid ASP.NET Core request.
+ //
+ // API Gateway REST API -> Amazon.Lambda.AspNetCoreServer.APIGatewayProxyFunction
+ // API Gateway HTTP API payload version 1.0 -> Amazon.Lambda.AspNetCoreServer.APIGatewayProxyFunction
+ // API Gateway HTTP API payload version 2.0 -> Amazon.Lambda.AspNetCoreServer.APIGatewayHttpApiV2ProxyFunction
+ // Application Load Balancer -> Amazon.Lambda.AspNetCoreServer.ApplicationLoadBalancerFunction
+ //
+ // Note: When using the AWS::Serverless::Function resource with an event type of "HttpApi" then payload version 2.0
+ // will be the default and you must make Amazon.Lambda.AspNetCoreServer.APIGatewayHttpApiV2ProxyFunction the base class.
+
+ Amazon.Lambda.AspNetCoreServer.APIGatewayProxyFunction
+ {
+ ///
+ /// The builder has configuration, logging and Amazon API Gateway already configured. The startup class
+ /// needs to be configured in this method using the UseStartup<>() method.
+ ///
+ ///
+ protected override void Init(IWebHostBuilder builder)
+ {
+ builder
+ .ConfigureAppConfiguration(config =>
+ {
+ config.AddJsonFile("appsettings.secret.json");
+ })
+ .UseStartup();
+ }
+
+ ///
+ /// Use this override to customize the services registered with the IHostBuilder.
+ ///
+ /// It is recommended not to call ConfigureWebHostDefaults to configure the IWebHostBuilder inside this method.
+ /// Instead customize the IWebHostBuilder in the Init(IWebHostBuilder) overload.
+ ///
+ ///
+ protected override void Init(IHostBuilder builder)
+ {
+ }
+ }
+}
diff --git a/ValheimServerGUI.Serverless/LocalEntryPoint.cs b/ValheimServerGUI.Serverless/LocalEntryPoint.cs
new file mode 100644
index 0000000..64df239
--- /dev/null
+++ b/ValheimServerGUI.Serverless/LocalEntryPoint.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Hosting;
+
+namespace ValheimServerGUI.Serverless
+{
+ ///
+ /// The Main function can be used to run the ASP.NET Core application locally using the Kestrel webserver.
+ ///
+ public class LocalEntryPoint
+ {
+ public static void Main(string[] args)
+ {
+ CreateHostBuilder(args).Build().Run();
+ }
+
+ public static IHostBuilder CreateHostBuilder(string[] args) =>
+ Host.CreateDefaultBuilder(args)
+ .ConfigureWebHostDefaults(webBuilder =>
+ {
+ webBuilder.ConfigureAppConfiguration(config =>
+ {
+ var executingLocation = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
+ config.AddJsonFile(System.IO.Path.Join(executingLocation, "appsettings.secret.json"));
+ config.AddJsonFile(System.IO.Path.Join(executingLocation, "appsettings.local.json"), optional: true);
+ });
+ webBuilder.UseStartup();
+ });
+ }
+}
diff --git a/ValheimServerGUI.Serverless/Middleware/RuneberryAuthMiddleware.cs b/ValheimServerGUI.Serverless/Middleware/RuneberryAuthMiddleware.cs
new file mode 100644
index 0000000..0fde96c
--- /dev/null
+++ b/ValheimServerGUI.Serverless/Middleware/RuneberryAuthMiddleware.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using ValheimServerGUI.Properties;
+
+namespace ValheimServerGUI.Serverless.Middleware
+{
+ [ApiController]
+ public class RuneberryAuthMiddleware
+ {
+ private static readonly bool ApiKeyEnabled;
+
+ static RuneberryAuthMiddleware()
+ {
+ ApiKeyEnabled = !string.IsNullOrWhiteSpace(Secrets.RuneberryApiKeyHeader) && Secrets.RuneberryApiKeyHeader.Any();
+ }
+
+ public static async Task Authorize(HttpContext context, Func next)
+ {
+ if (ApiKeyEnabled)
+ {
+ if (!context.Request.Headers.TryGetValue(Secrets.RuneberryApiKeyHeader, out var apiKey))
+ {
+ context.Response.StatusCode = 401;
+ await context.Response.WriteAsJsonAsync(new { message = "Missing API key" });
+ return;
+ }
+
+ if (!Secrets.RuneberryServerApiKeys.Contains(apiKey))
+ {
+ context.Response.StatusCode = 401;
+ await context.Response.WriteAsJsonAsync(new { message = "Invalid API key" });
+ return;
+ }
+ }
+
+ await next?.Invoke();
+ }
+ }
+}
diff --git a/ValheimServerGUI.Serverless/Properties/launchSettings.json b/ValheimServerGUI.Serverless/Properties/launchSettings.json
new file mode 100644
index 0000000..1844dff
--- /dev/null
+++ b/ValheimServerGUI.Serverless/Properties/launchSettings.json
@@ -0,0 +1,20 @@
+{
+ "profiles": {
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "ValheimServerGUI.Serverless": {
+ "commandName": "Project"
+ },
+ "Mock Lambda Test Tool": {
+ "commandName": "Executable",
+ "commandLineArgs": "--port 5050",
+ "workingDirectory": ".\\bin\\$(Configuration)\\net5.0",
+ "executablePath": "%USERPROFILE%\\.dotnet\\tools\\dotnet-lambda-test-tool-5.0.exe"
+ }
+ }
+}
\ No newline at end of file
diff --git a/ValheimServerGUI.Serverless/Readme.md b/ValheimServerGUI.Serverless/Readme.md
new file mode 100644
index 0000000..a57f49e
--- /dev/null
+++ b/ValheimServerGUI.Serverless/Readme.md
@@ -0,0 +1,108 @@
+# ASP.NET Core Web API Serverless Application
+
+This project shows how to run an ASP.NET Core Web API project as an AWS Lambda exposed through Amazon API Gateway. The NuGet package [Amazon.Lambda.AspNetCoreServer](https://www.nuget.org/packages/Amazon.Lambda.AspNetCoreServer) contains a Lambda function that is used to translate requests from API Gateway into the ASP.NET Core framework and then the responses from ASP.NET Core back to API Gateway.
+
+
+For more information about how the Amazon.Lambda.AspNetCoreServer package works and how to extend its behavior view its [README](https://github.com/aws/aws-lambda-dotnet/blob/master/Libraries/src/Amazon.Lambda.AspNetCoreServer/README.md) file in GitHub.
+
+
+### Configuring for API Gateway HTTP API ###
+
+API Gateway supports the original REST API and the new HTTP API. In addition HTTP API supports 2 different
+payload formats. When using the 2.0 format the base class of `LambdaEntryPoint` must be `Amazon.Lambda.AspNetCoreServer.APIGatewayHttpApiV2ProxyFunction`.
+For the 1.0 payload format the base class is the same as REST API which is `Amazon.Lambda.AspNetCoreServer.APIGatewayProxyFunction`.
+**Note:** when using the `AWS::Serverless::Function` CloudFormation resource with an event type of `HttpApi` the default payload
+format is 2.0 so the base class of `LambdaEntryPoint` must be `Amazon.Lambda.AspNetCoreServer.APIGatewayHttpApiV2ProxyFunction`.
+
+
+### Configuring for Application Load Balancer ###
+
+To configure this project to handle requests from an Application Load Balancer instead of API Gateway change
+the base class of `LambdaEntryPoint` from `Amazon.Lambda.AspNetCoreServer.APIGatewayProxyFunction` to
+`Amazon.Lambda.AspNetCoreServer.ApplicationLoadBalancerFunction`.
+
+### Project Files ###
+
+* serverless.template - an AWS CloudFormation Serverless Application Model template file for declaring your Serverless functions and other AWS resources
+* aws-lambda-tools-defaults.json - default argument settings for use with Visual Studio and command line deployment tools for AWS
+* LambdaEntryPoint.cs - class that derives from **Amazon.Lambda.AspNetCoreServer.APIGatewayProxyFunction**. The code in
+this file bootstraps the ASP.NET Core hosting framework. The Lambda function is defined in the base class.
+Change the base class to **Amazon.Lambda.AspNetCoreServer.ApplicationLoadBalancerFunction** when using an
+Application Load Balancer.
+* LocalEntryPoint.cs - for local development this contains the executable Main function which bootstraps the ASP.NET Core hosting framework with Kestrel, as for typical ASP.NET Core applications.
+* Startup.cs - usual ASP.NET Core Startup class used to configure the services ASP.NET Core will use.
+* web.config - used for local development.
+* Controllers\ValuesController - example Web API controller
+
+You may also have a test project depending on the options selected.
+
+## Packaging as a Docker image.
+
+This project is configured to package the Lambda function as a Docker image. The default configuration for the project and the Dockerfile is to build
+the .NET project on the host machine and then execute the `docker build` command which copies the .NET build artifacts from the host machine into
+the Docker image.
+
+The `--docker-host-build-output-dir` switch, which is set in the `aws-lambda-tools-defaults.json`, triggers the
+AWS .NET Lambda tooling to build the .NET project into the directory indicated by `--docker-host-build-output-dir`. The Dockerfile
+has a **COPY** command which copies the value from the directory pointed to by `--docker-host-build-output-dir` to the `/var/task` directory inside of the
+image.
+
+Alternatively the Docker file could be written to use [multi-stage](https://docs.docker.com/develop/develop-images/multistage-build/) builds and
+have the .NET project built inside the container. Below is an example of building .NET 5 project inside the image.
+
+```dockerfile
+FROM ecr.aws/lambda/dotnet:5.0 AS base
+
+FROM mcr.microsoft.com/dotnet/sdk:5.0-buster-slim as build
+WORKDIR /src
+COPY ["ValheimServerGUI.Serverless.csproj", "ValheimServerGUI.Serverless/"]
+RUN dotnet restore "ValheimServerGUI.Serverless/ValheimServerGUI.Serverless.csproj"
+
+WORKDIR "/src/ValheimServerGUI.Serverless"
+COPY . .
+RUN dotnet build "ValheimServerGUI.Serverless.csproj" --configuration Release --output /app/build
+
+FROM build AS publish
+RUN dotnet publish "ValheimServerGUI.Serverless.csproj" \
+ --configuration Release \
+ --runtime linux-x64 \
+ --self-contained false \
+ --output /app/publish \
+ -p:PublishReadyToRun=true
+
+FROM base AS final
+WORKDIR /var/task
+COPY --from=publish /app/publish .
+```
+
+## Here are some steps to follow from Visual Studio:
+
+To deploy your Serverless application, right click the project in Solution Explorer and select *Publish to AWS Lambda*.
+
+To view your deployed application open the Stack View window by double-clicking the stack name shown beneath the AWS CloudFormation node in the AWS Explorer tree. The Stack View also displays the root URL to your published application.
+
+## Here are some steps to follow to get started from the command line:
+
+Once you have edited your template and code you can deploy your application using the [Amazon.Lambda.Tools Global Tool](https://github.com/aws/aws-extensions-for-dotnet-cli#aws-lambda-amazonlambdatools) from the command line.
+
+Install Amazon.Lambda.Tools Global Tools if not already installed.
+```
+ dotnet tool install -g Amazon.Lambda.Tools
+```
+
+If already installed check if new version is available.
+```
+ dotnet tool update -g Amazon.Lambda.Tools
+```
+
+Execute unit tests
+```
+ cd "ValheimServerGUI.Serverless/test/ValheimServerGUI.Serverless.Tests"
+ dotnet test
+```
+
+Deploy application
+```
+ cd "ValheimServerGUI.Serverless/src/ValheimServerGUI.Serverless"
+ dotnet lambda deploy-serverless
+```
diff --git a/ValheimServerGUI.Serverless/Startup.cs b/ValheimServerGUI.Serverless/Startup.cs
new file mode 100644
index 0000000..9265370
--- /dev/null
+++ b/ValheimServerGUI.Serverless/Startup.cs
@@ -0,0 +1,52 @@
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using ValheimServerGUI.Serverless.Middleware;
+
+namespace ValheimServerGUI.Serverless
+{
+ public class Startup
+ {
+ public Startup(IConfiguration configuration)
+ {
+ Configuration = configuration;
+ }
+
+ public static IConfiguration Configuration { get; private set; }
+
+ // This method gets called by the runtime. Use this method to add services to the container
+ public void ConfigureServices(IServiceCollection services)
+ {
+ services.AddControllers();
+ }
+
+ // This method gets called by the runtime. Use this method to configure the HTTP request pipeline
+ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
+ {
+ if (env.IsDevelopment())
+ {
+ app.UseDeveloperExceptionPage();
+ }
+
+ app.UseHttpsRedirection();
+
+ app.UseRouting();
+
+ app.UseAuthorization();
+
+ app.Use(RuneberryAuthMiddleware.Authorize);
+
+ app.UseEndpoints(endpoints =>
+ {
+ endpoints.MapControllers();
+ endpoints.MapGet("/", async context =>
+ {
+ await context.Response.WriteAsync("Welcome to running ASP.NET Core on AWS Lambda");
+ });
+ });
+ }
+ }
+}
diff --git a/ValheimServerGUI.Serverless/ValheimServerGUI.Serverless.csproj b/ValheimServerGUI.Serverless/ValheimServerGUI.Serverless.csproj
new file mode 100644
index 0000000..8b38f55
--- /dev/null
+++ b/ValheimServerGUI.Serverless/ValheimServerGUI.Serverless.csproj
@@ -0,0 +1,28 @@
+
+
+ net5.0
+ true
+ Lambda
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ %(RecursiveDir)%(Filename)%(Extension)
+ PreserveNewest
+
+
+
\ No newline at end of file
diff --git a/ValheimServerGUI.Serverless/appsettings.Development.json b/ValheimServerGUI.Serverless/appsettings.Development.json
new file mode 100644
index 0000000..4c50c0a
--- /dev/null
+++ b/ValheimServerGUI.Serverless/appsettings.Development.json
@@ -0,0 +1,5 @@
+{
+ "AWS": {
+ "Region": "DefaultRegion"
+ }
+}
\ No newline at end of file
diff --git a/ValheimServerGUI.Serverless/appsettings.json b/ValheimServerGUI.Serverless/appsettings.json
new file mode 100644
index 0000000..5e78dfb
--- /dev/null
+++ b/ValheimServerGUI.Serverless/appsettings.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information"
+ }
+ },
+ "S3BucketName": "runeberry-valheim-server-gui",
+ "S3BucketRegion": "us-east-1"
+}
\ No newline at end of file
diff --git a/ValheimServerGUI.Serverless/aws-lambda-tools-defaults.json b/ValheimServerGUI.Serverless/aws-lambda-tools-defaults.json
new file mode 100644
index 0000000..dca533c
--- /dev/null
+++ b/ValheimServerGUI.Serverless/aws-lambda-tools-defaults.json
@@ -0,0 +1,18 @@
+
+{
+ "Information" : [
+ "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.",
+ "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.",
+ "dotnet lambda help",
+ "All the command line options for the Lambda command can be specified in this file."
+ ],
+ "profile" : "default",
+ "region" : "us-east-1",
+ "configuration" : "Release",
+ "s3-prefix" : "ValheimServerGUI.Serverless/",
+ "template" : "serverless.template",
+ "template-parameters" : "",
+ "docker-host-build-output-dir" : "./bin/Release/net5.0/linux-x64/publish",
+ "s3-bucket" : "runeberry-cf-templates",
+ "stack-name" : "valheim-server-gui-stack"
+}
\ No newline at end of file
diff --git a/ValheimServerGUI.Serverless/serverless.template b/ValheimServerGUI.Serverless/serverless.template
new file mode 100644
index 0000000..306b141
--- /dev/null
+++ b/ValheimServerGUI.Serverless/serverless.template
@@ -0,0 +1,71 @@
+{
+ "AWSTemplateFormatVersion": "2010-09-09",
+ "Transform": "AWS::Serverless-2016-10-31",
+ "Description": "An AWS Serverless Application that uses the ASP.NET Core framework running in Amazon Lambda.",
+ "Parameters": {},
+ "Conditions": {},
+ "Resources": {
+ "AspNetCoreFunction": {
+ "Type": "AWS::Serverless::Function",
+ "Properties": {
+ "PackageType": "Image",
+ "ImageConfig": {
+ "EntryPoint": [
+ "/lambda-entrypoint.sh"
+ ],
+ "Command": [
+ "ValheimServerGUI.Serverless::ValheimServerGUI.Serverless.LambdaEntryPoint::FunctionHandlerAsync"
+ ]
+ },
+ "ImageUri": "",
+ "MemorySize": 256,
+ "Timeout": 30,
+ "Role": null,
+ "Policies": [
+ "AWSLambda_FullAccess",
+ "AmazonS3FullAccess",
+ "CloudWatchLambdaInsightsExecutionRolePolicy"
+ ],
+ "Events": {
+ "ProxyResource": {
+ "Type": "Api",
+ "Properties": {
+ "Path": "/{proxy+}",
+ "Method": "ANY"
+ }
+ },
+ "RootResource": {
+ "Type": "Api",
+ "Properties": {
+ "Path": "/",
+ "Method": "ANY"
+ }
+ }
+ }
+ },
+ "Metadata": {
+ "Dockerfile": "Dockerfile",
+ "DockerContext": ".",
+ "DockerTag": ""
+ }
+ },
+ "S3Bucket": {
+ "Type": "AWS::S3::Bucket",
+ "DeletionPolicy": "Retain",
+ "Properties": {
+ "BucketName": "runeberry-valheim-server-gui",
+ "VersioningConfiguration": {
+ "Status": "Enabled"
+ }
+ }
+ }
+ },
+ "Outputs": {
+ "ApiURL": {
+ "Description": "API endpoint URL for Prod environment",
+ "Value": {
+ "Fn::Sub": "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ValheimServerGUI.Tests/ValheimServerGUI.Tests.csproj b/ValheimServerGUI.Tests/ValheimServerGUI.Tests.csproj
index 7d5a437..7fe53cc 100644
--- a/ValheimServerGUI.Tests/ValheimServerGUI.Tests.csproj
+++ b/ValheimServerGUI.Tests/ValheimServerGUI.Tests.csproj
@@ -7,7 +7,7 @@
-
+
diff --git a/ValheimServerGUI.Tools/CrashReport.cs b/ValheimServerGUI.Tools/CrashReport.cs
new file mode 100644
index 0000000..499bbfa
--- /dev/null
+++ b/ValheimServerGUI.Tools/CrashReport.cs
@@ -0,0 +1,42 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+
+namespace ValheimServerGUI.Tools
+{
+ public class CrashReport
+ {
+ [JsonProperty("id")]
+ public string CrashReportId { get; set; }
+
+ [JsonProperty("clientCorrelationId")]
+ public string ClientCorrelationId { get; set; }
+
+ [JsonProperty("source")]
+ public string Source { get; set; }
+
+ [JsonProperty("timestamp")]
+ public DateTimeOffset? Timestamp { get; set; }
+
+ [JsonProperty("appVersion")]
+ public string AppVersion { get; set; }
+
+ [JsonProperty("osVersion")]
+ public string OsVersion { get; set; }
+
+ [JsonProperty("dotnetVersion")]
+ public string DotnetVersion { get; set; }
+
+ [JsonProperty("currentCulture")]
+ public string CurrentCulture { get; set; }
+
+ [JsonProperty("currentUiCulture")]
+ public string CurrentUICulture { get; set; }
+
+ [JsonProperty("additionalInfo")]
+ public Dictionary AdditionalInfo { get; set; }
+
+ [JsonProperty("logs")]
+ public List Logs { get; set; }
+ }
+}
diff --git a/ValheimServerGUI/Tools/Data/DataFileProviderExtensions.cs b/ValheimServerGUI.Tools/Data/DataFileProviderExtensions.cs
similarity index 100%
rename from ValheimServerGUI/Tools/Data/DataFileProviderExtensions.cs
rename to ValheimServerGUI.Tools/Data/DataFileProviderExtensions.cs
diff --git a/ValheimServerGUI/Tools/Data/DataFileRepository.cs b/ValheimServerGUI.Tools/Data/DataFileRepository.cs
similarity index 100%
rename from ValheimServerGUI/Tools/Data/DataFileRepository.cs
rename to ValheimServerGUI.Tools/Data/DataFileRepository.cs
diff --git a/ValheimServerGUI/Tools/Data/DataFileRepositoryContext.cs b/ValheimServerGUI.Tools/Data/DataFileRepositoryContext.cs
similarity index 100%
rename from ValheimServerGUI/Tools/Data/DataFileRepositoryContext.cs
rename to ValheimServerGUI.Tools/Data/DataFileRepositoryContext.cs
diff --git a/ValheimServerGUI/Tools/Data/IDataRepository.cs b/ValheimServerGUI.Tools/Data/IDataRepository.cs
similarity index 100%
rename from ValheimServerGUI/Tools/Data/IDataRepository.cs
rename to ValheimServerGUI.Tools/Data/IDataRepository.cs
diff --git a/ValheimServerGUI/Tools/Data/IFileProvider.cs b/ValheimServerGUI.Tools/Data/IFileProvider.cs
similarity index 100%
rename from ValheimServerGUI/Tools/Data/IFileProvider.cs
rename to ValheimServerGUI.Tools/Data/IFileProvider.cs
diff --git a/ValheimServerGUI/Tools/Data/IPrimaryKeyEntity.cs b/ValheimServerGUI.Tools/Data/IPrimaryKeyEntity.cs
similarity index 100%
rename from ValheimServerGUI/Tools/Data/IPrimaryKeyEntity.cs
rename to ValheimServerGUI.Tools/Data/IPrimaryKeyEntity.cs
diff --git a/ValheimServerGUI/Tools/Data/JsonDataFile.cs b/ValheimServerGUI.Tools/Data/JsonDataFile.cs
similarity index 100%
rename from ValheimServerGUI/Tools/Data/JsonDataFile.cs
rename to ValheimServerGUI.Tools/Data/JsonDataFile.cs
diff --git a/ValheimServerGUI/Tools/Data/JsonFileProvider.cs b/ValheimServerGUI.Tools/Data/JsonFileProvider.cs
similarity index 100%
rename from ValheimServerGUI/Tools/Data/JsonFileProvider.cs
rename to ValheimServerGUI.Tools/Data/JsonFileProvider.cs
diff --git a/ValheimServerGUI/Tools/Delegates.cs b/ValheimServerGUI.Tools/Delegates.cs
similarity index 100%
rename from ValheimServerGUI/Tools/Delegates.cs
rename to ValheimServerGUI.Tools/Delegates.cs
diff --git a/ValheimServerGUI/Tools/Http/HttpClientProvider.cs b/ValheimServerGUI.Tools/Http/HttpClientProvider.cs
similarity index 100%
rename from ValheimServerGUI/Tools/Http/HttpClientProvider.cs
rename to ValheimServerGUI.Tools/Http/HttpClientProvider.cs
diff --git a/ValheimServerGUI/Tools/Http/RestClient.cs b/ValheimServerGUI.Tools/Http/RestClient.cs
similarity index 85%
rename from ValheimServerGUI/Tools/Http/RestClient.cs
rename to ValheimServerGUI.Tools/Http/RestClient.cs
index 4aca644..a4158f3 100644
--- a/ValheimServerGUI/Tools/Http/RestClient.cs
+++ b/ValheimServerGUI.Tools/Http/RestClient.cs
@@ -24,6 +24,11 @@ public RestClientRequest Get(string uri)
return BuildRequest(HttpMethod.Get, uri);
}
+ public RestClientRequest Post(string uri, object payload = null)
+ {
+ return BuildRequest(HttpMethod.Post, uri, payload);
+ }
+
private RestClientRequest BuildRequest(HttpMethod method, string uri, object payload = null)
{
return new RestClientRequest(this)
diff --git a/ValheimServerGUI/Tools/Http/RestClientContext.cs b/ValheimServerGUI.Tools/Http/RestClientContext.cs
similarity index 100%
rename from ValheimServerGUI/Tools/Http/RestClientContext.cs
rename to ValheimServerGUI.Tools/Http/RestClientContext.cs
diff --git a/ValheimServerGUI/Tools/Http/RestClientRequest.cs b/ValheimServerGUI.Tools/Http/RestClientRequest.cs
similarity index 94%
rename from ValheimServerGUI/Tools/Http/RestClientRequest.cs
rename to ValheimServerGUI.Tools/Http/RestClientRequest.cs
index a176935..9ebbcbc 100644
--- a/ValheimServerGUI/Tools/Http/RestClientRequest.cs
+++ b/ValheimServerGUI.Tools/Http/RestClientRequest.cs
@@ -24,6 +24,8 @@ public class RestClientRequest
public Type ResponseContentType { get; set; }
+ public List> ClientBuilders { get; } = new();
+
public List> RequestBuilders { get; } = new();
public List> Callbacks { get; } = new();
@@ -47,6 +49,12 @@ public async Task SendAsync()
try
{
var client = this.Context.HttpClientProvider.CreateClient();
+
+ foreach (var clientBuilder in this.ClientBuilders)
+ {
+ clientBuilder(client);
+ }
+
var requestMessage = new HttpRequestMessage(this.Method, this.Uri);
if (this.RequestContent != null)
diff --git a/ValheimServerGUI/Tools/Http/RestClientRequestExtensions.cs b/ValheimServerGUI.Tools/Http/RestClientRequestExtensions.cs
similarity index 77%
rename from ValheimServerGUI/Tools/Http/RestClientRequestExtensions.cs
rename to ValheimServerGUI.Tools/Http/RestClientRequestExtensions.cs
index 1a43e09..6edb01d 100644
--- a/ValheimServerGUI/Tools/Http/RestClientRequestExtensions.cs
+++ b/ValheimServerGUI.Tools/Http/RestClientRequestExtensions.cs
@@ -6,6 +6,18 @@ namespace ValheimServerGUI.Tools.Http
{
public static class RestClientRequestExtensions
{
+ public static RestClientRequest WithClientOptions(this RestClientRequest request, Action options)
+ {
+ request.ClientBuilders.Add(options);
+ return request;
+ }
+
+ public static RestClientRequest WithRequestOptions(this RestClientRequest request, Action options)
+ {
+ request.RequestBuilders.Add(options);
+ return request;
+ }
+
public static RestClientRequest WithResponseType(this RestClientRequest request)
{
request.ResponseContentType = typeof(T);
diff --git a/ValheimServerGUI/Tools/LoggerExtensions.cs b/ValheimServerGUI.Tools/LoggerExtensions.cs
similarity index 100%
rename from ValheimServerGUI/Tools/LoggerExtensions.cs
rename to ValheimServerGUI.Tools/LoggerExtensions.cs
diff --git a/ValheimServerGUI/Tools/Logging/ApplicationLogger.cs b/ValheimServerGUI.Tools/Logging/ApplicationLogger.cs
similarity index 100%
rename from ValheimServerGUI/Tools/Logging/ApplicationLogger.cs
rename to ValheimServerGUI.Tools/Logging/ApplicationLogger.cs
diff --git a/ValheimServerGUI.Tools/Logging/ConcurrentBuffer.cs b/ValheimServerGUI.Tools/Logging/ConcurrentBuffer.cs
new file mode 100644
index 0000000..54ea741
--- /dev/null
+++ b/ValheimServerGUI.Tools/Logging/ConcurrentBuffer.cs
@@ -0,0 +1,71 @@
+using System;
+using System.Collections;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace ValheimServerGUI.Tools.Logging
+{
+ public class ConcurrentBuffer : IEnumerable, IEnumerable, IReadOnlyCollection
+ {
+ public int BufferSize { get; }
+
+ private ConcurrentQueue ConcurrentQueue = new();
+
+ public ConcurrentBuffer(int bufferSize)
+ {
+ if (bufferSize < 0) throw new ArgumentException("Buffer size must be >= 0");
+
+ this.BufferSize = bufferSize;
+ }
+
+ public int Count => this.ConcurrentQueue.Count;
+
+ public bool IsReadOnly => false;
+
+ public void Enqueue(T item)
+ {
+ this.ConcurrentQueue.Enqueue(item);
+
+ while (this.ConcurrentQueue.Count > this.BufferSize)
+ {
+ this.ConcurrentQueue.TryDequeue(out var _);
+ }
+ }
+
+ public T Dequeue()
+ {
+ if (this.ConcurrentQueue.TryDequeue(out var item))
+ {
+ return item;
+ }
+
+ return default;
+ }
+
+ public void Clear()
+ {
+ this.ConcurrentQueue.Clear();
+ }
+
+ public bool Contains(T item)
+ {
+ return this.ConcurrentQueue.Contains(item);
+ }
+
+ public void CopyTo(T[] array, int arrayIndex)
+ {
+ this.ConcurrentQueue.CopyTo(array, arrayIndex);
+ }
+
+ public IEnumerator GetEnumerator()
+ {
+ return this.ConcurrentQueue.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return this.ConcurrentQueue.GetEnumerator();
+ }
+ }
+}
diff --git a/ValheimServerGUI/Tools/Logging/EventLogContext.cs b/ValheimServerGUI.Tools/Logging/EventLogContext.cs
similarity index 100%
rename from ValheimServerGUI/Tools/Logging/EventLogContext.cs
rename to ValheimServerGUI.Tools/Logging/EventLogContext.cs
diff --git a/ValheimServerGUI/Tools/Logging/EventLogger.cs b/ValheimServerGUI.Tools/Logging/EventLogger.cs
similarity index 86%
rename from ValheimServerGUI/Tools/Logging/EventLogger.cs
rename to ValheimServerGUI.Tools/Logging/EventLogger.cs
index 5adab5b..0de708b 100644
--- a/ValheimServerGUI/Tools/Logging/EventLogger.cs
+++ b/ValheimServerGUI.Tools/Logging/EventLogger.cs
@@ -1,10 +1,13 @@
using Microsoft.Extensions.Logging;
using System;
+using System.Collections.Generic;
namespace ValheimServerGUI.Tools.Logging
{
public class EventLogger : IEventLogger
{
+ private readonly ConcurrentBuffer ConcurrentBuffer = new(1000);
+
protected string CategoryName { get; set; }
protected virtual bool FilterLog(EventLogContext context)
@@ -19,6 +22,8 @@ protected virtual string FormatLog(EventLogContext context)
#region IEventLogger implementation
+ public IEnumerable LogBuffer => this.ConcurrentBuffer;
+
public event EventHandler LogReceived;
#endregion
@@ -67,6 +72,9 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except
return;
}
+ var formattedMessage = $"[{context.Timestamp:G}] {context.Message}";
+ this.ConcurrentBuffer.Enqueue(formattedMessage);
+
LogReceived?.Invoke(this, context);
}
diff --git a/ValheimServerGUI/Tools/Logging/IEventLogger.cs b/ValheimServerGUI.Tools/Logging/IEventLogger.cs
similarity index 77%
rename from ValheimServerGUI/Tools/Logging/IEventLogger.cs
rename to ValheimServerGUI.Tools/Logging/IEventLogger.cs
index 5827ea6..b315d30 100644
--- a/ValheimServerGUI/Tools/Logging/IEventLogger.cs
+++ b/ValheimServerGUI.Tools/Logging/IEventLogger.cs
@@ -1,10 +1,13 @@
using Microsoft.Extensions.Logging;
using System;
+using System.Collections.Generic;
namespace ValheimServerGUI.Tools.Logging
{
public interface IEventLogger : ILogger
{
+ IEnumerable LogBuffer { get; }
+
event EventHandler LogReceived;
}
diff --git a/ValheimServerGUI/Tools/ObjectExtensions.cs b/ValheimServerGUI.Tools/ObjectExtensions.cs
similarity index 100%
rename from ValheimServerGUI/Tools/ObjectExtensions.cs
rename to ValheimServerGUI.Tools/ObjectExtensions.cs
diff --git a/ValheimServerGUI/Tools/Processes/IProcessProvider.cs b/ValheimServerGUI.Tools/Processes/IProcessProvider.cs
similarity index 100%
rename from ValheimServerGUI/Tools/Processes/IProcessProvider.cs
rename to ValheimServerGUI.Tools/Processes/IProcessProvider.cs
diff --git a/ValheimServerGUI/Tools/Processes/ProcessExtensions.cs b/ValheimServerGUI.Tools/Processes/ProcessExtensions.cs
similarity index 100%
rename from ValheimServerGUI/Tools/Processes/ProcessExtensions.cs
rename to ValheimServerGUI.Tools/Processes/ProcessExtensions.cs
diff --git a/ValheimServerGUI/Tools/Processes/ProcessKeys.cs b/ValheimServerGUI.Tools/Processes/ProcessKeys.cs
similarity index 100%
rename from ValheimServerGUI/Tools/Processes/ProcessKeys.cs
rename to ValheimServerGUI.Tools/Processes/ProcessKeys.cs
diff --git a/ValheimServerGUI/Tools/Processes/ProcessProvider.cs b/ValheimServerGUI.Tools/Processes/ProcessProvider.cs
similarity index 100%
rename from ValheimServerGUI/Tools/Processes/ProcessProvider.cs
rename to ValheimServerGUI.Tools/Processes/ProcessProvider.cs
diff --git a/ValheimServerGUI.Tools/ValheimServerGUI.Tools.csproj b/ValheimServerGUI.Tools/ValheimServerGUI.Tools.csproj
new file mode 100644
index 0000000..54d8c70
--- /dev/null
+++ b/ValheimServerGUI.Tools/ValheimServerGUI.Tools.csproj
@@ -0,0 +1,16 @@
+
+
+
+ net5.0
+
+
+ true
+ ..\SolutionResources\ValheimServerGUI.snk
+
+
+
+
+
+
+
+
diff --git a/ValheimServerGUI.sln b/ValheimServerGUI.sln
index 1c8b095..0208450 100644
--- a/ValheimServerGUI.sln
+++ b/ValheimServerGUI.sln
@@ -7,7 +7,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ValheimServerGUI", "Valheim
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ValheimServerGUI.Controls", "ValheimServerGUI.Controls\ValheimServerGUI.Controls.csproj", "{36E75C0A-F596-4766-8148-D7C442501503}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ValheimServerGUI.Tests", "ValheimServerGUI.Tests\ValheimServerGUI.Tests.csproj", "{22F12ECA-242A-4D4D-9C2E-5949F353AD29}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ValheimServerGUI.Tests", "ValheimServerGUI.Tests\ValheimServerGUI.Tests.csproj", "{22F12ECA-242A-4D4D-9C2E-5949F353AD29}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ValheimServerGUI.Serverless", "ValheimServerGUI.Serverless\ValheimServerGUI.Serverless.csproj", "{14527A88-31E6-4FEB-9461-5291A08E8E7E}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ValheimServerGUI.Serverless.Tests", "ValheimServerGUI.Serverless.Tests\ValheimServerGUI.Serverless.Tests.csproj", "{B366DF72-040D-464A-A200-E9CCD4F17B33}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ValheimServerGUI.Tools", "ValheimServerGUI.Tools\ValheimServerGUI.Tools.csproj", "{39BE3CA2-906A-446F-95DA-E37C359B6197}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -27,6 +33,18 @@ Global
{22F12ECA-242A-4D4D-9C2E-5949F353AD29}.Debug|Any CPU.Build.0 = Debug|Any CPU
{22F12ECA-242A-4D4D-9C2E-5949F353AD29}.Release|Any CPU.ActiveCfg = Release|Any CPU
{22F12ECA-242A-4D4D-9C2E-5949F353AD29}.Release|Any CPU.Build.0 = Release|Any CPU
+ {14527A88-31E6-4FEB-9461-5291A08E8E7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {14527A88-31E6-4FEB-9461-5291A08E8E7E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {14527A88-31E6-4FEB-9461-5291A08E8E7E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {14527A88-31E6-4FEB-9461-5291A08E8E7E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B366DF72-040D-464A-A200-E9CCD4F17B33}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B366DF72-040D-464A-A200-E9CCD4F17B33}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B366DF72-040D-464A-A200-E9CCD4F17B33}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B366DF72-040D-464A-A200-E9CCD4F17B33}.Release|Any CPU.Build.0 = Release|Any CPU
+ {39BE3CA2-906A-446F-95DA-E37C359B6197}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {39BE3CA2-906A-446F-95DA-E37C359B6197}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {39BE3CA2-906A-446F-95DA-E37C359B6197}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {39BE3CA2-906A-446F-95DA-E37C359B6197}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/ValheimServerGUI/Forms/AboutForm.Designer.cs b/ValheimServerGUI/Forms/AboutForm.Designer.cs
index b058330..ad234ac 100644
--- a/ValheimServerGUI/Forms/AboutForm.Designer.cs
+++ b/ValheimServerGUI/Forms/AboutForm.Designer.cs
@@ -156,7 +156,6 @@ private void InitializeComponent()
this.Controls.Add(this.label1);
this.Controls.Add(this.pictureBox1);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
- this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "AboutForm";
diff --git a/ValheimServerGUI/Forms/AboutForm.cs b/ValheimServerGUI/Forms/AboutForm.cs
index 4328b7f..eac184b 100644
--- a/ValheimServerGUI/Forms/AboutForm.cs
+++ b/ValheimServerGUI/Forms/AboutForm.cs
@@ -10,6 +10,7 @@ public partial class AboutForm : Form
public AboutForm()
{
InitializeComponent();
+ this.AddApplicationIcon();
this.VersionLabel.Text = $"Version: {AssemblyHelper.GetApplicationVersion()}";
}
diff --git a/ValheimServerGUI/Forms/AsyncPopout.Designer.cs b/ValheimServerGUI/Forms/AsyncPopout.Designer.cs
new file mode 100644
index 0000000..eb81c64
--- /dev/null
+++ b/ValheimServerGUI/Forms/AsyncPopout.Designer.cs
@@ -0,0 +1,101 @@
+
+namespace ValheimServerGUI.Forms
+{
+ partial class AsyncPopout
+ {
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Windows Form Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ this.components = new System.ComponentModel.Container();
+ this.LoadingLabel = new System.Windows.Forms.Label();
+ this.CloseButton = new System.Windows.Forms.Button();
+ this.ProgressBar = new System.Windows.Forms.ProgressBar();
+ this.Timer = new System.Windows.Forms.Timer(this.components);
+ this.SuspendLayout();
+ //
+ // LoadingLabel
+ //
+ this.LoadingLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
+ | System.Windows.Forms.AnchorStyles.Left)
+ | System.Windows.Forms.AnchorStyles.Right)));
+ this.LoadingLabel.Location = new System.Drawing.Point(12, 9);
+ this.LoadingLabel.Name = "LoadingLabel";
+ this.LoadingLabel.Size = new System.Drawing.Size(260, 64);
+ this.LoadingLabel.TabIndex = 0;
+ this.LoadingLabel.Text = "Loading...";
+ this.LoadingLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
+ //
+ // CloseButton
+ //
+ this.CloseButton.Location = new System.Drawing.Point(197, 76);
+ this.CloseButton.Name = "CloseButton";
+ this.CloseButton.Size = new System.Drawing.Size(75, 23);
+ this.CloseButton.TabIndex = 1;
+ this.CloseButton.Text = "Cancel";
+ this.CloseButton.UseVisualStyleBackColor = true;
+ //
+ // ProgressBar
+ //
+ this.ProgressBar.Location = new System.Drawing.Point(13, 76);
+ this.ProgressBar.MarqueeAnimationSpeed = 16;
+ this.ProgressBar.Name = "ProgressBar";
+ this.ProgressBar.Size = new System.Drawing.Size(178, 23);
+ this.ProgressBar.Step = 2;
+ this.ProgressBar.Style = System.Windows.Forms.ProgressBarStyle.Marquee;
+ this.ProgressBar.TabIndex = 2;
+ //
+ // Timer
+ //
+ this.Timer.Enabled = true;
+ //
+ // AsyncPopout
+ //
+ this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.ClientSize = new System.Drawing.Size(284, 111);
+ this.ControlBox = false;
+ this.Controls.Add(this.ProgressBar);
+ this.Controls.Add(this.CloseButton);
+ this.Controls.Add(this.LoadingLabel);
+ this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
+ this.Name = "AsyncPopout";
+ this.ShowInTaskbar = false;
+ this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide;
+ this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
+ this.Text = "Loading...";
+ this.TopMost = true;
+ this.ResumeLayout(false);
+
+ }
+
+ #endregion
+
+ private System.Windows.Forms.Label LoadingLabel;
+ private System.Windows.Forms.Button CloseButton;
+ private System.Windows.Forms.ProgressBar ProgressBar;
+ private System.Windows.Forms.Timer Timer;
+ }
+}
\ No newline at end of file
diff --git a/ValheimServerGUI/Forms/AsyncPopout.cs b/ValheimServerGUI/Forms/AsyncPopout.cs
new file mode 100644
index 0000000..e3794de
--- /dev/null
+++ b/ValheimServerGUI/Forms/AsyncPopout.cs
@@ -0,0 +1,113 @@
+using System;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+using ValheimServerGUI.Tools;
+
+namespace ValheimServerGUI.Forms
+{
+ public partial class AsyncPopout : Form
+ {
+ protected Task Task;
+
+ protected AsyncPopoutOptions Options;
+
+ private event EventHandler TaskFinished;
+
+ private bool AutoCloseOnFinished;
+
+ private string FinishedMessage;
+
+ private AsyncPopout()
+ {
+ InitializeComponent();
+ this.AddApplicationIcon();
+
+ this.CloseButton.Click += this.BuildEventHandler(this.Close);
+ this.TaskFinished += this.BuildEventHandler(this.OnTaskFinished);
+ }
+
+ public AsyncPopout(Task task, Action optionsBuilder = null)
+ : this()
+ {
+ if (task == null) throw new ArgumentException("Task cannot be null", nameof(task));
+
+ var options = new AsyncPopoutOptions();
+ optionsBuilder?.Invoke(options);
+ this.Options = options;
+
+ this.Task = task;
+ this.Text = options.Title ?? this.Text;
+ this.LoadingLabel.Text = options.Text ?? this.LoadingLabel.Text;
+ }
+
+ protected override void OnLoad(EventArgs e)
+ {
+ base.OnLoad(e);
+
+ this.Task = this.Task.ContinueWith(this.TaskContinuationHandler);
+ }
+
+ protected void OnTaskFinished()
+ {
+ if (this.AutoCloseOnFinished)
+ {
+ this.Close();
+ return;
+ }
+
+ this.LoadingLabel.Text = this.FinishedMessage ?? "Task Complete!";
+ this.CloseButton.Text = "Close";
+ this.ProgressBar.Visible = false;
+ }
+
+ private Task TaskContinuationHandler(Task task)
+ {
+ if (task.Status == TaskStatus.RanToCompletion)
+ {
+ if (this.Options.CloseOnSuccess) return this.CloseAsync();
+
+ this.FinishedMessage = this.Options.SuccessMessage;
+ }
+ else if (task.Exception != null)
+ {
+ if (this.Options.CloseOnFailure) return this.CloseAsync();
+
+ var errMessage = this.Options.FailureMessage ?? "The task was cancelled due to an error.";
+ var ex = task.Exception.GetPrimaryException();
+ this.FinishedMessage = $"{errMessage}\r\n{ex.GetType().Name}\r\n{ex.Message}";
+ }
+ else
+ {
+ if (this.Options.CloseOnFailure) return this.CloseAsync();
+
+ this.FinishedMessage = this.Options.FailureMessage ?? "The task was cancelled due to an unknown error.";
+ }
+
+ this.TaskFinished?.Invoke(this, EventArgs.Empty);
+
+ return Task.CompletedTask;
+ }
+
+ private Task CloseAsync()
+ {
+ this.AutoCloseOnFinished = true;
+ this.TaskFinished?.Invoke(this, EventArgs.Empty);
+ return Task.CompletedTask;
+ }
+
+ public class AsyncPopoutOptions
+ {
+ public string Title { get; set; }
+
+ public string Text { get; set; }
+
+ public bool CloseOnSuccess { get; set; }
+
+ public string SuccessMessage { get; set; }
+
+ public bool CloseOnFailure { get; set; }
+
+ public string FailureMessage { get; set; }
+ }
+ }
+}
diff --git a/ValheimServerGUI/Forms/AsyncPopout.resx b/ValheimServerGUI/Forms/AsyncPopout.resx
new file mode 100644
index 0000000..f298a7b
--- /dev/null
+++ b/ValheimServerGUI/Forms/AsyncPopout.resx
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/ValheimServerGUI/Forms/BugReportForm.Designer.cs b/ValheimServerGUI/Forms/BugReportForm.Designer.cs
new file mode 100644
index 0000000..d2d77c2
--- /dev/null
+++ b/ValheimServerGUI/Forms/BugReportForm.Designer.cs
@@ -0,0 +1,117 @@
+
+namespace ValheimServerGUI.Forms
+{
+ partial class BugReportForm
+ {
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Windows Form Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(BugReportForm));
+ this.ButtonCancel = new System.Windows.Forms.Button();
+ this.ButtonSubmit = new System.Windows.Forms.Button();
+ this.ContactInfoField = new ValheimServerGUI.Forms.Controls.TextFormField();
+ this.BugReportField = new ValheimServerGUI.Forms.Controls.TextFormField();
+ this.SuspendLayout();
+ //
+ // ButtonCancel
+ //
+ this.ButtonCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
+ this.ButtonCancel.Location = new System.Drawing.Point(272, 182);
+ this.ButtonCancel.Name = "ButtonCancel";
+ this.ButtonCancel.Size = new System.Drawing.Size(75, 23);
+ this.ButtonCancel.TabIndex = 0;
+ this.ButtonCancel.Text = "Cancel";
+ this.ButtonCancel.UseVisualStyleBackColor = true;
+ //
+ // ButtonSubmit
+ //
+ this.ButtonSubmit.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
+ this.ButtonSubmit.Enabled = false;
+ this.ButtonSubmit.Location = new System.Drawing.Point(191, 182);
+ this.ButtonSubmit.Name = "ButtonSubmit";
+ this.ButtonSubmit.Size = new System.Drawing.Size(75, 23);
+ this.ButtonSubmit.TabIndex = 1;
+ this.ButtonSubmit.Text = "Submit";
+ this.ButtonSubmit.UseVisualStyleBackColor = true;
+ //
+ // ContactInfoField
+ //
+ this.ContactInfoField.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
+ | System.Windows.Forms.AnchorStyles.Right)));
+ this.ContactInfoField.HelpText = resources.GetString("ContactInfoField.HelpText");
+ this.ContactInfoField.HideValue = false;
+ this.ContactInfoField.LabelText = "(Optional) Contact details for follow-up";
+ this.ContactInfoField.Location = new System.Drawing.Point(12, 135);
+ this.ContactInfoField.MaxLength = 255;
+ this.ContactInfoField.Multiline = false;
+ this.ContactInfoField.Name = "ContactInfoField";
+ this.ContactInfoField.Size = new System.Drawing.Size(334, 41);
+ this.ContactInfoField.TabIndex = 2;
+ this.ContactInfoField.Value = "";
+ //
+ // BugReportField
+ //
+ this.BugReportField.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
+ | System.Windows.Forms.AnchorStyles.Left)
+ | System.Windows.Forms.AnchorStyles.Right)));
+ this.BugReportField.HelpText = resources.GetString("BugReportField.HelpText");
+ this.BugReportField.HideValue = false;
+ this.BugReportField.LabelText = "What\'s the problem? And how did you encounter it?";
+ this.BugReportField.Location = new System.Drawing.Point(12, 12);
+ this.BugReportField.MaxLength = 2000;
+ this.BugReportField.Multiline = true;
+ this.BugReportField.Name = "BugReportField";
+ this.BugReportField.Size = new System.Drawing.Size(334, 117);
+ this.BugReportField.TabIndex = 4;
+ this.BugReportField.Value = "";
+ //
+ // BugReportForm
+ //
+ this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.ClientSize = new System.Drawing.Size(359, 217);
+ this.Controls.Add(this.BugReportField);
+ this.Controls.Add(this.ContactInfoField);
+ this.Controls.Add(this.ButtonSubmit);
+ this.Controls.Add(this.ButtonCancel);
+ this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
+ this.MaximizeBox = false;
+ this.MinimizeBox = false;
+ this.Name = "BugReportForm";
+ this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
+ this.Text = "Submit a Bug Report";
+ this.ResumeLayout(false);
+
+ }
+
+ #endregion
+
+ private System.Windows.Forms.Button ButtonCancel;
+ private System.Windows.Forms.Button ButtonSubmit;
+ private Controls.TextFormField ContactInfoField;
+ private Controls.TextFormField BugReportField;
+ }
+}
\ No newline at end of file
diff --git a/ValheimServerGUI/Forms/BugReportForm.cs b/ValheimServerGUI/Forms/BugReportForm.cs
new file mode 100644
index 0000000..fd47ba6
--- /dev/null
+++ b/ValheimServerGUI/Forms/BugReportForm.cs
@@ -0,0 +1,95 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows.Forms;
+using ValheimServerGUI.Tools;
+using ValheimServerGUI.Tools.Logging;
+
+namespace ValheimServerGUI.Forms
+{
+ public partial class BugReportForm : Form
+ {
+ private readonly IRuneberryApiClient RuneberryApiClient;
+
+ private readonly IEventLogger Logger;
+
+ public BugReportForm(
+ IRuneberryApiClient runeberryApiClient,
+ IEventLogger logger)
+ {
+ this.RuneberryApiClient = runeberryApiClient;
+ this.Logger = logger;
+
+ InitializeComponent();
+ this.AddApplicationIcon();
+
+ this.ButtonSubmit.Click += this.BuildEventHandler(this.ButtonSubmit_Click);
+ this.ButtonCancel.Click += this.BuildEventHandler(this.ButtonCancel_Click);
+ this.BugReportField.ValueChanged += this.BuildEventHandler(this.BugReportField_ValueChanged);
+ }
+
+ protected override void OnShown(EventArgs e)
+ {
+ base.OnShown(e);
+
+ this.ClearForm();
+ }
+
+ protected override void OnClosed(EventArgs e)
+ {
+ base.OnClosed(e);
+
+ this.ClearForm();
+ }
+
+ private void ButtonCancel_Click()
+ {
+ this.Close();
+ }
+
+ private void ButtonSubmit_Click()
+ {
+ this.SubmitBugReport();
+ }
+
+ private void BugReportField_ValueChanged(string value)
+ {
+ // Only enable the Submit button when there is some content in the bug report
+ this.ButtonSubmit.Enabled = !string.IsNullOrWhiteSpace(value);
+ }
+
+ private void ClearForm()
+ {
+ this.BugReportField.Value = string.Empty;
+ this.ContactInfoField.Value = string.Empty;
+ }
+
+ private void SubmitBugReport()
+ {
+ var crashReport = AssemblyHelper.BuildCrashReport();
+
+ var additionalInfo = new Dictionary
+ {
+ { "BugReport", this.BugReportField.Value },
+ { "ContactInfo", this.ContactInfoField.Value },
+ };
+
+ crashReport.Source = "BugReport";
+ crashReport.AdditionalInfo = additionalInfo;
+ crashReport.Logs = this.Logger.LogBuffer.Reverse().Take(100).ToList();
+
+ var task = RuneberryApiClient.SendCrashReportAsync(crashReport);
+ var asyncPopout = new AsyncPopout(task, o =>
+ {
+ o.Title = "Bug Report";
+ o.Text = "Submitting bug report...";
+ o.SuccessMessage = "Bug report submitted. Thank you!";
+ o.FailureMessage = "Failed to submit bug report.\r\nContact Runeberry Software for further support.";
+ });
+
+ asyncPopout.ShowDialog();
+
+ this.Close();
+ }
+ }
+}
diff --git a/ValheimServerGUI/Forms/BugReportForm.resx b/ValheimServerGUI/Forms/BugReportForm.resx
new file mode 100644
index 0000000..2970a7a
--- /dev/null
+++ b/ValheimServerGUI/Forms/BugReportForm.resx
@@ -0,0 +1,290 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ If you'd like, supply your email address, Twitter handle,
+or any other way we can contact you in case we need
+more information to follow up on your bug report.
+
+Of course, we will never share this information with any
+third parties. (I don't even know how to do that)
+
+
+ Describe the bug you're experiencing in detail, including:
+ - A detailed description of the problem you're experiencing
+ - Any steps required to reproduce the bug
+
+Your bug report will be submitted alongside some details
+about your computer, which version you're running, etc.
+to help us further troubleshoot the problem. No personal
+or identifying details about your computer will be collected.
+
+
+
+
+ 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
+ 7f8DbWhZKaNP5FoAAAAASUVORK5CYII=
+
+
+
\ No newline at end of file
diff --git a/ValheimServerGUI/Forms/DirectoriesForm.Designer.cs b/ValheimServerGUI/Forms/DirectoriesForm.Designer.cs
index 088bb63..d6012a9 100644
--- a/ValheimServerGUI/Forms/DirectoriesForm.Designer.cs
+++ b/ValheimServerGUI/Forms/DirectoriesForm.Designer.cs
@@ -126,7 +126,6 @@ private void InitializeComponent()
this.Controls.Add(this.ButtonOK);
this.Controls.Add(this.ButtonCancel);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
- this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "DirectoriesForm";
diff --git a/ValheimServerGUI/Forms/DirectoriesForm.cs b/ValheimServerGUI/Forms/DirectoriesForm.cs
index 4599a39..1c4d9ce 100644
--- a/ValheimServerGUI/Forms/DirectoriesForm.cs
+++ b/ValheimServerGUI/Forms/DirectoriesForm.cs
@@ -1,6 +1,7 @@
using System;
using System.Windows.Forms;
using ValheimServerGUI.Game;
+using ValheimServerGUI.Tools;
namespace ValheimServerGUI.Forms
{
@@ -13,6 +14,7 @@ public partial class DirectoriesForm : Form
public DirectoriesForm()
{
InitializeComponent();
+ this.AddApplicationIcon();
}
public DirectoriesForm(IUserPreferencesProvider userPrefsProvider, IValheimFileProvider fileProvider) : this()
diff --git a/ValheimServerGUI/Forms/MainWindow.Designer.cs b/ValheimServerGUI/Forms/MainWindow.Designer.cs
index 73ecd2f..ef40276 100644
--- a/ValheimServerGUI/Forms/MainWindow.Designer.cs
+++ b/ValheimServerGUI/Forms/MainWindow.Designer.cs
@@ -40,6 +40,7 @@ private void InitializeComponent()
this.MenuItemHelp = new System.Windows.Forms.ToolStripMenuItem();
this.MenuItemHelpManual = new System.Windows.Forms.ToolStripMenuItem();
this.MenuItemHelpPortForwarding = new System.Windows.Forms.ToolStripMenuItem();
+ this.MenuItemHelpBugReport = new System.Windows.Forms.ToolStripMenuItem();
this.MenuItemHelpSeparator1 = new System.Windows.Forms.ToolStripSeparator();
this.MenuItemHelpUpdates = new System.Windows.Forms.ToolStripMenuItem();
this.MenuItemHelpAbout = new System.Windows.Forms.ToolStripMenuItem();
@@ -162,6 +163,7 @@ private void InitializeComponent()
this.MenuItemHelp.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.MenuItemHelpManual,
this.MenuItemHelpPortForwarding,
+ this.MenuItemHelpBugReport,
this.MenuItemHelpSeparator1,
this.MenuItemHelpUpdates,
this.MenuItemHelpAbout});
@@ -173,32 +175,39 @@ private void InitializeComponent()
//
this.MenuItemHelpManual.Image = ((System.Drawing.Image)(resources.GetObject("MenuItemHelpManual.Image")));
this.MenuItemHelpManual.Name = "MenuItemHelpManual";
- this.MenuItemHelpManual.Size = new System.Drawing.Size(171, 22);
+ this.MenuItemHelpManual.Size = new System.Drawing.Size(192, 22);
this.MenuItemHelpManual.Text = "Online &Manual";
//
// MenuItemHelpPortForwarding
//
this.MenuItemHelpPortForwarding.Image = global::ValheimServerGUI.Properties.Resources.OpenWeb_16x;
this.MenuItemHelpPortForwarding.Name = "MenuItemHelpPortForwarding";
- this.MenuItemHelpPortForwarding.Size = new System.Drawing.Size(171, 22);
+ this.MenuItemHelpPortForwarding.Size = new System.Drawing.Size(192, 22);
this.MenuItemHelpPortForwarding.Text = "&Port Forwarding";
//
+ // MenuItemHelpBugReport
+ //
+ this.MenuItemHelpBugReport.Image = global::ValheimServerGUI.Properties.Resources.NewBug_16x;
+ this.MenuItemHelpBugReport.Name = "MenuItemHelpBugReport";
+ this.MenuItemHelpBugReport.Size = new System.Drawing.Size(192, 22);
+ this.MenuItemHelpBugReport.Text = "Submit a &Bug Report...";
+ //
// MenuItemHelpSeparator1
//
this.MenuItemHelpSeparator1.Name = "MenuItemHelpSeparator1";
- this.MenuItemHelpSeparator1.Size = new System.Drawing.Size(168, 6);
+ this.MenuItemHelpSeparator1.Size = new System.Drawing.Size(189, 6);
//
// MenuItemHelpUpdates
//
this.MenuItemHelpUpdates.Image = global::ValheimServerGUI.Properties.Resources.UnsyncedCommits_16x_Horiz;
this.MenuItemHelpUpdates.Name = "MenuItemHelpUpdates";
- this.MenuItemHelpUpdates.Size = new System.Drawing.Size(171, 22);
+ this.MenuItemHelpUpdates.Size = new System.Drawing.Size(192, 22);
this.MenuItemHelpUpdates.Text = "Check for &Updates";
//
// MenuItemHelpAbout
//
this.MenuItemHelpAbout.Name = "MenuItemHelpAbout";
- this.MenuItemHelpAbout.Size = new System.Drawing.Size(171, 22);
+ this.MenuItemHelpAbout.Size = new System.Drawing.Size(192, 22);
this.MenuItemHelpAbout.Text = "&About...";
//
// StatusStrip
@@ -282,6 +291,7 @@ private void InitializeComponent()
this.WorldSelectNewNameField.LabelText = "New World Name";
this.WorldSelectNewNameField.Location = new System.Drawing.Point(6, 45);
this.WorldSelectNewNameField.MaxLength = 20;
+ this.WorldSelectNewNameField.Multiline = false;
this.WorldSelectNewNameField.Name = "WorldSelectNewNameField";
this.WorldSelectNewNameField.Size = new System.Drawing.Size(234, 41);
this.WorldSelectNewNameField.TabIndex = 18;
@@ -379,6 +389,7 @@ private void InitializeComponent()
this.ServerPasswordField.LabelText = "Server Password";
this.ServerPasswordField.Location = new System.Drawing.Point(0, 47);
this.ServerPasswordField.MaxLength = 64;
+ this.ServerPasswordField.Multiline = false;
this.ServerPasswordField.Name = "ServerPasswordField";
this.ServerPasswordField.Size = new System.Drawing.Size(243, 41);
this.ServerPasswordField.TabIndex = 11;
@@ -391,6 +402,7 @@ private void InitializeComponent()
this.ServerNameField.LabelText = "Server Name";
this.ServerNameField.Location = new System.Drawing.Point(0, 0);
this.ServerNameField.MaxLength = 64;
+ this.ServerNameField.Multiline = false;
this.ServerNameField.Name = "ServerNameField";
this.ServerNameField.Size = new System.Drawing.Size(243, 41);
this.ServerNameField.TabIndex = 10;
@@ -775,7 +787,6 @@ private void InitializeComponent()
this.Controls.Add(this.Tabs);
this.Controls.Add(this.StatusStrip);
this.Controls.Add(this.MenuStrip);
- this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.MainMenuStrip = this.MenuStrip;
this.MaximizeBox = false;
this.MinimumSize = new System.Drawing.Size(500, 371);
@@ -868,5 +879,6 @@ private void InitializeComponent()
private System.Windows.Forms.ToolStripStatusLabel StatusStripLabelRight;
private System.Windows.Forms.Timer UpdateCheckTimer;
private System.Windows.Forms.ToolStripMenuItem MenuItemFilePreferences;
+ private System.Windows.Forms.ToolStripMenuItem MenuItemHelpBugReport;
}
}
\ No newline at end of file
diff --git a/ValheimServerGUI/Forms/MainWindow.cs b/ValheimServerGUI/Forms/MainWindow.cs
index f40ef03..081d832 100644
--- a/ValheimServerGUI/Forms/MainWindow.cs
+++ b/ValheimServerGUI/Forms/MainWindow.cs
@@ -17,6 +17,11 @@ namespace ValheimServerGUI.Forms
{
public partial class MainWindow : Form
{
+#if DEBUG
+ private static readonly bool SimulateConstructorException = false;
+ private static readonly bool SimulateStartServerException = false;
+ private static readonly bool SimulateStopServerException = false;
+#endif
private static readonly string NL = Environment.NewLine;
private const string LogViewServer = "Server";
private const string LogViewApplication = "Application";
@@ -30,10 +35,7 @@ public partial class MainWindow : Form
{ ServerStatus.Starting, Resources.UnsyncedCommits_16x_Horiz },
{ ServerStatus.Running, Resources.StatusRun_16x },
{ ServerStatus.Stopping, Resources.UnsyncedCommits_16x_Horiz },
- };
-
- private readonly TimeSpan UpdateCheckInterval = TimeSpan.Parse(Resources.UpdateCheckInterval);
- private DateTime NextUpdateCheck = DateTime.MaxValue;
+ };
private readonly IFormProvider FormProvider;
private readonly IUserPreferencesProvider UserPrefsProvider;
@@ -43,7 +45,7 @@ public partial class MainWindow : Form
private readonly ValheimServerLogger ServerLogger;
private readonly IEventLogger Logger;
private readonly IIpAddressProvider IpAddressProvider;
- private readonly IGitHubClient GitHubClient;
+ private readonly ISoftwareUpdateProvider SoftwareUpdateProvider;
public MainWindow(
IFormProvider formProvider,
@@ -54,8 +56,11 @@ public MainWindow(
ValheimServerLogger serverLogger,
IEventLogger appLogger,
IIpAddressProvider ipAddressProvider,
- IGitHubClient gitHubClient)
+ ISoftwareUpdateProvider softwareUpdateProvider)
{
+#if DEBUG
+ if (SimulateConstructorException) throw new InvalidOperationException("Intentional exception thrown for testing");
+#endif
this.FormProvider = formProvider;
this.UserPrefsProvider = userPrefsProvider;
this.FileProvider = fileProvider;
@@ -64,9 +69,10 @@ public MainWindow(
this.ServerLogger = serverLogger;
this.Logger = appLogger;
this.IpAddressProvider = ipAddressProvider;
- this.GitHubClient = gitHubClient;
+ this.SoftwareUpdateProvider = softwareUpdateProvider;
InitializeComponent(); // WinForms generated code, always first
+ this.AddApplicationIcon();
InitializeImages();
InitializeServer();
InitializeFormEvents();
@@ -94,12 +100,15 @@ private void InitializeServer()
this.IpAddressProvider.ExternalIpReceived += this.BuildEventHandler(this.IpAddressProvider_ExternalIpReceived);
this.IpAddressProvider.InternalIpReceived += this.BuildEventHandler(this.IpAddressProvider_InternalIpReceived);
+
+ this.SoftwareUpdateProvider.UpdateCheckStarted += this.BuildEventHandler(this.SoftwareUpdateProvider_UpdateCheckStarted);
+ this.SoftwareUpdateProvider.UpdateCheckFinished += this.BuildEventHandler(this.SoftwareUpdateProvider_UpdateCheckFinished);
}
private void InitializeFormEvents()
{
// MainWindow
- this.Shown += this.BuildEventHandlerAsync(this.MainWindow_Load, 250);
+ this.Shown += this.BuildEventHandler(this.MainWindow_Load);
// Menu items
this.MenuItemFilePreferences.Click += this.MenuItemFilePreferences_Click;
@@ -107,7 +116,8 @@ private void InitializeFormEvents()
this.MenuItemFileClose.Click += this.MenuItemFileClose_Clicked;
this.MenuItemHelpManual.Click += this.MenuItemHelpManual_Click;
this.MenuItemHelpPortForwarding.Click += this.MenuItemHelpPortForwarding_Clicked;
- this.MenuItemHelpUpdates.Click += this.BuildEventHandlerAsync(this.MenuItemHelpUpdates_Clicked);
+ this.MenuItemHelpBugReport.Click += this.MenuItemHelpBugReport_Click;
+ this.MenuItemHelpUpdates.Click += this.BuildEventHandler(this.MenuItemHelpUpdates_Clicked);
this.MenuItemHelpAbout.Click += this.MenuItemHelpAbout_Clicked;
// Tray icon
@@ -119,7 +129,7 @@ private void InitializeFormEvents()
// Timers
this.ServerRefreshTimer.Tick += this.ServerRefreshTimer_Tick;
- this.UpdateCheckTimer.Tick += this.BuildEventHandlerAsync(this.UpdateCheckTimer_Tick);
+ this.UpdateCheckTimer.Tick += this.BuildEventHandler(this.UpdateCheckTimer_Tick);
// Tabs
this.TabPlayers.VisibleChanged += this.TabPlayers_VisibleChanged;
@@ -136,7 +146,7 @@ private void InitializeFormEvents()
this.CopyButtonExternalIpAddress.CopyFunction = () => this.LabelExternalIpAddress.Value;
this.CopyButtonInternalIpAddress.CopyFunction = () => this.LabelInternalIpAddress.Value;
this.CopyButtonLocalIpAddress.CopyFunction = () => this.LabelLocalIpAddress.Value;
- this.StatusStripLabelRight.Click += this.BuildEventHandlerAsync(this.StatusStripLabelRight_Click);
+ this.StatusStripLabelRight.Click += this.BuildEventHandler(this.StatusStripLabelRight_Click);
// Form fields
this.ShowPasswordField.ValueChanged += this.ShowPasswordField_Changed;
@@ -167,15 +177,9 @@ private void InitializeFormFields()
#region MainWindow Events
- private Task MainWindow_Load()
+ private void MainWindow_Load()
{
this.Logger.LogInformation($"Valheim Server GUI v{AssemblyHelper.GetApplicationVersion()} - Loaded OK");
-
- return Task.WhenAll(
- this.RefreshInternalIpAsync(),
- this.RefreshExternalIpAsync(),
- this.CheckForUpdatesAsync(false)
- );
}
protected override void OnShown(EventArgs e)
@@ -288,9 +292,15 @@ private void MenuItemHelpPortForwarding_Clicked(object sender, EventArgs e)
WebHelper.OpenWebAddress(Resources.UrlPortForwardingGuide);
}
- private async Task MenuItemHelpUpdates_Clicked()
+ private void MenuItemHelpBugReport_Click(object sender, EventArgs e)
{
- await this.CheckForUpdatesAsync(true);
+ var bugReportForm = FormProvider.GetForm();
+ bugReportForm.ShowDialog();
+ }
+
+ private void MenuItemHelpUpdates_Clicked()
+ {
+ this.CheckForUpdates(true);
}
private void MenuItemHelpAbout_Clicked(object sender, EventArgs e)
@@ -305,6 +315,9 @@ private void MenuItemHelpAbout_Clicked(object sender, EventArgs e)
private void ButtonStopServer_Click(object sender, EventArgs e)
{
+#if DEBUG
+ if (SimulateStopServerException) throw new InvalidOperationException("Intentional exception thrown for testing");
+#endif
Server.Stop();
}
@@ -429,12 +442,9 @@ private void ServerRefreshTimer_Tick(object sender, EventArgs e)
if (this.TabServerDetails.Visible) this.RefreshServerDetails();
}
- private async Task UpdateCheckTimer_Tick()
+ private void UpdateCheckTimer_Tick()
{
- if (DateTime.UtcNow > this.NextUpdateCheck)
- {
- await this.CheckForUpdatesAsync(false);
- }
+ this.CheckForUpdates(false);
}
private void PlayersTable_SelectionChanged(object sender, EventArgs e)
@@ -444,16 +454,16 @@ private void PlayersTable_SelectionChanged(object sender, EventArgs e)
this.ButtonRemovePlayer.Enabled = isSelected && row.Entity.PlayerStatus == PlayerStatus.Offline;
}
- private async Task StatusStripLabelRight_Click()
+ private void StatusStripLabelRight_Click()
{
if (!this.StatusStripLabelRight.IsLink) return;
- await this.CheckForUpdatesAsync(true);
+ this.CheckForUpdates(true);
}
#endregion
- #region Server Events
+ #region Service Events
private void OnApplicationLogReceived(EventLogContext logEvent)
{
@@ -525,6 +535,73 @@ private void IpAddressProvider_InternalIpReceived(string ip)
this.RefreshIpPorts();
}
+ private void SoftwareUpdateProvider_UpdateCheckStarted()
+ {
+ this.SetStatusTextRight("Checking for updates...", Resources.Loading_Blue_16x, false);
+ }
+
+ private void SoftwareUpdateProvider_UpdateCheckFinished(SoftwareUpdateEventArgs e)
+ {
+ if (!e.IsSuccessful)
+ {
+ this.SetStatusTextRight($"Update check failed", Resources.StatusCriticalError_16x, true);
+
+ if (e.IsManualCheck)
+ {
+ var exception = e.Exception.GetPrimaryException();
+ var result = MessageBox.Show(
+ $"Update check failed: {exception.Message}" + Environment.NewLine +
+ "Would you like to go to the download page?",
+ "Check for Updates",
+ MessageBoxButtons.YesNo,
+ MessageBoxIcon.Error);
+
+ if (result == DialogResult.Yes)
+ {
+ WebHelper.OpenWebAddress(Resources.UrlUpdates);
+ }
+ }
+ }
+ else if (e.IsNewerVersionAvailable)
+ {
+ this.SetStatusTextRight($"Update available ({e.LatestVersion})", Resources.StatusWarning_16x, true);
+
+ if (e.IsManualCheck)
+ {
+ var result = MessageBox.Show(
+ $"A newer version of ValheimServerGUI is available." + Environment.NewLine +
+ "Would you like to go to the download page?",
+ "Check for Updates",
+ MessageBoxButtons.YesNo,
+ MessageBoxIcon.Warning);
+
+ if (result == DialogResult.Yes)
+ {
+ WebHelper.OpenWebAddress(Resources.UrlUpdates);
+ }
+ }
+ }
+ else
+ {
+ this.SetStatusTextRight($"Up to date ({e.LatestVersion})", Resources.StatusOK_16x, false);
+
+ if (e.IsManualCheck)
+ {
+ var result = MessageBox.Show(
+ "You are running the latest version of ValheimServerGUI." + Environment.NewLine +
+ "Would you like to go to the download page?",
+ "Check for Updates",
+ MessageBoxButtons.YesNo,
+ MessageBoxIcon.Question);
+
+ if (result == DialogResult.Yes)
+ {
+ WebHelper.OpenWebAddress(Resources.UrlUpdates);
+ }
+ }
+ }
+ }
+
#endregion
#region Common Methods
@@ -550,6 +627,9 @@ private void RunStartupStuff()
private void StartServer()
{
+#if DEBUG
+ if (SimulateStartServerException) throw new InvalidOperationException("Intentional exception thrown for testing");
+#endif
string worldName;
bool newWorld = this.WorldSelectRadioNew.Value;
@@ -708,16 +788,6 @@ private void RefreshServerDetails()
}
}
- private async Task RefreshExternalIpAsync()
- {
- if (this.LabelExternalIpAddress.Value == IpLoadingText) await this.IpAddressProvider.GetExternalIpAddressAsync();
- }
-
- private async Task RefreshInternalIpAsync()
- {
- if (this.LabelInternalIpAddress.Value == IpLoadingText) await this.IpAddressProvider.GetInternalIpAddressAsync();
- }
-
private void RefreshIpPorts()
{
const string ipExpr = @"^([\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3})";
@@ -830,60 +900,9 @@ private void LoadFormValuesFromUserPrefs(UserPreferences prefs)
this.WorldSelectRadioExisting.Value = true;
}
- private async Task CheckForUpdatesAsync(bool isManualCheck)
+ private void CheckForUpdates(bool isManualCheck)
{
- if (!isManualCheck)
- {
- this.NextUpdateCheck = DateTime.UtcNow + this.UpdateCheckInterval;
-
- var prefs = this.UserPrefsProvider.LoadPreferences();
- if (!prefs.CheckForUpdates) return;
- }
-
- this.SetStatusTextRight("Checking for updates...", Resources.Loading_Blue_16x, false);
-
- var currentVersion = AssemblyHelper.GetApplicationVersion();
- var release = await this.GitHubClient.GetLatestReleaseAsync();
-
- if (AssemblyHelper.IsNewerVersion(release?.TagName))
- {
- this.SetStatusTextRight($"Update available ({release.TagName})", Resources.StatusWarning_16x, true);
-
- if (isManualCheck)
- {
- var result = MessageBox.Show(
- $"A newer version of ValheimServerGUI is available." + Environment.NewLine +
- "Would you like to go to the download page?",
- "Check for Updates",
- MessageBoxButtons.YesNo,
- MessageBoxIcon.Warning);
-
- if (result == DialogResult.Yes)
- {
- WebHelper.OpenWebAddress(Resources.UrlUpdates);
- }
- }
- }
- else
- {
- currentVersion = release.TagName ?? currentVersion; // Use the v-prefixed version if available
- this.SetStatusTextRight($"Up to date ({currentVersion})", Resources.StatusOK_16x, false);
-
- if (isManualCheck)
- {
- var result = MessageBox.Show(
- "You are running the latest version of ValheimServerGUI." + Environment.NewLine +
- "Would you like to go to the download page?",
- "Check for Updates",
- MessageBoxButtons.YesNo,
- MessageBoxIcon.Question);
-
- if (result == DialogResult.Yes)
- {
- WebHelper.OpenWebAddress(Resources.UrlUpdates);
- }
- }
- }
+ Task.Run(() => this.SoftwareUpdateProvider.CheckForUpdatesAsync(isManualCheck));
}
private void CloseApplicationOnServerStopped()
diff --git a/ValheimServerGUI/Forms/PlayerDetailsForm.Designer.cs b/ValheimServerGUI/Forms/PlayerDetailsForm.Designer.cs
index 313ca3e..e54cd13 100644
--- a/ValheimServerGUI/Forms/PlayerDetailsForm.Designer.cs
+++ b/ValheimServerGUI/Forms/PlayerDetailsForm.Designer.cs
@@ -161,7 +161,6 @@ private void InitializeComponent()
this.Controls.Add(this.ButtonOK);
this.Controls.Add(this.PlayerNameField);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
- this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "PlayerDetailsForm";
diff --git a/ValheimServerGUI/Forms/PlayerDetailsForm.cs b/ValheimServerGUI/Forms/PlayerDetailsForm.cs
index e1c69a8..844936f 100644
--- a/ValheimServerGUI/Forms/PlayerDetailsForm.cs
+++ b/ValheimServerGUI/Forms/PlayerDetailsForm.cs
@@ -16,6 +16,7 @@ public PlayerDetailsForm(IPlayerDataRepository playerDataProvider)
this.PlayerDataProvider = playerDataProvider;
InitializeComponent();
+ this.AddApplicationIcon();
this.ButtonRefresh.Click += ButtonRefresh_Click;
this.ButtonOK.Click += ButtonOK_Click;
diff --git a/ValheimServerGUI/Forms/PreferencesForm.Designer.cs b/ValheimServerGUI/Forms/PreferencesForm.Designer.cs
index f0bd746..b815fa4 100644
--- a/ValheimServerGUI/Forms/PreferencesForm.Designer.cs
+++ b/ValheimServerGUI/Forms/PreferencesForm.Designer.cs
@@ -125,7 +125,6 @@ private void InitializeComponent()
this.Controls.Add(this.ButtonOK);
this.Controls.Add(this.ButtonCancel);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
- this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "PreferencesForm";
diff --git a/ValheimServerGUI/Forms/PreferencesForm.cs b/ValheimServerGUI/Forms/PreferencesForm.cs
index 84765d2..f6e9fa3 100644
--- a/ValheimServerGUI/Forms/PreferencesForm.cs
+++ b/ValheimServerGUI/Forms/PreferencesForm.cs
@@ -15,6 +15,7 @@ public partial class PreferencesForm : Form
public PreferencesForm()
{
InitializeComponent();
+ this.AddApplicationIcon();
}
public PreferencesForm(IUserPreferencesProvider userPrefsProvider, ILogger logger) : this()
diff --git a/ValheimServerGUI/Forms/SplashForm.Designer.cs b/ValheimServerGUI/Forms/SplashForm.Designer.cs
new file mode 100644
index 0000000..5302c01
--- /dev/null
+++ b/ValheimServerGUI/Forms/SplashForm.Designer.cs
@@ -0,0 +1,78 @@
+
+namespace ValheimServerGUI.Forms
+{
+ partial class SplashForm
+ {
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Windows Form Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ this.AppNameLabel = new System.Windows.Forms.Label();
+ this.ProgressBar = new System.Windows.Forms.ProgressBar();
+ this.SuspendLayout();
+ //
+ // AppNameLabel
+ //
+ this.AppNameLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
+ | System.Windows.Forms.AnchorStyles.Left)
+ | System.Windows.Forms.AnchorStyles.Right)));
+ this.AppNameLabel.Location = new System.Drawing.Point(12, 9);
+ this.AppNameLabel.Name = "AppNameLabel";
+ this.AppNameLabel.Size = new System.Drawing.Size(174, 23);
+ this.AppNameLabel.TabIndex = 1;
+ this.AppNameLabel.Text = "ValheimServerGUI";
+ this.AppNameLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
+ //
+ // ProgressBar
+ //
+ this.ProgressBar.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
+ | System.Windows.Forms.AnchorStyles.Right)));
+ this.ProgressBar.Location = new System.Drawing.Point(12, 35);
+ this.ProgressBar.Name = "ProgressBar";
+ this.ProgressBar.Size = new System.Drawing.Size(174, 16);
+ this.ProgressBar.Style = System.Windows.Forms.ProgressBarStyle.Continuous;
+ this.ProgressBar.TabIndex = 2;
+ //
+ // SplashForm
+ //
+ this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.ClientSize = new System.Drawing.Size(198, 63);
+ this.ControlBox = false;
+ this.Controls.Add(this.AppNameLabel);
+ this.Controls.Add(this.ProgressBar);
+ this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
+ this.Name = "SplashForm";
+ this.ShowInTaskbar = false;
+ this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
+ this.ResumeLayout(false);
+
+ }
+
+ #endregion
+ private System.Windows.Forms.Label AppNameLabel;
+ private System.Windows.Forms.ProgressBar ProgressBar;
+ }
+}
\ No newline at end of file
diff --git a/ValheimServerGUI/Forms/SplashForm.cs b/ValheimServerGUI/Forms/SplashForm.cs
new file mode 100644
index 0000000..03b5f95
--- /dev/null
+++ b/ValheimServerGUI/Forms/SplashForm.cs
@@ -0,0 +1,292 @@
+using Microsoft.Extensions.Logging;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+using ValheimServerGUI.Properties;
+using ValheimServerGUI.Tools;
+
+namespace ValheimServerGUI.Forms
+{
+ public partial class SplashForm : Form
+ {
+#if DEBUG
+ private static readonly bool SimulateLongRunningStartup = false;
+ private static readonly bool SimulateStartupTaskException = false;
+ private static readonly bool SimulateAsyncPopoutOnStart = false;
+#endif
+ private Form MainForm;
+ private bool IsFirstShown = true;
+ private bool CloseAfterExceptionHandled = false;
+
+ private readonly List> StartupTasks = new();
+ private readonly List FinishedTasks = new();
+ private event EventHandler TaskFinished;
+
+ private readonly IFormProvider FormProvider;
+ private readonly IIpAddressProvider IpAddressProvider;
+ private readonly ISoftwareUpdateProvider SoftwareUpdateProvider;
+ private readonly IExceptionHandler ExceptionHandler;
+ private readonly ILogger Logger;
+
+ public SplashForm(
+ IFormProvider formProvider,
+ IIpAddressProvider ipAddressProvider,
+ ISoftwareUpdateProvider softwareUpdateProvider,
+ IExceptionHandler exceptionHandler,
+ ILogger logger)
+ {
+ this.FormProvider = formProvider;
+ this.IpAddressProvider = ipAddressProvider;
+ this.SoftwareUpdateProvider = softwareUpdateProvider;
+ this.ExceptionHandler = exceptionHandler;
+ this.Logger = logger;
+
+ Application.ThreadException += Application_ThreadException;
+ AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
+
+ try
+ {
+ InitializeComponent();
+ this.AddApplicationIcon();
+ InitializeAppName();
+ InitializeFormEvents();
+ }
+ catch (Exception e)
+ {
+ this.HandleException(e, "Startup Init Exception", true);
+ }
+ }
+
+ private void InitializeAppName()
+ {
+ this.AppNameLabel.Text = $"ValheimServerGUI v{AssemblyHelper.GetApplicationVersion()}";
+ }
+
+ private void InitializeFormEvents()
+ {
+ this.Shown += this.BuildEventHandler(this.SplashForm_OnShown);
+ this.ExceptionHandler.ExceptionHandled += this.BuildEventHandler(this.OnExceptionHandled);
+ }
+
+ #region Form events
+
+ protected void SplashForm_OnShown()
+ {
+ if (this.IsFirstShown)
+ {
+ this.IsFirstShown = false;
+
+ // For some reason the form is not actually fully rendered at this point
+ // (labels appear as white boxes) so I'm forcing a redraw here
+ this.Refresh();
+
+ this.OnFirstShown();
+ }
+ }
+
+ protected void OnFirstShown()
+ {
+ try
+ {
+ if (!this.VersionCheck())
+ {
+ this.Close();
+ return;
+ }
+
+ InitializeMainForm();
+ InitializeStartupTasks();
+ RunStartupTasks();
+ }
+ catch (Exception ex)
+ {
+ this.HandleException(ex, "Startup Run Exception", true);
+ }
+ }
+
+ private void InitializeMainForm()
+ {
+ this.MainForm = this.FormProvider.GetForm();
+
+ // Since the splash screen is the application's main form, it must continue running in the background
+ // So listen for whenever the MainWindow closes, and close the splash screen as well, in order to close the application
+ this.MainForm.FormClosed += this.OnMainFormClosed;
+ }
+
+ private void InitializeStartupTasks()
+ {
+ this.TaskFinished += this.BuildEventHandler(this.OnTaskFinished);
+
+ this.AddStartupTask(this.IpAddressProvider.GetExternalIpAddressAsync);
+ this.AddStartupTask(this.IpAddressProvider.GetInternalIpAddressAsync);
+ this.AddStartupTask(() => this.SoftwareUpdateProvider.CheckForUpdatesAsync(false));
+
+#if DEBUG
+ if (SimulateLongRunningStartup)
+ {
+ this.AddStartupTask(() => Task.Delay(2000));
+ this.AddStartupTask(() => Task.Delay(2500));
+ this.AddStartupTask(() => Task.Delay(3000));
+ }
+
+ if (SimulateStartupTaskException)
+ {
+ this.AddStartupTask(async () =>
+ {
+ await Task.Delay(500);
+ throw new InvalidOperationException("Intentional exception thrown for testing");
+ });
+ }
+#endif
+ }
+
+ #endregion
+
+ #region Event handlers
+
+ private void OnTaskFinished(Task task)
+ {
+ if (!this.StartupTasks.Any())
+ {
+ // Close the splash screen if there are no startup tasks
+ this.FinishStartup();
+ return;
+ }
+
+ if (task != null)
+ {
+ this.FinishedTasks.Add(task);
+
+ if (!task.IsCompletedSuccessfully)
+ {
+ this.Logger.LogWarning("Error encountered during startup task");
+ this.HandleException(task.Exception, "Startup Task Exception", true);
+ return;
+ }
+
+ //this.Logger.LogTrace($"Finishing startup task #{this.FinishedTasks.Count}");
+ }
+
+ var numTasks = this.StartupTasks.Count;
+ var numTasksFinished = this.FinishedTasks.Count;
+ var pctTasksFinished = numTasksFinished * 100 / numTasks;
+
+ this.ProgressBar.Value = pctTasksFinished;
+
+ if (numTasksFinished >= numTasks)
+ {
+ // Close the splash screen once all startup tasks have finished
+ this.FinishStartup();
+ return;
+ }
+ }
+
+ private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
+ {
+ var isMainFormVisible = this.MainForm != null && this.MainForm.Visible;
+ this.HandleException(e.ExceptionObject as Exception, "Unhandled Exception", !isMainFormVisible);
+ }
+
+ private void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e)
+ {
+ var isMainFormVisible = this.MainForm != null && this.MainForm.Visible;
+ this.HandleException(e.Exception, "Thread Exception", !isMainFormVisible);
+ }
+
+ private void OnExceptionHandled()
+ {
+ if (CloseAfterExceptionHandled) this.Close();
+ }
+
+ private void OnMainFormClosed(object sender, FormClosedEventArgs e)
+ {
+ this.Close();
+ }
+
+ #endregion
+
+ #region Common methods
+
+ private void AddStartupTask(Func taskFunc)
+ {
+ this.StartupTasks.Add(taskFunc);
+ }
+
+ private void RunStartupTasks()
+ {
+ //var i = 1;
+ foreach (var taskFunc in this.StartupTasks)
+ {
+ //this.Logger.LogTrace($"Beginning startup task #{i++}");
+
+ Task.Run(() => taskFunc().ContinueWith(t =>
+ {
+ this.TaskFinished?.Invoke(this, t);
+ return Task.CompletedTask;
+ }));
+ }
+ }
+
+ private bool VersionCheck()
+ {
+ var dotnetVersion = AssemblyHelper.GetDotnetRuntimeVersion();
+
+ if (dotnetVersion.Major < 5)
+ {
+ this.Logger.LogWarning($"Incompatible .NET version detected: {dotnetVersion}");
+
+ var nl = Environment.NewLine;
+ var result = MessageBox.Show(
+ $"ValheimServerGUI requires the .NET 5.0 Desktop Runtime (or higher) to be installed.{nl}" +
+ $"You are currently using .NET {dotnetVersion}.{nl}{nl}" +
+ "Would you like to go to the download page now?",
+ ".NET Upgrade Required",
+ MessageBoxButtons.YesNo,
+ MessageBoxIcon.Warning);
+
+ if (result == DialogResult.Yes)
+ {
+ WebHelper.OpenWebAddress(Resources.UrlDotnetDownload);
+ }
+
+ return false;
+ }
+
+ return true;
+ }
+
+ private void HandleException(Exception exception, string contextMessage, bool closeAfterHandle)
+ {
+ this.Logger.LogError($"Encountered exception - {exception.GetType().Name}: {exception.Message}");
+
+ this.CloseAfterExceptionHandled = closeAfterHandle;
+
+ this.ExceptionHandler.HandleException(exception, contextMessage);
+ }
+
+ private void FinishStartup()
+ {
+ this.MainForm.Show();
+#if DEBUG
+ if (SimulateAsyncPopoutOnStart)
+ {
+ var asyncPopout = new AsyncPopout(Task.Delay(5000), options =>
+ {
+ options.Text = "Testing AsyncPopout...";
+ options.Title = "Testing AsyncPopout";
+ options.SuccessMessage = "Task succeeded!";
+ options.FailureMessage = "Task failed!";
+ });
+
+ asyncPopout.Show();
+ }
+#endif
+ // Hide the splash screen so it's no longer visible once the application is loaded
+ this.Hide();
+ }
+
+ #endregion
+ }
+}
diff --git a/ValheimServerGUI/Forms/SplashForm.resx b/ValheimServerGUI/Forms/SplashForm.resx
new file mode 100644
index 0000000..f298a7b
--- /dev/null
+++ b/ValheimServerGUI/Forms/SplashForm.resx
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/ValheimServerGUI/Program.cs b/ValheimServerGUI/Program.cs
index 76df621..852440c 100644
--- a/ValheimServerGUI/Program.cs
+++ b/ValheimServerGUI/Program.cs
@@ -4,7 +4,6 @@
using System.Windows.Forms;
using ValheimServerGUI.Forms;
using ValheimServerGUI.Game;
-using ValheimServerGUI.Properties;
using ValheimServerGUI.Tools;
using ValheimServerGUI.Tools.Data;
using ValheimServerGUI.Tools.Http;
@@ -23,13 +22,9 @@ public static class Program
[STAThread]
public static void Main()
{
- if (!VersionCheck()) return;
-
Application.SetHighDpiMode(HighDpiMode.SystemAware);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
- Application.ThreadException += Application_ThreadException;
- AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
var services = new ServiceCollection();
ConfigureServices(services);
@@ -38,7 +33,7 @@ public static void Main()
try
{
- Application.Run(serviceProvider.GetRequiredService());
+ Application.Run(serviceProvider.GetRequiredService());
}
catch (Exception e)
{
@@ -62,7 +57,9 @@ public static void ConfigureServices(IServiceCollection services)
.AddSingleton()
.AddSingleton()
.AddSingleton()
- .AddSingleton();
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton();
// Game & server data
services
@@ -74,47 +71,13 @@ public static void ConfigureServices(IServiceCollection services)
// Forms
services
+ .AddSingleton()
.AddSingleton()
.AddSingleton()
.AddSingleton()
+ .AddSingleton()
.AddSingleton()
.AddTransient();
}
-
- private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
- {
- ExceptionHandler.HandleException(e.ExceptionObject as Exception, "Unhandled Exception");
- }
-
- private static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e)
- {
- ExceptionHandler.HandleException(e.Exception, "Thread Exception");
- }
-
- private static bool VersionCheck()
- {
- var dotnetVersion = AssemblyHelper.GetDotnetRuntimeVersion();
-
- if (dotnetVersion.Major < 5)
- {
- var nl = Environment.NewLine;
- var result = MessageBox.Show(
- $"ValheimServerGUI requires the .NET 5.0 Desktop Runtime (or higher) to be installed.{nl}" +
- $"You are currently using .NET {dotnetVersion}.{nl}{nl}" +
- "Would you like to go to the download page now?",
- ".NET Upgrade Required",
- MessageBoxButtons.YesNo,
- MessageBoxIcon.Warning);
-
- if (result == DialogResult.Yes)
- {
- WebHelper.OpenWebAddress(Resources.UrlDotnetDownload);
- }
-
- return false;
- }
-
- return true;
- }
}
}
diff --git a/ValheimServerGUI/Properties/Resources.Designer.cs b/ValheimServerGUI/Properties/Resources.Designer.cs
index 9201b01..74d12d2 100644
--- a/ValheimServerGUI/Properties/Resources.Designer.cs
+++ b/ValheimServerGUI/Properties/Resources.Designer.cs
@@ -176,6 +176,16 @@ internal static System.Drawing.Bitmap Loading_Blue_16x {
}
}
+ ///
+ /// Looks up a localized resource of type System.Drawing.Bitmap.
+ ///
+ internal static System.Drawing.Bitmap NewBug_16x {
+ get {
+ object obj = ResourceManager.GetObject("NewBug_16x", resourceCulture);
+ return ((System.Drawing.Bitmap)(obj));
+ }
+ }
+
///
/// Looks up a localized resource of type System.Drawing.Bitmap.
///
@@ -426,6 +436,15 @@ internal static string UrlPortForwardingGuide {
}
}
+ ///
+ /// Looks up a localized string similar to https://api.runeberry.com/vsg-api.
+ ///
+ internal static string UrlRuneberryApi {
+ get {
+ return ResourceManager.GetString("UrlRuneberryApi", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to https://twitter.com/Runeberries.
///
diff --git a/ValheimServerGUI/Properties/Resources.resx b/ValheimServerGUI/Properties/Resources.resx
index a6a5b92..5b5bb0c 100644
--- a/ValheimServerGUI/Properties/Resources.resx
+++ b/ValheimServerGUI/Properties/Resources.resx
@@ -154,6 +154,9 @@
..\Resources\Loading_Blue_16x.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
+ ..\Resources\NewBug_16x.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
..\Resources\OpenWeb_16x.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
@@ -232,6 +235,9 @@
https://github.com/runeberry/ValheimServerGUI/wiki/Connecting-to-your-Server
+
+ https://api.runeberry.com/vsg-api
+
https://twitter.com/Runeberries
diff --git a/ValheimServerGUI/Resources/NewBug_16x.png b/ValheimServerGUI/Resources/NewBug_16x.png
new file mode 100644
index 0000000..f6ce855
Binary files /dev/null and b/ValheimServerGUI/Resources/NewBug_16x.png differ
diff --git a/ValheimServerGUI/Tools/AssemblyHelper.cs b/ValheimServerGUI/Tools/AssemblyHelper.cs
index a703fab..821c06f 100644
--- a/ValheimServerGUI/Tools/AssemblyHelper.cs
+++ b/ValheimServerGUI/Tools/AssemblyHelper.cs
@@ -1,6 +1,10 @@
-using System;
+using DeviceId;
+using System;
+using System.Globalization;
using System.Linq;
using System.Reflection;
+using System.Security.Cryptography;
+using System.Text;
namespace ValheimServerGUI.Tools
{
@@ -34,5 +38,39 @@ public static Version GetDotnetRuntimeVersion()
{
return Environment.Version;
}
+
+ private static string ClientCorrelationId;
+
+ public static string GetClientCorrelationId()
+ {
+ if (ClientCorrelationId != null) return ClientCorrelationId;
+
+ var deviceId = new DeviceIdBuilder()
+ .AddMacAddress()
+ .AddMotherboardSerialNumber()
+ .ToString()
+ .ToLowerInvariant();
+
+ using var hash = MD5.Create();
+ var hexStrings = hash.ComputeHash(Encoding.UTF8.GetBytes(deviceId)).Select(b => b.ToString("x2"));
+ ClientCorrelationId = string.Join(string.Empty, hexStrings);
+
+ return ClientCorrelationId;
+ }
+
+ public static CrashReport BuildCrashReport()
+ {
+ return new CrashReport
+ {
+ CrashReportId = Guid.NewGuid().ToString(),
+ ClientCorrelationId = GetClientCorrelationId(),
+ Timestamp = DateTime.UtcNow,
+ AppVersion = GetApplicationVersion(),
+ OsVersion = Environment.OSVersion.VersionString,
+ DotnetVersion = Environment.Version.ToString(),
+ CurrentCulture = CultureInfo.CurrentCulture?.ToString(),
+ CurrentUICulture = CultureInfo.CurrentUICulture?.ToString(),
+ };
+ }
}
}
diff --git a/ValheimServerGUI/Tools/ExceptionExtensions.cs b/ValheimServerGUI/Tools/ExceptionExtensions.cs
new file mode 100644
index 0000000..5c9604c
--- /dev/null
+++ b/ValheimServerGUI/Tools/ExceptionExtensions.cs
@@ -0,0 +1,17 @@
+using System;
+
+namespace ValheimServerGUI.Tools
+{
+ public static class ExceptionExtensions
+ {
+ public static Exception GetPrimaryException(this Exception exception)
+ {
+ if (exception is AggregateException agg)
+ {
+ return agg.InnerException.GetPrimaryException() ?? agg;
+ }
+
+ return exception;
+ }
+ }
+}
diff --git a/ValheimServerGUI/Tools/ExceptionHandler.cs b/ValheimServerGUI/Tools/ExceptionHandler.cs
index b193a12..225ebef 100644
--- a/ValheimServerGUI/Tools/ExceptionHandler.cs
+++ b/ValheimServerGUI/Tools/ExceptionHandler.cs
@@ -1,87 +1,86 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Net.Mail;
-using System.Text;
-using System.Threading.Tasks;
using System.Windows.Forms;
+using ValheimServerGUI.Forms;
+using ValheimServerGUI.Tools.Logging;
namespace ValheimServerGUI.Tools
{
public interface IExceptionHandler
{
- void HandleException(Exception e, string additionalMessage = null);
+ event EventHandler ExceptionHandled;
+
+ void HandleException(Exception e, string contextMessage = null);
}
public class ExceptionHandler : IExceptionHandler
{
- private static readonly string NL = Environment.NewLine;
+ private readonly IRuneberryApiClient RuneberryApiClient;
+
+ private readonly IEventLogger Logger;
- public void HandleException(Exception e, string additionalMessage = null)
+ public ExceptionHandler(IRuneberryApiClient runeberryApiClient, IEventLogger logger)
+ {
+ RuneberryApiClient = runeberryApiClient;
+ Logger = logger;
+ }
+
+ public event EventHandler ExceptionHandled;
+
+ public void HandleException(Exception e, string contextMessage = null)
{
if (e == null) return;
- additionalMessage ??= "Unhandled Exception";
- var message = "An unhandled exception has been thrown, and ValheimServerGUI will be terminated.";
- //var stackTrace = string.Join(NL, e.StackTrace.Split(NL).Take(3));
- message += $"{NL}{NL}{BuildMessageBody(e, additionalMessage)}";
+ e = e.GetPrimaryException();
+
+ contextMessage ??= "Unknown Exception";
+ var userMessage = "A fatal error has occured. Would you like to send an automated crash report to the developer?";
var result = MessageBox.Show(
- message,
- additionalMessage,
- MessageBoxButtons.OK,
+ userMessage,
+ contextMessage,
+ MessageBoxButtons.YesNo,
MessageBoxIcon.Error);
- //if (result == DialogResult.Yes)
- //{
- // try
- // {
- // var body = BuildMessageBody(e, additionalMessage);
- // SendEmail("ValheimServerGUI - Automated bug report", additionalMessage);
-
- // MessageBox.Show("Bug report sent. Thank you!", additionalMessage, MessageBoxButtons.OK, MessageBoxIcon.Information);
- // }
- // catch(Exception e2)
- // {
- // MessageBox.Show("Failed to send bug report. Sorry!" + e2.Message, additionalMessage, MessageBoxButtons.OK, MessageBoxIcon.Warning);
- // }
- //}
- }
-
- //private void SendEmail(string subject, string body)
- //{
- // var mailClient = new SmtpClient("");
- // var mailMessage = new MailMessage();
+ if (result == DialogResult.Yes)
+ {
+ var crashReport = BuildCrashReport(e, contextMessage);
+ var task = RuneberryApiClient.SendCrashReportAsync(crashReport);
- // mailMessage.From = new MailAddress("");
- // mailMessage.From = new MailAddress("");
- // mailMessage.To.Add(new MailAddress(""));
+ var asyncPopout = new AsyncPopout(task, o =>
+ {
+ o.Title = "Crash Report";
+ o.Text = "Sending crash report...";
+ o.SuccessMessage = "Crash report received. Thank you!";
+ o.FailureMessage = "Failed to send crash report.\r\nContact Runeberry Software for further support.";
+ });
- // mailMessage.Subject = subject;
- // mailMessage.Body = body;
+ asyncPopout.ShowDialog();
+ }
- // mailClient.Send(mailMessage);
- // mailMessage.Dispose();
- //}
+ this.ExceptionHandled?.Invoke(this, EventArgs.Empty);
+ }
- private string BuildMessageBody(Exception e, string additionalMessage)
+ private CrashReport BuildCrashReport(Exception e, string contextMessage)
{
- var os = Environment.OSVersion;
-
- var body =
- $"{e.GetType().Name}: {e.Message}{NL}" +
- $"Timestamp: {DateTime.UtcNow:O}{NL}" +
- $"Context: {additionalMessage}{NL}" +
- $"Source: {e.Source}{NL}" +
- $"TargetSite: {e.TargetSite}{NL}" +
- NL +
- $"ValheimServerGUI version: {AssemblyHelper.GetApplicationVersion()}{NL}" +
- $"OS Version: {os.VersionString}{NL}" +
- $".NET Version: {Environment.Version}" +
- NL +
- $"Stack trace:{NL}{e.StackTrace}";
-
- return body;
+ var crashReport = AssemblyHelper.BuildCrashReport();
+
+ var additionalInfo = new Dictionary
+ {
+ { "ExceptionType", e.GetType().Name },
+ { "Message", e.Message },
+ { "Context", contextMessage },
+ { "Source", e.Source },
+ { "TargetSite", e.TargetSite?.ToString() },
+ { "StackTrace", e.StackTrace },
+ };
+
+ crashReport.Source = "CrashReport";
+ crashReport.AdditionalInfo = additionalInfo;
+ crashReport.Logs = this.Logger.LogBuffer.Reverse().Take(100).ToList();
+
+ return crashReport;
}
}
}
diff --git a/ValheimServerGUI/Tools/GitHubClient.cs b/ValheimServerGUI/Tools/GitHubClient.cs
index 64d1bb9..6721ca3 100644
--- a/ValheimServerGUI/Tools/GitHubClient.cs
+++ b/ValheimServerGUI/Tools/GitHubClient.cs
@@ -24,6 +24,11 @@ public async Task GetLatestReleaseAsync()
.WithHeader("User-Agent", "ValheimServerGUI")
.SendAsync();
+ if (releases == null)
+ {
+ throw new Exception("Unable to reach GitHub.");
+ }
+
var latestRelease = releases
.Where(r => r.Assets != null && r.Assets.Any())
.Where(r => !r.Prerelease && !r.Draft)
diff --git a/ValheimServerGUI/Tools/IpAddressProvider.cs b/ValheimServerGUI/Tools/IpAddressProvider.cs
index f8d0727..0eaf601 100644
--- a/ValheimServerGUI/Tools/IpAddressProvider.cs
+++ b/ValheimServerGUI/Tools/IpAddressProvider.cs
@@ -73,7 +73,6 @@ public Task GetInternalIpAddressAsync()
if (result != null)
{
- this.Logger.LogTrace("Found {0} internal IP address(es): {1}", results.Count(), string.Join(", ", results));
this.InternalIpReceived?.Invoke(this, result);
}
diff --git a/ValheimServerGUI/Tools/RuneberryApiClient.cs b/ValheimServerGUI/Tools/RuneberryApiClient.cs
new file mode 100644
index 0000000..4b71914
--- /dev/null
+++ b/ValheimServerGUI/Tools/RuneberryApiClient.cs
@@ -0,0 +1,58 @@
+using Newtonsoft.Json;
+using System;
+using System.Threading.Tasks;
+using ValheimServerGUI.Properties;
+using ValheimServerGUI.Tools.Http;
+
+namespace ValheimServerGUI.Tools
+{
+ public interface IRuneberryApiClient
+ {
+ Task SendCrashReportAsync(CrashReport report);
+ }
+
+ public class RuneberryApiClient : RestClient, IRuneberryApiClient
+ {
+ public RuneberryApiClient(IRestClientContext context) : base(context)
+ {
+ }
+
+ public async Task SendCrashReportAsync(CrashReport report)
+ {
+ var response = await this.Post($"{Resources.UrlRuneberryApi}/crash-report", report)
+ .WithHeader(Secrets.RuneberryApiKeyHeader, Secrets.RuneberryClientApiKey)
+ .SendAsync();
+
+ if (response == null || !response.IsSuccessStatusCode)
+ {
+ string message;
+
+ try
+ {
+ if (response != null)
+ {
+ var rawResponse = await response.Content.ReadAsStringAsync();
+ var exceptionResponse = JsonConvert.DeserializeObject(rawResponse);
+ message = $"({(int)response.StatusCode}) {exceptionResponse.Message}";
+ }
+ else
+ {
+ message = "Unable to reach Runeberry API";
+ }
+ }
+ catch
+ {
+ message = "Unknown error";
+ }
+
+ throw new Exception(message);
+ }
+ }
+
+ private class ExceptionResponse
+ {
+ [JsonProperty("message")]
+ public string Message { get; set; }
+ }
+ }
+}
diff --git a/ValheimServerGUI/Tools/SoftwareUpdateProvider.cs b/ValheimServerGUI/Tools/SoftwareUpdateProvider.cs
new file mode 100644
index 0000000..05c93b9
--- /dev/null
+++ b/ValheimServerGUI/Tools/SoftwareUpdateProvider.cs
@@ -0,0 +1,106 @@
+using System;
+using System.Threading.Tasks;
+using ValheimServerGUI.Game;
+using ValheimServerGUI.Properties;
+
+namespace ValheimServerGUI.Tools
+{
+ public interface ISoftwareUpdateProvider
+ {
+ event EventHandler UpdateCheckStarted;
+
+ event EventHandler UpdateCheckFinished;
+
+ Task CheckForUpdatesAsync(bool isManualCheck);
+ }
+
+ public class SoftwareUpdateEventArgs
+ {
+ public string LatestVersion { get; }
+
+ public bool IsNewerVersionAvailable { get; }
+
+ public bool IsManualCheck { get; }
+
+ public bool IsSuccessful { get; }
+
+ public Exception Exception { get; }
+
+ public SoftwareUpdateEventArgs(
+ string latestVersion,
+ bool isNewerVersionAvailable,
+ bool isManualCheck)
+ {
+ this.LatestVersion = latestVersion;
+ this.IsNewerVersionAvailable = isNewerVersionAvailable;
+ this.IsManualCheck = isManualCheck;
+ this.IsSuccessful = true;
+ }
+
+ public SoftwareUpdateEventArgs(
+ Exception e,
+ bool isManualCheck)
+ {
+ this.Exception = e;
+ this.IsNewerVersionAvailable = false;
+ this.IsManualCheck = isManualCheck;
+ this.IsSuccessful = false;
+ }
+ }
+
+ public class SoftwareUpdateProvider : ISoftwareUpdateProvider
+ {
+ private readonly IGitHubClient GitHubClient;
+ private readonly IUserPreferencesProvider UserPrefsProvider;
+
+ private readonly TimeSpan UpdateCheckInterval = TimeSpan.Parse(Resources.UpdateCheckInterval);
+ private DateTime NextAutomaticUpdateCheck = DateTime.MinValue;
+
+ public SoftwareUpdateProvider(IGitHubClient gitHubClient, IUserPreferencesProvider userPrefsProvider)
+ {
+ this.GitHubClient = gitHubClient;
+ this.UserPrefsProvider = userPrefsProvider;
+ }
+
+ public event EventHandler UpdateCheckStarted;
+
+ public event EventHandler UpdateCheckFinished;
+
+ public async Task CheckForUpdatesAsync(bool isManualCheck)
+ {
+ if (!isManualCheck)
+ {
+ // Only fulfill automated checks if enough time has passed since the last check
+ var now = DateTime.UtcNow;
+ if (now < this.NextAutomaticUpdateCheck) return;
+ this.NextAutomaticUpdateCheck = now + this.UpdateCheckInterval;
+
+ // Only fulfill automated checks if the user has update checks enabled
+ var prefs = this.UserPrefsProvider.LoadPreferences();
+ if (!prefs.CheckForUpdates) return;
+ }
+
+ this.UpdateCheckStarted?.Invoke(this, EventArgs.Empty);
+
+ SoftwareUpdateEventArgs eventArgs;
+
+ try
+ {
+ var currentVersion = AssemblyHelper.GetApplicationVersion();
+ var release = await this.GitHubClient.GetLatestReleaseAsync();
+ var newerVersionAvailable = AssemblyHelper.IsNewerVersion(release?.TagName);
+
+ // In case there was no response from GitHub, consider the current running version as the "latest version"
+ var latestVersion = release?.TagName ?? AssemblyHelper.GetApplicationVersion();
+
+ eventArgs = new SoftwareUpdateEventArgs(latestVersion, newerVersionAvailable, isManualCheck);
+ }
+ catch (Exception e)
+ {
+ eventArgs = new SoftwareUpdateEventArgs(e, isManualCheck);
+ }
+
+ this.UpdateCheckFinished?.Invoke(this, eventArgs);
+ }
+ }
+}
diff --git a/ValheimServerGUI/Tools/WinFormsExtensions.cs b/ValheimServerGUI/Tools/WinFormsExtensions.cs
index df522fe..27f943e 100644
--- a/ValheimServerGUI/Tools/WinFormsExtensions.cs
+++ b/ValheimServerGUI/Tools/WinFormsExtensions.cs
@@ -6,6 +6,7 @@
using System.Resources;
using System.Threading.Tasks;
using System.Windows.Forms;
+using ValheimServerGUI.Properties;
namespace ValheimServerGUI.Tools
{
@@ -81,6 +82,17 @@ await Task.Run(() =>
};
}
+ ///
+ /// (jb, 5/9/21) For some reason, you cannot set a Form's icon from a Resource in the Designer, so I've been setting it
+ /// using the file browser. However, I think this might be causing an issue when publishing the application as a trimmed
+ /// single-file executable - some users are encountering errors when trying to load *some image* on startup, and I think
+ /// this might be it.
+ ///
+ public static void AddApplicationIcon(this Form form)
+ {
+ form.Icon = Resources.ApplicationIcon;
+ }
+
#endregion
#region TextBox extensions
@@ -104,7 +116,7 @@ public static void AppendLine(this TextBox textBox, string line)
public static void AddImagesFromResourceFile(this ImageList list, Type resourcesType)
{
var resourceImages = new ResourceManager(resourcesType)
- .GetResourceSet(CultureInfo.CurrentUICulture, true, true)
+ .GetResourceSet(CultureInfo.InvariantCulture, true, true)
.Cast()
.Where(de => de.Key != null && de.Value != null && typeof(Image).IsAssignableFrom(de.Value.GetType()))
.ToDictionary(de => de.Key.ToString(), de => de.Value as Image);
diff --git a/ValheimServerGUI/ValheimServerGUI.csproj b/ValheimServerGUI/ValheimServerGUI.csproj
index 1006267..d9fb835 100644
--- a/ValheimServerGUI/ValheimServerGUI.csproj
+++ b/ValheimServerGUI/ValheimServerGUI.csproj
@@ -5,27 +5,36 @@
net5.0-windows
true
Resources\ApplicationIcon.ico
- true
- ValheimServerGUI.snk
Runeberry Software, LLC
ValheimServerGUI
A simple user interface for running Valheim Dedicated Server on Windows.
2021
GNU GPLv3
- 1.2.5
+ 1.3.0
+
+
+ true
+ ..\SolutionResources\ValheimServerGUI.snk
+
-
+
+
+
+
+
+
+
True
@@ -41,10 +50,4 @@
-
-
- PreserveNewest
-
-
-
\ No newline at end of file