diff --git a/.gitignore b/.gitignore index 5a6192030..7cf87e782 100644 --- a/.gitignore +++ b/.gitignore @@ -237,3 +237,4 @@ launchSettings.json **/Master/env_master /GameLogServer/log_env **/*.css +/Master/master/persistence diff --git a/Application/API/Master/ApiInstance.cs b/Application/API/Master/ApiInstance.cs index 846adb744..f2f0332ca 100644 --- a/Application/API/Master/ApiInstance.cs +++ b/Application/API/Master/ApiInstance.cs @@ -1,19 +1,36 @@ -using System; -using System.Collections.Generic; -using System.Text; +using System.Collections.Generic; using Newtonsoft.Json; -using RestEase; +using SharedLibraryCore.Helpers; namespace IW4MAdmin.Application.API.Master { + /// + /// Defines the structure of the IW4MAdmin instance for the master API + /// public class ApiInstance { + /// + /// Unique ID of the instance + /// [JsonProperty("id")] public string Id { get; set; } + + /// + /// Indicates how long the instance has been running + /// [JsonProperty("uptime")] public int Uptime { get; set; } + + /// + /// Specifices the version of the instance + /// [JsonProperty("version")] - public float Version { get; set; } + [JsonConverter(typeof(BuildNumberJsonConverter))] + public BuildNumber Version { get; set; } + + /// + /// List of servers the instance is monitoring + /// [JsonProperty("servers")] public List Servers { get; set; } } diff --git a/Application/API/Master/Heartbeat.cs b/Application/API/Master/Heartbeat.cs index 16e34a076..cab6a81cc 100644 --- a/Application/API/Master/Heartbeat.cs +++ b/Application/API/Master/Heartbeat.cs @@ -1,22 +1,21 @@ using System; -using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading; using System.Threading.Tasks; using RestEase; -using SharedLibraryCore; namespace IW4MAdmin.Application.API.Master { - public class HeartbeatState - { - public bool Connected { get; set; } - public CancellationToken Token { get; set; } - } - + /// + /// Defines the heartbeat functionality for IW4MAdmin + /// public class Heartbeat { + /// + /// Sends heartbeat to master server + /// + /// + /// + /// public static async Task Send(ApplicationManager mgr, bool firstHeartbeat = false) { var api = Endpoint.Get(); @@ -35,7 +34,7 @@ namespace IW4MAdmin.Application.API.Master { Id = mgr.GetApplicationSettings().Configuration().Id, Uptime = (int)(DateTime.UtcNow - mgr.StartTime).TotalSeconds, - Version = (float)Program.Version, + Version = Program.Version, Servers = mgr.Servers.Select(s => new ApiServer() { @@ -52,14 +51,21 @@ namespace IW4MAdmin.Application.API.Master }).ToList() }; + Response response = null; + if (firstHeartbeat) { - var message = await api.AddInstance(instance); + response = await api.AddInstance(instance); } else { - var message = await api.UpdateInstance(instance.Id, instance); + response = await api.UpdateInstance(instance.Id, instance); + } + + if (response.ResponseMessage.StatusCode != System.Net.HttpStatusCode.OK) + { + mgr.Logger.WriteWarning($"Response code from master is {response.ResponseMessage.StatusCode}, message is {response.StringContent}"); } } } diff --git a/Application/API/Master/IMasterApi.cs b/Application/API/Master/IMasterApi.cs index ea09511d7..d31c22e82 100644 --- a/Application/API/Master/IMasterApi.cs +++ b/Application/API/Master/IMasterApi.cs @@ -4,6 +4,7 @@ using System.Text; using System.Threading.Tasks; using Newtonsoft.Json; using RestEase; +using SharedLibraryCore.Helpers; namespace IW4MAdmin.Application.API.Master { @@ -22,9 +23,12 @@ namespace IW4MAdmin.Application.API.Master public class VersionInfo { [JsonProperty("current-version-stable")] - public float CurrentVersionStable { get; set; } + [JsonConverter(typeof(BuildNumberJsonConverter))] + public BuildNumber CurrentVersionStable { get; set; } + [JsonProperty("current-version-prerelease")] - public float CurrentVersionPrerelease { get; set; } + [JsonConverter(typeof(BuildNumberJsonConverter))] + public BuildNumber CurrentVersionPrerelease { get; set; } } public class ResultMessage @@ -38,11 +42,14 @@ namespace IW4MAdmin.Application.API.Master #if !DEBUG private static readonly IMasterApi api = RestClient.For("http://api.raidmax.org:5000"); #else - private static IMasterApi api = RestClient.For("http://127.0.0.1"); + private static readonly IMasterApi api = RestClient.For("http://127.0.0.1"); #endif public static IMasterApi Get() => api; } + /// + /// Defines the capabilities of the master API + /// [Header("User-Agent", "IW4MAdmin-RestEase")] public interface IMasterApi { @@ -53,13 +60,15 @@ namespace IW4MAdmin.Application.API.Master Task Authenticate([Body] AuthenticationId Id); [Post("instance/")] - Task AddInstance([Body] ApiInstance instance); + [AllowAnyStatusCode] + Task> AddInstance([Body] ApiInstance instance); [Put("instance/{id}")] - Task UpdateInstance([Path] string id, [Body] ApiInstance instance); + [AllowAnyStatusCode] + Task> UpdateInstance([Path] string id, [Body] ApiInstance instance); - [Get("version")] - Task GetVersion(); + [Get("version/{apiVersion}")] + Task GetVersion([Path] int apiVersion); [Get("localization")] Task> GetLocalization(); diff --git a/Application/BuildScripts/PostBuild.bat b/Application/BuildScripts/PostBuild.bat index db0ed34ae..6433f9de2 100644 --- a/Application/BuildScripts/PostBuild.bat +++ b/Application/BuildScripts/PostBuild.bat @@ -24,11 +24,4 @@ xcopy /Y "%SolutionDir%BUILD\Plugins" "%SolutionDir%Publish\WindowsPrerelease\Pl echo Copying script plugins for publish xcopy /Y "%SolutionDir%Plugins\ScriptPlugins" "%SolutionDir%Publish\Windows\Plugins\" -xcopy /Y "%SolutionDir%Plugins\ScriptPlugins" "%SolutionDir%Publish\WindowsPrerelease\Plugins\" - -echo Copying GSC files for publish -xcopy /Y "%SolutionDir%_customcallbacks.gsc" "%SolutionDir%Publish\Windows\userraw\scripts\" -xcopy /Y "%SolutionDir%_customcallbacks.gsc" "%SolutionDir%Publish\WindowsPrerelease\userraw\scripts\" - -xcopy /Y "%SolutionDir%_commands.gsc" "%SolutionDir%Publish\Windows\userraw\scripts\" -xcopy /Y "%SolutionDir%_commands.gsc" "%SolutionDir%Publish\WindowsPrerelease\userraw\scripts\" \ No newline at end of file +xcopy /Y "%SolutionDir%Plugins\ScriptPlugins" "%SolutionDir%Publish\WindowsPrerelease\Plugins\" \ No newline at end of file diff --git a/Application/BuildScripts/PostPublish.bat b/Application/BuildScripts/PostPublish.bat index aa0064bd7..b3e398306 100644 --- a/Application/BuildScripts/PostPublish.bat +++ b/Application/BuildScripts/PostPublish.bat @@ -17,7 +17,7 @@ echo deleting misc files if exist "%PublishDir%\web.config" del "%PublishDir%\web.config" if exist "%PublishDir%\libman.json" del "%PublishDir%\libman.json" del "%PublishDir%\*.exe" -REM del "%PublishDir%\*.pdb" +del "%PublishDir%\*.pdb" echo setting up default folders if not exist "%PublishDir%\Configuration" md "%PublishDir%\Configuration" diff --git a/Application/Main.cs b/Application/Main.cs index f0381394f..d94358b8c 100644 --- a/Application/Main.cs +++ b/Application/Main.cs @@ -1,6 +1,7 @@ using IW4MAdmin.Application.Migration; using Microsoft.Extensions.DependencyInjection; using SharedLibraryCore; +using SharedLibraryCore.Helpers; using SharedLibraryCore.Interfaces; using System; using System.Text; @@ -11,9 +12,10 @@ namespace IW4MAdmin.Application { public class Program { - public static double Version { get; private set; } = Utilities.GetVersionAsDouble(); + public static BuildNumber Version { get; private set; } = BuildNumber.Parse(Utilities.GetVersionAsString()); public static ApplicationManager ServerManager; private static Task ApplicationTask; + private static readonly BuildNumber _fallbackVersion = BuildNumber.Parse("99.99.99.99"); /// /// entrypoint of the application @@ -145,12 +147,12 @@ namespace IW4MAdmin.Application var version = new API.Master.VersionInfo() { - CurrentVersionStable = 99.99f + CurrentVersionStable = _fallbackVersion }; try { - version = await api.GetVersion(); + version = await api.GetVersion(1); } catch (Exception e) @@ -164,7 +166,7 @@ namespace IW4MAdmin.Application ServerManager.Logger.WriteDebug(e.Message); } - if (version.CurrentVersionStable == 99.99f) + if (version.CurrentVersionStable == _fallbackVersion) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine(loc["MANAGER_VERSION_FAIL"]); @@ -175,16 +177,16 @@ namespace IW4MAdmin.Application else if (version.CurrentVersionStable > Version) { Console.ForegroundColor = ConsoleColor.DarkYellow; - Console.WriteLine($"IW4MAdmin {loc["MANAGER_VERSION_UPDATE"]} [v{version.CurrentVersionStable.ToString("0.0")}]"); - Console.WriteLine(loc["MANAGER_VERSION_CURRENT"].FormatExt($"[v{Version.ToString("0.0")}]")); + Console.WriteLine($"IW4MAdmin {loc["MANAGER_VERSION_UPDATE"]} [v{version.CurrentVersionStable.ToString()}]"); + Console.WriteLine(loc["MANAGER_VERSION_CURRENT"].FormatExt($"[v{Version.ToString()}]")); Console.ForegroundColor = ConsoleColor.Gray; } #else else if (version.CurrentVersionPrerelease > Version) { Console.ForegroundColor = ConsoleColor.DarkYellow; - Console.WriteLine($"IW4MAdmin-Prerelease {loc["MANAGER_VERSION_UPDATE"]} [v{version.CurrentVersionPrerelease.ToString("0.0")}-pr]"); - Console.WriteLine(loc["MANAGER_VERSION_CURRENT"].FormatExt($"[v{Version.ToString("0.0")}-pr]")); + Console.WriteLine($"IW4MAdmin-Prerelease {loc["MANAGER_VERSION_UPDATE"]} [v{version.CurrentVersionPrerelease.ToString()}-pr]"); + Console.WriteLine(loc["MANAGER_VERSION_CURRENT"].FormatExt($"[v{Version.ToString()}-pr]")); Console.ForegroundColor = ConsoleColor.Gray; } #endif diff --git a/_commands.gsc b/GameFiles/IW4x/userraw/_commands.gsc similarity index 100% rename from _commands.gsc rename to GameFiles/IW4x/userraw/_commands.gsc diff --git a/_customcallbacks.gsc b/GameFiles/IW4x/userraw/_customcallbacks.gsc similarity index 99% rename from _customcallbacks.gsc rename to GameFiles/IW4x/userraw/_customcallbacks.gsc index 51301033d..efe725204 100644 --- a/_customcallbacks.gsc +++ b/GameFiles/IW4x/userraw/_customcallbacks.gsc @@ -8,7 +8,7 @@ init() SetDvarIfUninitialized( "sv_framewaittime", 0.05 ); SetDvarIfUninitialized( "sv_additionalwaittime", 0.1 ); SetDvarIfUninitialized( "sv_maxstoredframes", 12 ); - SetDvarIfUninitialized( "sv_printradarupdates", false ); + SetDvarIfUninitialized( "sv_printradarupdates", 0 ); SetDvarIfUninitialized( "sv_printradar_updateinterval", 500 ); SetDvarIfUninitialized( "sv_iw4madmin_url", "http://127.0.0.1:1624" ); diff --git a/IW4MAdmin.sln b/IW4MAdmin.sln index 0447d709f..352215090 100644 --- a/IW4MAdmin.sln +++ b/IW4MAdmin.sln @@ -6,8 +6,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Plugins", "Plugins", "{26E8 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8C8F3945-0AEF-4949-A1F7-B18E952E50BC}" ProjectSection(SolutionItems) = preProject - _commands.gsc = _commands.gsc - _customcallbacks.gsc = _customcallbacks.gsc + GameFiles\IW4x\userraw\_commands.gsc = GameFiles\IW4x\userraw\_commands.gsc + GameFiles\IW4x\userraw\_customcallbacks.gsc = GameFiles\IW4x\userraw\_customcallbacks.gsc azure-pipelines.yml = azure-pipelines.yml PostPublish.ps1 = PostPublish.ps1 README.md = README.md diff --git a/SharedLibraryCore/Helpers/BuildNumber.cs b/SharedLibraryCore/Helpers/BuildNumber.cs new file mode 100644 index 000000000..c828262c9 --- /dev/null +++ b/SharedLibraryCore/Helpers/BuildNumber.cs @@ -0,0 +1,141 @@ +using System; +using System.Linq; + +/*https://stackoverflow.com/questions/26581572/how-to-convert-string-into-version-in-net-3-5*/ +namespace SharedLibraryCore.Helpers +{ + public class BuildNumber : IComparable + { + public int Major { get; private set; } + public int Minor { get; private set; } + public int Build { get; private set; } + public int Revision { get; private set; } + + private BuildNumber() { } + + public static bool TryParse(string input, out BuildNumber buildNumber) + { + try + { + buildNumber = Parse(input); + return true; + } + catch + { + buildNumber = null; + return false; + } + } + + /// + /// Parses a build number string into a BuildNumber class + /// + /// The build number string to parse + /// A new BuildNumber class set from the buildNumber string + /// Thrown if there are less than 2 or + /// more than 4 version parts to the build number + /// Thrown if string cannot be parsed + /// to a series of integers + /// Thrown if any version + /// integer is less than zero + public static BuildNumber Parse(string buildNumber) + { + if (buildNumber == null) throw new ArgumentNullException("buildNumber"); + + var versions = buildNumber + .Split(new[] { '.' }, + StringSplitOptions.RemoveEmptyEntries) + .Select(v => v.Trim()) + .ToList(); + + if (versions.Count < 2) + { + throw new ArgumentException("BuildNumber string was too short"); + } + + if (versions.Count > 4) + { + throw new ArgumentException("BuildNumber string was too long"); + } + + return new BuildNumber + { + Major = ParseVersion(versions[0]), + Minor = ParseVersion(versions[1]), + Build = versions.Count > 2 ? ParseVersion(versions[2]) : -1, + Revision = versions.Count > 3 ? ParseVersion(versions[3]) : -1 + }; + } + + private static int ParseVersion(string input) + { + int version; + + if (!int.TryParse(input, out version)) + { + throw new FormatException( + "buildNumber string was not in a correct format"); + } + + if (version < 0) + { + throw new ArgumentOutOfRangeException( + "buildNumber", + "Versions must be greater than or equal to zero"); + } + + return version; + } + + public override string ToString() + { + return string.Format("{0}.{1}{2}{3}", Major, Minor, + Build < 0 ? "" : "." + Build, + Revision < 0 ? "" : "." + Revision); + } + + public int CompareTo(object obj) + { + if (obj == null) return 1; + var buildNumber = obj as BuildNumber; + if (buildNumber == null) return 1; + if (ReferenceEquals(this, buildNumber)) return 0; + + return (Major == buildNumber.Major) + ? (Minor == buildNumber.Minor) + ? (Build == buildNumber.Build) + ? Revision.CompareTo(buildNumber.Revision) + : Build.CompareTo(buildNumber.Build) + : Minor.CompareTo(buildNumber.Minor) + : Major.CompareTo(buildNumber.Major); + } + + public static bool operator >(BuildNumber first, BuildNumber second) + { + return (first.CompareTo(second) > 0); + } + + public static bool operator <(BuildNumber first, BuildNumber second) + { + return (first.CompareTo(second) < 0); + } + + public override bool Equals(object obj) + { + return (CompareTo(obj) == 0); + } + + public override int GetHashCode() + { + unchecked + { + var hash = 17; + hash = hash * 23 + Major.GetHashCode(); + hash = hash * 23 + Minor.GetHashCode(); + hash = hash * 23 + Build.GetHashCode(); + hash = hash * 23 + Revision.GetHashCode(); + return hash; + } + } + } +} diff --git a/SharedLibraryCore/Helpers/BuildNumberJsonConverter.cs b/SharedLibraryCore/Helpers/BuildNumberJsonConverter.cs new file mode 100644 index 000000000..b5e639e4e --- /dev/null +++ b/SharedLibraryCore/Helpers/BuildNumberJsonConverter.cs @@ -0,0 +1,26 @@ +using Newtonsoft.Json; +using System; + +namespace SharedLibraryCore.Helpers +{ + /// + /// JSON converter for the build number + /// + public class BuildNumberJsonConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + { + return objectType == typeof(string); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + return BuildNumber.Parse(reader.Value.ToString()); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + writer.WriteValue(value.ToString()); + } + } +} diff --git a/WebfrontCore/WebfrontCore.csproj b/WebfrontCore/WebfrontCore.csproj index 29e977675..d3d8ddbe7 100644 --- a/WebfrontCore/WebfrontCore.csproj +++ b/WebfrontCore/WebfrontCore.csproj @@ -66,10 +66,13 @@ - + + + + diff --git a/azure-pipelines.yml b/azure-pipelines.yml index dbad2be95..6cce89f84 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -95,6 +95,12 @@ steps: Contents: '*.dll' TargetFolder: '$(outputFolder)\Plugins' +- task: CopyFiles@2 + inputs: + SourceFolder: '$(Build.Repository.LocalPath)\GameFiles' + Contents: '*.gsc' + TargetFolder: '$(outputFolder)\GameFiles' + - task: CmdLine@2 inputs: script: 'xcopy /s /y /f wwwroot $(outputFolder)\wwwroot' @@ -106,7 +112,7 @@ steps: rootFolderOrFile: '$(outputFolder)' includeRootFolder: false archiveType: 'zip' - archiveFile: '$(Build.ArtifactStagingDirectory)/IW4MAdmin-$(VersionInformation.Major).$(VersionInformation.Minor)-$(buildConfiguration)$(VersionInformation.Build)r$(VersionInformation.Revision)-$(Build.BuildId).zip' + archiveFile: '$(Build.ArtifactStagingDirectory)/IW4MAdmin-$(VersionInformation.Major).$(VersionInformation.Minor)-$(buildConfiguration)$(VersionInformation.Build)b$(Build.BuildId).zip' replaceExistingArchive: true - task: FtpUpload@2 @@ -130,8 +136,8 @@ steps: action: 'create' target: '$(Build.SourceVersion)' tagSource: 'userSpecifiedTag' - tag: '$(VersionInformation.Major).$(VersionInformation.Minor)-$(buildConfiguration)$(VersionInformation.Build)r$(VersionInformation.Revision)-$(Build.BuildId)' - title: 'Version $(VersionInformation.Major).$(VersionInformation.Minor) $(buildConfiguration) $(VersionInformation.Build) Revision $(VersionInformation.Revision) Build $(Build.BuildId)' + tag: '$(VersionInformation.Major).$(VersionInformation.Minor)-$(buildConfiguration)$(VersionInformation.Build)b$(Build.BuildId)' + title: 'Version $(VersionInformation.Major).$(VersionInformation.Minor) $(buildConfiguration) Feature $(VersionInformation.Build) Build $(Build.BuildId)' assets: '$(Build.ArtifactStagingDirectory)/*.zip' isPreRelease: true releaseNotesSource: 'inline'