diff --git a/.github/workflows/dotnet-core.yml b/.github/workflows/dotnet-core.yml index 181852b..720dae1 100644 --- a/.github/workflows/dotnet-core.yml +++ b/.github/workflows/dotnet-core.yml @@ -8,10 +8,10 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-20.04, windows-latest] - framework: ['8.0'] + os: [ubuntu-22.04, windows-latest] + framework: ['9.0'] include: - - os: ubuntu-20.04 + - os: ubuntu-22.04 target: linux-x64 - os: windows-latest target: win-x64 @@ -20,7 +20,7 @@ jobs: steps: - uses: actions/checkout@v4 - - if: matrix.os == 'ubuntu-20.04' + - if: matrix.os == 'ubuntu-22.04' name: Install Linux packages run: | sudo apt-get update diff --git a/Commands/ExitCommand.cs b/Commands/ExitCommand.cs index b16989f..bd95758 100644 --- a/Commands/ExitCommand.cs +++ b/Commands/ExitCommand.cs @@ -1,11 +1,9 @@ +using System.Threading.Tasks; using LocalAdmin.V2.Commands.Meta; -using LocalAdmin.V2.Core; namespace LocalAdmin.V2.Commands; -internal sealed class ExitCommand : CommandBase +internal sealed class ExitCommand() : CommandBase("Exit", "Stops the server.", true) { - public ExitCommand() : base("Exit", "Stops the server.", true) { } - - internal override void Execute(string[] arguments) { } + internal override ValueTask Execute(string[] arguments) => ValueTask.CompletedTask; } \ No newline at end of file diff --git a/Commands/ForceRestartCommand.cs b/Commands/ForceRestartCommand.cs index c209f7f..fbe92fc 100644 --- a/Commands/ForceRestartCommand.cs +++ b/Commands/ForceRestartCommand.cs @@ -1,14 +1,14 @@ +using System.Threading.Tasks; using LocalAdmin.V2.Commands.Meta; namespace LocalAdmin.V2.Commands; -internal sealed class ForceRestartCommand : CommandBase +internal sealed class ForceRestartCommand() : CommandBase("Forcerestart", "Kills and restarts the server.") { - public ForceRestartCommand() : base("Forcerestart", "Kills and restarts the server.") { } - - internal override void Execute(string[] arguments) + internal override ValueTask Execute(string[] arguments) { - Core.LocalAdmin.Singleton!.ExitAction = Core.LocalAdmin.ShutdownAction.Restart; + Core.LocalAdmin.Singleton.ExitAction = Core.LocalAdmin.ShutdownAction.Restart; Core.LocalAdmin.Singleton.Exit(0, restart: true); + return ValueTask.CompletedTask; } } \ No newline at end of file diff --git a/Commands/HeartbeatCancelCommand.cs b/Commands/HeartbeatCancelCommand.cs index cbe425d..25d47bd 100644 --- a/Commands/HeartbeatCancelCommand.cs +++ b/Commands/HeartbeatCancelCommand.cs @@ -1,30 +1,30 @@ using System; +using System.Threading.Tasks; using LocalAdmin.V2.Commands.Meta; using LocalAdmin.V2.IO; namespace LocalAdmin.V2.Commands; -internal sealed class HeartbeatCancelCommand : CommandBase +internal sealed class HeartbeatCancelCommand() : CommandBase("hbc", "Cancels heartbeat restart countdown.") { - public HeartbeatCancelCommand() : base("hbc", "Cancels heartbeat restart countdown.") { } - - internal override void Execute(string[] arguments) + internal override ValueTask Execute(string[] arguments) { - if (Core.LocalAdmin.Singleton!.CurrentHeartbeatStatus != Core.LocalAdmin.HeartbeatStatus.Active) + if (Core.LocalAdmin.Singleton.CurrentHeartbeatStatus != Core.LocalAdmin.HeartbeatStatus.Active) { ConsoleUtil.WriteLine("Heartbeat is not active!", ConsoleColor.Yellow); - return; + return ValueTask.CompletedTask; } if (Core.LocalAdmin.Singleton.HeartbeatWarningStage == 0) { ConsoleUtil.WriteLine("Heartbeat restart countdown has not started!", ConsoleColor.Yellow); - return; + return ValueTask.CompletedTask; } Core.LocalAdmin.Singleton.CurrentHeartbeatStatus = Core.LocalAdmin.HeartbeatStatus.AwaitingFirstHeartbeat; ConsoleUtil.WriteLine("Heartbeat restart countdown has been cancelled.", ConsoleColor.DarkGreen); ConsoleUtil.WriteLine("Crash detection will be resumed after receiving any heartbeat. If you want to disable heartbeat completely for this LocalAdmin session (until server is restarted) run \"hbctrl 0\" command.", ConsoleColor.DarkGreen); + return ValueTask.CompletedTask; } } \ No newline at end of file diff --git a/Commands/HeartbeatControlCommand.cs b/Commands/HeartbeatControlCommand.cs index 95e5b82..2099b80 100644 --- a/Commands/HeartbeatControlCommand.cs +++ b/Commands/HeartbeatControlCommand.cs @@ -1,25 +1,24 @@ using System; +using System.Threading.Tasks; using LocalAdmin.V2.Commands.Meta; using LocalAdmin.V2.IO; namespace LocalAdmin.V2.Commands; -internal sealed class HeartbeatControlCommand : CommandBase +internal sealed class HeartbeatControlCommand() : CommandBase("hbctrl", "Controls Heartbeat") { - public HeartbeatControlCommand() : base("hbctrl", "Controls Heartbeat") { } - - internal override void Execute(string[] arguments) + internal override ValueTask Execute(string[] arguments) { - if (!Core.LocalAdmin.Singleton!.EnableGameHeartbeat) + if (!Core.LocalAdmin.Singleton.EnableGameHeartbeat) { ConsoleUtil.WriteLine("Heartbeat is not enabled in the LA config!", ConsoleColor.Yellow); - return; + return ValueTask.CompletedTask; } if (arguments.Length != 1) { ConsoleUtil.WriteLine("Usage: hbctrl ", ConsoleColor.Yellow); - return; + return ValueTask.CompletedTask; } switch (arguments[0].ToLowerInvariant()) @@ -68,26 +67,20 @@ internal override void Execute(string[] arguments) ConsoleUtil.WriteLine("Unknown subcommand. Run \"hbctrl\" for get command usage.", ConsoleColor.Red); break; } + return ValueTask.CompletedTask; } private static string HeartbeatStatusString { get { - switch (Core.LocalAdmin.Singleton!.CurrentHeartbeatStatus) + return Core.LocalAdmin.Singleton.CurrentHeartbeatStatus switch { - case Core.LocalAdmin.HeartbeatStatus.Disabled: - return "DISABLED"; - - case Core.LocalAdmin.HeartbeatStatus.AwaitingFirstHeartbeat: - return "ACTIVE - AWAITING FIRST HEARTBEAT"; - - case Core.LocalAdmin.HeartbeatStatus.Active: - return "ACTIVE - MONITORING"; - - default: - return "(unknown)"; - } + Core.LocalAdmin.HeartbeatStatus.Disabled => "DISABLED", + Core.LocalAdmin.HeartbeatStatus.AwaitingFirstHeartbeat => "ACTIVE - AWAITING FIRST HEARTBEAT", + Core.LocalAdmin.HeartbeatStatus.Active => "ACTIVE - MONITORING", + _ => "(unknown)" + }; } } } \ No newline at end of file diff --git a/Commands/HelpCommand.cs b/Commands/HelpCommand.cs index 4346984..10b1131 100644 --- a/Commands/HelpCommand.cs +++ b/Commands/HelpCommand.cs @@ -1,15 +1,14 @@ -using LocalAdmin.V2.Commands.Meta; -using LocalAdmin.V2.IO; using System; using System.Linq; +using System.Threading.Tasks; +using LocalAdmin.V2.Commands.Meta; +using LocalAdmin.V2.IO; namespace LocalAdmin.V2.Commands; -internal sealed class HelpCommand : CommandBase +internal sealed class HelpCommand() : CommandBase("Help", "Prints all available commands.", true) { - public HelpCommand() : base("Help", "Prints all available commands.", true) { } - - internal override void Execute(string[] arguments) + internal override ValueTask Execute(string[] arguments) { var commands = Core.LocalAdmin.Singleton?.CommandService.GetAllCommands().OrderBy(p => p.Name); @@ -18,9 +17,10 @@ internal override void Execute(string[] arguments) if (commands is not null) foreach (var item in commands) - ConsoleUtil.WriteLine($"{item.Name} - {item.Description}"); - - ConsoleUtil.WriteLine("------------" + Environment.NewLine, ConsoleColor.DarkGray); + ConsoleUtil.WriteLine($"{item.Name.ToUpperInvariant()} - {item.Description}"); + + ConsoleUtil.WriteLine($"------------{Environment.NewLine}", ConsoleColor.DarkGray); ConsoleUtil.WriteLine("---- Game Commands ----", ConsoleColor.DarkGray); + return ValueTask.CompletedTask; } } \ No newline at end of file diff --git a/Commands/LaCfgCommand.cs b/Commands/LaCfgCommand.cs index 3fa775a..1eed017 100644 --- a/Commands/LaCfgCommand.cs +++ b/Commands/LaCfgCommand.cs @@ -1,16 +1,17 @@ using System; +using System.Threading.Tasks; using LocalAdmin.V2.Commands.Meta; using LocalAdmin.V2.IO; namespace LocalAdmin.V2.Commands; -internal sealed class LaCfgCommand : CommandBase +internal sealed class LaCfgCommand() : CommandBase("lacfg", + "Prints the current LocalAdmin configuration and the configuration file path.") { - public LaCfgCommand() : base("lacfg", "Prints the current LocalAdmin configuration and the configuration file path.") { } - - internal override void Execute(string[] arguments) + internal override ValueTask Execute(string[] arguments) { ConsoleUtil.WriteLine($"Current LocalAdmin config file path is: {Core.LocalAdmin.CurrentConfigPath ?? "(null)"}", ConsoleColor.DarkGreen); - ConsoleUtil.WriteLine($"Current LocalAdmin Configuration:{Environment.NewLine}{LocalAdmin.V2.Core.LocalAdmin.Configuration!.ToString()}"); + ConsoleUtil.WriteLine($"Current LocalAdmin Configuration:{Environment.NewLine}{Core.LocalAdmin.Configuration!}"); + return ValueTask.CompletedTask; } } \ No newline at end of file diff --git a/Commands/LicenseCommand.cs b/Commands/LicenseCommand.cs index d2599e7..d9c3ae9 100644 --- a/Commands/LicenseCommand.cs +++ b/Commands/LicenseCommand.cs @@ -1,22 +1,21 @@ +using System; +using System.Threading.Tasks; using LocalAdmin.V2.Commands.Meta; using LocalAdmin.V2.IO; -using System; namespace LocalAdmin.V2.Commands; -internal sealed class LicenseCommand : CommandBase +internal sealed class LicenseCommand() : CommandBase("License", "Prints LocalAdmin license details.") { - public LicenseCommand() : base("License", "Prints LocalAdmin license details.") { } - - internal override void Execute(string[] arguments) + internal override ValueTask Execute(string[] arguments) { ConsoleUtil.WriteLine("MIT License", ConsoleColor.Cyan); - ConsoleUtil.WriteLine("Copyright by Łukasz \"zabszk\" Jurczyk and KernelError, 2019 - 2024", ConsoleColor.Gray); + ConsoleUtil.WriteLine("Copyright by Łukasz \"zabszk\" Jurczyk and KernelError, 2019 - 2025", ConsoleColor.Gray); ConsoleUtil.WriteLine("Permission is hereby granted, free of charge, to any person obtaining a copy\r\nof this software and associated documentation files (the \"Software\"), to deal\r\nin the Software without restriction, including without limitation the rights\r\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\ncopies of the Software, and to permit persons to whom the Software is\r\nfurnished to do so, subject to the following conditions:", ConsoleColor.Gray); ConsoleUtil.WriteLine("\r\nThe above copyright notice and this permission notice shall be included in all\r\ncopies or substantial portions of the Software.", ConsoleColor.Gray); ConsoleUtil.WriteLine("\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r\nSOFTWARE.", ConsoleColor.Gray); ConsoleUtil.WriteLine("", ConsoleColor.Gray); - ConsoleUtil.WriteLine("LocalAdmin includes Utf8Json developed by Yoshifumi Kawai licensed under The MIT License.", ConsoleColor.Gray); + return ValueTask.CompletedTask; } } \ No newline at end of file diff --git a/Commands/Meta/CommandBase.cs b/Commands/Meta/CommandBase.cs index 7a4d7b8..1fa0176 100644 --- a/Commands/Meta/CommandBase.cs +++ b/Commands/Meta/CommandBase.cs @@ -1,24 +1,16 @@ +using System.Threading.Tasks; + namespace LocalAdmin.V2.Commands.Meta; -internal abstract class CommandBase +internal abstract class CommandBase(string name, string description, bool sendToGame = false) { - public readonly string Name; - public readonly string Description; - public readonly bool SendToGame; - - protected CommandBase(string name, bool sendToGame = false) - { - Name = name.ToUpperInvariant(); - Description = "No Description Provided"; - SendToGame = sendToGame; - } + public readonly string Name = name; + public readonly string Description = description; + public readonly bool SendToGame = sendToGame; - protected CommandBase(string name, string description, bool sendToGame = false) + protected CommandBase(string name, bool sendToGame = false) : this(name, "No Description Provided", sendToGame) { - Name = name.ToUpperInvariant(); - Description = description; - SendToGame = sendToGame; } - internal abstract void Execute(string[] arguments); + internal abstract ValueTask Execute(string[] arguments); } \ No newline at end of file diff --git a/Commands/Meta/CommandService.cs b/Commands/Meta/CommandService.cs index a6e12b1..497d799 100644 --- a/Commands/Meta/CommandService.cs +++ b/Commands/Meta/CommandService.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; namespace LocalAdmin.V2.Commands.Meta; @@ -21,11 +20,11 @@ internal void UnregisterCommand(CommandBase command) internal CommandBase? GetCommandByName(string name) { - return _commands.TryGetValue(name, out CommandBase? command) ? command : null; + return _commands.GetValueOrDefault(name); } internal CommandBase[] GetAllCommands() { - return _commands.Values.ToArray(); + return [.. _commands.Values]; } } \ No newline at end of file diff --git a/Commands/PluginManager/PluginManagerCommand.cs b/Commands/PluginManager/PluginManagerCommand.cs index c0a9d91..0e0d7a8 100644 --- a/Commands/PluginManager/PluginManagerCommand.cs +++ b/Commands/PluginManager/PluginManagerCommand.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using System.Linq; +using System.Threading.Tasks; using LocalAdmin.V2.Commands.Meta; using LocalAdmin.V2.Commands.PluginManager.Subcommands; using LocalAdmin.V2.IO; @@ -8,13 +9,11 @@ namespace LocalAdmin.V2.Commands.PluginManager; -internal sealed class PluginManagerCommand : CommandBase +internal sealed class PluginManagerCommand() : CommandBase("p", "Plugin Manager.") { - public PluginManagerCommand() : base("p", "Plugin Manager.") { } - private static Stopwatch? _securityWarningStopwatch; - internal override async void Execute(string[] arguments) + internal override async ValueTask Execute(string[] arguments) { if (!Core.LocalAdmin.DismissPluginsSecurityWarning && !Core.LocalAdmin.DataJson!.PluginManagerWarningDismissed) { @@ -44,8 +43,8 @@ internal override async void Execute(string[] arguments) _securityWarningStopwatch = null; ConsoleUtil.WriteLine("Plugin manager has been enabled. USE AT YOUR OWN RISK.", ConsoleColor.Yellow); - Core.LocalAdmin.DataJson.PluginManagerWarningDismissed = true; - await Core.LocalAdmin.DataJson.TrySave(PathManager.InternalJsonDataPath); + Core.LocalAdmin.DataJson = Core.LocalAdmin.DataJson with { PluginManagerWarningDismissed = true }; + await Core.LocalAdmin.Singleton.SaveJsonOrTerminate(); return; } @@ -78,11 +77,11 @@ internal override async void Execute(string[] arguments) ConsoleUtil.WriteLine("Run \"p token\" command to get more details.", ConsoleColor.Yellow); } - ConsoleUtil.WriteLine("------------" + Environment.NewLine, ConsoleColor.DarkGray); + ConsoleUtil.WriteLine($"------------{Environment.NewLine}", ConsoleColor.DarkGray); return; } - bool optionsSet = arguments.Length >= 2 && arguments[1].Length > 1 && arguments[1].StartsWith("-", StringComparison.Ordinal); + bool optionsSet = arguments.Length >= 2 && arguments[1].Length > 1 && arguments[1].StartsWith('-'); string[]? args; var options = string.Empty; @@ -102,7 +101,7 @@ internal override async void Execute(string[] arguments) case "c": case "ch": case "chk": - CheckCommand.Check(options); + await CheckCommand.Check(options); break; //Install @@ -125,26 +124,26 @@ internal override async void Execute(string[] arguments) case "install": case "i": - InstallCommand.Install(args, options); + await InstallCommand.Install(args, options); break; case "list": case "l": case "ls": - ListCommand.List(options); + await ListCommand.List(options); break; case "maintenance": case "m": case "mn": case "mnt": - MaintenanceCommand.Maintenance(options); + await MaintenanceCommand.Maintenance(options); break; case "refresh": case "ref": case "rf": - _ = OfficialPluginsList.RefreshOfficialPluginsList(); + await OfficialPluginsList.RefreshOfficialPluginsList(); break; case "remove": @@ -152,7 +151,7 @@ internal override async void Execute(string[] arguments) case "rm": case "uninstall": case "un": - _ = PluginInstaller.TryUninstallPlugin(args[0], + await PluginInstaller.TryUninstallPlugin(args[0], options.Contains('g', StringComparison.Ordinal) ? "global" : Core.LocalAdmin.GamePort.ToString(), options.Contains('i', StringComparison.Ordinal), options.Contains('s', StringComparison.Ordinal)); @@ -161,18 +160,18 @@ internal override async void Execute(string[] arguments) case "token": case "t": case "pat": - TokenCommand.Token(args == null || args.Length == 0 ? null : args[0]); + await TokenCommand.Token(args == null || args.Length == 0 ? null : args[0]); break; case "update": case "u": case "up": case "upd": - UpdateCommand.Update(options); + await UpdateCommand.Update(options); break; default: - ConsoleUtil.WriteLine("[PLUGIN MANAGER] Unknown command: p " + arguments[0].ToLowerInvariant(), ConsoleColor.Red); + ConsoleUtil.WriteLine($"[PLUGIN MANAGER] Unknown command: p {arguments[0].ToLowerInvariant()}", ConsoleColor.Red); break; } } diff --git a/Commands/PluginManager/Subcommands/CheckCommand.cs b/Commands/PluginManager/Subcommands/CheckCommand.cs index bc5148e..8986718 100644 --- a/Commands/PluginManager/Subcommands/CheckCommand.cs +++ b/Commands/PluginManager/Subcommands/CheckCommand.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using LocalAdmin.V2.IO; using LocalAdmin.V2.PluginsManager; @@ -6,34 +7,21 @@ namespace LocalAdmin.V2.Commands.PluginManager.Subcommands; internal static class CheckCommand { - internal static async void Check(string options) + internal static async Task Check(string options) { bool iSet = options.Contains('i', StringComparison.Ordinal), gSet = options.Contains('g', StringComparison.Ordinal), lSet = options.Contains('l', StringComparison.Ordinal); - bool local = false, global = false; - - switch (gSet) + if (!lSet && !gSet) { - case false when !lSet: - case true when lSet: - local = global = true; - break; - - case true: - global = true; - break; - - default: - local = true; - break; + lSet = gSet = true; } - if (local) + if (lSet) await PluginUpdater.CheckForUpdates(Core.LocalAdmin.GamePort.ToString(), iSet); - if (global) + if (gSet) await PluginUpdater.CheckForUpdates("global", iSet); ConsoleUtil.WriteLine("[PLUGIN MANAGER] Checking for plugins update complete.", ConsoleColor.DarkGreen); diff --git a/Commands/PluginManager/Subcommands/InstallCommand.cs b/Commands/PluginManager/Subcommands/InstallCommand.cs index 7a7b886..d547a6e 100644 --- a/Commands/PluginManager/Subcommands/InstallCommand.cs +++ b/Commands/PluginManager/Subcommands/InstallCommand.cs @@ -1,14 +1,15 @@ using System; using System.Linq; -using LocalAdmin.V2.Core; +using System.Threading.Tasks; using LocalAdmin.V2.IO; +using LocalAdmin.V2.JSON.Objects; using LocalAdmin.V2.PluginsManager; namespace LocalAdmin.V2.Commands.PluginManager.Subcommands; internal static class InstallCommand { - internal static async void Install(string[] args, string options) + internal static async Task Install(string[] args, string options) { PluginInstaller.QueryResult res; string version; @@ -35,14 +36,13 @@ internal static async void Install(string[] args, string options) if (args.Length == 1 || args.Length == 2 && args[1].Equals("latest", StringComparison.OrdinalIgnoreCase)) { if (!performUpdate) - await Core.LocalAdmin.Singleton!.LoadJsonOrTerminate(); + await Core.LocalAdmin.Singleton.LoadJsonOrTerminate(); res = await PluginInstaller.TryCachePlugin(args[0], true); if (!res.Success) return; - if (!await Core.LocalAdmin.DataJson!.TrySave(PathManager.InternalJsonDataPath)) - return; + await Core.LocalAdmin.Singleton.SaveJsonOrTerminate(); version = "latest"; } diff --git a/Commands/PluginManager/Subcommands/ListCommand.cs b/Commands/PluginManager/Subcommands/ListCommand.cs index 3b71541..25a80f0 100644 --- a/Commands/PluginManager/Subcommands/ListCommand.cs +++ b/Commands/PluginManager/Subcommands/ListCommand.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; using LocalAdmin.V2.IO; using LocalAdmin.V2.PluginsManager; @@ -7,45 +8,32 @@ namespace LocalAdmin.V2.Commands.PluginManager.Subcommands; internal static class ListCommand { - internal static async void List(string options) + internal static async Task List(string options) { bool iSet = options.Contains('i', StringComparison.Ordinal), gSet = options.Contains('g', StringComparison.Ordinal), lSet = options.Contains('l', StringComparison.Ordinal), sSet = options.Contains('s', StringComparison.Ordinal); - bool local = false, global = false; - - switch (gSet) + if (!lSet && !gSet) { - case false when !lSet: - case true when lSet: - local = global = true; - break; - - case true: - global = true; - break; - - default: - local = true; - break; + lSet = gSet = true; } List? localPlugins = null, globalPlugins = null; - if (local) + if (lSet) localPlugins = await PluginStorage.ListPlugins(Core.LocalAdmin.GamePort.ToString(), iSet, sSet); - if (global) + if (gSet) globalPlugins = await PluginStorage.ListPlugins("global", iSet, sSet); ConsoleUtil.WriteLine(null, ConsoleColor.Gray); - if (local) + if (lSet) PrintPlugins(Core.LocalAdmin.GamePort.ToString(), localPlugins); - if (global) + if (gSet) PrintPlugins("global", globalPlugins); } @@ -68,13 +56,15 @@ private static void PrintPlugins(string port, List>> UPDATE AVAILABLE <<<"), color); + ConsoleUtil.WriteLine( + $"Latest version: {plugin.LatestVersion}{(upToDate ? "" : " >>> UPDATE AVAILABLE <<<")}", color); ConsoleUtil.WriteLine($"Target version: {plugin.TargetVersion}", color); ConsoleUtil.WriteLine($"Plugin integrity check: {(plugin.IntegrityCheckPassed ? "PASSED" : "FAILED - PLUGIN MANUALLY MODIFIED")}", color); ConsoleUtil.WriteLine($"Dependencies: {(plugin.Dependencies == null ? "(none)" : string.Join(", ", plugin.Dependencies))}", color); diff --git a/Commands/PluginManager/Subcommands/MaintenanceCommand.cs b/Commands/PluginManager/Subcommands/MaintenanceCommand.cs index 042eb00..60797c1 100644 --- a/Commands/PluginManager/Subcommands/MaintenanceCommand.cs +++ b/Commands/PluginManager/Subcommands/MaintenanceCommand.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using LocalAdmin.V2.IO; using LocalAdmin.V2.PluginsManager; @@ -6,34 +7,21 @@ namespace LocalAdmin.V2.Commands.PluginManager.Subcommands; internal static class MaintenanceCommand { - internal static async void Maintenance(string options) + internal static async Task Maintenance(string options) { bool iSet = options.Contains('i', StringComparison.Ordinal), gSet = options.Contains('g', StringComparison.Ordinal), lSet = options.Contains('l', StringComparison.Ordinal); - bool local = false, global = false; - - switch (gSet) + if (!lSet && !gSet) { - case false when !lSet: - case true when lSet: - local = global = true; - break; - - case true: - global = true; - break; - - default: - local = true; - break; + lSet = gSet = true; } - if (local) + if (lSet) await PluginInstaller.PluginsMaintenance(Core.LocalAdmin.GamePort.ToString(), iSet); - if (global) + if (gSet) await PluginInstaller.PluginsMaintenance("global", iSet); ConsoleUtil.WriteLine("[PLUGIN MANAGER] Plugins maintenance complete.", ConsoleColor.DarkGreen); diff --git a/Commands/PluginManager/Subcommands/TokenCommand.cs b/Commands/PluginManager/Subcommands/TokenCommand.cs index aea2b56..3021571 100644 --- a/Commands/PluginManager/Subcommands/TokenCommand.cs +++ b/Commands/PluginManager/Subcommands/TokenCommand.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using LocalAdmin.V2.IO; using LocalAdmin.V2.PluginsManager; @@ -6,7 +7,7 @@ namespace LocalAdmin.V2.Commands.PluginManager.Subcommands; internal static class TokenCommand { - internal static async void Token(string? token) + internal static async Task Token(string? token) { if (token == null) { @@ -15,7 +16,7 @@ internal static async void Token(string? token) ConsoleUtil.WriteLine( "[PLUGIN MANAGER] Setting GitHub Personal Access Token (PAT) is important, because it greatly increases rate limits.", ConsoleColor.DarkGray); - ConsoleUtil.WriteLine("[PLUGIN MANAGER] Token is currently: " + (set ? "SET" : "NOT SET"), + ConsoleUtil.WriteLine($"[PLUGIN MANAGER] Token is currently: {(set ? "SET" : "NOT SET")}", set ? ConsoleColor.DarkGreen : ConsoleColor.Yellow); ConsoleUtil.WriteLine("[PLUGIN MANAGER]", ConsoleColor.DarkGray); @@ -48,15 +49,18 @@ internal static async void Token(string? token) } ConsoleUtil.WriteLine("[PLUGIN MANAGER] Reading LocalAdmin config file...", ConsoleColor.Blue); - await Core.LocalAdmin.Singleton!.LoadJsonOrTerminate(); + await Core.LocalAdmin.Singleton.LoadJsonOrTerminate(); - Core.LocalAdmin.DataJson!.GitHubPersonalAccessToken = token.Equals("UNSET", StringComparison.OrdinalIgnoreCase) ? null : token; + Core.LocalAdmin.DataJson = Core.LocalAdmin.DataJson! with + { + GitHubPersonalAccessToken = token.Equals("UNSET", StringComparison.OrdinalIgnoreCase) ? null : token + }; ConsoleUtil.WriteLine("[PLUGIN MANAGER] Writing LocalAdmin config file...", ConsoleColor.Blue); - if (await Core.LocalAdmin.DataJson.TrySave(PathManager.InternalJsonDataPath)) - ConsoleUtil.WriteLine("[PLUGIN MANAGER] GitHub Personal Access Token has been updated.", ConsoleColor.DarkGreen); - else - ConsoleUtil.WriteLine("[PLUGIN MANAGER] Failed to save data.json.", ConsoleColor.Red); + + await Core.LocalAdmin.Singleton.SaveJsonOrTerminate(); + + ConsoleUtil.WriteLine("[PLUGIN MANAGER] GitHub Personal Access Token has been updated.", ConsoleColor.DarkGreen); PluginInstaller.RefreshPat(); } diff --git a/Commands/PluginManager/Subcommands/UpdateCommand.cs b/Commands/PluginManager/Subcommands/UpdateCommand.cs index 53db911..05ed0dc 100644 --- a/Commands/PluginManager/Subcommands/UpdateCommand.cs +++ b/Commands/PluginManager/Subcommands/UpdateCommand.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using LocalAdmin.V2.IO; using LocalAdmin.V2.PluginsManager; @@ -6,7 +7,7 @@ namespace LocalAdmin.V2.Commands.PluginManager.Subcommands; internal static class UpdateCommand { - internal static async void Update(string options) + internal static async Task Update(string options) { bool iSet = options.Contains('i', StringComparison.Ordinal), gSet = options.Contains('g', StringComparison.Ordinal), @@ -14,28 +15,15 @@ internal static async void Update(string options) oSet = options.Contains('o', StringComparison.Ordinal), sSet = options.Contains('s', StringComparison.Ordinal); - bool local = false, global = false; - - switch (gSet) + if (!lSet && !gSet) { - case false when !lSet: - case true when lSet: - local = global = true; - break; - - case true: - global = true; - break; - - default: - local = true; - break; + lSet = gSet = true; } - if (local) + if (lSet) await PluginUpdater.UpdatePlugins(Core.LocalAdmin.GamePort.ToString(), iSet, oSet, sSet); - if (global) + if (gSet) await PluginUpdater.UpdatePlugins("global", iSet, oSet, sSet); ConsoleUtil.WriteLine("[PLUGIN MANAGER] Updating plugins update complete.", ConsoleColor.DarkGreen); diff --git a/Commands/ResaveCommand.cs b/Commands/ResaveCommand.cs index 057699f..510d4a4 100644 --- a/Commands/ResaveCommand.cs +++ b/Commands/ResaveCommand.cs @@ -1,29 +1,28 @@ using System; using System.IO; using System.Text; +using System.Threading.Tasks; using LocalAdmin.V2.Commands.Meta; using LocalAdmin.V2.IO; namespace LocalAdmin.V2.Commands; -internal sealed class ResaveCommand : CommandBase +internal sealed class ResaveCommand() : CommandBase("resave", "Resaves the LocalAdmin configuration file.") { - public ResaveCommand() : base("resave", "Resaves the LocalAdmin configuration file.") { } - - internal override void Execute(string[] arguments) + internal override ValueTask Execute(string[] arguments) { try { if (Core.LocalAdmin.CurrentConfigPath == null) { ConsoleUtil.WriteLine("Failed to resave config - CurrentConfigPath is null.", ConsoleColor.Yellow); - return; + return ValueTask.CompletedTask; } if (Core.LocalAdmin.Configuration == null) { ConsoleUtil.WriteLine("Failed to resave config - Configuration is null.", ConsoleColor.Yellow); - return; + return ValueTask.CompletedTask; } File.WriteAllText(Core.LocalAdmin.CurrentConfigPath, Core.LocalAdmin.Configuration.SerializeConfig(), Encoding.UTF8); @@ -32,8 +31,9 @@ internal override void Execute(string[] arguments) catch (Exception e) { ConsoleUtil.WriteLine("Failed to resave LocalAdmin config file!", ConsoleColor.Yellow); - ConsoleUtil.WriteLine("Path: " + Core.LocalAdmin.CurrentConfigPath, ConsoleColor.Yellow); - ConsoleUtil.WriteLine("Exception: " + e.Message, ConsoleColor.Yellow); + ConsoleUtil.WriteLine($"Path: {Core.LocalAdmin.CurrentConfigPath}", ConsoleColor.Yellow); + ConsoleUtil.WriteLine($"Exception: {e.Message}", ConsoleColor.Yellow); } + return ValueTask.CompletedTask; } } \ No newline at end of file diff --git a/Commands/RestartCommand.cs b/Commands/RestartCommand.cs index 4f124ac..5db9fd1 100644 --- a/Commands/RestartCommand.cs +++ b/Commands/RestartCommand.cs @@ -1,16 +1,16 @@ +using System.Threading.Tasks; using LocalAdmin.V2.Commands.Meta; namespace LocalAdmin.V2.Commands; -internal sealed class RestartCommand : CommandBase +internal sealed class RestartCommand() : CommandBase("Restart", "Restarts the server.") { - public RestartCommand() : base("Restart", "Restarts the server.") { } - - internal override void Execute(string[] arguments) + internal override ValueTask Execute(string[] arguments) { - Core.LocalAdmin.Singleton!.DisableExitActionSignals = true; + Core.LocalAdmin.Singleton.DisableExitActionSignals = true; Core.LocalAdmin.Singleton.ExitAction = Core.LocalAdmin.ShutdownAction.Restart; if (Core.LocalAdmin.Singleton.Server is { Connected: true }) Core.LocalAdmin.Singleton.Server.WriteLine("exit"); + return ValueTask.CompletedTask; } } \ No newline at end of file diff --git a/Core/ConfigWizard.cs b/Core/ConfigWizard.cs index 252b9e0..e8ff731 100644 --- a/Core/ConfigWizard.cs +++ b/Core/ConfigWizard.cs @@ -23,8 +23,7 @@ public static void RunConfigWizard(bool useDefault) Console.WriteLine("Welcome to LocalAdmin Configuration Wizard!"); Console.WriteLine(); Console.WriteLine( - "We will ask you a couple of questions. You can always change your answers by running LocalAdmin with \"--reconfigure\" argument or manually editing configuration files in " + - (LocalAdmin.ConfigPath ?? PathManager.GameUserDataRoot) + "config directory."); + $"We will ask you a couple of questions. You can always change your answers by running LocalAdmin with \"--reconfigure\" argument or manually editing configuration files in {LocalAdmin.ConfigPath ?? PathManager.GameUserDataRoot}config directory."); Console.WriteLine(); Console.WriteLine(LocalAdmin.Configuration == null ? "This is the default LocalAdmin configuration:" : "That's your current LocalAdmin configuration:"); @@ -155,7 +154,7 @@ public static void RunConfigWizard(bool useDefault) LocalAdmin.Configuration.LaLogsUseZForUtc = BoolInput("Should UTC timezone should be displayed as \"Z\" instead of \"+00:00\"?"); if (LocalAdmin.Configuration is { LaLogsUseZForUtc: true, LaLiveViewUseUtc: true } && withoutTimezone != null) - LocalAdmin.Configuration.LaLiveViewTimeFormat = withoutTimezone + "Z"; + LocalAdmin.Configuration.LaLiveViewTimeFormat = $"{withoutTimezone}Z"; LocalAdmin.Configuration.LaShowStdoutStderr = BoolInput("Should standard outputs (contain a lot of debug information) be visible on the LocalAdmin live view?"); LocalAdmin.Configuration.LaNoSetCursor = BoolInput("Should cursor position management be DISABLED (disable only if you are experiencing issues with the console, may cause issues especially on linux)?"); @@ -202,7 +201,7 @@ private static bool BoolInput(string question) { while (true) { - Console.WriteLine(question + " [yes/no]: "); + Console.WriteLine($"{question} [yes/no]: "); var input = Console.ReadLine(); if (input == null) continue; @@ -219,7 +218,7 @@ private static ushort UshortInput(string question) { while (true) { - Console.WriteLine(question + " "); + Console.WriteLine($"{question} "); var input = Console.ReadLine(); if (input == null) continue; @@ -255,7 +254,7 @@ private static void SaveConfig(bool silent = false) if (parent == null) { Console.WriteLine("FATAL ERROR: Can't create config directory (Directory processing error)."); - Console.WriteLine("Path: " + LocalAdmin.ConfigPath); + Console.WriteLine($"Path: {LocalAdmin.ConfigPath}"); Environment.Exit(1); return; } @@ -269,27 +268,24 @@ private static void SaveConfig(bool silent = false) catch (Exception e) { Console.WriteLine("FATAL ERROR: Can't create config directory."); - Console.WriteLine("Path: " + parent.FullName); - Console.WriteLine("Exception: " + e.Message); + Console.WriteLine($"Path: {parent.FullName}"); + Console.WriteLine($"Exception: {e.Message}"); Environment.Exit(1); return; } } - if (File.Exists(parent.FullName)) + try { - try - { - File.Delete(parent.FullName); - } - catch (Exception e) - { - Console.WriteLine("FATAL ERROR: Can't delete config file."); - Console.WriteLine("Path: " + parent.FullName); - Console.WriteLine("Exception: " + e.Message); - Environment.Exit(1); - return; - } + File.Delete(parent.FullName); + } + catch (Exception e) + { + Console.WriteLine("FATAL ERROR: Can't delete config file."); + Console.WriteLine($"Path: {parent.FullName}"); + Console.WriteLine($"Exception: {e.Message}"); + Environment.Exit(1); + return; } try @@ -299,8 +295,8 @@ private static void SaveConfig(bool silent = false) catch (Exception e) { Console.WriteLine("FATAL ERROR: Can't write config file."); - Console.WriteLine("Path: " + LocalAdmin.ConfigPath); - Console.WriteLine("Exception: " + e.Message); + Console.WriteLine($"Path: {LocalAdmin.ConfigPath}"); + Console.WriteLine($"Exception: {e.Message}"); Environment.Exit(1); return; } @@ -312,40 +308,34 @@ private static void SaveConfig(bool silent = false) var cfgPath = $"{PathManager.GameUserDataRoot}config{Path.DirectorySeparatorChar}{LocalAdmin.GamePort}{Path.DirectorySeparatorChar}"; - if (!Directory.Exists(cfgPath)) + try { - try - { - Directory.CreateDirectory(cfgPath); - } - catch (Exception e) - { - Console.WriteLine("FATAL ERROR: Can't create config directory."); - Console.WriteLine("Path: " + cfgPath); - Console.WriteLine("Exception: " + e.Message); - Environment.Exit(1); - return; - } + Directory.CreateDirectory(cfgPath); + } + catch (Exception e) + { + Console.WriteLine("FATAL ERROR: Can't create config directory."); + Console.WriteLine($"Path: {cfgPath}"); + Console.WriteLine($"Exception: {e.Message}"); + Environment.Exit(1); + return; } cfgPath += "config_localadmin.txt"; if (input != null && input.Equals("this", StringComparison.OrdinalIgnoreCase)) { - if (File.Exists(cfgPath)) + try { - try - { - File.Delete(cfgPath); - } - catch (Exception e) - { - Console.WriteLine("FATAL ERROR: Can't delete config file."); - Console.WriteLine("Path: " + cfgPath); - Console.WriteLine("Exception: " + e.Message); - Environment.Exit(1); - return; - } + File.Delete(cfgPath); + } + catch (Exception e) + { + Console.WriteLine("FATAL ERROR: Can't delete config file."); + Console.WriteLine($"Path: {cfgPath}"); + Console.WriteLine($"Exception: {e.Message}"); + Environment.Exit(1); + return; } try @@ -356,8 +346,8 @@ private static void SaveConfig(bool silent = false) catch (Exception e) { Console.WriteLine("FATAL ERROR: Can't write config file."); - Console.WriteLine("Path: " + cfgPath); - Console.WriteLine("Exception: " + e.Message); + Console.WriteLine($"Path: {cfgPath}"); + Console.WriteLine($"Exception: {e.Message}"); Environment.Exit(1); return; } @@ -366,56 +356,47 @@ private static void SaveConfig(bool silent = false) return; } - if (File.Exists(cfgPath)) + try { - try - { - File.Delete(cfgPath); - } - catch (Exception e) - { - Console.WriteLine("FATAL ERROR: Can't delete **LOCAL** config file."); - Console.WriteLine("Path: " + cfgPath); - Console.WriteLine("Exception: " + e.Message); - Environment.Exit(1); - return; - } + File.Delete(cfgPath); + } + catch (Exception e) + { + Console.WriteLine("FATAL ERROR: Can't delete **LOCAL** config file."); + Console.WriteLine($"Path: {cfgPath}"); + Console.WriteLine($"Exception: {e.Message}"); + Environment.Exit(1); + return; } cfgPath = $"{PathManager.GameUserDataRoot}config{Path.DirectorySeparatorChar}"; - if (!Directory.Exists(cfgPath)) + try { - try - { - Directory.CreateDirectory(cfgPath); - } - catch (Exception e) - { - Console.WriteLine("FATAL ERROR: Can't **GLOBAL** config directory."); - Console.WriteLine("Path: " + cfgPath); - Console.WriteLine("Exception: " + e.Message); - Environment.Exit(1); - return; - } + Directory.CreateDirectory(cfgPath); + } + catch (Exception e) + { + Console.WriteLine("FATAL ERROR: Can't **GLOBAL** config directory."); + Console.WriteLine($"Path: {cfgPath}"); + Console.WriteLine($"Exception: {e.Message}"); + Environment.Exit(1); + return; } cfgPath += "config_localadmin_global.txt"; - if (File.Exists(cfgPath)) + try { - try - { - File.Delete(cfgPath); - } - catch (Exception e) - { - Console.WriteLine("FATAL ERROR: Can't delete **GLOBAL** config file."); - Console.WriteLine("Path: " + cfgPath); - Console.WriteLine("Exception: " + e.Message); - Environment.Exit(1); - return; - } + File.Delete(cfgPath); + } + catch (Exception e) + { + Console.WriteLine("FATAL ERROR: Can't delete **GLOBAL** config file."); + Console.WriteLine($"Path: {cfgPath}"); + Console.WriteLine($"Exception: {e.Message}"); + Environment.Exit(1); + return; } try @@ -426,8 +407,8 @@ private static void SaveConfig(bool silent = false) catch (Exception e) { Console.WriteLine("FATAL ERROR: Can't write **GLOBAL** config file."); - Console.WriteLine("Path: " + cfgPath); - Console.WriteLine("Exception: " + e.Message); + Console.WriteLine($"Path: {cfgPath}"); + Console.WriteLine($"Exception: {e.Message}"); Environment.Exit(1); return; } diff --git a/Core/LocalAdmin.cs b/Core/LocalAdmin.cs index 982ac44..db6c262 100644 --- a/Core/LocalAdmin.cs +++ b/Core/LocalAdmin.cs @@ -1,20 +1,21 @@ using LocalAdmin.V2.Commands; using LocalAdmin.V2.Commands.Meta; +using LocalAdmin.V2.Commands.PluginManager; using LocalAdmin.V2.IO; using LocalAdmin.V2.IO.ExitHandlers; +using LocalAdmin.V2.IO.Logging; +using LocalAdmin.V2.JSON; +using LocalAdmin.V2.JSON.Objects; +using LocalAdmin.V2.PluginsManager; using System; using System.Collections.Concurrent; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; -using System.Runtime.InteropServices; -using System.Text; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; -using LocalAdmin.V2.Commands.PluginManager; -using LocalAdmin.V2.IO.Logging; -using LocalAdmin.V2.PluginsManager; namespace LocalAdmin.V2.Core; /* @@ -30,14 +31,14 @@ namespace LocalAdmin.V2.Core; public sealed class LocalAdmin : IDisposable { - public const string VersionString = "2.5.16"; + public const string VersionString = "2.6.0"; private const ushort DefaultPort = 7777; private static readonly ConcurrentQueue InputQueue = new(); private static readonly Stopwatch RestartsStopwatch = new(); private static string? _previousPat; private static bool _firstRun = true; - private static string _gameArguments = string.Empty; + private static string[] _gameArguments = []; private static bool _exit, _processRefreshFail; private static bool _noTrueColor; private static bool _stdPrint; @@ -56,17 +57,14 @@ public sealed class LocalAdmin : IDisposable private static int? _processId; internal static readonly Stopwatch HeartbeatStopwatch = new(); - internal static LocalAdmin? Singleton; + internal static LocalAdmin Singleton = null!; internal static ushort GamePort; internal static string? ConfigPath, CurrentConfigPath, LaLogsPath, GameLogsPath; internal static ulong LogLengthLimit = 25000000000, LogEntriesLimit = 10000000000; internal static Config? Configuration; internal static DataJson? DataJson; private string BaseWindowTitle => - (_idleMode ? "[IDLE] " : string.Empty) + - $"LocalAdmin v. {VersionString}" + - (GamePort != 0 ? $" | Port: {GamePort}" : string.Empty) + - (_processId.HasValue ? $" | PID: {_processId}" : string.Empty); + $"{(_idleMode ? "[IDLE] " : string.Empty)}LocalAdmin v. {VersionString}{(GamePort != 0 ? $" | Port: {GamePort}" : string.Empty)}{(_processId.HasValue ? $" | PID: {_processId}" : string.Empty)}"; internal static bool NoSetCursor, PrintControlMessages, AutoFlush = true, EnableLogging = true, NoPadding, DismissPluginsSecurityWarning; internal ShutdownAction ExitAction = ShutdownAction.Crash; @@ -138,16 +136,11 @@ internal async Task Start(string[] args) ConsoleUtil.WriteLine("Such error should never occur on Windows.", ConsoleColor.Red); ConsoleUtil.WriteLine("Open issue on the LocalAdmin GitHub repository (https://github.com/northwood-studios/LocalAdmin-V2/issues) or contact our technical support!", ConsoleColor.Red); } - else if (OperatingSystem.IsLinux()) + else { ConsoleUtil.WriteLine("Make sure to export a valid path, for example using command: export HOME=/home/username-here", ConsoleColor.Red); ConsoleUtil.WriteLine("You may want to add that command to the top of ~/.bashrc file and restart the terminal session to avoid having to enter that command every time.", ConsoleColor.Red); } - else - { - ConsoleUtil.WriteLine("You are running LocalAdmin on an unsupported platform, please switch to Windows or Linux!", ConsoleColor.Red); - throw new PlatformNotSupportedException(); - } ConsoleUtil.WriteLine("To skip this check, use --skipHomeCheck argument.", ConsoleColor.Red); Terminate(); return; @@ -188,7 +181,7 @@ internal async Task Start(string[] args) if (args.Contains("--acceptEULA", StringComparer.Ordinal) || Environment.GetEnvironmentVariable("ACCEPT_SCPSL_EULA")?.ToUpperInvariant() is "1" or "TRUE") { - DataJson!.EulaAccepted = DateTime.UtcNow; + DataJson = DataJson with { EulaAccepted = DateTime.UtcNow }; autoEula = true; await SaveJsonOrTerminate(); @@ -201,7 +194,7 @@ internal async Task Start(string[] args) ConsoleUtil.WriteLine("", ConsoleColor.Cyan); ConsoleUtil.WriteLine("Do you accept the EULA? [yes/no]", ConsoleColor.Cyan); - ReadInput((input) => + ReadInput(input => { if (input == null) return false; @@ -211,7 +204,7 @@ internal async Task Start(string[] args) case "y": case "yes": case "1": - DataJson.EulaAccepted = DateTime.UtcNow; + DataJson = DataJson with { EulaAccepted = DateTime.UtcNow }; return true; case "n": @@ -248,7 +241,7 @@ internal async Task Start(string[] args) Console.WriteLine(string.Empty); ConsoleUtil.Write($"Port number (default: {DefaultPort}): ", ConsoleColor.Green); - ReadInput((input) => + ReadInput(input => { if (!string.IsNullOrEmpty(input)) return ushort.TryParse(input, out GamePort); @@ -270,7 +263,7 @@ internal async Task Start(string[] args) switch (capture) { case CaptureArgs.None: - if (arg.StartsWith("-", StringComparison.Ordinal) && + if (arg.StartsWith('-') && !arg.StartsWith("--", StringComparison.Ordinal) && arg.Length > 1) { for (var i = 1; i < arg.Length; i++) @@ -397,7 +390,7 @@ internal async Task Start(string[] args) break; case CaptureArgs.ArgsPassthrough: - _gameArguments += $"\"{arg}\" "; + _gameArguments = [..args, arg]; break; case CaptureArgs.ConfigPath: @@ -448,8 +441,8 @@ internal async Task Start(string[] args) } capture = CaptureArgs.None; - } break; + } case CaptureArgs.LogEntriesLimit: { @@ -466,11 +459,11 @@ internal async Task Start(string[] args) } capture = CaptureArgs.None; - } break; + } default: - throw new ArgumentOutOfRangeException(); + throw new UnreachableException(); } } } @@ -479,20 +472,21 @@ internal async Task Start(string[] args) { CurrentConfigPath = ConfigPath; - if (File.Exists(ConfigPath)) - Configuration = Config.DeserializeConfig(await File.ReadAllLinesAsync(ConfigPath, Encoding.UTF8)); - else reconfigure = true; + if (await FileUtils.TryReadLinesAsync(ConfigPath, FileShare.Read) is { } configuration) + Configuration = Config.DeserializeConfig([.. configuration]); + else + reconfigure = true; } else { CurrentConfigPath = Path.Combine(PathManager.GameUserDataRoot, "config", GamePort.ToString(), "config_localadmin.txt"); - if (File.Exists(CurrentConfigPath)) - Configuration = Config.DeserializeConfig(await File.ReadAllLinesAsync(CurrentConfigPath, Encoding.UTF8)); + if (await FileUtils.TryReadLinesAsync(CurrentConfigPath, FileShare.Read) is { } configuration) + Configuration = Config.DeserializeConfig([.. configuration]); else { CurrentConfigPath = Path.Combine(PathManager.GameUserDataRoot, "config", "config_localadmin_global.txt"); - if (File.Exists(CurrentConfigPath)) - Configuration = Config.DeserializeConfig(await File.ReadAllLinesAsync(CurrentConfigPath, Encoding.UTF8)); + if (await FileUtils.TryReadLinesAsync(CurrentConfigPath, FileShare.Read) is { } globalConfiguration) + Configuration = Config.DeserializeConfig([.. globalConfiguration]); else reconfigure = true; } @@ -541,12 +535,11 @@ internal async Task Start(string[] args) } RegisterCommands(); + SetupReader(); StartSession(); - _readerTask!.Start(); - if (autoEula) ConsoleUtil.WriteLine("SCP: Secret Laboratory EULA (https://link.scpslgame.com/eula) was accepted by providing a startup argument or setting an environment variable.", ConsoleColor.Yellow); @@ -613,7 +606,7 @@ private static void Menu() ConsoleUtil.WriteLine($"SCP: Secret Laboratory - LocalAdmin v. {VersionString}", ConsoleColor.Cyan); ConsoleUtil.WriteLine(string.Empty, ConsoleColor.Cyan); ConsoleUtil.WriteLine("Licensed under The MIT License (use command \"license\" to get license text).", ConsoleColor.Cyan); - ConsoleUtil.WriteLine("Copyright by Łukasz \"zabszk\" Jurczyk and KernelError, 2019 - 2024", ConsoleColor.Cyan); + ConsoleUtil.WriteLine("Copyright by Łukasz \"zabszk\" Jurczyk and KernelError, 2019 - 2025", ConsoleColor.Cyan); ConsoleUtil.WriteLine(string.Empty, ConsoleColor.Cyan); ConsoleUtil.WriteLine("Type 'help' to get list of available commands.", ConsoleColor.Cyan); ConsoleUtil.WriteLine(string.Empty, ConsoleColor.Cyan); @@ -626,50 +619,10 @@ private static void SetupExitHandlers() if (OperatingSystem.IsWindows()) WindowsHandler.Handler.Setup(); - else if (OperatingSystem.IsLinux()) - { -#if LINUX_SIGNALS - try - { - UnixHandler.Handler.Setup(); - } - catch (DllNotFoundException ex) - { - if (!CheckMonoException(ex)) throw; - } - catch (EntryPointNotFoundException ex) - { - if (!CheckMonoException(ex)) throw; - } - catch (TypeInitializationException ex) - { - switch (ex.InnerException) - { - case DllNotFoundException dll: - if (!CheckMonoException(dll)) throw; - break; - case EntryPointNotFoundException dll: - if (!CheckMonoException(dll)) throw; - break; - default: - throw; - } - } -#else - ConsoleUtil.WriteLine("Invalid Linux build! Please download LocalAdmin from GitHub here: https://github.com/northwood-studios/LocalAdmin-V2/releases", ConsoleColor.Red); -#endif - } + else + UnixHandler.Handler.Setup(); } -#if LINUX_SIGNALS - private static bool CheckMonoException(Exception ex) - { - if (!ex.Message.Contains("MonoPosixHelper")) return false; - ConsoleUtil.WriteLine("Native exit handling for Linux requires Mono to be installed!", ConsoleColor.Yellow); - return true; - } -#endif - private void SetupServer() { Server = new TcpServer(); @@ -701,9 +654,10 @@ private static void SetupKeyboardInput() private void SetupReader() { - async void ReaderTaskMethod() + async Task ReaderTaskMethod() { - while (Server == null) await Task.Delay(20); + while (Server == null) + await Task.Delay(20); while (!_exit) { @@ -754,7 +708,7 @@ async void ReaderTaskMethod() if (command != null) { - command.Execute(split.Skip(1).ToArray()); + await command.Execute(split.Skip(1).ToArray()); if (!command.SendToGame) continue; } @@ -780,50 +734,61 @@ async void ReaderTaskMethod() } } - _readerTask = new Task(ReaderTaskMethod); + _readerTask = ReaderTaskMethod(); } private void RunScpsl() { if (File.Exists(_scpslExecutable)) { - ConsoleUtil.WriteLine("Executing: " + _scpslExecutable, ConsoleColor.DarkGreen); + ConsoleUtil.WriteLine($"Executing: {_scpslExecutable}", ConsoleColor.DarkGreen); var printStd = Configuration!.LaShowStdoutStderr || _stdPrint; var redirectStreams = Configuration.LaLogStdoutStderr || printStd; - var extraArgs = string.Empty; - - if (_noTrueColor || !Configuration.EnableTrueColor) - extraArgs = " -disableAnsiColors"; - - if (EnableGameHeartbeat) - extraArgs += " -heartbeat"; - var startInfo = new ProcessStartInfo { FileName = _scpslExecutable, - Arguments = - $"-batchmode -nographics -txbuffer {Configuration.SlToLaBufferSize} -rxbuffer {Configuration.LaToSlBufferSize} -port{GamePort} -console{Server!.ConsolePort} -id{Environment.ProcessId}{extraArgs} {_gameArguments}", CreateNoWindow = true, UseShellExecute = false, RedirectStandardOutput = true, - RedirectStandardError = true + RedirectStandardError = true, + ArgumentList = + { + "-batchmode", + "-nographics", + "-txbuffer", + Configuration.SlToLaBufferSize.ToString(), + "-rxbuffer", + Configuration.LaToSlBufferSize.ToString(), + $"-port{GamePort}", + $"-console{Server!.ConsolePort}", + $"-id{Environment.ProcessId}" + } }; + if (_noTrueColor || !Configuration.EnableTrueColor) + startInfo.ArgumentList.Add("-disableAnsiColors"); + + if (EnableGameHeartbeat) + startInfo.ArgumentList.Add("-heartbeat"); + + foreach (string argument in _gameArguments) + startInfo.ArgumentList.Add(argument); + _gameProcess = Process.Start(startInfo); _processId = _gameProcess!.Id; SetTerminalTitle(); - ConsoleUtil.WriteLine("Game process started with PID: " + _processId, ConsoleColor.DarkGreen); + ConsoleUtil.WriteLine($"Game process started with PID: {_processId}", ConsoleColor.DarkGreen); _gameProcess!.OutputDataReceived += (_, args) => { if (!redirectStreams || string.IsNullOrWhiteSpace(args.Data)) return; - ConsoleUtil.WriteLine("[STDOUT] " + args.Data, ConsoleColor.Gray, + ConsoleUtil.WriteLine($"[STDOUT] {args.Data}", ConsoleColor.Gray, log: Configuration.LaLogStdoutStderr, display: printStd); }; @@ -833,7 +798,7 @@ private void RunScpsl() if (!redirectStreams || string.IsNullOrWhiteSpace(args.Data)) return; - ConsoleUtil.WriteLine("[STDERR] " + args.Data, ConsoleColor.DarkMagenta, + ConsoleUtil.WriteLine($"[STDERR] {args.Data}", ConsoleColor.DarkMagenta, log: Configuration.LaLogStdoutStderr, display: printStd); }; @@ -895,10 +860,8 @@ private void RunScpsl() if (OperatingSystem.IsWindows()) Exit((int)WindowsErrorCode.ERROR_FILE_NOT_FOUND, true); - else if (OperatingSystem.IsLinux()) - Exit((int)UnixErrorCode.ERROR_FILE_NOT_FOUND, true); else - Exit(1); + Exit((int)UnixErrorCode.ERROR_FILE_NOT_FOUND, true); } } @@ -960,7 +923,7 @@ public void Exit(int code = -1, bool waitForKey = false, bool restart = false) try { if (_readerTask is { IsCompleted: true }) - _readerTask?.Dispose(); + _readerTask.Dispose(); } catch { @@ -970,7 +933,7 @@ public void Exit(int code = -1, bool waitForKey = false, bool restart = false) try { if (_heartbeatMonitoringTask is { IsCompleted: true }) - _heartbeatMonitoringTask?.Dispose(); + _heartbeatMonitoringTask.Dispose(); } catch { @@ -1009,45 +972,44 @@ internal async Task LoadJsonOrTerminate() { try { - if (!Directory.Exists(PathManager.ConfigPath)) - Directory.CreateDirectory(PathManager.ConfigPath); + Directory.CreateDirectory(PathManager.ConfigPath); - if (!File.Exists(PathManager.InternalJsonDataPath)) + if (await FileUtils.TryReadJsonAsync(PathManager.InternalJsonDataPath, FileShare.Read, JsonGenerated.Default.DataJson) is { } dataJson) { - DataJson = new DataJson(); - await SaveJsonOrTerminate(); + DataJson = dataJson; } else { - DataJson = await JsonFile.Load(PathManager.InternalJsonDataPath); - JsonFile.UnlockFile(PathManager.InternalJsonDataPath); - - if (DataJson == null) - { - ConsoleUtil.WriteLine("Json file is corrupted! Terminating LocalAdmin. If the issue persists, please delete the file and restart LocalAdmin.", ConsoleColor.Red); - Terminate(); - } + DataJson = new DataJson(null, null, false, null, + [], []); + await SaveJsonOrTerminate(); } - if (_previousPat != DataJson!.GitHubPersonalAccessToken) + if (_previousPat != DataJson.GitHubPersonalAccessToken) { _previousPat = DataJson.GitHubPersonalAccessToken; PluginInstaller.RefreshPat(); } } - catch (Exception e) + catch (Exception ex) { - ConsoleUtil.WriteLine($"Failed to read JSON config file: {e.Message}", ConsoleColor.Red); - DataJson = null; + ConsoleUtil.WriteLine($"Failed to read JSON config file: {ex.Message}", ConsoleColor.Red); Terminate(); - throw; } } internal async Task SaveJsonOrTerminate() { - if (!(await DataJson!.TrySave(PathManager.InternalJsonDataPath))) + try + { + await using FileStream fileStream = await FileUtils.OpenAsync(PathManager.InternalJsonDataPath, FileMode.Create, FileAccess.Write, FileShare.None); + await JsonSerializer.SerializeAsync(fileStream, DataJson, JsonGenerated.Default.DataJson); + } + catch (Exception ex) + { + ConsoleUtil.WriteLine($"Failed to write JSON config file: {ex.Message}", ConsoleColor.Red); Terminate(); + } } private void Terminate() @@ -1088,7 +1050,7 @@ internal void HandleHeartbeat() private void StartHeartbeatMonitoring() { - async void HeartbeatMonitoringMethod() + async Task HeartbeatMonitoringMethod() { { ushort i = 0; @@ -1130,7 +1092,7 @@ async void HeartbeatMonitoringMethod() continue; case HeartbeatStatus.Active: - if (HeartbeatStopwatch.ElapsedMilliseconds <= (_heartbeatSpanMaxThreshold * 1000)) + if (HeartbeatStopwatch.ElapsedMilliseconds <= _heartbeatSpanMaxThreshold * 1000) { if (HeartbeatWarningStage != 0) ConsoleUtil.WriteLine("Heartbeat has been received. Restart procedure aborted.", ConsoleColor.DarkGreen); @@ -1160,8 +1122,7 @@ async void HeartbeatMonitoringMethod() } } } - _heartbeatMonitoringTask = new Task(HeartbeatMonitoringMethod); - _heartbeatMonitoringTask.Start(); + _heartbeatMonitoringTask = HeartbeatMonitoringMethod(); } ~LocalAdmin() diff --git a/Core/Program.cs b/Core/Program.cs index 1e5af9a..9e842e3 100644 --- a/Core/Program.cs +++ b/Core/Program.cs @@ -1,16 +1,19 @@ +using System.Globalization; +using System.Runtime.CompilerServices; using System.Threading.Tasks; +[assembly: DisableRuntimeMarshalling] + namespace LocalAdmin.V2.Core; internal static class Program { private static async Task Main(string[] args) { - Utf8Json.Resolvers.CompositeResolver.RegisterAndSetAsDefault( - Utf8Json.Resolvers.GeneratedResolver.Instance, - Utf8Json.Resolvers.BuiltinResolver.Instance - ); - + CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; + CultureInfo.CurrentUICulture = CultureInfo.InvariantCulture; + CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture; + CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.InvariantCulture; while (true) { using var la = new LocalAdmin(); diff --git a/Core/StartupArgManager.cs b/Core/StartupArgManager.cs index 26cd394..6786149 100644 --- a/Core/StartupArgManager.cs +++ b/Core/StartupArgManager.cs @@ -1,8 +1,8 @@ -using LocalAdmin.V2.IO; using System; using System.Collections.Generic; using System.IO; using System.Linq; +using LocalAdmin.V2.IO; namespace LocalAdmin.V2.Core { @@ -22,36 +22,34 @@ public static string[] MergeStartupArgs(IEnumerable cmdArgs) { MigrateArgsFile(); - List startupArgs = new List(); - startupArgs.AddRange(cmdArgs); + string[] startupArgs = [.. cmdArgs]; try { if (!File.Exists(StartupArgsPath)) - return startupArgs.ToArray(); + return [.. startupArgs]; - startupArgs.AddRange(File.ReadAllLines(StartupArgsPath).Where(arg => !arg.StartsWith("#", StringComparison.Ordinal))); - return startupArgs.ToArray(); + return [.. startupArgs, .. File.ReadLines(StartupArgsPath).Where(arg => !arg.StartsWith('#'))]; } catch (Exception ex) { ConsoleUtil.WriteLine($"An error occured while trying to merge arguments: {ex}", ConsoleColor.Red); - return startupArgs.ToArray(); + return [.. startupArgs]; } } private static void MigrateArgsFile() { - const string ObsoleteFile = "laargs.txt"; + const string obsoleteFile = "laargs.txt"; try { - if (!File.Exists(ObsoleteFile)) + if (!File.Exists(obsoleteFile)) return; - if (string.IsNullOrWhiteSpace(File.ReadAllText(ObsoleteFile))) + if (string.IsNullOrWhiteSpace(File.ReadAllText(obsoleteFile))) { - File.Delete(ObsoleteFile); + File.Delete(obsoleteFile); ConsoleUtil.WriteLine("Obsolete configuration file 'laargs.txt' is empty and has been deleted.", ConsoleColor.Gray); return; } @@ -59,7 +57,7 @@ private static void MigrateArgsFile() if (File.Exists(StartupArgsPath)) return; - File.Move(ObsoleteFile, StartupArgsPath); + File.Move(obsoleteFile, StartupArgsPath); ConsoleUtil.WriteLine("Successfully migrated your old 'laargs' configuration.", ConsoleColor.DarkGreen); } catch (Exception ex) diff --git a/Core/TcpServer.cs b/Core/TcpServer.cs index dc0270d..67e7b1c 100644 --- a/Core/TcpServer.cs +++ b/Core/TcpServer.cs @@ -1,12 +1,13 @@ +using LocalAdmin.V2.IO; +using LocalAdmin.V2.IO.Logging; using System; using System.Buffers; using System.Net; using System.Net.Sockets; using System.Runtime.InteropServices; using System.Text; +using System.Threading; using System.Threading.Tasks; -using LocalAdmin.V2.IO; -using LocalAdmin.V2.IO.Logging; namespace LocalAdmin.V2.Core; @@ -28,19 +29,18 @@ private enum OutputCodes : byte public event EventHandler? Received; - private readonly TcpListener _listener; + private readonly TcpListener _listener = new(IPAddress.Loopback, 0); private TcpClient? _client; private NetworkStream? _networkStream; + private volatile bool _exit = true; + internal ushort ConsolePort; internal bool Connected; - private bool _exit = true; private int _txBuffer; - private readonly object _lck = new(); + private readonly Lock _lck = new(); private readonly UTF8Encoding _encoding = new(false, true); - public TcpServer() => _listener = new TcpListener(IPAddress.Loopback, 0); - public void Start() { lock (_lck) @@ -51,13 +51,14 @@ public void Start() ConsolePort = (ushort)((IPEndPoint)_listener.LocalEndpoint).Port; _listener.BeginAcceptTcpClient(result => { - lock (_lck) + _client = _listener.EndAcceptTcpClient(result); + + if (_exit) { - if (_exit) - return; + _client.Close(); + return; } - _client = _listener.EndAcceptTcpClient(result); _client.NoDelay = true; _client.ReceiveBufferSize = LocalAdmin.Configuration!.SlToLaBufferSize; @@ -75,105 +76,84 @@ public void Start() var lengthBuffer = new byte[offset]; var restartReceived = false; - while (true) + while (!_exit) { - await Task.Delay(10); - - lock (_lck) + try { - if (_exit) - break; - } - - if (_networkStream?.DataAvailable != true) - continue; - - int readAmount = await _networkStream.ReadAsync(codeBuffer.AsMemory(0, 1)); + await Task.Delay(10); - if (readAmount == 0) - continue; + await _networkStream.ReadExactlyAsync(codeBuffer.AsMemory()); - if (codeBuffer[0] < 16) - { - readAmount = await _networkStream.ReadAsync(lengthBuffer.AsMemory(0, offset)); - - if (readAmount < 4) + if (codeBuffer[0] < 16) { - Received?.Invoke(this, - "4[LocalAdmin] Received **INVALID** data message length. Length: " + readAmount); - continue; - } + await _networkStream.ReadExactlyAsync(lengthBuffer.AsMemory()); - var length = MemoryMarshal.Cast(lengthBuffer)[0]; - var buffer = ArrayPool.Shared.Rent(length); + var length = MemoryMarshal.Read(lengthBuffer); + var buffer = ArrayPool.Shared.Rent(length); - while (_client.Available < length) - await Task.Delay(20); + await _networkStream.ReadExactlyAsync(buffer.AsMemory(0, length)); - readAmount = await _networkStream.ReadAsync(buffer.AsMemory(0, length)); + var message = $"{codeBuffer[0]:X}{_encoding.GetString(buffer, 0, length)}"; + ArrayPool.Shared.Return(buffer); - if (readAmount != length) + Received?.Invoke(this, message); + } + else { - Received?.Invoke(this, - $"4[LocalAdmin] Received **INVALID** data message. Length received: {readAmount}. Length expected: {length}."); - continue; + if (LocalAdmin.PrintControlMessages) + Received?.Invoke(this, $"7[LocalAdmin] Received control message: {codeBuffer[0]}"); + + switch ((OutputCodes)codeBuffer[0]) + { + case OutputCodes.RoundRestart: + if (restartReceived) + Logger.Initialize(); + else + restartReceived = true; + break; + + case OutputCodes.IdleEnter: + LocalAdmin.Singleton?.SetIdleModeState(true); + break; + + case OutputCodes.IdleExit: + LocalAdmin.Singleton?.SetIdleModeState(false); + break; + + case OutputCodes.ExitActionReset: + if (!LocalAdmin.Singleton!.DisableExitActionSignals) + LocalAdmin.Singleton.ExitAction = LocalAdmin.ShutdownAction.Crash; + break; + + case OutputCodes.ExitActionShutdown: + if (!LocalAdmin.Singleton!.DisableExitActionSignals) + LocalAdmin.Singleton.ExitAction = LocalAdmin.ShutdownAction.Shutdown; + break; + + case OutputCodes.ExitActionSilentShutdown: + if (!LocalAdmin.Singleton!.DisableExitActionSignals) + LocalAdmin.Singleton.ExitAction = LocalAdmin.ShutdownAction.SilentShutdown; + break; + + case OutputCodes.ExitActionRestart: + if (!LocalAdmin.Singleton!.DisableExitActionSignals) + LocalAdmin.Singleton.ExitAction = LocalAdmin.ShutdownAction.Restart; + break; + + case OutputCodes.Heartbeat: + LocalAdmin.Singleton!.HandleHeartbeat(); + break; + + default: + Received?.Invoke(this, + $"4[LocalAdmin] Received **INVALID** control message: {codeBuffer[0]}"); + break; + } } - - var message = $"{codeBuffer[0]:X}{_encoding.GetString(buffer, 0, length)}"; - ArrayPool.Shared.Return(buffer); - - Received?.Invoke(this, message); } - else + catch (Exception ex) { - if (LocalAdmin.PrintControlMessages) - Received?.Invoke(this, "7[LocalAdmin] Received control message: " + codeBuffer[0]); - - switch ((OutputCodes)codeBuffer[0]) - { - case OutputCodes.RoundRestart: - if (restartReceived) - Logger.Initialize(); - else restartReceived = true; - break; - - case OutputCodes.IdleEnter: - LocalAdmin.Singleton?.SetIdleModeState(true); - break; - - case OutputCodes.IdleExit: - LocalAdmin.Singleton?.SetIdleModeState(false); - break; - - case OutputCodes.ExitActionReset: - if (!LocalAdmin.Singleton!.DisableExitActionSignals) - LocalAdmin.Singleton.ExitAction = LocalAdmin.ShutdownAction.Crash; - break; - - case OutputCodes.ExitActionShutdown: - if (!LocalAdmin.Singleton!.DisableExitActionSignals) - LocalAdmin.Singleton.ExitAction = LocalAdmin.ShutdownAction.Shutdown; - break; - - case OutputCodes.ExitActionSilentShutdown: - if (!LocalAdmin.Singleton!.DisableExitActionSignals) - LocalAdmin.Singleton.ExitAction = LocalAdmin.ShutdownAction.SilentShutdown; - break; - - case OutputCodes.ExitActionRestart: - if (!LocalAdmin.Singleton!.DisableExitActionSignals) - LocalAdmin.Singleton.ExitAction = LocalAdmin.ShutdownAction.Restart; - break; - - case OutputCodes.Heartbeat: - LocalAdmin.Singleton!.HandleHeartbeat(); - break; - - default: - Received?.Invoke(this, - "4[LocalAdmin] Received **INVALID** control message: " + codeBuffer[0]); - break; - } + ConsoleUtil.WriteLine($"Failed to read a message from the game: {ex.Message}", ConsoleColor.Red); } } }); @@ -185,12 +165,11 @@ public void Stop() { lock (_lck) { - if (_exit) + if (Interlocked.Exchange(ref _exit, true)) return; - _exit = true; - _listener.Stop(); _client?.Close(); + _listener.Stop(); } } @@ -198,23 +177,27 @@ public void WriteLine(string input) { lock (_lck) { - if (_exit) return; + if (_exit) + return; + const int offset = sizeof(int); var buffer = ArrayPool.Shared.Rent(Encoding.UTF8.GetMaxByteCount(input.Length) + offset); - var length = _encoding.GetBytes(input, 0, input.Length, buffer, offset); + var length = _encoding.GetBytes(input.AsSpan(), buffer.AsSpan(offset)); + MemoryMarshal.Write(buffer, length); + + length += offset; - if (length + offset > _txBuffer) + if (length > _txBuffer) { ConsoleUtil.WriteLine("Failed to send command - configured LA to SL buffer size is too small. Please increase it in the LocalAdmin config file to run this command!", ConsoleColor.Red); - ArrayPool.Shared.Return(buffer); - return; + } + else + { + _networkStream!.Write(buffer); } - MemoryMarshal.Cast(buffer)[0] = length; - - _networkStream!.Write(buffer, 0, length + offset); ArrayPool.Shared.Return(buffer); } } diff --git a/IO/Config.cs b/IO/Config.cs index db7e452..cd30d3d 100644 --- a/IO/Config.cs +++ b/IO/Config.cs @@ -5,7 +5,7 @@ namespace LocalAdmin.V2.IO; public class Config { - private static readonly string[] SplitArray = { ": " }; + private static readonly string[] SplitArray = [": "]; public bool RestartOnCrash = true; public bool EnableHeartbeat = true; diff --git a/IO/ConsoleUtil.cs b/IO/ConsoleUtil.cs index 383b69b..3416fbc 100644 --- a/IO/ConsoleUtil.cs +++ b/IO/ConsoleUtil.cs @@ -1,15 +1,16 @@ -using System; +using System; using System.Globalization; using System.IO; +using System.Threading; using LocalAdmin.V2.IO.Logging; namespace LocalAdmin.V2.IO; public static class ConsoleUtil { - private static readonly char[] ToTrim = { '\n', '\r' }; + private static readonly char[] ToTrim = ['\n', '\r']; - private static readonly object Lck = new object(); + private static readonly Lock Lck = new(); private static string? _liveTimestampPadding, _logsTimestampPadding; diff --git a/IO/ExitHandlers/AppDomainHandler.cs b/IO/ExitHandlers/AppDomainHandler.cs index 0e34994..65bca51 100644 --- a/IO/ExitHandlers/AppDomainHandler.cs +++ b/IO/ExitHandlers/AppDomainHandler.cs @@ -1,10 +1,10 @@ -using System; +using System; namespace LocalAdmin.V2.IO.ExitHandlers; internal sealed class AppDomainHandler : IExitHandler { - public static readonly AppDomainHandler Handler = new AppDomainHandler(); + public static readonly AppDomainHandler Handler = new(); public void Setup() { @@ -15,8 +15,7 @@ public void Setup() private static void Exit(object? sender, EventArgs e) { - if (Core.LocalAdmin.Singleton != null) - Core.LocalAdmin.Singleton.Exit(0); + Core.LocalAdmin.Singleton?.Exit(0); } private static void DomainUnload(object? sender, EventArgs e) @@ -38,15 +37,13 @@ private static void UnhandledException(object sender, UnhandledExceptionEventArg { ConsoleUtil.WriteLine($"Unhandled Exception: {ex}", ConsoleColor.Red); - if (Core.LocalAdmin.Singleton != null) - Core.LocalAdmin.Singleton.Exit(ex.HResult); + Core.LocalAdmin.Singleton?.Exit(ex.HResult); } else { ConsoleUtil.WriteLine("Unhandled Exception!", ConsoleColor.Red); - if (Core.LocalAdmin.Singleton != null) - Core.LocalAdmin.Singleton.Exit(1); + Core.LocalAdmin.Singleton?.Exit(1); } } } \ No newline at end of file diff --git a/IO/ExitHandlers/ProcessHandler.cs b/IO/ExitHandlers/ProcessHandler.cs index 2fd8f54..14a9735 100644 --- a/IO/ExitHandlers/ProcessHandler.cs +++ b/IO/ExitHandlers/ProcessHandler.cs @@ -1,11 +1,11 @@ -using System; +using System; using System.Diagnostics; namespace LocalAdmin.V2.IO.ExitHandlers; internal sealed class ProcessHandler : IExitHandler { - public static readonly ProcessHandler Handler = new ProcessHandler(); + public static readonly ProcessHandler Handler = new(); public void Setup() { diff --git a/IO/ExitHandlers/UnixHandler.cs b/IO/ExitHandlers/UnixHandler.cs index 1e02452..4cab3ef 100644 --- a/IO/ExitHandlers/UnixHandler.cs +++ b/IO/ExitHandlers/UnixHandler.cs @@ -1,37 +1,48 @@ -#if LINUX_SIGNALS -using Mono.Unix; -using Mono.Unix.Native; using System; -using System.Threading; +using System.Runtime.InteropServices; namespace LocalAdmin.V2.IO.ExitHandlers { /// /// Native signal processing on Unix systems. /// - internal sealed class UnixHandler : IExitHandler + internal sealed class UnixHandler : IExitHandler, IDisposable { - public static readonly UnixHandler Handler = new UnixHandler(); - private static readonly UnixSignal[] Signals = { - new UnixSignal(Signum.SIGINT), // CTRL + C pressed - new UnixSignal(Signum.SIGTERM), // Sending KILL - new UnixSignal(Signum.SIGUSR1), - new UnixSignal(Signum.SIGUSR2), - new UnixSignal(Signum.SIGHUP) // Terminal is closed - }; - + public static readonly UnixHandler Handler = new(); + private PosixSignalRegistration[]? _signals; public void Setup() { - new Thread(() => + Dispose(); + + Action handler = SignalHandler; + _signals = + [ + PosixSignalRegistration.Create(PosixSignal.SIGINT, handler), // CTRL + C pressed + PosixSignalRegistration.Create(PosixSignal.SIGTERM, handler), // Sending KILL + PosixSignalRegistration.Create((PosixSignal)10, handler), // SIGUSR1 + PosixSignalRegistration.Create((PosixSignal)12, handler), // SIGUSR1 + PosixSignalRegistration.Create(PosixSignal.SIGHUP, handler), // Terminal is closed + PosixSignalRegistration.Create(PosixSignal.SIGQUIT, handler) // QUIT pressed + ]; + } + + private static void SignalHandler(PosixSignalContext obj) + { + if (Core.LocalAdmin.Singleton == null) + Environment.Exit(0); + else + Core.LocalAdmin.HandleExitSignal(); + } + + public void Dispose() + { + foreach (PosixSignalRegistration signal in _signals.AsSpan()) { - // Blocking operation with infinite expectation of any signal - UnixSignal.WaitAny(Signals, -1); - if (Core.LocalAdmin.Singleton == null) - Environment.Exit(0); - else Core.LocalAdmin.HandleExitSignal(); - }).Start(); + signal.Dispose(); + } + + _signals = null; } } -} -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/IO/ExitHandlers/WindowsHandler.cs b/IO/ExitHandlers/WindowsHandler.cs index f65413e..ed0631b 100644 --- a/IO/ExitHandlers/WindowsHandler.cs +++ b/IO/ExitHandlers/WindowsHandler.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel; using System.Runtime.InteropServices; @@ -7,45 +7,40 @@ namespace LocalAdmin.V2.IO.ExitHandlers; /// /// Native signal processing on Windows NT systems. /// -internal sealed class WindowsHandler : IExitHandler +internal sealed unsafe partial class WindowsHandler : IExitHandler { - public static readonly WindowsHandler Handler = new WindowsHandler(); - - // .Net Core sometimes crashes when the delegate isn't in a field - private static readonly HandlerRoutine Routine = OnNativeSignal; + public static readonly WindowsHandler Handler = new(); public void Setup() { - if (!SetConsoleCtrlHandler(Routine, true)) + if (SetConsoleCtrlHandler(&OnNativeSignal, 1) != 0) { throw new Win32Exception(); } } - private static bool OnNativeSignal(CtrlTypes ctrl) + [UnmanagedCallersOnly] + private static int OnNativeSignal(CtrlTypes ctrl) { if (Core.LocalAdmin.Singleton == null) Environment.Exit(0); - else Core.LocalAdmin.HandleExitSignal(); + else + Core.LocalAdmin.HandleExitSignal(); - return true; + return 1; } -#region Native - - [DllImport("Kernel32", SetLastError = true)] - private static extern bool SetConsoleCtrlHandler(HandlerRoutine handler, bool add); + #region Native + [LibraryImport("Kernel32", SetLastError = true)] + private static partial int SetConsoleCtrlHandler(delegate* unmanaged handler, int add); - private delegate bool HandlerRoutine(CtrlTypes ctrlType); - - private enum CtrlTypes + private enum CtrlTypes : uint { - CTRL_C_EVENT, - CTRL_BREAK_EVENT, - CTRL_CLOSE_EVENT, - CTRL_LOGOFF_EVENT, - CTRL_SHUTDOWN_EVENT + CtrlCEvent = 0, + CtrlBreakEvent = 1, + CtrlCloseEvent = 2, + CtrlLogoffEvent = 5, + CtrlShutdownEvent = 6 } - -#endregion + #endregion } \ No newline at end of file diff --git a/IO/FileUtils.cs b/IO/FileUtils.cs index d2264ff..225678f 100644 --- a/IO/FileUtils.cs +++ b/IO/FileUtils.cs @@ -1,24 +1,96 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; using System.IO; +using System.Text.Json; +using System.Text.Json.Serialization.Metadata; +using System.Threading.Tasks; namespace LocalAdmin.V2.IO; internal static class FileUtils { - internal static bool DeleteIfExists(string path) + public static async ValueTask OpenAsync(string path, FileMode mode, FileAccess access, FileShare share, uint timeout = 2000) { - if (!File.Exists(path)) - return false; + const int retryTime = 50; + long count = Math.DivRem(timeout, retryTime, out long remainder) - (remainder == 0 ? 1 : 0); + for (long i = 0; i < count; i++) + { + try + { + return OpenCore(path, mode, access, share); + } + catch + { + await Task.Delay(retryTime); + } + } + return OpenCore(path, mode, access, share); + + static FileStream OpenCore(string path, FileMode mode, FileAccess access, FileShare share) + { + return new FileStream(path, mode, access, share, 4096, FileOptions.Asynchronous); + } + } + + public static async ValueTask TryOpenAsync(string path, FileAccess access, FileShare share, uint timeout = 2000) + { + try + { + return await OpenAsync(path, FileMode.Open, access, share, timeout); + } + catch (Exception ex) when (ex is FileNotFoundException or DirectoryNotFoundException) + { + return null; + } + } + + public static async ValueTask TryReadTextAsync(string path, FileShare share, uint timeout = 2000) + { + await using FileStream? stream = await TryOpenAsync(path, FileAccess.Read, share, timeout); + if (stream == null) + return null; + using StreamReader reader = new(stream); + return await reader.ReadToEndAsync(); + } + public static async ValueTask?> TryReadLinesAsync(string path, FileShare share, uint timeout = 2000) + { + string? text = await TryReadTextAsync(path, share, timeout); + if (text == null) + return null; + + List lines = []; + foreach (ReadOnlySpan line in text.AsSpan().EnumerateLines()) + lines.Add(line.ToString()); + return lines; + } + + public static async ValueTask TryReadJsonAsync(string path, FileShare share, JsonTypeInfo json, uint timeout = 2000) where T : class + { + await using FileStream? stream = await TryOpenAsync(path, FileAccess.Read, share, timeout); + if (stream == null) + return null; + return await JsonSerializer.DeserializeAsync(stream, json); + } + + public static bool DeleteIfExists(string path) + { + bool existed = File.Exists(path); File.Delete(path); - return true; + return existed; } - internal static bool DeleteDirectoryIfExists(string path) + public static bool DeleteDirectoryIfExists(string path) { - if (!Directory.Exists(path)) + try + { + Directory.Delete(path, true); + return true; + } + catch (DirectoryNotFoundException) + { return false; - - Directory.Delete(path, true); - return true; + } } } \ No newline at end of file diff --git a/IO/JsonFile.cs b/IO/JsonFile.cs deleted file mode 100644 index 7c59b7a..0000000 --- a/IO/JsonFile.cs +++ /dev/null @@ -1,96 +0,0 @@ -using System; -using System.IO; -using System.Text; -using System.Threading.Tasks; -using Utf8Json; - -namespace LocalAdmin.V2.IO; - -internal static class JsonFile -{ - internal static async Task Load(string path, uint timeout = 2000, bool keepLock = false) - { - try - { - bool lockGranted = await LockFile(path, timeout); - var result = await Task.FromResult(JsonSerializer.Deserialize(await File.ReadAllTextAsync(path, Encoding.UTF8))); - - if (lockGranted && !keepLock) - UnlockFile(path); - - return result; - } - catch (Exception e) - { - ConsoleUtil.WriteLine($"Failed to load file {path}. Exception: {e.Message}", ConsoleColor.Red); - ConsoleUtil.WriteLine($"Stack trace: {e.StackTrace}"); - - if (e.InnerException != null) - { - ConsoleUtil.WriteLine($"Inner exception: {e.InnerException.Message}"); - ConsoleUtil.WriteLine($"Inner exception: {e.InnerException.StackTrace}"); - } - - return default; - } - } - - internal static async Task TrySave(this T obj, string path, uint timeout = 2000, bool forceUnlock = false) where T : class - { - try - { - bool lockGranted = await LockFile(path, timeout); - await File.WriteAllTextAsync(path, JsonSerializer.ToJsonString(obj), Encoding.UTF8); - - if (lockGranted || forceUnlock) - UnlockFile(path); - - return true; - } - catch (Exception e) - { - ConsoleUtil.WriteLine($"Failed to save file {path}. Exception: {e.Message}", ConsoleColor.Red); - return false; - } - } - - private static async Task LockFile(string path, uint timeout) - { - try - { - path += ".lock"; - timeout /= 10; - - if (timeout < 1) - timeout = 1; - - for (var i = 0; i < timeout && File.Exists(path); i++) - await Task.Delay(10); - - if (File.Exists(path)) - return false; - - var fs = File.Create(path); - fs.Close(); - return true; - } - catch (Exception e) - { - ConsoleUtil.WriteLine($"Failed to process lock {path}. Exception: {e.Message}", ConsoleColor.Red); - return false; - } - } - - internal static void UnlockFile(string path) - { - try - { - path += ".lock"; - FileUtils.DeleteIfExists(path); - } - catch (Exception e) - { - ConsoleUtil.WriteLine($"Failed to process unlock {path}. Exception: {e.Message}", ConsoleColor.Red); - } - } -} \ No newline at end of file diff --git a/IO/Logging/LogCleaner.cs b/IO/Logging/LogCleaner.cs index a127121..f1a496e 100644 --- a/IO/Logging/LogCleaner.cs +++ b/IO/Logging/LogCleaner.cs @@ -100,9 +100,8 @@ private static void Cleanup() Core.LocalAdmin.Configuration.RoundLogsCompressionThresholdDays) { stage = "Compression - p2"; - var p2 = root + "LA-ToCompress-" + d + Path.DirectorySeparatorChar; - if (!Directory.Exists(p2)) - Directory.CreateDirectory(p2); + var p2 = $"{root}LA-ToCompress-{d}{Path.DirectorySeparatorChar}"; + Directory.CreateDirectory(p2); stage = "Compression - moving"; File.Move(file, p2 + name); @@ -169,17 +168,17 @@ private static void Cleanup() if (name.Length != 24 || !name.StartsWith("LA-ToCompress-", StringComparison.Ordinal)) continue; - var d = root + "Round Logs Archive " + name.Substring(14); + var d = $"{root}Round Logs Archive {name[14..]}"; if (Directory.Exists(d)) { - ConsoleUtil.WriteLine($"[Log Maintenance] Failed to compress old round log directory. Target directory already exists.", ConsoleColor.Red); + ConsoleUtil.WriteLine("[Log Maintenance] Failed to compress old round log directory. Target directory already exists.", ConsoleColor.Red); continue; } - if (File.Exists(d + ".zip")) + if (File.Exists($"{d}.zip")) { - ConsoleUtil.WriteLine($"[Log Maintenance] Failed to compress old round log directory. Target ZIP file already exists.", ConsoleColor.Red); + ConsoleUtil.WriteLine("[Log Maintenance] Failed to compress old round log directory. Target ZIP file already exists.", ConsoleColor.Red); continue; } @@ -187,7 +186,7 @@ private static void Cleanup() Directory.Move(dir, d); stage = "Compressing directory"; - ZipFile.CreateFromDirectory(d, d + ".zip", CompressionLevel.Optimal, true, Encoding.UTF8); + ZipFile.CreateFromDirectory(d, $"{d}.zip", CompressionLevel.Optimal, true, Encoding.UTF8); stage = "Removing uncompressed directory"; Directory.Delete(d, true); @@ -227,7 +226,7 @@ private static void Cleanup() CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out var date)) continue; - var diff = (now - date); + var diff = now - date; stage = "File name processed"; if (diff.TotalDays > Core.LocalAdmin.Configuration.LaLogsExpirationDays) diff --git a/IO/Logging/Logger.cs b/IO/Logging/Logger.cs index b0c23aa..c666b85 100644 --- a/IO/Logging/Logger.cs +++ b/IO/Logging/Logger.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Text; @@ -20,12 +20,11 @@ public static void Initialize() EndLogging(); string dir = Core.LocalAdmin.LaLogsPath ?? PathManager.GameUserDataRoot + LogFolderName + Path.DirectorySeparatorChar + Core.LocalAdmin.GamePort + Path.DirectorySeparatorChar; - if (!Directory.Exists(dir)) - Directory.CreateDirectory(dir); + Directory.CreateDirectory(dir); _totalLength = 0; _totalEntries = 0; - _logPath = dir + $"LocalAdmin Log {DateTime.Now:yyyy-MM-dd HH.mm.ss}.txt"; + _logPath = $"{dir}LocalAdmin Log {DateTime.Now:yyyy-MM-dd HH.mm.ss}.txt"; _logging = true; Log($"{ConsoleUtil.GetLogsTimestamp()} Logging started."); @@ -83,7 +82,7 @@ private static void AppendLog(string text, bool flush = false, bool bypass = fal } catch (Exception e) { - Console.Write("Failed to write log: " + e.Message); + Console.Write($"Failed to write log: {e.Message}"); } } diff --git a/IO/PathManager.cs b/IO/PathManager.cs index 0d1effc..c7d8bec 100644 --- a/IO/PathManager.cs +++ b/IO/PathManager.cs @@ -1,6 +1,5 @@ using System; using System.IO; -using System.Runtime.InteropServices; namespace LocalAdmin.V2.IO; @@ -19,12 +18,12 @@ static PathManager() ProcessHostPolicy(); GameUserDataRoot = _configDirOverride - ? "AppData" + Path.DirectorySeparatorChar - : GetSpecialFolderPath() + "SCP Secret Laboratory" + Path.DirectorySeparatorChar; + ? $"AppData{Path.DirectorySeparatorChar}" + : $"{GetSpecialFolderPath()}SCP Secret Laboratory{Path.DirectorySeparatorChar}"; ConfigPath = $"{GameUserDataRoot}config{Path.DirectorySeparatorChar}"; - InternalJsonDataPath = ConfigPath + "localadmin_internal_data.json"; + InternalJsonDataPath = $"{ConfigPath}localadmin_internal_data.json"; } private static string GetSpecialFolderPath() @@ -46,17 +45,18 @@ private static string GetSpecialFolderPath() CorrectPathFound = true; if (OperatingSystem.IsLinux()) - return path + Path.DirectorySeparatorChar + ".config" + Path.DirectorySeparatorChar; + return $"{path}{Path.DirectorySeparatorChar}.config{Path.DirectorySeparatorChar}"; if (OperatingSystem.IsWindows()) - return path + Path.DirectorySeparatorChar + "AppData" + Path.DirectorySeparatorChar + "Roaming" + Path.DirectorySeparatorChar; + return + $"{path}{Path.DirectorySeparatorChar}AppData{Path.DirectorySeparatorChar}Roaming{Path.DirectorySeparatorChar}"; ConsoleUtil.WriteLine("Failed to get special folder path - unsupported platform!", ConsoleColor.Red); throw new PlatformNotSupportedException(); } CorrectPathFound = false; - ConsoleUtil.WriteLine($"Failed to get special folder path - it's always null or empty!", ConsoleColor.Red); + ConsoleUtil.WriteLine("Failed to get special folder path - it's always null or empty!", ConsoleColor.Red); return string.Empty; } diff --git a/IO/Sha.cs b/IO/Sha.cs index 5ac5553..c3333ed 100644 --- a/IO/Sha.cs +++ b/IO/Sha.cs @@ -1,6 +1,6 @@ +using System; using System.IO; using System.Security.Cryptography; -using System.Text; namespace LocalAdmin.V2.IO; @@ -9,15 +9,6 @@ internal static class Sha internal static string Sha256File(string path) { using var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - using var sha256 = SHA256.Create(); - return HashToString(sha256.ComputeHash(fs)); - } - - private static string HashToString(byte[] hash) - { - var sb = new StringBuilder(); - foreach (var t in hash) - sb.Append(t.ToString("X2")); - return sb.ToString(); + return Convert.ToHexString(SHA256.HashData(fs)); } } \ No newline at end of file diff --git a/JSON/JsonGenerated.cs b/JSON/JsonGenerated.cs index 0bc829f..680abb0 100644 --- a/JSON/JsonGenerated.cs +++ b/JSON/JsonGenerated.cs @@ -1,1029 +1,12 @@ -#pragma warning disable 618 -#pragma warning disable 612 -#pragma warning disable 414 -#pragma warning disable 168 - -namespace Utf8Json.Resolvers -{ - using System; - using Utf8Json; - - public class GeneratedResolver : global::Utf8Json.IJsonFormatterResolver - { - public static readonly global::Utf8Json.IJsonFormatterResolver Instance = new GeneratedResolver(); - - GeneratedResolver() - { - - } - - public global::Utf8Json.IJsonFormatter GetFormatter() - { - return FormatterCache.formatter; - } - - static class FormatterCache - { - public static readonly global::Utf8Json.IJsonFormatter formatter; - - static FormatterCache() - { - var f = GeneratedResolverGetFormatterHelper.GetFormatter(typeof(T)); - if (f != null) - { - formatter = (global::Utf8Json.IJsonFormatter)f; - } - } - } - } - - internal static class GeneratedResolverGetFormatterHelper - { - static readonly global::System.Collections.Generic.Dictionary lookup; - - static GeneratedResolverGetFormatterHelper() - { - lookup = new global::System.Collections.Generic.Dictionary(14) - { - {typeof(global::System.Collections.Generic.Dictionary), 0 }, - {typeof(global::System.Collections.Generic.Dictionary), 1 }, - {typeof(global::System.Collections.Generic.Dictionary), 2 }, - {typeof(global::System.Collections.Generic.List), 3 }, - {typeof(global::System.Collections.Generic.Dictionary), 4 }, - {typeof(global::System.Collections.Generic.List), 5 }, - {typeof(global::LocalAdmin.V2.Core.PluginVersionCache), 6 }, - {typeof(global::LocalAdmin.V2.Core.PluginAlias), 7 }, - {typeof(global::LocalAdmin.V2.Core.DataJson), 8 }, - {typeof(global::LocalAdmin.V2.PluginsManager.InstalledPlugin), 9 }, - {typeof(global::LocalAdmin.V2.PluginsManager.Dependency), 10 }, - {typeof(global::LocalAdmin.V2.PluginsManager.ServerPluginsConfig), 11 }, - {typeof(global::LocalAdmin.V2.PluginsManager.GitHubReleaseAsset), 12 }, - {typeof(global::LocalAdmin.V2.PluginsManager.GitHubRelease), 13 }, - }; - } - - internal static object GetFormatter(Type t) - { - int key; - if (!lookup.TryGetValue(t, out key)) return null; - - switch (key) - { - case 0: return new global::Utf8Json.Formatters.DictionaryFormatter(); - case 1: return new global::Utf8Json.Formatters.DictionaryFormatter(); - case 2: return new global::Utf8Json.Formatters.DictionaryFormatter(); - case 3: return new global::Utf8Json.Formatters.ListFormatter(); - case 4: return new global::Utf8Json.Formatters.DictionaryFormatter(); - case 5: return new global::Utf8Json.Formatters.ListFormatter(); - case 6: return new Utf8Json.Formatters.LocalAdmin.V2.Core.PluginVersionCacheFormatter(); - case 7: return new Utf8Json.Formatters.LocalAdmin.V2.Core.PluginAliasFormatter(); - case 8: return new Utf8Json.Formatters.LocalAdmin.V2.Core.DataJsonFormatter(); - case 9: return new Utf8Json.Formatters.LocalAdmin.V2.PluginsManager.InstalledPluginFormatter(); - case 10: return new Utf8Json.Formatters.LocalAdmin.V2.PluginsManager.DependencyFormatter(); - case 11: return new Utf8Json.Formatters.LocalAdmin.V2.PluginsManager.ServerPluginsConfigFormatter(); - case 12: return new Utf8Json.Formatters.LocalAdmin.V2.PluginsManager.GitHubReleaseAssetFormatter(); - case 13: return new Utf8Json.Formatters.LocalAdmin.V2.PluginsManager.GitHubReleaseFormatter(); - default: return null; - } - } - } -} - -#pragma warning disable 168 -#pragma warning restore 414 -#pragma warning restore 618 -#pragma warning restore 612 - -#pragma warning disable 618 -#pragma warning disable 612 -#pragma warning disable 414 -#pragma warning disable 219 -#pragma warning disable 168 - -namespace Utf8Json.Formatters.LocalAdmin.V2.Core -{ - using System; - using Utf8Json; - - - public sealed class PluginVersionCacheFormatter : global::Utf8Json.IJsonFormatter - { - readonly global::Utf8Json.Internal.AutomataDictionary ____keyMapping; - readonly byte[][] ____stringByteKeys; - - public PluginVersionCacheFormatter() - { - this.____keyMapping = new global::Utf8Json.Internal.AutomataDictionary() - { - { JsonWriter.GetEncodedPropertyNameWithoutQuotation("Version"), 0}, - { JsonWriter.GetEncodedPropertyNameWithoutQuotation("ReleaseId"), 1}, - { JsonWriter.GetEncodedPropertyNameWithoutQuotation("PublishmentTime"), 2}, - { JsonWriter.GetEncodedPropertyNameWithoutQuotation("LastRefreshed"), 3}, - { JsonWriter.GetEncodedPropertyNameWithoutQuotation("DllDownloadUrl"), 4}, - { JsonWriter.GetEncodedPropertyNameWithoutQuotation("DependenciesDownloadUrl"), 5}, - }; - - this.____stringByteKeys = new byte[][] - { - JsonWriter.GetEncodedPropertyNameWithBeginObject("Version"), - JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator("ReleaseId"), - JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator("PublishmentTime"), - JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator("LastRefreshed"), - JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator("DllDownloadUrl"), - JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator("DependenciesDownloadUrl"), - - }; - } - - public void Serialize(ref JsonWriter writer, global::LocalAdmin.V2.Core.PluginVersionCache value, global::Utf8Json.IJsonFormatterResolver formatterResolver) - { - - - writer.WriteRaw(this.____stringByteKeys[0]); - writer.WriteString(value.Version); - writer.WriteRaw(this.____stringByteKeys[1]); - writer.WriteUInt32(value.ReleaseId); - writer.WriteRaw(this.____stringByteKeys[2]); - formatterResolver.GetFormatterWithVerify().Serialize(ref writer, value.PublishmentTime, formatterResolver); - writer.WriteRaw(this.____stringByteKeys[3]); - formatterResolver.GetFormatterWithVerify().Serialize(ref writer, value.LastRefreshed, formatterResolver); - writer.WriteRaw(this.____stringByteKeys[4]); - writer.WriteString(value.DllDownloadUrl); - writer.WriteRaw(this.____stringByteKeys[5]); - formatterResolver.GetFormatterWithVerify().Serialize(ref writer, value.DependenciesDownloadUrl, formatterResolver); - - writer.WriteEndObject(); - } - - public global::LocalAdmin.V2.Core.PluginVersionCache Deserialize(ref JsonReader reader, global::Utf8Json.IJsonFormatterResolver formatterResolver) - { - if (reader.ReadIsNull()) - { - throw new InvalidOperationException("typecode is null, struct not supported"); - } - - - var __Version__ = default(string); - var __Version__b__ = false; - var __ReleaseId__ = default(uint); - var __ReleaseId__b__ = false; - var __PublishmentTime__ = default(global::System.DateTime); - var __PublishmentTime__b__ = false; - var __LastRefreshed__ = default(global::System.DateTime); - var __LastRefreshed__b__ = false; - var __DllDownloadUrl__ = default(string); - var __DllDownloadUrl__b__ = false; - var __DependenciesDownloadUrl__ = default(string?); - var __DependenciesDownloadUrl__b__ = false; - - var ____count = 0; - reader.ReadIsBeginObjectWithVerify(); - while (!reader.ReadIsEndObjectWithSkipValueSeparator(ref ____count)) - { - var stringKey = reader.ReadPropertyNameSegmentRaw(); - int key; - if (!____keyMapping.TryGetValueSafe(stringKey, out key)) - { - reader.ReadNextBlock(); - goto NEXT_LOOP; - } - - switch (key) - { - case 0: - __Version__ = reader.ReadString(); - __Version__b__ = true; - break; - case 1: - __ReleaseId__ = reader.ReadUInt32(); - __ReleaseId__b__ = true; - break; - case 2: - __PublishmentTime__ = formatterResolver.GetFormatterWithVerify().Deserialize(ref reader, formatterResolver); - __PublishmentTime__b__ = true; - break; - case 3: - __LastRefreshed__ = formatterResolver.GetFormatterWithVerify().Deserialize(ref reader, formatterResolver); - __LastRefreshed__b__ = true; - break; - case 4: - __DllDownloadUrl__ = reader.ReadString(); - __DllDownloadUrl__b__ = true; - break; - case 5: - __DependenciesDownloadUrl__ = formatterResolver.GetFormatterWithVerify().Deserialize(ref reader, formatterResolver); - __DependenciesDownloadUrl__b__ = true; - break; - default: - reader.ReadNextBlock(); - break; - } - - NEXT_LOOP: - continue; - } - - var ____result = new global::LocalAdmin.V2.Core.PluginVersionCache(__Version__, __ReleaseId__, __PublishmentTime__, __LastRefreshed__, __DllDownloadUrl__, __DependenciesDownloadUrl__); - if(__Version__b__) ____result.Version = __Version__; - if(__ReleaseId__b__) ____result.ReleaseId = __ReleaseId__; - if(__PublishmentTime__b__) ____result.PublishmentTime = __PublishmentTime__; - if(__LastRefreshed__b__) ____result.LastRefreshed = __LastRefreshed__; - if(__DllDownloadUrl__b__) ____result.DllDownloadUrl = __DllDownloadUrl__; - if(__DependenciesDownloadUrl__b__) ____result.DependenciesDownloadUrl = __DependenciesDownloadUrl__; - - return ____result; - } - } - - - public sealed class PluginAliasFormatter : global::Utf8Json.IJsonFormatter - { - readonly global::Utf8Json.Internal.AutomataDictionary ____keyMapping; - readonly byte[][] ____stringByteKeys; - - public PluginAliasFormatter() - { - this.____keyMapping = new global::Utf8Json.Internal.AutomataDictionary() - { - { JsonWriter.GetEncodedPropertyNameWithoutQuotation("Repository"), 0}, - { JsonWriter.GetEncodedPropertyNameWithoutQuotation("Flags"), 1}, - }; - - this.____stringByteKeys = new byte[][] - { - JsonWriter.GetEncodedPropertyNameWithBeginObject("Repository"), - JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator("Flags"), - - }; - } - - public void Serialize(ref JsonWriter writer, global::LocalAdmin.V2.Core.PluginAlias value, global::Utf8Json.IJsonFormatterResolver formatterResolver) - { - - - writer.WriteRaw(this.____stringByteKeys[0]); - writer.WriteString(value.Repository); - writer.WriteRaw(this.____stringByteKeys[1]); - writer.WriteByte(value.Flags); - - writer.WriteEndObject(); - } - - public global::LocalAdmin.V2.Core.PluginAlias Deserialize(ref JsonReader reader, global::Utf8Json.IJsonFormatterResolver formatterResolver) - { - if (reader.ReadIsNull()) - { - throw new InvalidOperationException("typecode is null, struct not supported"); - } - - - var __Repository__ = default(string); - var __Repository__b__ = false; - var __Flags__ = default(byte); - var __Flags__b__ = false; - - var ____count = 0; - reader.ReadIsBeginObjectWithVerify(); - while (!reader.ReadIsEndObjectWithSkipValueSeparator(ref ____count)) - { - var stringKey = reader.ReadPropertyNameSegmentRaw(); - int key; - if (!____keyMapping.TryGetValueSafe(stringKey, out key)) - { - reader.ReadNextBlock(); - goto NEXT_LOOP; - } - - switch (key) - { - case 0: - __Repository__ = reader.ReadString(); - __Repository__b__ = true; - break; - case 1: - __Flags__ = reader.ReadByte(); - __Flags__b__ = true; - break; - default: - reader.ReadNextBlock(); - break; - } - - NEXT_LOOP: - continue; - } - - var ____result = new global::LocalAdmin.V2.Core.PluginAlias(__Repository__, __Flags__); - - return ____result; - } - } - - - public sealed class DataJsonFormatter : global::Utf8Json.IJsonFormatter - { - readonly global::Utf8Json.Internal.AutomataDictionary ____keyMapping; - readonly byte[][] ____stringByteKeys; - - public DataJsonFormatter() - { - this.____keyMapping = new global::Utf8Json.Internal.AutomataDictionary() - { - { JsonWriter.GetEncodedPropertyNameWithoutQuotation("GitHubPersonalAccessToken"), 0}, - { JsonWriter.GetEncodedPropertyNameWithoutQuotation("EulaAccepted"), 1}, - { JsonWriter.GetEncodedPropertyNameWithoutQuotation("PluginManagerWarningDismissed"), 2}, - { JsonWriter.GetEncodedPropertyNameWithoutQuotation("LastPluginAliasesRefresh"), 3}, - { JsonWriter.GetEncodedPropertyNameWithoutQuotation("PluginVersionCache"), 4}, - { JsonWriter.GetEncodedPropertyNameWithoutQuotation("PluginAliases"), 5}, - }; - - this.____stringByteKeys = new byte[][] - { - JsonWriter.GetEncodedPropertyNameWithBeginObject("GitHubPersonalAccessToken"), - JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator("EulaAccepted"), - JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator("PluginManagerWarningDismissed"), - JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator("LastPluginAliasesRefresh"), - JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator("PluginVersionCache"), - JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator("PluginAliases"), - - }; - } - - public void Serialize(ref JsonWriter writer, global::LocalAdmin.V2.Core.DataJson value, global::Utf8Json.IJsonFormatterResolver formatterResolver) - { - if (value == null) - { - writer.WriteNull(); - return; - } - - - writer.WriteRaw(this.____stringByteKeys[0]); - formatterResolver.GetFormatterWithVerify().Serialize(ref writer, value.GitHubPersonalAccessToken, formatterResolver); - writer.WriteRaw(this.____stringByteKeys[1]); - formatterResolver.GetFormatterWithVerify().Serialize(ref writer, value.EulaAccepted, formatterResolver); - writer.WriteRaw(this.____stringByteKeys[2]); - writer.WriteBoolean(value.PluginManagerWarningDismissed); - writer.WriteRaw(this.____stringByteKeys[3]); - formatterResolver.GetFormatterWithVerify().Serialize(ref writer, value.LastPluginAliasesRefresh, formatterResolver); - writer.WriteRaw(this.____stringByteKeys[4]); - formatterResolver.GetFormatterWithVerify>().Serialize(ref writer, value.PluginVersionCache, formatterResolver); - writer.WriteRaw(this.____stringByteKeys[5]); - formatterResolver.GetFormatterWithVerify>().Serialize(ref writer, value.PluginAliases, formatterResolver); - - writer.WriteEndObject(); - } - - public global::LocalAdmin.V2.Core.DataJson Deserialize(ref JsonReader reader, global::Utf8Json.IJsonFormatterResolver formatterResolver) - { - if (reader.ReadIsNull()) - { - return null; - } - - - var __GitHubPersonalAccessToken__ = default(string?); - var __GitHubPersonalAccessToken__b__ = false; - var __EulaAccepted__ = default(global::System.DateTime?); - var __EulaAccepted__b__ = false; - var __PluginManagerWarningDismissed__ = default(bool); - var __PluginManagerWarningDismissed__b__ = false; - var __LastPluginAliasesRefresh__ = default(global::System.DateTime?); - var __LastPluginAliasesRefresh__b__ = false; - var __PluginVersionCache__ = default(global::System.Collections.Generic.Dictionary); - var __PluginVersionCache__b__ = false; - var __PluginAliases__ = default(global::System.Collections.Generic.Dictionary); - var __PluginAliases__b__ = false; - - var ____count = 0; - reader.ReadIsBeginObjectWithVerify(); - while (!reader.ReadIsEndObjectWithSkipValueSeparator(ref ____count)) - { - var stringKey = reader.ReadPropertyNameSegmentRaw(); - int key; - if (!____keyMapping.TryGetValueSafe(stringKey, out key)) - { - reader.ReadNextBlock(); - goto NEXT_LOOP; - } - - switch (key) - { - case 0: - __GitHubPersonalAccessToken__ = formatterResolver.GetFormatterWithVerify().Deserialize(ref reader, formatterResolver); - __GitHubPersonalAccessToken__b__ = true; - break; - case 1: - __EulaAccepted__ = formatterResolver.GetFormatterWithVerify().Deserialize(ref reader, formatterResolver); - __EulaAccepted__b__ = true; - break; - case 2: - __PluginManagerWarningDismissed__ = reader.ReadBoolean(); - __PluginManagerWarningDismissed__b__ = true; - break; - case 3: - __LastPluginAliasesRefresh__ = formatterResolver.GetFormatterWithVerify().Deserialize(ref reader, formatterResolver); - __LastPluginAliasesRefresh__b__ = true; - break; - case 4: - __PluginVersionCache__ = formatterResolver.GetFormatterWithVerify>().Deserialize(ref reader, formatterResolver); - __PluginVersionCache__b__ = true; - break; - case 5: - __PluginAliases__ = formatterResolver.GetFormatterWithVerify>().Deserialize(ref reader, formatterResolver); - __PluginAliases__b__ = true; - break; - default: - reader.ReadNextBlock(); - break; - } - - NEXT_LOOP: - continue; - } - - var ____result = new global::LocalAdmin.V2.Core.DataJson(__GitHubPersonalAccessToken__, __EulaAccepted__, __PluginManagerWarningDismissed__, __LastPluginAliasesRefresh__, __PluginVersionCache__, __PluginAliases__); - if(__GitHubPersonalAccessToken__b__) ____result.GitHubPersonalAccessToken = __GitHubPersonalAccessToken__; - if(__EulaAccepted__b__) ____result.EulaAccepted = __EulaAccepted__; - if(__PluginManagerWarningDismissed__b__) ____result.PluginManagerWarningDismissed = __PluginManagerWarningDismissed__; - if(__LastPluginAliasesRefresh__b__) ____result.LastPluginAliasesRefresh = __LastPluginAliasesRefresh__; - if(__PluginVersionCache__b__) ____result.PluginVersionCache = __PluginVersionCache__; - if(__PluginAliases__b__) ____result.PluginAliases = __PluginAliases__; - - return ____result; - } - } - -} - -#pragma warning disable 168 -#pragma warning restore 219 -#pragma warning restore 414 -#pragma warning restore 618 -#pragma warning restore 612 -#pragma warning disable 618 -#pragma warning disable 612 -#pragma warning disable 414 -#pragma warning disable 219 -#pragma warning disable 168 - -namespace Utf8Json.Formatters.LocalAdmin.V2.PluginsManager -{ - using System; - using Utf8Json; - - - public sealed class InstalledPluginFormatter : global::Utf8Json.IJsonFormatter - { - readonly global::Utf8Json.Internal.AutomataDictionary ____keyMapping; - readonly byte[][] ____stringByteKeys; - - public InstalledPluginFormatter() - { - this.____keyMapping = new global::Utf8Json.Internal.AutomataDictionary() - { - { JsonWriter.GetEncodedPropertyNameWithoutQuotation("TargetVersion"), 0}, - { JsonWriter.GetEncodedPropertyNameWithoutQuotation("CurrentVersion"), 1}, - { JsonWriter.GetEncodedPropertyNameWithoutQuotation("FileHash"), 2}, - { JsonWriter.GetEncodedPropertyNameWithoutQuotation("InstallationDate"), 3}, - { JsonWriter.GetEncodedPropertyNameWithoutQuotation("UpdateDate"), 4}, - }; - - this.____stringByteKeys = new byte[][] - { - JsonWriter.GetEncodedPropertyNameWithBeginObject("TargetVersion"), - JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator("CurrentVersion"), - JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator("FileHash"), - JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator("InstallationDate"), - JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator("UpdateDate"), - - }; - } - - public void Serialize(ref JsonWriter writer, global::LocalAdmin.V2.PluginsManager.InstalledPlugin value, global::Utf8Json.IJsonFormatterResolver formatterResolver) - { - if (value == null) - { - writer.WriteNull(); - return; - } - - - writer.WriteRaw(this.____stringByteKeys[0]); - formatterResolver.GetFormatterWithVerify().Serialize(ref writer, value.TargetVersion, formatterResolver); - writer.WriteRaw(this.____stringByteKeys[1]); - formatterResolver.GetFormatterWithVerify().Serialize(ref writer, value.CurrentVersion, formatterResolver); - writer.WriteRaw(this.____stringByteKeys[2]); - formatterResolver.GetFormatterWithVerify().Serialize(ref writer, value.FileHash, formatterResolver); - writer.WriteRaw(this.____stringByteKeys[3]); - formatterResolver.GetFormatterWithVerify().Serialize(ref writer, value.InstallationDate, formatterResolver); - writer.WriteRaw(this.____stringByteKeys[4]); - formatterResolver.GetFormatterWithVerify().Serialize(ref writer, value.UpdateDate, formatterResolver); - - writer.WriteEndObject(); - } - - public global::LocalAdmin.V2.PluginsManager.InstalledPlugin Deserialize(ref JsonReader reader, global::Utf8Json.IJsonFormatterResolver formatterResolver) - { - if (reader.ReadIsNull()) - { - return null; - } - - - var __TargetVersion__ = default(string?); - var __TargetVersion__b__ = false; - var __CurrentVersion__ = default(string?); - var __CurrentVersion__b__ = false; - var __FileHash__ = default(string?); - var __FileHash__b__ = false; - var __InstallationDate__ = default(global::System.DateTime); - var __InstallationDate__b__ = false; - var __UpdateDate__ = default(global::System.DateTime); - var __UpdateDate__b__ = false; - - var ____count = 0; - reader.ReadIsBeginObjectWithVerify(); - while (!reader.ReadIsEndObjectWithSkipValueSeparator(ref ____count)) - { - var stringKey = reader.ReadPropertyNameSegmentRaw(); - int key; - if (!____keyMapping.TryGetValueSafe(stringKey, out key)) - { - reader.ReadNextBlock(); - goto NEXT_LOOP; - } - - switch (key) - { - case 0: - __TargetVersion__ = formatterResolver.GetFormatterWithVerify().Deserialize(ref reader, formatterResolver); - __TargetVersion__b__ = true; - break; - case 1: - __CurrentVersion__ = formatterResolver.GetFormatterWithVerify().Deserialize(ref reader, formatterResolver); - __CurrentVersion__b__ = true; - break; - case 2: - __FileHash__ = formatterResolver.GetFormatterWithVerify().Deserialize(ref reader, formatterResolver); - __FileHash__b__ = true; - break; - case 3: - __InstallationDate__ = formatterResolver.GetFormatterWithVerify().Deserialize(ref reader, formatterResolver); - __InstallationDate__b__ = true; - break; - case 4: - __UpdateDate__ = formatterResolver.GetFormatterWithVerify().Deserialize(ref reader, formatterResolver); - __UpdateDate__b__ = true; - break; - default: - reader.ReadNextBlock(); - break; - } - - NEXT_LOOP: - continue; - } - - var ____result = new global::LocalAdmin.V2.PluginsManager.InstalledPlugin(__TargetVersion__, __CurrentVersion__, __FileHash__, __InstallationDate__, __UpdateDate__); - if(__TargetVersion__b__) ____result.TargetVersion = __TargetVersion__; - if(__CurrentVersion__b__) ____result.CurrentVersion = __CurrentVersion__; - if(__FileHash__b__) ____result.FileHash = __FileHash__; - if(__InstallationDate__b__) ____result.InstallationDate = __InstallationDate__; - if(__UpdateDate__b__) ____result.UpdateDate = __UpdateDate__; - - return ____result; - } - } - - - public sealed class DependencyFormatter : global::Utf8Json.IJsonFormatter - { - readonly global::Utf8Json.Internal.AutomataDictionary ____keyMapping; - readonly byte[][] ____stringByteKeys; - - public DependencyFormatter() - { - this.____keyMapping = new global::Utf8Json.Internal.AutomataDictionary() - { - { JsonWriter.GetEncodedPropertyNameWithoutQuotation("FileHash"), 0}, - { JsonWriter.GetEncodedPropertyNameWithoutQuotation("InstallationDate"), 1}, - { JsonWriter.GetEncodedPropertyNameWithoutQuotation("UpdateDate"), 2}, - { JsonWriter.GetEncodedPropertyNameWithoutQuotation("ManuallyInstalled"), 3}, - { JsonWriter.GetEncodedPropertyNameWithoutQuotation("InstalledByPlugins"), 4}, - }; - - this.____stringByteKeys = new byte[][] - { - JsonWriter.GetEncodedPropertyNameWithBeginObject("FileHash"), - JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator("InstallationDate"), - JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator("UpdateDate"), - JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator("ManuallyInstalled"), - JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator("InstalledByPlugins"), - - }; - } - - public void Serialize(ref JsonWriter writer, global::LocalAdmin.V2.PluginsManager.Dependency value, global::Utf8Json.IJsonFormatterResolver formatterResolver) - { - if (value == null) - { - writer.WriteNull(); - return; - } - - - writer.WriteRaw(this.____stringByteKeys[0]); - formatterResolver.GetFormatterWithVerify().Serialize(ref writer, value.FileHash, formatterResolver); - writer.WriteRaw(this.____stringByteKeys[1]); - formatterResolver.GetFormatterWithVerify().Serialize(ref writer, value.InstallationDate, formatterResolver); - writer.WriteRaw(this.____stringByteKeys[2]); - formatterResolver.GetFormatterWithVerify().Serialize(ref writer, value.UpdateDate, formatterResolver); - writer.WriteRaw(this.____stringByteKeys[3]); - writer.WriteBoolean(value.ManuallyInstalled); - writer.WriteRaw(this.____stringByteKeys[4]); - formatterResolver.GetFormatterWithVerify>().Serialize(ref writer, value.InstalledByPlugins, formatterResolver); - - writer.WriteEndObject(); - } - - public global::LocalAdmin.V2.PluginsManager.Dependency Deserialize(ref JsonReader reader, global::Utf8Json.IJsonFormatterResolver formatterResolver) - { - if (reader.ReadIsNull()) - { - return null; - } - - - var __FileHash__ = default(string?); - var __FileHash__b__ = false; - var __InstallationDate__ = default(global::System.DateTime); - var __InstallationDate__b__ = false; - var __UpdateDate__ = default(global::System.DateTime); - var __UpdateDate__b__ = false; - var __ManuallyInstalled__ = default(bool); - var __ManuallyInstalled__b__ = false; - var __InstalledByPlugins__ = default(global::System.Collections.Generic.List); - var __InstalledByPlugins__b__ = false; - - var ____count = 0; - reader.ReadIsBeginObjectWithVerify(); - while (!reader.ReadIsEndObjectWithSkipValueSeparator(ref ____count)) - { - var stringKey = reader.ReadPropertyNameSegmentRaw(); - int key; - if (!____keyMapping.TryGetValueSafe(stringKey, out key)) - { - reader.ReadNextBlock(); - goto NEXT_LOOP; - } - - switch (key) - { - case 0: - __FileHash__ = formatterResolver.GetFormatterWithVerify().Deserialize(ref reader, formatterResolver); - __FileHash__b__ = true; - break; - case 1: - __InstallationDate__ = formatterResolver.GetFormatterWithVerify().Deserialize(ref reader, formatterResolver); - __InstallationDate__b__ = true; - break; - case 2: - __UpdateDate__ = formatterResolver.GetFormatterWithVerify().Deserialize(ref reader, formatterResolver); - __UpdateDate__b__ = true; - break; - case 3: - __ManuallyInstalled__ = reader.ReadBoolean(); - __ManuallyInstalled__b__ = true; - break; - case 4: - __InstalledByPlugins__ = formatterResolver.GetFormatterWithVerify>().Deserialize(ref reader, formatterResolver); - __InstalledByPlugins__b__ = true; - break; - default: - reader.ReadNextBlock(); - break; - } - - NEXT_LOOP: - continue; - } - - var ____result = new global::LocalAdmin.V2.PluginsManager.Dependency(__FileHash__, __InstallationDate__, __UpdateDate__, __ManuallyInstalled__, __InstalledByPlugins__); - if(__FileHash__b__) ____result.FileHash = __FileHash__; - if(__InstallationDate__b__) ____result.InstallationDate = __InstallationDate__; - if(__UpdateDate__b__) ____result.UpdateDate = __UpdateDate__; - if(__ManuallyInstalled__b__) ____result.ManuallyInstalled = __ManuallyInstalled__; - if(__InstalledByPlugins__b__) ____result.InstalledByPlugins = __InstalledByPlugins__; - - return ____result; - } - } - - - public sealed class ServerPluginsConfigFormatter : global::Utf8Json.IJsonFormatter - { - readonly global::Utf8Json.Internal.AutomataDictionary ____keyMapping; - readonly byte[][] ____stringByteKeys; - - public ServerPluginsConfigFormatter() - { - this.____keyMapping = new global::Utf8Json.Internal.AutomataDictionary() - { - { JsonWriter.GetEncodedPropertyNameWithoutQuotation("InstalledPlugins"), 0}, - { JsonWriter.GetEncodedPropertyNameWithoutQuotation("Dependencies"), 1}, - { JsonWriter.GetEncodedPropertyNameWithoutQuotation("LastUpdateCheck"), 2}, - }; - - this.____stringByteKeys = new byte[][] - { - JsonWriter.GetEncodedPropertyNameWithBeginObject("InstalledPlugins"), - JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator("Dependencies"), - JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator("LastUpdateCheck"), - - }; - } - - public void Serialize(ref JsonWriter writer, global::LocalAdmin.V2.PluginsManager.ServerPluginsConfig value, global::Utf8Json.IJsonFormatterResolver formatterResolver) - { - if (value == null) - { - writer.WriteNull(); - return; - } - - - writer.WriteRaw(this.____stringByteKeys[0]); - formatterResolver.GetFormatterWithVerify>().Serialize(ref writer, value.InstalledPlugins, formatterResolver); - writer.WriteRaw(this.____stringByteKeys[1]); - formatterResolver.GetFormatterWithVerify>().Serialize(ref writer, value.Dependencies, formatterResolver); - writer.WriteRaw(this.____stringByteKeys[2]); - formatterResolver.GetFormatterWithVerify().Serialize(ref writer, value.LastUpdateCheck, formatterResolver); - - writer.WriteEndObject(); - } - - public global::LocalAdmin.V2.PluginsManager.ServerPluginsConfig Deserialize(ref JsonReader reader, global::Utf8Json.IJsonFormatterResolver formatterResolver) - { - if (reader.ReadIsNull()) - { - return null; - } - - - var __InstalledPlugins__ = default(global::System.Collections.Generic.Dictionary); - var __InstalledPlugins__b__ = false; - var __Dependencies__ = default(global::System.Collections.Generic.Dictionary); - var __Dependencies__b__ = false; - var __LastUpdateCheck__ = default(global::System.DateTime?); - var __LastUpdateCheck__b__ = false; - - var ____count = 0; - reader.ReadIsBeginObjectWithVerify(); - while (!reader.ReadIsEndObjectWithSkipValueSeparator(ref ____count)) - { - var stringKey = reader.ReadPropertyNameSegmentRaw(); - int key; - if (!____keyMapping.TryGetValueSafe(stringKey, out key)) - { - reader.ReadNextBlock(); - goto NEXT_LOOP; - } - - switch (key) - { - case 0: - __InstalledPlugins__ = formatterResolver.GetFormatterWithVerify>().Deserialize(ref reader, formatterResolver); - __InstalledPlugins__b__ = true; - break; - case 1: - __Dependencies__ = formatterResolver.GetFormatterWithVerify>().Deserialize(ref reader, formatterResolver); - __Dependencies__b__ = true; - break; - case 2: - __LastUpdateCheck__ = formatterResolver.GetFormatterWithVerify().Deserialize(ref reader, formatterResolver); - __LastUpdateCheck__b__ = true; - break; - default: - reader.ReadNextBlock(); - break; - } - - NEXT_LOOP: - continue; - } - - var ____result = new global::LocalAdmin.V2.PluginsManager.ServerPluginsConfig(__InstalledPlugins__, __Dependencies__, __LastUpdateCheck__); - if(__InstalledPlugins__b__) ____result.InstalledPlugins = __InstalledPlugins__; - if(__Dependencies__b__) ____result.Dependencies = __Dependencies__; - if(__LastUpdateCheck__b__) ____result.LastUpdateCheck = __LastUpdateCheck__; - - return ____result; - } - } - - - public sealed class GitHubReleaseAssetFormatter : global::Utf8Json.IJsonFormatter - { - readonly global::Utf8Json.Internal.AutomataDictionary ____keyMapping; - readonly byte[][] ____stringByteKeys; - - public GitHubReleaseAssetFormatter() - { - this.____keyMapping = new global::Utf8Json.Internal.AutomataDictionary() - { - { JsonWriter.GetEncodedPropertyNameWithoutQuotation("name"), 0}, - { JsonWriter.GetEncodedPropertyNameWithoutQuotation("url"), 1}, - }; - - this.____stringByteKeys = new byte[][] - { - JsonWriter.GetEncodedPropertyNameWithBeginObject("name"), - JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator("url"), - - }; - } - - public void Serialize(ref JsonWriter writer, global::LocalAdmin.V2.PluginsManager.GitHubReleaseAsset value, global::Utf8Json.IJsonFormatterResolver formatterResolver) - { - - - writer.WriteRaw(this.____stringByteKeys[0]); - writer.WriteString(value.name); - writer.WriteRaw(this.____stringByteKeys[1]); - writer.WriteString(value.url); - - writer.WriteEndObject(); - } - - public global::LocalAdmin.V2.PluginsManager.GitHubReleaseAsset Deserialize(ref JsonReader reader, global::Utf8Json.IJsonFormatterResolver formatterResolver) - { - if (reader.ReadIsNull()) - { - throw new InvalidOperationException("typecode is null, struct not supported"); - } - - - var __name__ = default(string); - var __name__b__ = false; - var __url__ = default(string); - var __url__b__ = false; - - var ____count = 0; - reader.ReadIsBeginObjectWithVerify(); - while (!reader.ReadIsEndObjectWithSkipValueSeparator(ref ____count)) - { - var stringKey = reader.ReadPropertyNameSegmentRaw(); - int key; - if (!____keyMapping.TryGetValueSafe(stringKey, out key)) - { - reader.ReadNextBlock(); - goto NEXT_LOOP; - } - - switch (key) - { - case 0: - __name__ = reader.ReadString(); - __name__b__ = true; - break; - case 1: - __url__ = reader.ReadString(); - __url__b__ = true; - break; - default: - reader.ReadNextBlock(); - break; - } - - NEXT_LOOP: - continue; - } - - var ____result = new global::LocalAdmin.V2.PluginsManager.GitHubReleaseAsset(__name__, __url__); - - return ____result; - } - } - - - public sealed class GitHubReleaseFormatter : global::Utf8Json.IJsonFormatter - { - readonly global::Utf8Json.Internal.AutomataDictionary ____keyMapping; - readonly byte[][] ____stringByteKeys; - - public GitHubReleaseFormatter() - { - this.____keyMapping = new global::Utf8Json.Internal.AutomataDictionary() - { - { JsonWriter.GetEncodedPropertyNameWithoutQuotation("message"), 0}, - { JsonWriter.GetEncodedPropertyNameWithoutQuotation("id"), 1}, - { JsonWriter.GetEncodedPropertyNameWithoutQuotation("tag_name"), 2}, - { JsonWriter.GetEncodedPropertyNameWithoutQuotation("published_at"), 3}, - { JsonWriter.GetEncodedPropertyNameWithoutQuotation("assets"), 4}, - }; - - this.____stringByteKeys = new byte[][] - { - JsonWriter.GetEncodedPropertyNameWithBeginObject("message"), - JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator("id"), - JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator("tag_name"), - JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator("published_at"), - JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator("assets"), - - }; - } - - public void Serialize(ref JsonWriter writer, global::LocalAdmin.V2.PluginsManager.GitHubRelease value, global::Utf8Json.IJsonFormatterResolver formatterResolver) - { - - - writer.WriteRaw(this.____stringByteKeys[0]); - formatterResolver.GetFormatterWithVerify().Serialize(ref writer, value.message, formatterResolver); - writer.WriteRaw(this.____stringByteKeys[1]); - writer.WriteUInt32(value.id); - writer.WriteRaw(this.____stringByteKeys[2]); - formatterResolver.GetFormatterWithVerify().Serialize(ref writer, value.tag_name, formatterResolver); - writer.WriteRaw(this.____stringByteKeys[3]); - formatterResolver.GetFormatterWithVerify().Serialize(ref writer, value.published_at, formatterResolver); - writer.WriteRaw(this.____stringByteKeys[4]); - formatterResolver.GetFormatterWithVerify>().Serialize(ref writer, value.assets, formatterResolver); - - writer.WriteEndObject(); - } - - public global::LocalAdmin.V2.PluginsManager.GitHubRelease Deserialize(ref JsonReader reader, global::Utf8Json.IJsonFormatterResolver formatterResolver) - { - if (reader.ReadIsNull()) - { - throw new InvalidOperationException("typecode is null, struct not supported"); - } - - - var __message__ = default(string?); - var __message__b__ = false; - var __id__ = default(uint); - var __id__b__ = false; - var __tag_name__ = default(string?); - var __tag_name__b__ = false; - var __published_at__ = default(global::System.DateTime); - var __published_at__b__ = false; - var __assets__ = default(global::System.Collections.Generic.List); - var __assets__b__ = false; - - var ____count = 0; - reader.ReadIsBeginObjectWithVerify(); - while (!reader.ReadIsEndObjectWithSkipValueSeparator(ref ____count)) - { - var stringKey = reader.ReadPropertyNameSegmentRaw(); - int key; - if (!____keyMapping.TryGetValueSafe(stringKey, out key)) - { - reader.ReadNextBlock(); - goto NEXT_LOOP; - } - - switch (key) - { - case 0: - __message__ = formatterResolver.GetFormatterWithVerify().Deserialize(ref reader, formatterResolver); - __message__b__ = true; - break; - case 1: - __id__ = reader.ReadUInt32(); - __id__b__ = true; - break; - case 2: - __tag_name__ = formatterResolver.GetFormatterWithVerify().Deserialize(ref reader, formatterResolver); - __tag_name__b__ = true; - break; - case 3: - __published_at__ = formatterResolver.GetFormatterWithVerify().Deserialize(ref reader, formatterResolver); - __published_at__b__ = true; - break; - case 4: - __assets__ = formatterResolver.GetFormatterWithVerify>().Deserialize(ref reader, formatterResolver); - __assets__b__ = true; - break; - default: - reader.ReadNextBlock(); - break; - } - - NEXT_LOOP: - continue; - } - - var ____result = new global::LocalAdmin.V2.PluginsManager.GitHubRelease(__message__, __id__, __tag_name__, __published_at__, __assets__); - - return ____result; - } - } - -} - -#pragma warning disable 168 -#pragma warning restore 219 -#pragma warning restore 414 -#pragma warning restore 618 -#pragma warning restore 612 +using System.Collections.Generic; +using System.Text.Json.Serialization; +using LocalAdmin.V2.JSON.Objects; + +namespace LocalAdmin.V2.JSON; + +[JsonSourceGenerationOptions(WriteIndented = true, UseStringEnumConverter = true)] +[JsonSerializable(typeof(GitHubRelease))] +[JsonSerializable(typeof(Dictionary))] +[JsonSerializable(typeof(DataJson))] +[JsonSerializable(typeof(ServerPluginsConfig))] +internal partial class JsonGenerated : JsonSerializerContext; \ No newline at end of file diff --git a/JSON/Objects/DataJson.cs b/JSON/Objects/DataJson.cs index 1341856..2fda03a 100644 --- a/JSON/Objects/DataJson.cs +++ b/JSON/Objects/DataJson.cs @@ -1,86 +1,29 @@ using System; using System.Collections.Generic; -using Utf8Json; + // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract // ReSharper disable NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract // ReSharper disable ArrangeObjectCreationWhenTypeNotEvident -namespace LocalAdmin.V2.Core; - -public class DataJson -{ - public string? GitHubPersonalAccessToken; - - public DateTime? EulaAccepted; - - public bool PluginManagerWarningDismissed; - - public DateTime? LastPluginAliasesRefresh; - - public Dictionary PluginVersionCache; - - public Dictionary PluginAliases; - - internal DataJson() - { - PluginVersionCache = new(); - PluginAliases = new(); - } - - [SerializationConstructor] - public DataJson(string? gitHubPersonalAccessToken, DateTime? eulaAccepted, bool pluginManagerWarningDismissed, DateTime? lastPluginAliasesRefresh, Dictionary pluginVersionCache, Dictionary pluginAliases) - { - GitHubPersonalAccessToken = gitHubPersonalAccessToken; - EulaAccepted = eulaAccepted; - PluginManagerWarningDismissed = pluginManagerWarningDismissed; - LastPluginAliasesRefresh = lastPluginAliasesRefresh; - PluginVersionCache = pluginVersionCache; - PluginAliases = pluginAliases; - - PluginVersionCache ??= new(); - PluginAliases ??= new(); - } -} - -public struct PluginVersionCache -{ - public string Version; - - public uint ReleaseId; - - public DateTime PublishmentTime; - - public DateTime LastRefreshed; - - public string DllDownloadUrl; - - public string? DependenciesDownloadUrl; - - [SerializationConstructor] - public PluginVersionCache(string version, uint releaseId, DateTime publishmentTime, DateTime lastRefreshed, string dllDownloadUrl, string? dependenciesDownloadUrl) - { - Version = version; - ReleaseId = releaseId; - PublishmentTime = publishmentTime; - LastRefreshed = lastRefreshed; - DllDownloadUrl = dllDownloadUrl; - DependenciesDownloadUrl = dependenciesDownloadUrl; - } -} - -public readonly struct PluginAlias -{ - public readonly string Repository; - - public readonly byte Flags; - - [SerializationConstructor] - public PluginAlias(string repository, byte flags) - { - Repository = repository; - Flags = flags; - } -} +namespace LocalAdmin.V2.JSON.Objects; + +public record DataJson( + string? GitHubPersonalAccessToken, + DateTime? EulaAccepted, + bool PluginManagerWarningDismissed, + DateTime? LastPluginAliasesRefresh, + Dictionary PluginVersionCache, + Dictionary PluginAliases); + +public readonly record struct PluginVersionCache( + string Version, + uint ReleaseId, + DateTime PublishmentTime, + DateTime LastRefreshed, + string DllDownloadUrl, + string? DependenciesDownloadUrl); + +public readonly record struct PluginAlias(string Repository, byte Flags); [Flags] internal enum PluginAliasFlags : byte diff --git a/JSON/Objects/GitHubRelease.cs b/JSON/Objects/GitHubRelease.cs index d3855a9..f4a8db6 100644 --- a/JSON/Objects/GitHubRelease.cs +++ b/JSON/Objects/GitHubRelease.cs @@ -1,45 +1,16 @@ using System; using System.Collections.Generic; -using Utf8Json; -// ReSharper disable InconsistentNaming - -namespace LocalAdmin.V2.PluginsManager; - -public readonly struct GitHubRelease -{ - public readonly string? message; - - public readonly uint id; - - public readonly string? tag_name; +using System.Text.Json.Serialization; - public readonly DateTime published_at; - - public readonly List assets; - - [SerializationConstructor] - public GitHubRelease(string? message, uint id, string? tag_name, DateTime published_at, List assets) - { - this.message = message; - this.id = id; - this.tag_name = tag_name; - this.published_at = published_at; - this.assets = assets; - - this.assets ??= new(); - } -} +// ReSharper disable InconsistentNaming -public readonly struct GitHubReleaseAsset -{ - public readonly string name; +namespace LocalAdmin.V2.JSON.Objects; - public readonly string url; +public readonly record struct GitHubRelease( + string? message, + uint id, + [property: JsonPropertyName("tag_name")] string? tagName, + [property: JsonPropertyName("published_at")] DateTime publishedAt, + List assets); - [SerializationConstructor] - public GitHubReleaseAsset(string name, string url) - { - this.name = name; - this.url = url; - } -} \ No newline at end of file +public readonly record struct GitHubReleaseAsset(string name, string url); \ No newline at end of file diff --git a/JSON/Objects/ServerPluginsConfig.cs b/JSON/Objects/ServerPluginsConfig.cs index eb9fd52..bad5dcf 100644 --- a/JSON/Objects/ServerPluginsConfig.cs +++ b/JSON/Objects/ServerPluginsConfig.cs @@ -1,81 +1,23 @@ using System; using System.Collections.Generic; -using Utf8Json; -namespace LocalAdmin.V2.PluginsManager; - -public class ServerPluginsConfig -{ - public Dictionary InstalledPlugins; - - public Dictionary Dependencies; - - public DateTime? LastUpdateCheck; - - internal ServerPluginsConfig() - { - InstalledPlugins = new(); - Dependencies = new(); - } - - [SerializationConstructor] - public ServerPluginsConfig(Dictionary installedPlugins, Dictionary dependencies, DateTime? lastUpdateCheck) - { - InstalledPlugins = installedPlugins; - Dependencies = dependencies; - LastUpdateCheck = lastUpdateCheck; - } -} - -public class InstalledPlugin -{ - public string? TargetVersion; - - public string? CurrentVersion; - - public string? FileHash; - - public DateTime InstallationDate; - - public DateTime UpdateDate; - - internal InstalledPlugin() { } - - [SerializationConstructor] - public InstalledPlugin(string? targetVersion, string? currentVersion, string? fileHash, DateTime installationDate, DateTime updateDate) - { - TargetVersion = targetVersion; - CurrentVersion = currentVersion; - FileHash = fileHash; - InstallationDate = installationDate; - UpdateDate = updateDate; - } -} - -public class Dependency -{ - public string? FileHash; - - public DateTime InstallationDate; - - public DateTime UpdateDate; - - public bool ManuallyInstalled; - - public List InstalledByPlugins; - - internal Dependency() - { - InstalledByPlugins = new(); - } - - [SerializationConstructor] - public Dependency(string? fileHash, DateTime installationDate, DateTime updateDate, bool manuallyInstalled, List installedByPlugins) - { - FileHash = fileHash; - InstallationDate = installationDate; - UpdateDate = updateDate; - ManuallyInstalled = manuallyInstalled; - InstalledByPlugins = installedByPlugins; - } -} \ No newline at end of file +namespace LocalAdmin.V2.JSON.Objects; + +public record ServerPluginsConfig( + Dictionary InstalledPlugins, + Dictionary Dependencies, + DateTime? LastUpdateCheck); + +public record InstalledPlugin( + string? TargetVersion, + string? CurrentVersion, + string? FileHash, + DateTime InstallationDate, + DateTime UpdateDate); + +public record Dependency( + string? FileHash, + DateTime InstallationDate, + DateTime UpdateDate, + bool ManuallyInstalled, + List InstalledByPlugins); \ No newline at end of file diff --git a/LICENSE b/LICENSE index 6461efc..36560f2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 - 2024 Łukasz "zabszk" Jurczyk and KernelError +Copyright (c) 2019 - 2025 Łukasz "zabszk" Jurczyk and KernelError Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/LocalAdmin V2.csproj b/LocalAdmin V2.csproj index 4abacbf..60a0e9e 100644 --- a/LocalAdmin V2.csproj +++ b/LocalAdmin V2.csproj @@ -1,30 +1,27 @@ Exe - true - net8.0-windows - net8.0 + net9.0 app.manifest - 10 + 13 enable + true + LocalAdmin.V2 LocalAdmin LocalAdmin V2 LocalAdmin V2 Northwood Studios Łukasz "zabszk" Jurczyk, KernelError - Copyright by Łukasz "zabszk" Jurczyk and KernelError, 2019 - 2024 + Copyright by Łukasz "zabszk" Jurczyk and KernelError, 2019 - 2025 - true - false - true - false - false - true - Speed - true - true - false + true + true + false + true + true + true + Speed @@ -34,43 +31,9 @@ false - - - LINUX_SIGNALS - - - x64 - - - x64 - none - - - - - - - true - - Always - - - Always - - - - - - - - - - - - diff --git a/NOTICE-LocalAdmin b/NOTICE-LocalAdmin deleted file mode 100644 index e8117d2..0000000 --- a/NOTICE-LocalAdmin +++ /dev/null @@ -1,24 +0,0 @@ -LocalAdmin includes Utf8Json developed by Yoshifumi Kawai licensed under The MIT License. -Full license text of the Utf8Json license: - -MIT License - -Copyright (c) 2017 Yoshifumi Kawai - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/PluginsManager/OfficialPluginsList.cs b/PluginsManager/OfficialPluginsList.cs index 7a8d874..8e9a1d9 100644 --- a/PluginsManager/OfficialPluginsList.cs +++ b/PluginsManager/OfficialPluginsList.cs @@ -1,10 +1,10 @@ using System; -using System.Collections.Generic; using System.Net.Http; +using System.Net.Http.Json; using System.Threading.Tasks; -using LocalAdmin.V2.Core; using LocalAdmin.V2.IO; -using Utf8Json; +using LocalAdmin.V2.JSON; +using LocalAdmin.V2.JSON.Objects; namespace LocalAdmin.V2.PluginsManager; @@ -13,7 +13,7 @@ internal static class OfficialPluginsList private static readonly HttpClient HttpClient = new() { Timeout = TimeSpan.FromSeconds(45), - DefaultRequestHeaders = { { "User-Agent", "LocalAdmin v. " + Core.LocalAdmin.VersionString } } + DefaultRequestHeaders = { { "User-Agent", $"LocalAdmin v. {Core.LocalAdmin.VersionString}" } } }; internal static bool IsRefreshNeeded() @@ -36,7 +36,7 @@ internal static async Task RefreshOfficialPluginsList() { try { - ConsoleUtil.WriteLine($"[PLUGIN MANAGER] Refreshing plugins list...", ConsoleColor.Blue); + ConsoleUtil.WriteLine("[PLUGIN MANAGER] Refreshing plugins list...", ConsoleColor.Blue); var response = await HttpClient.GetAsync("https://gra2.scpslgame.com/localadmin.php?v=1"); if (!response.IsSuccessStatusCode) @@ -51,19 +51,18 @@ internal static async Task RefreshOfficialPluginsList() return; } - var data = JsonSerializer.Deserialize>(await response.Content.ReadAsStringAsync()); + var data = await response.Content.ReadFromJsonAsync(JsonGenerated.Default.DictionaryStringPluginAlias); if (data == null) { - ConsoleUtil.WriteLine($"[PLUGIN MANAGER] Failed to refresh plugins list! (deserialization error)", ConsoleColor.Red); + ConsoleUtil.WriteLine("[PLUGIN MANAGER] Failed to refresh plugins list! (deserialization error)", ConsoleColor.Red); return; } ConsoleUtil.WriteLine("[PLUGIN MANAGER] Reading LocalAdmin config file...", ConsoleColor.Blue); - await Core.LocalAdmin.Singleton!.LoadJsonOrTerminate(); + await Core.LocalAdmin.Singleton.LoadJsonOrTerminate(); - Core.LocalAdmin.DataJson!.PluginAliases = data; - Core.LocalAdmin.DataJson.LastPluginAliasesRefresh = DateTime.UtcNow; + Core.LocalAdmin.DataJson = Core.LocalAdmin.DataJson! with { PluginAliases = data, LastPluginAliasesRefresh = DateTime.UtcNow }; ConsoleUtil.WriteLine("[PLUGIN MANAGER] Writing LocalAdmin config file...", ConsoleColor.Blue); await Core.LocalAdmin.Singleton.SaveJsonOrTerminate(); @@ -79,11 +78,8 @@ internal static async Task RefreshOfficialPluginsList() internal static string ResolvePluginAlias(string alias, PluginAliasFlags requiredFlags) { if (Core.LocalAdmin.DataJson == null || Core.LocalAdmin.DataJson.PluginAliases == null || - !Core.LocalAdmin.DataJson.PluginAliases.ContainsKey(alias)) + !Core.LocalAdmin.DataJson.PluginAliases.TryGetValue(alias, out PluginAlias pluginAlias)) return alias; - - var pluginAlias = Core.LocalAdmin.DataJson.PluginAliases[alias]; - if (((PluginAliasFlags)pluginAlias.Flags & requiredFlags) == 0) return alias; diff --git a/PluginsManager/PluginInstaller.cs b/PluginsManager/PluginInstaller.cs index 1d7662a..af0f401 100644 --- a/PluginsManager/PluginInstaller.cs +++ b/PluginsManager/PluginInstaller.cs @@ -2,14 +2,15 @@ using System.Collections.Generic; using System.IO; using System.IO.Compression; -using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; +using System.Net.Http.Json; +using System.Text.Json; using System.Threading.Tasks; -using LocalAdmin.V2.Core; using LocalAdmin.V2.IO; -using Utf8Json; +using LocalAdmin.V2.JSON; +using LocalAdmin.V2.JSON.Objects; namespace LocalAdmin.V2.PluginsManager; @@ -41,23 +42,23 @@ private static async Task QueryRelease(string name, string url, boo if (response.StatusCode == HttpStatusCode.Unauthorized) { ConsoleUtil.WriteLine($"[PLUGIN MANAGER] Failed to query {url}! Is the GitHub Personal Access Token set correctly? (Status code: {response.StatusCode})", ConsoleColor.Red); - return new(); + return new QueryResult(); } if (!response.IsSuccessStatusCode && response.StatusCode != HttpStatusCode.NotFound) { ConsoleUtil.WriteLine($"[PLUGIN MANAGER] Failed to query {url}! (Status code: {response.StatusCode})", ConsoleColor.Red); - return new(); + return new QueryResult(); } - var data = JsonSerializer.Deserialize(await response.Content.ReadAsStringAsync()); + var data = await response.Content.ReadFromJsonAsync(JsonGenerated.Default.GitHubRelease); - if (data.tag_name == null) + if (data.tagName == null) { if (interactive) ConsoleUtil.WriteLine($"[PLUGIN MANAGER] Failed to process plugin {name} - response is null.", ConsoleColor.Red); - return new(); + return new QueryResult(); } if (data.message != null) @@ -66,19 +67,19 @@ private static async Task QueryRelease(string name, string url, boo { if (interactive) ConsoleUtil.WriteLine($"[PLUGIN MANAGER] Failed to process plugin {name} - plugin release not found or no public release/specified version found.", ConsoleColor.Red); - return new(); + return new QueryResult(); } if (interactive) ConsoleUtil.WriteLine($"[PLUGIN MANAGER] Failed to process plugin {name}. Exception: {data.message}", ConsoleColor.Red); - return new(); + return new QueryResult(); } if (data.assets == null || data.assets.Count == 0) { if (interactive) ConsoleUtil.WriteLine($"[PLUGIN MANAGER] Failed to process plugin {name} - no assets found.", ConsoleColor.Red); - return new(); + return new QueryResult(); } string? pluginUrl = null; @@ -100,7 +101,7 @@ private static async Task QueryRelease(string name, string url, boo if (interactive) ConsoleUtil.WriteLine($"[PLUGIN MANAGER] Failed to process plugin {name} - multiple plugin DLLs marked for NW API usage found.", ConsoleColor.Red); - return new(); + return new QueryResult(); } if (thisNw) @@ -111,9 +112,8 @@ private static async Task QueryRelease(string name, string url, boo pluginUrl = asset.url; designatedForNwApi = thisNw; } - else if (asset.name.Equals("dependencies-nw.zip", StringComparison.OrdinalIgnoreCase)) - dependenciesUrl = asset.url; - else if (dependenciesUrl == null && asset.name.Equals("dependencies.zip", StringComparison.OrdinalIgnoreCase)) + else if (asset.name.Equals("dependencies-nw.zip", StringComparison.OrdinalIgnoreCase) || + dependenciesUrl == null && asset.name.Equals("dependencies.zip", StringComparison.OrdinalIgnoreCase)) dependenciesUrl = asset.url; } @@ -121,30 +121,30 @@ private static async Task QueryRelease(string name, string url, boo { if (interactive) ConsoleUtil.WriteLine($"[PLUGIN MANAGER] Failed to process plugin {name} - no plugin DLL found.", ConsoleColor.Red); - return new(); + return new QueryResult(); } if (nonNwApiFound > 1) { if (interactive) ConsoleUtil.WriteLine($"[PLUGIN MANAGER] Failed to process plugin {name} - multiple matching plugin DLLs found, none is explicitly designated for NW API usage.", ConsoleColor.Red); - return new(); + return new QueryResult(); } - return new(new PluginVersionCache + return new QueryResult(new PluginVersionCache { - Version = data.tag_name!, + Version = data.tagName!, ReleaseId = data.id, DependenciesDownloadUrl = dependenciesUrl, DllDownloadUrl = pluginUrl, LastRefreshed = DateTime.UtcNow, - PublishmentTime = data.published_at + PublishmentTime = data.publishedAt }); } catch (Exception e) { ConsoleUtil.WriteLine($"[PLUGIN MANAGER] Failed to process plugin {url}! Exception: {e.Message}", ConsoleColor.Red); - return new(); + return new QueryResult(); } } @@ -155,9 +155,7 @@ internal static async Task TryCachePlugin(string name, bool interac if (!response.Success) return response; - if (Core.LocalAdmin.DataJson!.PluginVersionCache!.ContainsKey(name)) - Core.LocalAdmin.DataJson.PluginVersionCache![name] = response.Result; - else Core.LocalAdmin.DataJson.PluginVersionCache!.Add(name, response.Result); + Core.LocalAdmin.DataJson!.PluginVersionCache[name] = response.Result; return response; } @@ -175,40 +173,28 @@ internal static async Task TryInstallPlugin(string name, PluginVersionCach var pluginsPath = PluginsPath(port); var depPath = DependenciesPath(port); - if (!Directory.Exists(pluginsPath)) - Directory.CreateDirectory(pluginsPath); - - if (!Directory.Exists(depPath)) - Directory.CreateDirectory(depPath); + Directory.CreateDirectory(pluginsPath); + Directory.CreateDirectory(depPath); var safeName = name.Replace("/", "_", StringComparison.Ordinal); - var metadataPath = pluginsPath + "metadata.json"; - var pluginPath = pluginsPath + $"{safeName}.dll"; - var abort = false; - ServerPluginsConfig? metadata = null; + var metadataPath = $"{pluginsPath}metadata.json"; + var pluginPath = $"{pluginsPath}{safeName}.dll"; - if (!File.Exists(metadataPath)) - { - var mt = new ServerPluginsConfig(); - bool ts = await mt.TrySave(metadataPath); + uint timeout = ignoreLocks ? 0 : DefaultLockTime; + await using FileStream fileStream = await FileUtils.OpenAsync(metadataPath, FileMode.OpenOrCreate, + FileAccess.ReadWrite, FileShare.None, timeout); - if (!ts) - { - ConsoleUtil.WriteLine("[PLUGIN MANAGER] Could not create metadata file! Aborting download!", - ConsoleColor.Red); - return false; - } - } + ConsoleUtil.WriteLine("[PLUGIN MANAGER] Loading metadata file...", ConsoleColor.Blue); + ServerPluginsConfig metadata = (fileStream.Length != 0 ? + await JsonSerializer.DeserializeAsync(fileStream, JsonGenerated.Default.ServerPluginsConfig) : null) ?? + new ServerPluginsConfig([], [], null); if (!overwriteFiles) { ConsoleUtil.WriteLine("[PLUGIN MANAGER] Checking if plugin is already installed...", ConsoleColor.Blue); - metadata = await JsonFile.Load(metadataPath, ignoreLocks ? 0 : DefaultLockTime); - - if (metadata!.InstalledPlugins.ContainsKey(name)) + if (metadata.InstalledPlugins.TryGetValue(name, out InstalledPlugin? installedPlugin)) { - var installedPlugin = metadata.InstalledPlugins[name]; if (installedPlugin.CurrentVersion == plugin.Version) { ConsoleUtil.WriteLine( @@ -218,14 +204,11 @@ internal static async Task TryInstallPlugin(string name, PluginVersionCach return true; } } - - metadata = null; } - if (!Directory.Exists(tempPath)) - Directory.CreateDirectory(tempPath); + Directory.CreateDirectory(tempPath); - List currentDependencies = new(); + List currentDependencies = []; if (plugin.DependenciesDownloadUrl != null) { @@ -234,10 +217,11 @@ internal static async Task TryInstallPlugin(string name, PluginVersionCach var extractDir = $"{tempPath}{safeName}-dependencies"; + string targetPath = $"{tempPath}{safeName}-dependencies.zip"; try { - bool dwlOk = await Download(name + "-dependencies", plugin.DependenciesDownloadUrl, - $"{tempPath}{safeName}-dependencies.zip"); + bool dwlOk = await Download($"{name}-dependencies", plugin.DependenciesDownloadUrl, + targetPath); if (!dwlOk) { @@ -247,17 +231,14 @@ internal static async Task TryInstallPlugin(string name, PluginVersionCach return false; } - if (Directory.Exists(extractDir)) - Directory.Delete(extractDir, true); + FileUtils.DeleteDirectoryIfExists(extractDir); Directory.CreateDirectory(extractDir); ConsoleUtil.WriteLine($"[PLUGIN MANAGER] Unpacking dependencies for plugin {name}...", ConsoleColor.Blue); - ZipFile.ExtractToDirectory($"{tempPath}{safeName}-dependencies.zip", extractDir); + ZipFile.ExtractToDirectory(targetPath, extractDir); - ConsoleUtil.WriteLine("[PLUGIN MANAGER] Loading metadata file...", ConsoleColor.Blue); - metadata = await JsonFile.Load(metadataPath, ignoreLocks ? 0 : DefaultLockTime, true); ConsoleUtil.WriteLine($"[PLUGIN MANAGER] Processing dependencies for plugin {name}...", ConsoleColor.Blue); @@ -272,10 +253,10 @@ internal static async Task TryInstallPlugin(string name, PluginVersionCach var installed = File.Exists(depPath + fn); var newHash = Sha.Sha256File(dep); - if (!installed && metadata!.Dependencies.ContainsKey(fn)) + if (!installed) metadata.Dependencies.Remove(fn); - if (!metadata!.Dependencies.ContainsKey(fn)) + if (!metadata.Dependencies.TryGetValue(fn, out Dependency? depMeta)) { var usedBy = new List { name }; @@ -302,21 +283,16 @@ internal static async Task TryInstallPlugin(string name, PluginVersionCach ConsoleColor.Yellow); } - metadata.Dependencies.Add(fn, new Dependency - { - FileHash = newHash, - InstallationDate = DateTime.UtcNow, - UpdateDate = DateTime.UtcNow, - InstalledByPlugins = usedBy, - ManuallyInstalled = installed - }); + metadata.Dependencies.Add(fn, + new Dependency(FileHash: newHash, InstallationDate: DateTime.UtcNow, + UpdateDate: DateTime.UtcNow, InstalledByPlugins: usedBy, + ManuallyInstalled: installed)); File.Move(dep, depPath + fn, true); ConsoleUtil.WriteLine($"[PLUGIN MANAGER] Installed dependency {fn}.", ConsoleColor.Blue); } else { - var depMeta = metadata.Dependencies[fn]; var currentHash = Sha.Sha256File(depPath + fn); var overwrite = false; @@ -356,8 +332,7 @@ internal static async Task TryInstallPlugin(string name, PluginVersionCach if (overwrite) { - metadata.Dependencies[fn].FileHash = newHash; - metadata.Dependencies[fn].UpdateDate = DateTime.UtcNow; + metadata.Dependencies[fn] = metadata.Dependencies[fn] with { FileHash = newHash, UpdateDate = DateTime.UtcNow }; } if (!metadata.Dependencies[fn].InstalledByPlugins.Contains(name)) @@ -377,27 +352,10 @@ internal static async Task TryInstallPlugin(string name, PluginVersionCach } finally { - if (metadata != null) - { - ConsoleUtil.WriteLine("[PLUGIN MANAGER] Writing metadata...", ConsoleColor.Blue); - if (!await metadata.TrySave(metadataPath, 0, true)) - { - abort = true; - ConsoleUtil.WriteLine( - "[PLUGIN MANAGER] Failed to save metadata. Aborting further installation!", - ConsoleColor.Red); - } - - metadata = null; - } - ConsoleUtil.WriteLine("[PLUGIN MANAGER] Cleaning up...", ConsoleColor.Blue); FileUtils.DeleteDirectoryIfExists(extractDir); - FileUtils.DeleteIfExists($"{tempPath}{safeName}-dependencies.zip"); + FileUtils.DeleteIfExists(targetPath); } - - if (abort) - return false; } ConsoleUtil.WriteLine($"[PLUGIN MANAGER] Downloading plugin {name}...", ConsoleColor.Blue); @@ -420,26 +378,15 @@ internal static async Task TryInstallPlugin(string name, PluginVersionCach var hash = Sha.Sha256File(pluginPath); - ConsoleUtil.WriteLine("[PLUGIN MANAGER] Reading metadata...", ConsoleColor.Blue); - metadata = await JsonFile.Load(metadataPath, ignoreLocks ? 0 : DefaultLockTime, true); - ConsoleUtil.WriteLine("[PLUGIN MANAGER] Processing metadata...", ConsoleColor.Blue); - if (metadata!.InstalledPlugins.ContainsKey(name)) - { - metadata.InstalledPlugins[name].FileHash = hash; - metadata.InstalledPlugins[name].UpdateDate = DateTime.UtcNow; - metadata.InstalledPlugins[name].CurrentVersion = plugin.Version; - metadata.InstalledPlugins[name].TargetVersion = targetVersion; - } - else - metadata.InstalledPlugins.Add(name, new InstalledPlugin - { - FileHash = hash, - InstallationDate = DateTime.UtcNow, - UpdateDate = DateTime.UtcNow, - CurrentVersion = plugin.Version, - TargetVersion = targetVersion - }); + metadata.InstalledPlugins[name] = new InstalledPlugin + ( + targetVersion, + plugin.Version, + hash, + metadata.InstalledPlugins.TryGetValue(name, out InstalledPlugin? value) ? value.InstallationDate : DateTime.UtcNow, + DateTime.UtcNow + ); foreach (var dependency in metadata.Dependencies) { @@ -469,27 +416,17 @@ internal static async Task TryInstallPlugin(string name, PluginVersionCach ConsoleColor.Red); return false; } - finally - { - if (metadata != null) - { - ConsoleUtil.WriteLine("[PLUGIN MANAGER] Writing metadata...", ConsoleColor.Blue); - if (!await metadata.TrySave(metadataPath, 0, true)) - { - ConsoleUtil.WriteLine("[PLUGIN MANAGER] Failed to save metadata!", ConsoleColor.Red); - abort = true; - } - } - } - if (abort) - return false; + ConsoleUtil.WriteLine("[PLUGIN MANAGER] Writing metadata...", ConsoleColor.Blue); + + fileStream.SetLength(0); + await JsonSerializer.SerializeAsync(fileStream, metadata, JsonGenerated.Default.ServerPluginsConfig); ConsoleUtil.WriteLine($"[PLUGIN MANAGER] Plugin {name} has been successfully installed!", ConsoleColor.DarkGreen); if (runMaintenance) { - ConsoleUtil.WriteLine($"[PLUGIN MANAGER] Performing automatic maintenance...", ConsoleColor.Blue); + ConsoleUtil.WriteLine("[PLUGIN MANAGER] Performing automatic maintenance...", ConsoleColor.Blue); await PluginsMaintenance(port, false); } @@ -573,31 +510,27 @@ internal static async Task TryUninstallPlugin(string name, string port, bo name = OfficialPluginsList.ResolvePluginAlias(name, PluginAliasFlags.All); - if (name.Count(x => x == '/') != 1) + if (name.AsSpan().Count('/') != 1) { ConsoleUtil.WriteLine("[PLUGIN MANAGER] Plugin name is invalid!", ConsoleColor.Red); return false; } - ServerPluginsConfig? metadata = null; var pluginsPath = PluginsPath(port); - if (!Directory.Exists(pluginsPath)) - Directory.CreateDirectory(pluginsPath); + Directory.CreateDirectory(pluginsPath); - var success = false; - var metadataPath = PluginsPath(port) + "metadata.json"; + var metadataPath = $"{PluginsPath(port)}metadata.json"; try { var depPath = DependenciesPath(port); - if (!Directory.Exists(depPath)) - Directory.CreateDirectory(depPath); + Directory.CreateDirectory(depPath); var safeName = name.Replace("/", "_", StringComparison.Ordinal); - var pluginPath = PluginsPath(port) + $"{safeName}.dll"; + var pluginPath = $"{PluginsPath(port)}{safeName}.dll"; try { @@ -612,15 +545,20 @@ internal static async Task TryUninstallPlugin(string name, string port, bo return false; } - if (!File.Exists(metadataPath)) + ConsoleUtil.WriteLine("[PLUGIN MANAGER] Reading metadata...", ConsoleColor.Blue); + uint timeout = ignoreLocks ? 0 : DefaultLockTime; + + await using FileStream? fileStream = + await FileUtils.TryOpenAsync(metadataPath, FileAccess.ReadWrite, FileShare.None, timeout); + + if (fileStream == null) { ConsoleUtil.WriteLine("[PLUGIN MANAGER] Metadata file does not exist.", ConsoleColor.Yellow); ConsoleUtil.WriteLine("[PLUGIN MANAGER] Uninstallation complete.", ConsoleColor.Blue); return true; } - ConsoleUtil.WriteLine("[PLUGIN MANAGER] Reading metadata...", ConsoleColor.Blue); - metadata = await JsonFile.Load(metadataPath, ignoreLocks ? 0 : DefaultLockTime, true); + var metadata = await JsonSerializer.DeserializeAsync(fileStream, JsonGenerated.Default.ServerPluginsConfig); if (metadata == null) { @@ -630,18 +568,13 @@ internal static async Task TryUninstallPlugin(string name, string port, bo } ConsoleUtil.WriteLine("[PLUGIN MANAGER] Processing metadata...", ConsoleColor.Blue); - if (metadata.InstalledPlugins.ContainsKey(name)) - { - metadata.InstalledPlugins.Remove(name); - await metadata.TrySave(metadataPath, 0); - } + metadata.InstalledPlugins.Remove(name); - List depToRemove = new(); + List depToRemove = []; foreach (var dep in metadata.Dependencies) { - if (dep.Value.InstalledByPlugins.Contains(name)) - dep.Value.InstalledByPlugins.Remove(name); + dep.Value.InstalledByPlugins.Remove(name); if (dep.Value.InstalledByPlugins.Count == 0 && !dep.Value.ManuallyInstalled) depToRemove.Add(dep.Key); @@ -667,7 +600,12 @@ internal static async Task TryUninstallPlugin(string name, string port, bo } } - success = true; + ConsoleUtil.WriteLine("[PLUGIN MANAGER] Writing metadata...", ConsoleColor.Blue); + + fileStream.SetLength(0); + await JsonSerializer.SerializeAsync(fileStream, metadata, JsonGenerated.Default.ServerPluginsConfig); + + ConsoleUtil.WriteLine($"[PLUGIN MANAGER] Plugin {name} has been successfully uninstalled!", ConsoleColor.DarkGreen); return true; } catch (Exception e) @@ -676,19 +614,6 @@ internal static async Task TryUninstallPlugin(string name, string port, bo ConsoleColor.Red); return false; } - finally - { - if (metadata != null) - { - ConsoleUtil.WriteLine("[PLUGIN MANAGER] Writing metadata...", ConsoleColor.Blue); - - if (!await metadata.TrySave(metadataPath, 0, true)) - ConsoleUtil.WriteLine("[PLUGIN MANAGER] Failed to save metadata!", ConsoleColor.Red); - } - - if (success) - ConsoleUtil.WriteLine($"[PLUGIN MANAGER] Plugin {name} has been successfully uninstalled!", ConsoleColor.DarkGreen); - } } internal static async Task PluginsMaintenance(string port, bool ignoreLocks) @@ -703,24 +628,24 @@ internal static async Task PluginsMaintenance(string port, bool ignoreLock var depPath = DependenciesPath(port); - if (!Directory.Exists(depPath)) - Directory.CreateDirectory(depPath); + Directory.CreateDirectory(depPath); - ServerPluginsConfig? metadata = null; - var success = false; - var metadataPath = pluginsPath + "metadata.json"; + var metadataPath = $"{pluginsPath}metadata.json"; try { - if (!File.Exists(metadataPath)) + ConsoleUtil.WriteLine("[PLUGIN MANAGER] Reading metadata...", ConsoleColor.Blue); + + uint timeout = ignoreLocks ? 0 : DefaultLockTime; + await using FileStream? fileStream = await FileUtils.TryOpenAsync(metadataPath, FileAccess.ReadWrite, FileShare.None, timeout); + + if (fileStream == null) { ConsoleUtil.WriteLine($"[PLUGIN MANAGER] Metadata file for port {port} doesn't exist. No need to perform maintenance.", ConsoleColor.Blue); - success = true; return true; } - ConsoleUtil.WriteLine("[PLUGIN MANAGER] Reading metadata...", ConsoleColor.Blue); - metadata = await JsonFile.Load(metadataPath, ignoreLocks ? 0 : DefaultLockTime, true); + ServerPluginsConfig? metadata = await JsonSerializer.DeserializeAsync(fileStream, JsonGenerated.Default.ServerPluginsConfig); if (metadata == null) { @@ -728,11 +653,11 @@ internal static async Task PluginsMaintenance(string port, bool ignoreLock return false; } - List depToRemove = new(), plToRemove = new(); + List depToRemove = [], plToRemove = []; foreach (var pl in metadata.InstalledPlugins) { - var pluginPath = pluginsPath + $"{pl.Key.Replace("/", "_", StringComparison.Ordinal)}.dll"; + var pluginPath = $"{pluginsPath}{pl.Key.Replace("/", "_", StringComparison.Ordinal)}.dll"; if (File.Exists(pluginPath)) continue; @@ -791,7 +716,12 @@ internal static async Task PluginsMaintenance(string port, bool ignoreLock } } - success = true; + ConsoleUtil.WriteLine("[PLUGIN MANAGER] Writing metadata...", ConsoleColor.Blue); + + fileStream.SetLength(0); + await JsonSerializer.SerializeAsync(fileStream, metadata, JsonGenerated.Default.ServerPluginsConfig); + + ConsoleUtil.WriteLine($"[PLUGIN MANAGER] Plugins maintenance for port {port} complete!", ConsoleColor.DarkGreen); return true; } catch (Exception e) @@ -800,36 +730,11 @@ internal static async Task PluginsMaintenance(string port, bool ignoreLock ConsoleColor.Red); return false; } - finally - { - if (metadata != null) - { - ConsoleUtil.WriteLine("[PLUGIN MANAGER] Writing metadata...", ConsoleColor.Blue); - - if (!await metadata.TrySave(metadataPath, 0, true)) - ConsoleUtil.WriteLine("[PLUGIN MANAGER] Failed to save metadata!", ConsoleColor.Red); - } - - if (success) - ConsoleUtil.WriteLine($"[PLUGIN MANAGER] Plugins maintenance for port {port} complete!", ConsoleColor.DarkGreen); - } } - internal readonly struct QueryResult + internal readonly record struct QueryResult(PluginVersionCache Result) { - public QueryResult() - { - Success = false; - Result = default; - } - - public QueryResult(PluginVersionCache result) - { - Success = true; - Result = result; - } - - public readonly bool Success; - public readonly PluginVersionCache Result; + public readonly bool Success = true; + public readonly PluginVersionCache Result = Result; } } \ No newline at end of file diff --git a/PluginsManager/PluginStorage.cs b/PluginsManager/PluginStorage.cs index 0ae8620..97165cc 100644 --- a/PluginsManager/PluginStorage.cs +++ b/PluginsManager/PluginStorage.cs @@ -3,6 +3,8 @@ using System.IO; using System.Threading.Tasks; using LocalAdmin.V2.IO; +using LocalAdmin.V2.JSON; +using LocalAdmin.V2.JSON.Objects; namespace LocalAdmin.V2.PluginsManager; @@ -21,20 +23,13 @@ internal static class PluginStorage return null; } - var metadataPath = pluginsPath + "metadata.json"; - - if (!File.Exists(metadataPath)) - { - ConsoleUtil.WriteLine($"[PLUGIN MANAGER] Metadata file for port {port} doesn't exist. Skipped.", ConsoleColor.Blue); - return null; - } - ConsoleUtil.WriteLine("[PLUGIN MANAGER] Reading metadata...", ConsoleColor.Blue); - var metadata = await JsonFile.Load(metadataPath, ignoreLocks ? 0 : PluginInstaller.DefaultLockTime); - if (metadata == null) + var metadataPath = $"{pluginsPath}metadata.json"; + uint timeout = ignoreLocks ? 0 : PluginInstaller.DefaultLockTime; + if (await FileUtils.TryReadJsonAsync(metadataPath, FileShare.Read, JsonGenerated.Default.ServerPluginsConfig, timeout) is not {} metadata) { - ConsoleUtil.WriteLine($"[PLUGIN MANAGER] Failed to parse metadata file for port {port}!", ConsoleColor.Red); + ConsoleUtil.WriteLine($"[PLUGIN MANAGER] Failed to read metadata file for port {port}. Skipped.", ConsoleColor.Blue); return null; } @@ -59,23 +54,23 @@ internal static class PluginStorage ConsoleUtil.WriteLine("[PLUGIN MANAGER] Plugins update check failed! Aborting plugins update.", ConsoleColor.Yellow); ConsoleUtil.WriteLine($"[PLUGIN MANAGER] Reading metadata for port {port}...", ConsoleColor.Blue); - metadata = await JsonFile.Load(metadataPath, ignoreLocks ? 0 : PluginInstaller.DefaultLockTime); - if (metadata == null || metadata.InstalledPlugins.Count == 0) + if (await FileUtils.TryReadJsonAsync(metadataPath, FileShare.Read, JsonGenerated.Default.ServerPluginsConfig, timeout) is not { } newMetadata) { ConsoleUtil.WriteLine($"[PLUGIN MANAGER] No plugins installed for port {port}. Skipped.", ConsoleColor.Blue); return null; } + metadata = newMetadata; } ConsoleUtil.WriteLine("[PLUGIN MANAGER] Reading LocalAdmin config file...", ConsoleColor.Blue); - await Core.LocalAdmin.Singleton!.LoadJsonOrTerminate(); + await Core.LocalAdmin.Singleton.LoadJsonOrTerminate(); - List plugins = new(); + List plugins = []; foreach (var plugin in metadata.InstalledPlugins) { - var pluginPath = pluginsPath + $"{plugin.Key.Replace("/", "_", StringComparison.Ordinal)}.dll"; + var pluginPath = $"{pluginsPath}{plugin.Key.Replace("/", "_", StringComparison.Ordinal)}.dll"; if (!File.Exists(pluginPath)) { @@ -86,8 +81,8 @@ internal static class PluginStorage var currentHash = Sha.Sha256File(pluginPath); string? latestVersion = null; - if (Core.LocalAdmin.DataJson!.PluginVersionCache!.ContainsKey(plugin.Key)) - latestVersion = Core.LocalAdmin.DataJson.PluginVersionCache[plugin.Key].Version; + if (Core.LocalAdmin.DataJson!.PluginVersionCache.TryGetValue(plugin.Key, out PluginVersionCache value)) + latestVersion = value.Version; List? dependencies = null; @@ -96,7 +91,7 @@ internal static class PluginStorage if (!dep.Value.InstalledByPlugins.Contains(plugin.Key)) continue; - dependencies ??= new(); + dependencies ??= []; dependencies.Add(dep.Key); } diff --git a/PluginsManager/PluginUpdater.cs b/PluginsManager/PluginUpdater.cs index 9cb4497..5ec3cb4 100644 --- a/PluginsManager/PluginUpdater.cs +++ b/PluginsManager/PluginUpdater.cs @@ -1,8 +1,11 @@ using System; using System.Collections.Generic; using System.IO; +using System.Text.Json; using System.Threading.Tasks; using LocalAdmin.V2.IO; +using LocalAdmin.V2.JSON; +using LocalAdmin.V2.JSON.Objects; namespace LocalAdmin.V2.PluginsManager; @@ -10,38 +13,42 @@ internal static class PluginUpdater { internal static async Task CheckForUpdates(string port, bool ignoreLocks) { - var metadataPath = PluginInstaller.PluginsPath(port) + "metadata.json"; + var metadataPath = $"{PluginInstaller.PluginsPath(port)}metadata.json"; try { - if (!File.Exists(metadataPath)) + ConsoleUtil.WriteLine($"[PLUGIN MANAGER] Reading metadata for port {port}...", ConsoleColor.Blue); + uint timeout = ignoreLocks ? 0 : PluginInstaller.DefaultLockTime; + await using FileStream? fileStream = + await FileUtils.TryOpenAsync(metadataPath, FileAccess.ReadWrite, FileShare.None, timeout); + + if (fileStream == null) { ConsoleUtil.WriteLine($"[PLUGIN MANAGER] No metadata file for port {port}. Skipped.", ConsoleColor.Blue); return true; } - ConsoleUtil.WriteLine($"[PLUGIN MANAGER] Reading metadata for port {port}...", ConsoleColor.Blue); - var metadata = await JsonFile.Load(metadataPath, ignoreLocks ? 0 : PluginInstaller.DefaultLockTime, true); + var metadata = await JsonSerializer.DeserializeAsync(fileStream, JsonGenerated.Default.ServerPluginsConfig); if (metadata == null) { ConsoleUtil.WriteLine($"[PLUGIN MANAGER] No plugins installed for port {port}. Skipped.", ConsoleColor.Blue); - JsonFile.UnlockFile(metadataPath); return true; } if (metadata.InstalledPlugins.Count == 0) { ConsoleUtil.WriteLine($"[PLUGIN MANAGER] No plugins installed for port {port}. Skipped.", ConsoleColor.Blue); - metadata.LastUpdateCheck = DateTime.UtcNow; + metadata = metadata with { LastUpdateCheck = DateTime.UtcNow }; ConsoleUtil.WriteLine("[PLUGIN MANAGER] Writing metadata...", ConsoleColor.Blue); - await metadata.TrySave(metadataPath, 0, true); + fileStream.SetLength(0); + await JsonSerializer.SerializeAsync(fileStream, metadata, JsonGenerated.Default.ServerPluginsConfig); return true; } ConsoleUtil.WriteLine("[PLUGIN MANAGER] Reading LocalAdmin config file...", ConsoleColor.Blue); - await Core.LocalAdmin.Singleton!.LoadJsonOrTerminate(); + await Core.LocalAdmin.Singleton.LoadJsonOrTerminate(); ConsoleUtil.WriteLine("[PLUGIN MANAGER] Processing installed plugins...", ConsoleColor.Blue); @@ -83,23 +90,19 @@ internal static async Task CheckForUpdates(string port, bool ignoreLocks) ConsoleUtil.WriteLine("[PLUGIN MANAGER] Writing LocalAdmin config file...", ConsoleColor.Blue); await Core.LocalAdmin.Singleton.SaveJsonOrTerminate(); - metadata.LastUpdateCheck = DateTime.UtcNow; + metadata = metadata with { LastUpdateCheck = DateTime.UtcNow }; ConsoleUtil.WriteLine("[PLUGIN MANAGER] Writing metadata...", ConsoleColor.Blue); - if (await metadata.TrySave(metadataPath, 0, true)) - return true; - ConsoleUtil.WriteLine( - "[PLUGIN MANAGER] Failed to save metadata.", - ConsoleColor.Red); + fileStream.SetLength(0); + await JsonSerializer.SerializeAsync(fileStream, metadata, JsonGenerated.Default.ServerPluginsConfig); - return false; + return true; } catch (Exception e) { ConsoleUtil.WriteLine($"[PLUGIN MANAGER] Failed to check for plugin updates for port {port}! Exception: {e.Message}", ConsoleColor.Red); - JsonFile.UnlockFile(metadataPath); return false; } @@ -108,7 +111,7 @@ internal static async Task CheckForUpdates(string port, bool ignoreLocks) internal static async Task UpdatePlugins(string port, bool ignoreLocks, bool overwrite, bool skipUpdateCheck) { var pluginsPath = PluginInstaller.PluginsPath(port); - var metadataPath = pluginsPath + "metadata.json"; + var metadataPath = $"{pluginsPath}metadata.json"; try { @@ -119,9 +122,10 @@ internal static async Task UpdatePlugins(string port, bool ignoreLocks, bool ove } ConsoleUtil.WriteLine($"[PLUGIN MANAGER] Reading metadata for port {port}...", ConsoleColor.Blue); - var metadata = await JsonFile.Load(metadataPath, ignoreLocks ? 0 : PluginInstaller.DefaultLockTime); + uint timeout = ignoreLocks ? 0 : PluginInstaller.DefaultLockTime; - if (metadata == null || metadata.InstalledPlugins.Count == 0) + if (await FileUtils.TryReadJsonAsync(metadataPath, FileShare.Read, JsonGenerated.Default.ServerPluginsConfig, timeout) is not {} metadata + || metadata.InstalledPlugins.Count == 0) { ConsoleUtil.WriteLine($"[PLUGIN MANAGER] No plugins installed for port {port}. Skipped.", ConsoleColor.Blue); return; @@ -151,20 +155,21 @@ internal static async Task UpdatePlugins(string port, bool ignoreLocks, bool ove } ConsoleUtil.WriteLine($"[PLUGIN MANAGER] Reading metadata for port {port}...", ConsoleColor.Blue); - metadata = await JsonFile.Load(metadataPath, ignoreLocks ? 0 : PluginInstaller.DefaultLockTime); - - if (metadata == null || metadata.InstalledPlugins.Count == 0) + if (await FileUtils.TryReadJsonAsync(metadataPath, FileShare.Read, JsonGenerated.Default.ServerPluginsConfig, timeout) is not { } newMetadata + || newMetadata.InstalledPlugins.Count == 0) { ConsoleUtil.WriteLine($"[PLUGIN MANAGER] No plugins installed for port {port}. Skipped.", ConsoleColor.Blue); return; } + + metadata = newMetadata; } ConsoleUtil.WriteLine("[PLUGIN MANAGER] Reading LocalAdmin config file...", ConsoleColor.Blue); - await Core.LocalAdmin.Singleton!.LoadJsonOrTerminate(); + await Core.LocalAdmin.Singleton.LoadJsonOrTerminate(); var i = 0; - List toRemove = new(); + List toRemove = []; foreach (var plugin in metadata.InstalledPlugins) { @@ -173,7 +178,7 @@ internal static async Task UpdatePlugins(string port, bool ignoreLocks, bool ove ConsoleUtil.WriteLine($"[PLUGIN MANAGER] Processing plugin {plugin.Key} ({i}/{metadata.InstalledPlugins.Count})...", ConsoleColor.Blue); var safeName = plugin.Key.Replace("/", "_", StringComparison.Ordinal); - var pluginPath = pluginsPath + $"{safeName}.dll"; + var pluginPath = $"{pluginsPath}{safeName}.dll"; if (!File.Exists(pluginPath)) { @@ -204,7 +209,7 @@ internal static async Task UpdatePlugins(string port, bool ignoreLocks, bool ove continue; } - if (!Core.LocalAdmin.DataJson!.PluginVersionCache!.ContainsKey(plugin.Key)) + if (!Core.LocalAdmin.DataJson!.PluginVersionCache.ContainsKey(plugin.Key)) { ConsoleUtil.WriteLine($"[PLUGIN MANAGER] Plugin {plugin.Key} is not cached! Skipped.", ConsoleColor.Yellow); continue; @@ -224,15 +229,16 @@ internal static async Task UpdatePlugins(string port, bool ignoreLocks, bool ove if (toRemove.Count != 0) { - ConsoleUtil.WriteLine($"[PLUGIN MANAGER] Removing manually uninstalled plugins from metadata file...", ConsoleColor.Blue); + ConsoleUtil.WriteLine("[PLUGIN MANAGER] Removing manually uninstalled plugins from metadata file...", ConsoleColor.Blue); ConsoleUtil.WriteLine($"[PLUGIN MANAGER] Reading metadata for port {port}...", ConsoleColor.Blue); - metadata = await JsonFile.Load(metadataPath, ignoreLocks ? 0 : PluginInstaller.DefaultLockTime, true); + await using FileStream fileStream = await FileUtils.OpenAsync(metadataPath, FileMode.Open, FileAccess.ReadWrite, FileShare.None, timeout); + + metadata = await JsonSerializer.DeserializeAsync(fileStream, JsonGenerated.Default.ServerPluginsConfig); if (metadata == null || metadata.InstalledPlugins.Count == 0) { ConsoleUtil.WriteLine("[PLUGIN MANAGER] Reading metadata filed.", ConsoleColor.Red); - JsonFile.UnlockFile(metadataPath); return; } @@ -242,8 +248,9 @@ internal static async Task UpdatePlugins(string port, bool ignoreLocks, bool ove } ConsoleUtil.WriteLine("[PLUGIN MANAGER] Writing metadata...", ConsoleColor.Blue); - if (!await metadata.TrySave(metadataPath, 0, true)) - return; + + fileStream.SetLength(0); + await JsonSerializer.SerializeAsync(fileStream, metadata, JsonGenerated.Default.ServerPluginsConfig); ConsoleUtil.WriteLine("[PLUGIN MANAGER] Writing metadata complete.", ConsoleColor.Blue); } @@ -252,7 +259,6 @@ internal static async Task UpdatePlugins(string port, bool ignoreLocks, bool ove { ConsoleUtil.WriteLine($"[PLUGIN MANAGER] Failed to update plugins for port {port}! Exception: {e.Message}", ConsoleColor.Red); - JsonFile.UnlockFile(metadataPath); } } } \ No newline at end of file diff --git a/app.manifest b/app.manifest index 59de87b..6c99604 100644 --- a/app.manifest +++ b/app.manifest @@ -13,12 +13,6 @@ - - - - - -