From fdec9dc5cec0f234fdfa86af62a3b98056fa073f Mon Sep 17 00:00:00 2001 From: AlexanderK <73738345+AlexanderK666@users.noreply.github.com> Date: Wed, 17 Jul 2024 17:04:22 +0300 Subject: [PATCH 1/6] added support for input transfer during logging Now when logging commands in the console, input transfer works, which improves user interaction with the server console, where logs are written in a thread and begin to interfere with viewing the current input. --- Core/LocalAdmin.cs | 163 +++++++++++++++++++++++++++++---------------- IO/ConsoleUtil.cs | 30 ++++++++- 2 files changed, 133 insertions(+), 60 deletions(-) diff --git a/Core/LocalAdmin.cs b/Core/LocalAdmin.cs index 2acaa63..774439b 100644 --- a/Core/LocalAdmin.cs +++ b/Core/LocalAdmin.cs @@ -189,36 +189,42 @@ internal async Task Start(string[] args) ConsoleUtil.WriteLine("", ConsoleColor.Cyan); ConsoleUtil.WriteLine("Do you accept the EULA? [yes/no]", ConsoleColor.Cyan); - ReadInput((input) => + bool onCheckInput(string? input) + { + if (input == null) + return false; + + switch (input.ToLowerInvariant()) { - if (input == null) + case "y": + case "yes": + case "1": + DataJson!.EulaAccepted = DateTime.UtcNow; + return true; + + case "n": + case "no": + case "nope": + case "0": + ConsoleUtil.WriteLine("You have to accept the EULA to use LocalAdmin and SCP: Secret Laboratory Dedicated Server.", ConsoleColor.Red); + Terminate(); + return true; + + default: return false; + } + } - switch (input.ToLowerInvariant()) - { - case "y": - case "yes": - case "1": - DataJson.EulaAccepted = DateTime.UtcNow; - return true; - - case "n": - case "no": - case "nope": - case "0": - ConsoleUtil.WriteLine("You have to accept the EULA to use LocalAdmin and SCP: Secret Laboratory Dedicated Server.", ConsoleColor.Red); - Terminate(); - return true; - - default: - return false; - } + void onValidInput() + { + } - }, () => { }, - () => - { - ConsoleUtil.WriteLine("Do you accept the EULA? [yes/no]", ConsoleColor.Red); - }); + void onInvalidInput() + { + ConsoleUtil.WriteLine("Do you accept the EULA? [yes/no]", ConsoleColor.Red); + } + + ReadInput(onCheckInput, onValidInput, onInvalidInput); if (!_exit) await SaveJsonOrTerminate(); @@ -233,22 +239,29 @@ internal async Task Start(string[] args) { ConsoleUtil.WriteLine("You can pass port number as first startup argument.", ConsoleColor.Green); + ConsoleUtil.ResetLine(); Console.WriteLine(string.Empty); ConsoleUtil.Write($"Port number (default: {DefaultPort}): ", ConsoleColor.Green); + + bool onCheckInput(string? input) + { + if (!string.IsNullOrEmpty(input)) + return ushort.TryParse(input, out GamePort); - ReadInput((input) => - { - if (!string.IsNullOrEmpty(input)) - return ushort.TryParse(input, out GamePort); - GamePort = DefaultPort; - return true; + GamePort = DefaultPort; + return true; + } - }, () => { }, - () => - { - ConsoleUtil.WriteLine("Port number must be a unsigned short integer.", - ConsoleColor.Red); - }); + void onValidInput() + { + } + + void onInvalidInput() + { + ConsoleUtil.WriteLine("Port number must be a unsigned short integer.", ConsoleColor.Red); + } + + ReadInput(onCheckInput, onValidInput, onInvalidInput); } var capture = CaptureArgs.None; @@ -581,8 +594,8 @@ private void StartSession() SetTerminalTitle(); Logger.Initialize(); - ConsoleUtil.WriteLine($"Started new session on port {GamePort}.", ConsoleColor.DarkGreen); - ConsoleUtil.WriteLine("Trying to start server...", ConsoleColor.Gray); + ConsoleUtil.WriteLine($"Started new session on port {GamePort}.", ConsoleColor.DarkGreen, inputIntro: false); + ConsoleUtil.WriteLine("Trying to start server...", ConsoleColor.Gray, inputIntro: false); SetupServer(); @@ -594,14 +607,21 @@ private void StartSession() private static void Menu() { + var headerLines = new string[] + { + $"SCP: Secret Laboratory - LocalAdmin v. {VersionString}", + string.Empty, + "Licensed under The MIT License (use command \"license\" to get license text).", + "Copyright by Łukasz \"zabszk\" Jurczyk and KernelError, 2019 - 2024", + "Type 'help' to get list of available commands.", + string.Empty + }; + ConsoleUtil.Clear(); - 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(string.Empty, ConsoleColor.Cyan); - ConsoleUtil.WriteLine("Type 'help' to get list of available commands.", ConsoleColor.Cyan); - ConsoleUtil.WriteLine(string.Empty, ConsoleColor.Cyan); + + foreach (var line in headerLines) { + ConsoleUtil.WriteLine(line, ConsoleColor.Cyan, inputIntro: false); + } } private static void SetupExitHandlers() @@ -668,18 +688,46 @@ private void SetupServer() Server.Start(); } + internal static string CurrentInput = ""; + private static void SetupKeyboardInput() { new Task(() => { + ConsoleKeyInfo consoleKeyInfo; + while (!_exit) { - var input = Console.ReadLine(); + ConsoleUtil.WriteInputIntro(); - if (string.IsNullOrWhiteSpace(input)) + do + { + consoleKeyInfo = Console.ReadKey(true); + + if (consoleKeyInfo.Key == ConsoleKey.Backspace) + { + if (Console.CursorLeft < ConsoleUtil.InputIntro.Length+1) + continue; + + Console.CursorLeft--; + Console.Write(" "); + Console.CursorLeft--; + } + else + { + Console.Write(consoleKeyInfo.KeyChar); + } + + CurrentInput += consoleKeyInfo.KeyChar; + } while (consoleKeyInfo.Key != ConsoleKey.Enter); + + //var CurrentInput = Console.ReadLine(); + + if (string.IsNullOrWhiteSpace(CurrentInput)) continue; - InputQueue.Enqueue(input); + InputQueue.Enqueue(CurrentInput); + CurrentInput = string.Empty; } }).Start(); } @@ -705,13 +753,14 @@ async void ReaderTaskMethod() if (currentLineCursor > 0) { - Console.SetCursorPosition(0, currentLineCursor - 1); - - ConsoleUtil.WriteLine($"{string.Empty.PadLeft(Console.WindowWidth)}>>> {input}", ConsoleColor.DarkMagenta, -1); - Console.SetCursorPosition(0, currentLineCursor); + Console.CursorTop = currentLineCursor - 1; + ConsoleUtil.ResetLine(); } - else - ConsoleUtil.WriteLine($">>> {input}", ConsoleColor.DarkMagenta, -1); + + ConsoleUtil.WriteLine($">>> {input}", ConsoleColor.DarkMagenta, -1); + + if (currentLineCursor > 0) + Console.CursorTop = currentLineCursor; if (!_processRefreshFail && _gameProcess != null) { @@ -772,7 +821,7 @@ private void RunScpsl() { if (File.Exists(_scpslExecutable)) { - ConsoleUtil.WriteLine("Executing: " + _scpslExecutable, ConsoleColor.DarkGreen); + ConsoleUtil.WriteLine("Executing: " + _scpslExecutable, ConsoleColor.DarkGreen, inputIntro: false); var printStd = Configuration!.LaShowStdoutStderr || _stdPrint; var redirectStreams = Configuration.LaLogStdoutStderr || printStd; diff --git a/IO/ConsoleUtil.cs b/IO/ConsoleUtil.cs index 383b69b..280a06e 100644 --- a/IO/ConsoleUtil.cs +++ b/IO/ConsoleUtil.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Globalization; using System.IO; using LocalAdmin.V2.IO.Logging; @@ -7,6 +7,10 @@ namespace LocalAdmin.V2.IO; public static class ConsoleUtil { + internal const string InputIntro = "> "; + + internal static int InputCursorLeft => InputIntro.Length + Core.LocalAdmin.CurrentInput.Length; + private static readonly char[] ToTrim = { '\n', '\r' }; private static readonly object Lck = new object(); @@ -93,6 +97,8 @@ public static void Write(string content, ConsoleColor color = ConsoleColor.White if (height > 0 && !Core.LocalAdmin.NoSetCursor) Console.CursorTop += height; + ResetLine(); + Console.Write($"{GetLiveViewTimestamp()} {(multiline ? content.Replace("\n", GetLiveViewPadding(), StringComparison.Ordinal) : content)}"); Console.ForegroundColor = ConsoleColor.White; @@ -103,7 +109,7 @@ public static void Write(string content, ConsoleColor color = ConsoleColor.White } } - public static void WriteLine(string? content, ConsoleColor color = ConsoleColor.White, int height = 0, bool log = true, bool display = true) + public static void WriteLine(string? content, ConsoleColor color = ConsoleColor.White, int height = 0, bool log = true, bool display = true, bool inputIntro = true) { lock (Lck) { @@ -124,13 +130,31 @@ public static void WriteLine(string? content, ConsoleColor color = ConsoleColor. if (height > 0 && !Core.LocalAdmin.NoSetCursor) Console.CursorTop += height; + ResetLine(); + Console.WriteLine($"{GetLiveViewTimestamp()} {(multiline ? content.Replace("\n", GetLiveViewPadding(), StringComparison.Ordinal) : content)}"); - Console.ForegroundColor = ConsoleColor.White; + if(inputIntro) + WriteInputIntro(); } if (log) Logger.Log($"{GetLogsTimestamp()} {(multiline ? content.Replace("\n", GetLogsPadding(), StringComparison.Ordinal) : content)}"); } } + + public static void WriteInputIntro() + { + Console.ForegroundColor = ConsoleColor.DarkGray; + Console.Write("> "); + Console.ForegroundColor = ConsoleColor.White; + Console.Write(Core.LocalAdmin.CurrentInput); + } + + public static void ResetLine() + { + Console.CursorLeft = 0; + Console.Write(string.Empty.PadLeft(Console.WindowWidth)); + Console.CursorLeft = 0; + } } \ No newline at end of file From eb15613595cc1b0b2ea1980087e5c7f76d24c883 Mon Sep 17 00:00:00 2001 From: AlexanderK <73738345+AlexanderK666@users.noreply.github.com> Date: Wed, 17 Jul 2024 17:16:03 +0300 Subject: [PATCH 2/6] Uploaded from the correct folder --- Core/ConfigWizard.cs | 2 + Core/LocalAdmin.cs | 194 ++++++++++++++++++++++++++++++++++++++----- IO/ConsoleUtil.cs | 23 +++++ 3 files changed, 196 insertions(+), 23 deletions(-) diff --git a/Core/ConfigWizard.cs b/Core/ConfigWizard.cs index 252b9e0..edf963d 100644 --- a/Core/ConfigWizard.cs +++ b/Core/ConfigWizard.cs @@ -37,6 +37,7 @@ public static void RunConfigWizard(bool useDefault) !input.Equals("keep", StringComparison.OrdinalIgnoreCase)) { Console.WriteLine("Do you want to edit that configuration? [edit/keep]: "); + ConsoleUtil.WriteInputIntro(); input = Console.ReadLine(); } @@ -242,6 +243,7 @@ private static void SaveConfig(bool silent = false) { Console.WriteLine( $"Do you want to save the configuration only for THIS server (on port {LocalAdmin.GamePort} or should it become a GLOBAL configuration (default one for all future servers - servers not configured yet)? [this/global]: "); + ConsoleUtil.WriteInputIntro(); input = Console.ReadLine(); } } diff --git a/Core/LocalAdmin.cs b/Core/LocalAdmin.cs index 774439b..5ff4025 100644 --- a/Core/LocalAdmin.cs +++ b/Core/LocalAdmin.cs @@ -15,6 +15,7 @@ using LocalAdmin.V2.Commands.PluginManager; using LocalAdmin.V2.IO.Logging; using LocalAdmin.V2.PluginsManager; +using System.Collections.Generic; namespace LocalAdmin.V2.Core; /* @@ -77,6 +78,11 @@ public sealed class LocalAdmin : IDisposable internal TcpServer? Server { get; private set; } internal bool EnableGameHeartbeat { get; private set; } + private const int CommandsHistorySize = 15; + private static string _currentInput = ""; + private static readonly object _inputLockObject = new object(); + private static List _commandsHistory = new List(); + internal enum ShutdownAction : byte { Crash, @@ -113,7 +119,7 @@ internal LocalAdmin() _scpslExecutable = "SCPSL.x86_64"; else { - ConsoleUtil.WriteLine("Failed - Unsupported platform! Please switch to the Windows, or Linux platform to continue.", ConsoleColor.Red); + ConsoleUtil.WriteLine("Failed - Unsupported platform! Please switch to the Windows, or Linux platform to continue.", ConsoleColor.Red, inputIntro: false); // shut up dotnet _scpslExecutable = string.Empty; Exit(1); @@ -131,24 +137,24 @@ internal async Task Start(string[] args) if (!PathManager.CorrectPathFound && !args.Contains("--skipHomeCheck", StringComparer.Ordinal)) { - ConsoleUtil.WriteLine($"Welcome to LocalAdmin version {VersionString}!", ConsoleColor.Red); - ConsoleUtil.WriteLine("Can't obtain a valid user home directory path!", ConsoleColor.Red); + ConsoleUtil.WriteLine($"Welcome to LocalAdmin version {VersionString}!", ConsoleColor.Red, inputIntro: false); + ConsoleUtil.WriteLine("Can't obtain a valid user home directory path!", ConsoleColor.Red, inputIntro: false); if (OperatingSystem.IsWindows()) { - 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); + ConsoleUtil.WriteLine("Such error should never occur on Windows.", ConsoleColor.Red, inputIntro: false); + ConsoleUtil.WriteLine("Open issue on the LocalAdmin GitHub repository (https://github.com/northwood-studios/LocalAdmin-V2/issues) or contact our technical support!", ConsoleColor.Red, inputIntro: false); } else if (OperatingSystem.IsLinux()) { - 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); + ConsoleUtil.WriteLine("Make sure to export a valid path, for example using command: export HOME=/home/username-here", ConsoleColor.Red, inputIntro: false); + 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, inputIntro: false); } else { - ConsoleUtil.WriteLine("You are running LocalAdmin on an unsupported platform, please switch to Windows or Linux!", ConsoleColor.Red); + ConsoleUtil.WriteLine("You are running LocalAdmin on an unsupported platform, please switch to Windows or Linux!", ConsoleColor.Red, inputIntro: false); throw new PlatformNotSupportedException(); } - ConsoleUtil.WriteLine("To skip this check, use --skipHomeCheck argument.", ConsoleColor.Red); + ConsoleUtil.WriteLine("To skip this check, use --skipHomeCheck argument.", ConsoleColor.Red, inputIntro: false); Terminate(); return; } @@ -172,7 +178,7 @@ internal async Task Start(string[] args) if (_restarts > _restartsLimit) { - ConsoleUtil.WriteLine("Restarts limit exceeded.", ConsoleColor.Red); + ConsoleUtil.WriteLine("Restarts limit exceeded.", ConsoleColor.Red, inputIntro: false); Terminate(); } } @@ -183,12 +189,24 @@ internal async Task Start(string[] args) if (DataJson!.EulaAccepted == null) { - ConsoleUtil.WriteLine($"Welcome to LocalAdmin version {VersionString}!", ConsoleColor.Cyan); - ConsoleUtil.WriteLine("Before starting please read and accept the SCP:SL EULA.", ConsoleColor.Cyan); - ConsoleUtil.WriteLine("You can find it on the following website: https://link.scpslgame.com/eula", ConsoleColor.Cyan); - ConsoleUtil.WriteLine("", ConsoleColor.Cyan); - ConsoleUtil.WriteLine("Do you accept the EULA? [yes/no]", ConsoleColor.Cyan); + var headerLines = new string[] + { + $"Welcome to LocalAdmin version {VersionString}!", + "Before starting please read and accept the SCP:SL EULA.", + "You can find it on the following website: https://link.scpslgame.com/eula", + string.Empty, + "Do you accept the EULA? [yes/no]", + }; + +<<<<<<< Updated upstream +======= + foreach (var line in headerLines) + { + ConsoleUtil.WriteLine(line, ConsoleColor.Cyan); + } + +>>>>>>> Stashed changes bool onCheckInput(string? input) { if (input == null) @@ -206,13 +224,21 @@ bool onCheckInput(string? input) case "no": case "nope": case "0": +<<<<<<< Updated upstream ConsoleUtil.WriteLine("You have to accept the EULA to use LocalAdmin and SCP: Secret Laboratory Dedicated Server.", ConsoleColor.Red); +======= + ConsoleUtil.WriteLine("You have to accept the EULA to use LocalAdmin and SCP: Secret Laboratory Dedicated Server.", ConsoleColor.Red, inputIntro: false); +>>>>>>> Stashed changes Terminate(); return true; default: return false; +<<<<<<< Updated upstream } +======= + } +>>>>>>> Stashed changes } void onValidInput() @@ -238,8 +264,12 @@ void onInvalidInput() if (args.Length == 0 || !ushort.TryParse(args[0], out GamePort)) { ConsoleUtil.WriteLine("You can pass port number as first startup argument.", +<<<<<<< Updated upstream ConsoleColor.Green); ConsoleUtil.ResetLine(); +======= + ConsoleColor.Green, inputIntro: false); +>>>>>>> Stashed changes Console.WriteLine(string.Empty); ConsoleUtil.Write($"Port number (default: {DefaultPort}): ", ConsoleColor.Green); @@ -248,17 +278,34 @@ bool onCheckInput(string? input) if (!string.IsNullOrEmpty(input)) return ushort.TryParse(input, out GamePort); +<<<<<<< Updated upstream + GamePort = DefaultPort; + return true; + } + +======= + bool onCheckInput(string? input) + { + if (!string.IsNullOrEmpty(input)) + return ushort.TryParse(input, out GamePort); + GamePort = DefaultPort; return true; } +>>>>>>> Stashed changes void onValidInput() { } void onInvalidInput() { +<<<<<<< Updated upstream ConsoleUtil.WriteLine("Port number must be a unsigned short integer.", ConsoleColor.Red); +======= + ConsoleUtil.WriteLine("Port number must be a unsigned short integer.", + ConsoleColor.Red, inputIntro: false); +>>>>>>> Stashed changes } ReadInput(onCheckInput, onValidInput, onInvalidInput); @@ -420,7 +467,7 @@ void onInvalidInput() if (!int.TryParse(arg, out _restartsLimit) || _restartsLimit < -1) { _restartsLimit = 4; - ConsoleUtil.WriteLine("restartsLimit argument value must be an integer greater or equal to -1.", ConsoleColor.Red); + ConsoleUtil.WriteLine("restartsLimit argument value must be an integer greater or equal to -1.", ConsoleColor.Red, inputIntro: false); } capture = CaptureArgs.None; break; @@ -429,7 +476,7 @@ void onInvalidInput() if (!int.TryParse(arg, out _restartsTimeWindow) || _restartsLimit < 0) { _restartsTimeWindow = 480; - ConsoleUtil.WriteLine("restartsTimeWindow argument value must be an integer greater or equal to 0.", ConsoleColor.Red); + ConsoleUtil.WriteLine("restartsTimeWindow argument value must be an integer greater or equal to 0.", ConsoleColor.Red, inputIntro: false); } capture = CaptureArgs.None; break; @@ -444,7 +491,7 @@ void onInvalidInput() { ConsoleUtil.WriteLine( "logLengthLimit argument value must be an integer greater or equal to 0.", - ConsoleColor.Red); + ConsoleColor.Red, inputIntro: false); LogLengthLimit = 25000000000; } @@ -462,7 +509,8 @@ void onInvalidInput() { ConsoleUtil.WriteLine( "logEntriesLimit argument value must be an integer greater or equal to 0.", - ConsoleColor.Red); + ConsoleColor.Red, + inputIntro: false); LogEntriesLimit = 10000000000; } @@ -607,19 +655,34 @@ private void StartSession() private static void Menu() { +<<<<<<< Updated upstream var headerLines = new string[] { +======= + var headerLines = new string[] { +>>>>>>> Stashed changes $"SCP: Secret Laboratory - LocalAdmin v. {VersionString}", string.Empty, "Licensed under The MIT License (use command \"license\" to get license text).", "Copyright by Łukasz \"zabszk\" Jurczyk and KernelError, 2019 - 2024", +<<<<<<< Updated upstream "Type 'help' to get list of available commands.", string.Empty +======= + string.Empty, + "Type 'help' to get list of available commands.", + string.Empty, +>>>>>>> Stashed changes }; ConsoleUtil.Clear(); +<<<<<<< Updated upstream foreach (var line in headerLines) { +======= + foreach (var line in headerLines) + { +>>>>>>> Stashed changes ConsoleUtil.WriteLine(line, ConsoleColor.Cyan, inputIntro: false); } } @@ -688,22 +751,65 @@ private void SetupServer() Server.Start(); } +<<<<<<< Updated upstream internal static string CurrentInput = ""; +======= + internal static string CurrentInput + { + get + { + lock (_inputLockObject) + { + return _currentInput; + } + } + set + { + lock (_inputLockObject) + { + _currentInput = value; + } + } + } +>>>>>>> Stashed changes private static void SetupKeyboardInput() { new Task(() => { ConsoleKeyInfo consoleKeyInfo; +<<<<<<< Updated upstream + + while (!_exit) + { +======= + int historyIndex = -1; + + static void AddToCommandHistory(string command) + { + if (_commandsHistory.Contains(command)) + { + _commandsHistory.Remove(command); + } + else if (_commandsHistory.Count == CommandsHistorySize) + { + _commandsHistory.RemoveAt(_commandsHistory.Count - 1); + } + _commandsHistory.Insert(0, command); + } while (!_exit) { + NEXT_CYCLE: + ConsoleUtil.ResetLine(); +>>>>>>> Stashed changes ConsoleUtil.WriteInputIntro(); do { consoleKeyInfo = Console.ReadKey(true); +<<<<<<< Updated upstream if (consoleKeyInfo.Key == ConsoleKey.Backspace) { if (Console.CursorLeft < ConsoleUtil.InputIntro.Length+1) @@ -720,12 +826,50 @@ private static void SetupKeyboardInput() CurrentInput += consoleKeyInfo.KeyChar; } while (consoleKeyInfo.Key != ConsoleKey.Enter); +======= + if (consoleKeyInfo.Key == ConsoleKey.UpArrow && _commandsHistory.Count > 0) + { + historyIndex = Math.Abs((historyIndex + 1) % _commandsHistory.Count); + CurrentInput = _commandsHistory[historyIndex]; + goto NEXT_CYCLE; + } + else if (consoleKeyInfo.Key == ConsoleKey.DownArrow && _commandsHistory.Count > 0) + { + historyIndex = Math.Abs((historyIndex - 1) % _commandsHistory.Count); + CurrentInput = _commandsHistory[historyIndex]; + goto NEXT_CYCLE; + } + else if (consoleKeyInfo.Key == ConsoleKey.Backspace) + { + if (Console.CursorLeft < ConsoleUtil.InputIntro.Length + 1) + continue; + + CurrentInput = CurrentInput[..^1]; + Console.Write("\b \b"); + } + else if (consoleKeyInfo.Key != ConsoleKey.Enter && !char.IsControl(consoleKeyInfo.KeyChar)) + { + CurrentInput += consoleKeyInfo.KeyChar; + Console.Write(consoleKeyInfo.KeyChar); + } + } while (consoleKeyInfo.Key != ConsoleKey.Enter && !_exit); +>>>>>>> Stashed changes //var CurrentInput = Console.ReadLine(); if (string.IsNullOrWhiteSpace(CurrentInput)) continue; +<<<<<<< Updated upstream +======= + AddToCommandHistory(CurrentInput); + + if (NoSetCursor) + { + Console.WriteLine(); + } + +>>>>>>> Stashed changes InputQueue.Enqueue(CurrentInput); CurrentInput = string.Empty; } @@ -749,6 +893,7 @@ async void ReaderTaskMethod() if (string.IsNullOrWhiteSpace(input)) continue; +<<<<<<< Updated upstream var currentLineCursor = NoSetCursor ? 0 : Console.CursorTop; if (currentLineCursor > 0) @@ -761,6 +906,9 @@ async void ReaderTaskMethod() if (currentLineCursor > 0) Console.CursorTop = currentLineCursor; +======= + ConsoleUtil.WriteLine($">>> {input}", ConsoleColor.DarkMagenta, -1); +>>>>>>> Stashed changes if (!_processRefreshFail && _gameProcess != null) { @@ -768,7 +916,7 @@ async void ReaderTaskMethod() { if (_gameProcess.HasExited) { - ConsoleUtil.WriteLine("Failed to send command - the game process was terminated...", ConsoleColor.Red); + ConsoleUtil.WriteLine("Failed to send command - the game process was terminated...", ConsoleColor.Red, inputIntro: false); _exit = true; continue; } @@ -1022,7 +1170,7 @@ public void Exit(int code = -1, bool waitForKey = false, bool restart = false) if (waitForKey && ExitAction != ShutdownAction.SilentShutdown) { - ConsoleUtil.WriteLine("Press any key to close...", ConsoleColor.DarkGray); + ConsoleUtil.WriteLine("Press any key to close...", ConsoleColor.DarkGray, inputIntro: false); Console.ReadKey(true); } @@ -1058,7 +1206,7 @@ internal async Task LoadJsonOrTerminate() if (DataJson == null) { - ConsoleUtil.WriteLine("Json file is corrupted! Terminating LocalAdmin. If the issue persists, please delete the file and restart LocalAdmin.", ConsoleColor.Red); + ConsoleUtil.WriteLine("Json file is corrupted! Terminating LocalAdmin. If the issue persists, please delete the file and restart LocalAdmin.", ConsoleColor.Red, inputIntro: false); Terminate(); } } @@ -1071,7 +1219,7 @@ internal async Task LoadJsonOrTerminate() } catch (Exception e) { - ConsoleUtil.WriteLine($"Failed to read JSON config file: {e.Message}", ConsoleColor.Red); + ConsoleUtil.WriteLine($"Failed to read JSON config file: {e.Message}", ConsoleColor.Red, inputIntro: false); DataJson = null; Terminate(); throw; diff --git a/IO/ConsoleUtil.cs b/IO/ConsoleUtil.cs index 280a06e..0023b1d 100644 --- a/IO/ConsoleUtil.cs +++ b/IO/ConsoleUtil.cs @@ -134,7 +134,11 @@ public static void WriteLine(string? content, ConsoleColor color = ConsoleColor. Console.WriteLine($"{GetLiveViewTimestamp()} {(multiline ? content.Replace("\n", GetLiveViewPadding(), StringComparison.Ordinal) : content)}"); +<<<<<<< Updated upstream if(inputIntro) +======= + if (inputIntro) +>>>>>>> Stashed changes WriteInputIntro(); } @@ -145,16 +149,35 @@ public static void WriteLine(string? content, ConsoleColor color = ConsoleColor. public static void WriteInputIntro() { +<<<<<<< Updated upstream Console.ForegroundColor = ConsoleColor.DarkGray; Console.Write("> "); Console.ForegroundColor = ConsoleColor.White; Console.Write(Core.LocalAdmin.CurrentInput); +======= + lock (Lck) + { + Console.ForegroundColor = ConsoleColor.DarkGray; + Console.Write("> "); + Console.ForegroundColor = ConsoleColor.White; + Console.Write(Core.LocalAdmin.CurrentInput); + } +>>>>>>> Stashed changes } public static void ResetLine() { +<<<<<<< Updated upstream Console.CursorLeft = 0; Console.Write(string.Empty.PadLeft(Console.WindowWidth)); Console.CursorLeft = 0; +======= + lock (Lck) + { + Console.CursorLeft = 0; + Console.Write(string.Empty.PadLeft(Console.WindowWidth)); + Console.CursorLeft = 0; + } +>>>>>>> Stashed changes } } \ No newline at end of file From 361a198cf341e8a000a377344f8e2dbe09f10f2e Mon Sep 17 00:00:00 2001 From: AlexanderK <73738345+AlexanderK666@users.noreply.github.com> Date: Wed, 17 Jul 2024 17:27:42 +0300 Subject: [PATCH 3/6] Looks like it's fixed --- Core/LocalAdmin.cs | 135 ++++++--------------------------------------- IO/ConsoleUtil.cs | 17 ------ 2 files changed, 18 insertions(+), 134 deletions(-) diff --git a/Core/LocalAdmin.cs b/Core/LocalAdmin.cs index 5ff4025..7be776e 100644 --- a/Core/LocalAdmin.cs +++ b/Core/LocalAdmin.cs @@ -83,6 +83,24 @@ public sealed class LocalAdmin : IDisposable private static readonly object _inputLockObject = new object(); private static List _commandsHistory = new List(); + internal static string CurrentInput + { + get + { + lock (_inputLockObject) + { + return _currentInput; + } + } + set + { + lock (_inputLockObject) + { + _currentInput = value; + } + } + } + internal enum ShutdownAction : byte { Crash, @@ -198,15 +216,11 @@ internal async Task Start(string[] args) "Do you accept the EULA? [yes/no]", }; -<<<<<<< Updated upstream -======= foreach (var line in headerLines) { ConsoleUtil.WriteLine(line, ConsoleColor.Cyan); } - ->>>>>>> Stashed changes bool onCheckInput(string? input) { if (input == null) @@ -224,21 +238,13 @@ bool onCheckInput(string? input) case "no": case "nope": case "0": -<<<<<<< Updated upstream - ConsoleUtil.WriteLine("You have to accept the EULA to use LocalAdmin and SCP: Secret Laboratory Dedicated Server.", ConsoleColor.Red); -======= ConsoleUtil.WriteLine("You have to accept the EULA to use LocalAdmin and SCP: Secret Laboratory Dedicated Server.", ConsoleColor.Red, inputIntro: false); ->>>>>>> Stashed changes Terminate(); return true; default: return false; -<<<<<<< Updated upstream } -======= - } ->>>>>>> Stashed changes } void onValidInput() @@ -264,12 +270,7 @@ void onInvalidInput() if (args.Length == 0 || !ushort.TryParse(args[0], out GamePort)) { ConsoleUtil.WriteLine("You can pass port number as first startup argument.", -<<<<<<< Updated upstream - ConsoleColor.Green); - ConsoleUtil.ResetLine(); -======= ConsoleColor.Green, inputIntro: false); ->>>>>>> Stashed changes Console.WriteLine(string.Empty); ConsoleUtil.Write($"Port number (default: {DefaultPort}): ", ConsoleColor.Green); @@ -278,34 +279,18 @@ bool onCheckInput(string? input) if (!string.IsNullOrEmpty(input)) return ushort.TryParse(input, out GamePort); -<<<<<<< Updated upstream GamePort = DefaultPort; return true; } -======= - bool onCheckInput(string? input) - { - if (!string.IsNullOrEmpty(input)) - return ushort.TryParse(input, out GamePort); - - GamePort = DefaultPort; - return true; - } - ->>>>>>> Stashed changes void onValidInput() { } void onInvalidInput() { -<<<<<<< Updated upstream - ConsoleUtil.WriteLine("Port number must be a unsigned short integer.", ConsoleColor.Red); -======= ConsoleUtil.WriteLine("Port number must be a unsigned short integer.", ConsoleColor.Red, inputIntro: false); ->>>>>>> Stashed changes } ReadInput(onCheckInput, onValidInput, onInvalidInput); @@ -655,34 +640,20 @@ private void StartSession() private static void Menu() { -<<<<<<< Updated upstream - var headerLines = new string[] - { -======= var headerLines = new string[] { ->>>>>>> Stashed changes $"SCP: Secret Laboratory - LocalAdmin v. {VersionString}", string.Empty, "Licensed under The MIT License (use command \"license\" to get license text).", "Copyright by Łukasz \"zabszk\" Jurczyk and KernelError, 2019 - 2024", -<<<<<<< Updated upstream - "Type 'help' to get list of available commands.", - string.Empty -======= string.Empty, "Type 'help' to get list of available commands.", string.Empty, ->>>>>>> Stashed changes }; ConsoleUtil.Clear(); -<<<<<<< Updated upstream - foreach (var line in headerLines) { -======= foreach (var line in headerLines) { ->>>>>>> Stashed changes ConsoleUtil.WriteLine(line, ConsoleColor.Cyan, inputIntro: false); } } @@ -751,38 +722,11 @@ private void SetupServer() Server.Start(); } -<<<<<<< Updated upstream - internal static string CurrentInput = ""; -======= - internal static string CurrentInput - { - get - { - lock (_inputLockObject) - { - return _currentInput; - } - } - set - { - lock (_inputLockObject) - { - _currentInput = value; - } - } - } ->>>>>>> Stashed changes - private static void SetupKeyboardInput() { new Task(() => { ConsoleKeyInfo consoleKeyInfo; -<<<<<<< Updated upstream - - while (!_exit) - { -======= int historyIndex = -1; static void AddToCommandHistory(string command) @@ -802,31 +746,12 @@ static void AddToCommandHistory(string command) { NEXT_CYCLE: ConsoleUtil.ResetLine(); ->>>>>>> Stashed changes ConsoleUtil.WriteInputIntro(); do { consoleKeyInfo = Console.ReadKey(true); -<<<<<<< Updated upstream - if (consoleKeyInfo.Key == ConsoleKey.Backspace) - { - if (Console.CursorLeft < ConsoleUtil.InputIntro.Length+1) - continue; - - Console.CursorLeft--; - Console.Write(" "); - Console.CursorLeft--; - } - else - { - Console.Write(consoleKeyInfo.KeyChar); - } - - CurrentInput += consoleKeyInfo.KeyChar; - } while (consoleKeyInfo.Key != ConsoleKey.Enter); -======= if (consoleKeyInfo.Key == ConsoleKey.UpArrow && _commandsHistory.Count > 0) { historyIndex = Math.Abs((historyIndex + 1) % _commandsHistory.Count); @@ -853,15 +778,7 @@ static void AddToCommandHistory(string command) Console.Write(consoleKeyInfo.KeyChar); } } while (consoleKeyInfo.Key != ConsoleKey.Enter && !_exit); ->>>>>>> Stashed changes - - //var CurrentInput = Console.ReadLine(); - if (string.IsNullOrWhiteSpace(CurrentInput)) - continue; - -<<<<<<< Updated upstream -======= AddToCommandHistory(CurrentInput); if (NoSetCursor) @@ -869,7 +786,6 @@ static void AddToCommandHistory(string command) Console.WriteLine(); } ->>>>>>> Stashed changes InputQueue.Enqueue(CurrentInput); CurrentInput = string.Empty; } @@ -893,22 +809,7 @@ async void ReaderTaskMethod() if (string.IsNullOrWhiteSpace(input)) continue; -<<<<<<< Updated upstream - var currentLineCursor = NoSetCursor ? 0 : Console.CursorTop; - - if (currentLineCursor > 0) - { - Console.CursorTop = currentLineCursor - 1; - ConsoleUtil.ResetLine(); - } - - ConsoleUtil.WriteLine($">>> {input}", ConsoleColor.DarkMagenta, -1); - - if (currentLineCursor > 0) - Console.CursorTop = currentLineCursor; -======= ConsoleUtil.WriteLine($">>> {input}", ConsoleColor.DarkMagenta, -1); ->>>>>>> Stashed changes if (!_processRefreshFail && _gameProcess != null) { diff --git a/IO/ConsoleUtil.cs b/IO/ConsoleUtil.cs index 0023b1d..94baf9a 100644 --- a/IO/ConsoleUtil.cs +++ b/IO/ConsoleUtil.cs @@ -134,11 +134,7 @@ public static void WriteLine(string? content, ConsoleColor color = ConsoleColor. Console.WriteLine($"{GetLiveViewTimestamp()} {(multiline ? content.Replace("\n", GetLiveViewPadding(), StringComparison.Ordinal) : content)}"); -<<<<<<< Updated upstream if(inputIntro) -======= - if (inputIntro) ->>>>>>> Stashed changes WriteInputIntro(); } @@ -149,12 +145,6 @@ public static void WriteLine(string? content, ConsoleColor color = ConsoleColor. public static void WriteInputIntro() { -<<<<<<< Updated upstream - Console.ForegroundColor = ConsoleColor.DarkGray; - Console.Write("> "); - Console.ForegroundColor = ConsoleColor.White; - Console.Write(Core.LocalAdmin.CurrentInput); -======= lock (Lck) { Console.ForegroundColor = ConsoleColor.DarkGray; @@ -162,22 +152,15 @@ public static void WriteInputIntro() Console.ForegroundColor = ConsoleColor.White; Console.Write(Core.LocalAdmin.CurrentInput); } ->>>>>>> Stashed changes } public static void ResetLine() { -<<<<<<< Updated upstream - Console.CursorLeft = 0; - Console.Write(string.Empty.PadLeft(Console.WindowWidth)); - Console.CursorLeft = 0; -======= lock (Lck) { Console.CursorLeft = 0; Console.Write(string.Empty.PadLeft(Console.WindowWidth)); Console.CursorLeft = 0; } ->>>>>>> Stashed changes } } \ No newline at end of file From da53c866675ece404294a4345e932dab3965c8e2 Mon Sep 17 00:00:00 2001 From: AlexanderK <73738345+AlexanderK666@users.noreply.github.com> Date: Wed, 17 Jul 2024 19:54:52 +0300 Subject: [PATCH 4/6] improved readability --- Core/LocalAdmin.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/Core/LocalAdmin.cs b/Core/LocalAdmin.cs index 7be776e..b70b16f 100644 --- a/Core/LocalAdmin.cs +++ b/Core/LocalAdmin.cs @@ -731,14 +731,9 @@ private static void SetupKeyboardInput() static void AddToCommandHistory(string command) { - if (_commandsHistory.Contains(command)) - { - _commandsHistory.Remove(command); - } - else if (_commandsHistory.Count == CommandsHistorySize) - { + if (!_commandsHistory.Remove(command) && _commandsHistory.Count == CommandsHistorySize) _commandsHistory.RemoveAt(_commandsHistory.Count - 1); - } + _commandsHistory.Insert(0, command); } From a606ecf269fda88f42d1092a88536b95d7f6b1fb Mon Sep 17 00:00:00 2001 From: AlexanderK <73738345+AlexanderK666@users.noreply.github.com> Date: Sun, 21 Jul 2024 22:33:35 +0300 Subject: [PATCH 5/6] New commands input has been reworked Currently partially tested on Windows, but some issues were found. --- Core/ConfigWizard.cs | 2 - Core/LocalAdmin.cs | 301 +++++++++++++++---------------------------- IO/CommandsInput.cs | 140 ++++++++++++++++++++ IO/Config.cs | 9 ++ IO/ConsoleUtil.cs | 46 ++++--- 5 files changed, 279 insertions(+), 219 deletions(-) create mode 100644 IO/CommandsInput.cs diff --git a/Core/ConfigWizard.cs b/Core/ConfigWizard.cs index edf963d..252b9e0 100644 --- a/Core/ConfigWizard.cs +++ b/Core/ConfigWizard.cs @@ -37,7 +37,6 @@ public static void RunConfigWizard(bool useDefault) !input.Equals("keep", StringComparison.OrdinalIgnoreCase)) { Console.WriteLine("Do you want to edit that configuration? [edit/keep]: "); - ConsoleUtil.WriteInputIntro(); input = Console.ReadLine(); } @@ -243,7 +242,6 @@ private static void SaveConfig(bool silent = false) { Console.WriteLine( $"Do you want to save the configuration only for THIS server (on port {LocalAdmin.GamePort} or should it become a GLOBAL configuration (default one for all future servers - servers not configured yet)? [this/global]: "); - ConsoleUtil.WriteInputIntro(); input = Console.ReadLine(); } } diff --git a/Core/LocalAdmin.cs b/Core/LocalAdmin.cs index b70b16f..e763413 100644 --- a/Core/LocalAdmin.cs +++ b/Core/LocalAdmin.cs @@ -15,7 +15,6 @@ using LocalAdmin.V2.Commands.PluginManager; using LocalAdmin.V2.IO.Logging; using LocalAdmin.V2.PluginsManager; -using System.Collections.Generic; namespace LocalAdmin.V2.Core; /* @@ -34,7 +33,8 @@ public sealed class LocalAdmin : IDisposable public const string VersionString = "2.5.15"; private const ushort DefaultPort = 7777; - private static readonly ConcurrentQueue InputQueue = new(); + internal static bool Exited => _exit; + private static readonly Stopwatch RestartsStopwatch = new(); private static string? _previousPat; private static bool _firstRun = true; @@ -78,29 +78,6 @@ public sealed class LocalAdmin : IDisposable internal TcpServer? Server { get; private set; } internal bool EnableGameHeartbeat { get; private set; } - private const int CommandsHistorySize = 15; - private static string _currentInput = ""; - private static readonly object _inputLockObject = new object(); - private static List _commandsHistory = new List(); - - internal static string CurrentInput - { - get - { - lock (_inputLockObject) - { - return _currentInput; - } - } - set - { - lock (_inputLockObject) - { - _currentInput = value; - } - } - } - internal enum ShutdownAction : byte { Crash, @@ -137,7 +114,7 @@ internal LocalAdmin() _scpslExecutable = "SCPSL.x86_64"; else { - ConsoleUtil.WriteLine("Failed - Unsupported platform! Please switch to the Windows, or Linux platform to continue.", ConsoleColor.Red, inputIntro: false); + ConsoleUtil.WriteLine("Failed - Unsupported platform! Please switch to the Windows, or Linux platform to continue.", ConsoleColor.Red); // shut up dotnet _scpslExecutable = string.Empty; Exit(1); @@ -155,24 +132,24 @@ internal async Task Start(string[] args) if (!PathManager.CorrectPathFound && !args.Contains("--skipHomeCheck", StringComparer.Ordinal)) { - ConsoleUtil.WriteLine($"Welcome to LocalAdmin version {VersionString}!", ConsoleColor.Red, inputIntro: false); - ConsoleUtil.WriteLine("Can't obtain a valid user home directory path!", ConsoleColor.Red, inputIntro: false); + ConsoleUtil.WriteLine($"Welcome to LocalAdmin version {VersionString}!", ConsoleColor.Red); + ConsoleUtil.WriteLine("Can't obtain a valid user home directory path!", ConsoleColor.Red); if (OperatingSystem.IsWindows()) { - ConsoleUtil.WriteLine("Such error should never occur on Windows.", ConsoleColor.Red, inputIntro: false); - ConsoleUtil.WriteLine("Open issue on the LocalAdmin GitHub repository (https://github.com/northwood-studios/LocalAdmin-V2/issues) or contact our technical support!", ConsoleColor.Red, inputIntro: false); + 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()) { - ConsoleUtil.WriteLine("Make sure to export a valid path, for example using command: export HOME=/home/username-here", ConsoleColor.Red, inputIntro: false); - 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, inputIntro: false); + 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, inputIntro: false); + 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, inputIntro: false); + ConsoleUtil.WriteLine("To skip this check, use --skipHomeCheck argument.", ConsoleColor.Red); Terminate(); return; } @@ -196,7 +173,7 @@ internal async Task Start(string[] args) if (_restarts > _restartsLimit) { - ConsoleUtil.WriteLine("Restarts limit exceeded.", ConsoleColor.Red, inputIntro: false); + ConsoleUtil.WriteLine("Restarts limit exceeded.", ConsoleColor.Red); Terminate(); } } @@ -207,56 +184,42 @@ internal async Task Start(string[] args) if (DataJson!.EulaAccepted == null) { - var headerLines = new string[] - { - $"Welcome to LocalAdmin version {VersionString}!", - "Before starting please read and accept the SCP:SL EULA.", - "You can find it on the following website: https://link.scpslgame.com/eula", - string.Empty, - "Do you accept the EULA? [yes/no]", - }; - - foreach (var line in headerLines) - { - ConsoleUtil.WriteLine(line, ConsoleColor.Cyan); - } - - bool onCheckInput(string? input) - { - if (input == null) - return false; + ConsoleUtil.WriteLine($"Welcome to LocalAdmin version {VersionString}!", ConsoleColor.Cyan); + ConsoleUtil.WriteLine("Before starting please read and accept the SCP:SL EULA.", ConsoleColor.Cyan); + ConsoleUtil.WriteLine("You can find it on the following website: https://link.scpslgame.com/eula", ConsoleColor.Cyan); + ConsoleUtil.WriteLine("", ConsoleColor.Cyan, skipInputIntro: true); + ConsoleUtil.WriteLine("Do you accept the EULA? [yes/no]", ConsoleColor.Cyan); - switch (input.ToLowerInvariant()) + ReadInput((input) => { - case "y": - case "yes": - case "1": - DataJson!.EulaAccepted = DateTime.UtcNow; - return true; - - case "n": - case "no": - case "nope": - case "0": - ConsoleUtil.WriteLine("You have to accept the EULA to use LocalAdmin and SCP: Secret Laboratory Dedicated Server.", ConsoleColor.Red, inputIntro: false); - Terminate(); - return true; - - default: + if (input == null) return false; - } - } - - void onValidInput() - { - } - void onInvalidInput() - { - ConsoleUtil.WriteLine("Do you accept the EULA? [yes/no]", ConsoleColor.Red); - } + switch (input.ToLowerInvariant()) + { + case "y": + case "yes": + case "1": + DataJson.EulaAccepted = DateTime.UtcNow; + return true; + + case "n": + case "no": + case "nope": + case "0": + ConsoleUtil.WriteLine("You have to accept the EULA to use LocalAdmin and SCP: Secret Laboratory Dedicated Server.", ConsoleColor.Red); + Terminate(); + return true; + + default: + return false; + } - ReadInput(onCheckInput, onValidInput, onInvalidInput); + }, () => { }, + () => + { + ConsoleUtil.WriteLine("Do you accept the EULA? [yes/no]", ConsoleColor.Red); + }); if (!_exit) await SaveJsonOrTerminate(); @@ -264,36 +227,30 @@ void onInvalidInput() var reconfigure = false; var useDefault = false; + var useOldCommandsInput = false; if (_firstRun) { if (args.Length == 0 || !ushort.TryParse(args[0], out GamePort)) { ConsoleUtil.WriteLine("You can pass port number as first startup argument.", - ConsoleColor.Green, inputIntro: false); + ConsoleColor.Green, skipInputIntro: true); Console.WriteLine(string.Empty); ConsoleUtil.Write($"Port number (default: {DefaultPort}): ", ConsoleColor.Green); - - bool onCheckInput(string? input) - { - if (!string.IsNullOrEmpty(input)) - return ushort.TryParse(input, out GamePort); - GamePort = DefaultPort; - return true; - } - - void onValidInput() - { - } - - void onInvalidInput() - { - ConsoleUtil.WriteLine("Port number must be a unsigned short integer.", - ConsoleColor.Red, inputIntro: false); - } + ReadInput((input) => + { + if (!string.IsNullOrEmpty(input)) + return ushort.TryParse(input, out GamePort); + GamePort = DefaultPort; + return true; - ReadInput(onCheckInput, onValidInput, onInvalidInput); + }, () => { }, + () => + { + ConsoleUtil.WriteLine("Port number must be a unsigned short integer.", + ConsoleColor.Red); + }); } var capture = CaptureArgs.None; @@ -344,6 +301,10 @@ void onInvalidInput() case 't': _noTerminalTitle = true; break; + + case 'i': + useOldCommandsInput = true; + break; } } } @@ -423,6 +384,10 @@ void onInvalidInput() _noTerminalTitle = true; break; + case "--oldCommandsInput": + useOldCommandsInput = true; + break; + case "--": capture = CaptureArgs.ArgsPassthrough; break; @@ -452,7 +417,7 @@ void onInvalidInput() if (!int.TryParse(arg, out _restartsLimit) || _restartsLimit < -1) { _restartsLimit = 4; - ConsoleUtil.WriteLine("restartsLimit argument value must be an integer greater or equal to -1.", ConsoleColor.Red, inputIntro: false); + ConsoleUtil.WriteLine("restartsLimit argument value must be an integer greater or equal to -1.", ConsoleColor.Red); } capture = CaptureArgs.None; break; @@ -461,7 +426,7 @@ void onInvalidInput() if (!int.TryParse(arg, out _restartsTimeWindow) || _restartsLimit < 0) { _restartsTimeWindow = 480; - ConsoleUtil.WriteLine("restartsTimeWindow argument value must be an integer greater or equal to 0.", ConsoleColor.Red, inputIntro: false); + ConsoleUtil.WriteLine("restartsTimeWindow argument value must be an integer greater or equal to 0.", ConsoleColor.Red); } capture = CaptureArgs.None; break; @@ -476,7 +441,7 @@ void onInvalidInput() { ConsoleUtil.WriteLine( "logLengthLimit argument value must be an integer greater or equal to 0.", - ConsoleColor.Red, inputIntro: false); + ConsoleColor.Red); LogLengthLimit = 25000000000; } @@ -494,8 +459,7 @@ void onInvalidInput() { ConsoleUtil.WriteLine( "logEntriesLimit argument value must be an integer greater or equal to 0.", - ConsoleColor.Red, - inputIntro: false); + ConsoleColor.Red); LogEntriesLimit = 10000000000; } @@ -541,6 +505,8 @@ void onInvalidInput() AutoFlush &= Configuration.LaLogAutoFlush; EnableLogging &= Configuration.EnableLaLogs; + Configuration.LaEnableNewCommandsInput &= !useOldCommandsInput; + if (Configuration.EnableHeartbeat) { EnableGameHeartbeat = true; @@ -551,7 +517,7 @@ void onInvalidInput() StartHeartbeatMonitoring(); } - InputQueue.Clear(); + CommandsInput.ClearQueue(); if (_firstRun) { @@ -571,7 +537,7 @@ void onInvalidInput() { _exit = false; _firstRun = false; - SetupKeyboardInput(); + CommandsInput.Start(Configuration.LaEnableNewCommandsInput); } RegisterCommands(); @@ -627,8 +593,8 @@ private void StartSession() SetTerminalTitle(); Logger.Initialize(); - ConsoleUtil.WriteLine($"Started new session on port {GamePort}.", ConsoleColor.DarkGreen, inputIntro: false); - ConsoleUtil.WriteLine("Trying to start server...", ConsoleColor.Gray, inputIntro: false); + ConsoleUtil.WriteLine($"Started new session on port {GamePort}.", ConsoleColor.DarkGreen); + ConsoleUtil.WriteLine("Trying to start server...", ConsoleColor.Gray); SetupServer(); @@ -640,22 +606,14 @@ private void StartSession() private static void Menu() { - var headerLines = new string[] { - $"SCP: Secret Laboratory - LocalAdmin v. {VersionString}", - string.Empty, - "Licensed under The MIT License (use command \"license\" to get license text).", - "Copyright by Łukasz \"zabszk\" Jurczyk and KernelError, 2019 - 2024", - string.Empty, - "Type 'help' to get list of available commands.", - string.Empty, - }; - ConsoleUtil.Clear(); - - foreach (var line in headerLines) - { - ConsoleUtil.WriteLine(line, ConsoleColor.Cyan, inputIntro: false); - } + 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(string.Empty, ConsoleColor.Cyan); + ConsoleUtil.WriteLine("Type 'help' to get list of available commands.", ConsoleColor.Cyan); + ConsoleUtil.WriteLine(string.Empty, ConsoleColor.Cyan); } private static void SetupExitHandlers() @@ -722,71 +680,6 @@ private void SetupServer() Server.Start(); } - private static void SetupKeyboardInput() - { - new Task(() => - { - ConsoleKeyInfo consoleKeyInfo; - int historyIndex = -1; - - static void AddToCommandHistory(string command) - { - if (!_commandsHistory.Remove(command) && _commandsHistory.Count == CommandsHistorySize) - _commandsHistory.RemoveAt(_commandsHistory.Count - 1); - - _commandsHistory.Insert(0, command); - } - - while (!_exit) - { - NEXT_CYCLE: - ConsoleUtil.ResetLine(); - ConsoleUtil.WriteInputIntro(); - - do - { - consoleKeyInfo = Console.ReadKey(true); - - if (consoleKeyInfo.Key == ConsoleKey.UpArrow && _commandsHistory.Count > 0) - { - historyIndex = Math.Abs((historyIndex + 1) % _commandsHistory.Count); - CurrentInput = _commandsHistory[historyIndex]; - goto NEXT_CYCLE; - } - else if (consoleKeyInfo.Key == ConsoleKey.DownArrow && _commandsHistory.Count > 0) - { - historyIndex = Math.Abs((historyIndex - 1) % _commandsHistory.Count); - CurrentInput = _commandsHistory[historyIndex]; - goto NEXT_CYCLE; - } - else if (consoleKeyInfo.Key == ConsoleKey.Backspace) - { - if (Console.CursorLeft < ConsoleUtil.InputIntro.Length + 1) - continue; - - CurrentInput = CurrentInput[..^1]; - Console.Write("\b \b"); - } - else if (consoleKeyInfo.Key != ConsoleKey.Enter && !char.IsControl(consoleKeyInfo.KeyChar)) - { - CurrentInput += consoleKeyInfo.KeyChar; - Console.Write(consoleKeyInfo.KeyChar); - } - } while (consoleKeyInfo.Key != ConsoleKey.Enter && !_exit); - - AddToCommandHistory(CurrentInput); - - if (NoSetCursor) - { - Console.WriteLine(); - } - - InputQueue.Enqueue(CurrentInput); - CurrentInput = string.Empty; - } - }).Start(); - } - private void SetupReader() { async void ReaderTaskMethod() @@ -795,7 +688,7 @@ async void ReaderTaskMethod() while (!_exit) { - if (!InputQueue.TryDequeue(out var input)) + if (!CommandsInput.TryDequeueCommand(out var input)) { await Task.Delay(65); continue; @@ -804,7 +697,17 @@ async void ReaderTaskMethod() if (string.IsNullOrWhiteSpace(input)) continue; - ConsoleUtil.WriteLine($">>> {input}", ConsoleColor.DarkMagenta, -1); + var currentLineCursor = NoSetCursor || Configuration!.LaEnableNewCommandsInput ? 0 : Console.CursorTop; + + if (currentLineCursor > 0) + { + Console.SetCursorPosition(0, currentLineCursor - 1); + + ConsoleUtil.WriteLine($"{string.Empty.PadLeft(Console.WindowWidth)}>>> {input}", ConsoleColor.DarkMagenta, -1); + Console.SetCursorPosition(0, currentLineCursor); + } + else + ConsoleUtil.WriteLine($">>> {input}", ConsoleColor.DarkMagenta, -1); if (!_processRefreshFail && _gameProcess != null) { @@ -812,7 +715,7 @@ async void ReaderTaskMethod() { if (_gameProcess.HasExited) { - ConsoleUtil.WriteLine("Failed to send command - the game process was terminated...", ConsoleColor.Red, inputIntro: false); + ConsoleUtil.WriteLine("Failed to send command - the game process was terminated...", ConsoleColor.Red); _exit = true; continue; } @@ -838,7 +741,9 @@ async void ReaderTaskMethod() var exit = false; - if (input.Equals("exit", StringComparison.OrdinalIgnoreCase) || input.StartsWith("exit ", StringComparison.OrdinalIgnoreCase) || input.Equals("quit", StringComparison.OrdinalIgnoreCase) || input.StartsWith("quit ", StringComparison.OrdinalIgnoreCase) || input.Equals("stop", StringComparison.OrdinalIgnoreCase) || input.StartsWith("stop ", StringComparison.OrdinalIgnoreCase)) + if (input.StartsWith("exit", StringComparison.OrdinalIgnoreCase) || + input.StartsWith("quit", StringComparison.OrdinalIgnoreCase) || + input.StartsWith("stop", StringComparison.OrdinalIgnoreCase)) { DisableExitActionSignals = true; ExitAction = ShutdownAction.SilentShutdown; @@ -865,7 +770,7 @@ private void RunScpsl() { if (File.Exists(_scpslExecutable)) { - ConsoleUtil.WriteLine("Executing: " + _scpslExecutable, ConsoleColor.DarkGreen, inputIntro: false); + ConsoleUtil.WriteLine("Executing: " + _scpslExecutable, ConsoleColor.DarkGreen); var printStd = Configuration!.LaShowStdoutStderr || _stdPrint; var redirectStreams = Configuration.LaLogStdoutStderr || printStd; @@ -1066,7 +971,7 @@ public void Exit(int code = -1, bool waitForKey = false, bool restart = false) if (waitForKey && ExitAction != ShutdownAction.SilentShutdown) { - ConsoleUtil.WriteLine("Press any key to close...", ConsoleColor.DarkGray, inputIntro: false); + ConsoleUtil.WriteLine("Press any key to close...", ConsoleColor.DarkGray); Console.ReadKey(true); } @@ -1102,7 +1007,7 @@ internal async Task LoadJsonOrTerminate() if (DataJson == null) { - ConsoleUtil.WriteLine("Json file is corrupted! Terminating LocalAdmin. If the issue persists, please delete the file and restart LocalAdmin.", ConsoleColor.Red, inputIntro: false); + ConsoleUtil.WriteLine("Json file is corrupted! Terminating LocalAdmin. If the issue persists, please delete the file and restart LocalAdmin.", ConsoleColor.Red); Terminate(); } } @@ -1115,7 +1020,7 @@ internal async Task LoadJsonOrTerminate() } catch (Exception e) { - ConsoleUtil.WriteLine($"Failed to read JSON config file: {e.Message}", ConsoleColor.Red, inputIntro: false); + ConsoleUtil.WriteLine($"Failed to read JSON config file: {e.Message}", ConsoleColor.Red); DataJson = null; Terminate(); throw; @@ -1138,7 +1043,7 @@ private void Terminate() internal static void HandleExitSignal() { Console.WriteLine("exit"); - InputQueue.Enqueue("exit"); + CommandsInput.EnqueueCommand("exit"); } internal void HandleHeartbeat() diff --git a/IO/CommandsInput.cs b/IO/CommandsInput.cs new file mode 100644 index 0000000..b69ddee --- /dev/null +++ b/IO/CommandsInput.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; + +namespace LocalAdmin.V2.IO; + +using LocalAdmin = Core.LocalAdmin; + +internal static class CommandsInput +{ + internal const string IntroText = "> "; + internal const int IntroLength = 2; + + private const int HistorySize = 15; + + public static IReadOnlyList History => _commandsHistory; + + public static string CurrentInput + { + get + { + lock (_lock) + { + return _currentInput; + } + } + private set + { + _currentInput = value; + } + } + + private static readonly ConcurrentQueue _commandsQueue = new(); + private static readonly List _commandsHistory = new(); + private static readonly object _lock = new(); + + private static bool _useNewMethod = false; + private static string _currentInput = ""; + + public static void Start(bool useNewMethod) + { + _useNewMethod = useNewMethod; + Task.Run(HandleInputInternal); + } + + public static bool TryDequeueCommand([MaybeNullWhen(false)] out string result) + { + return _commandsQueue.TryDequeue(out result); + } + + internal static void EnqueueCommand(string command) + { + _commandsQueue.Enqueue(command); + } + + internal static void ClearQueue() + { + _commandsQueue.Clear(); + } + + internal static void AddToHistory(string command) + { + if (!_commandsHistory.Remove(command) && _commandsHistory.Count == HistorySize) + _commandsHistory.RemoveAt(_commandsHistory.Count-1); + + _commandsHistory.Insert(0, command); + } + + private static void HandleInputInternal() + { + ConsoleKeyInfo consoleKeyInfo; + int historyIndex = -1; + + void ReadByNewMethod() + { + MethodEntry: + ConsoleUtil.ResetCurrentLine(); + ConsoleUtil.WriteCommandsInput(); + + do + { +#if LINUX_SIGNALS + while (!Console.KeyAvailable && !LocalAdmin.Exited) + { + Task.Delay(50).Wait(); + } + + if (LocalAdmin.Exited) + return; +#endif + + consoleKeyInfo = Console.ReadKey(intercept: true); + + if (consoleKeyInfo.Key == ConsoleKey.UpArrow && _commandsHistory.Count > 0) + { + historyIndex = Math.Abs((historyIndex + 1) % _commandsHistory.Count); + CurrentInput = _commandsHistory[historyIndex]; + goto MethodEntry; + } + else if (consoleKeyInfo.Key == ConsoleKey.DownArrow && _commandsHistory.Count > 0) + { + historyIndex = Math.Abs((historyIndex - 1) % _commandsHistory.Count); + CurrentInput = _commandsHistory[historyIndex]; + goto MethodEntry; + } + else if (consoleKeyInfo.Key == ConsoleKey.Backspace && CurrentInput.Length > 0) + { + CurrentInput = CurrentInput[..^1]; + ConsoleUtil.RemoveLastCharacter(); + } + else if (!char.IsControl(consoleKeyInfo.KeyChar) && IntroLength+CurrentInput.Length < Console.BufferWidth) + { + CurrentInput += consoleKeyInfo.KeyChar; + ConsoleUtil.WriteChar(consoleKeyInfo.KeyChar); + } + } while (consoleKeyInfo.Key != ConsoleKey.Enter && !LocalAdmin.Exited); + + if (LocalAdmin.NoSetCursor) + Console.WriteLine(); + } + + while (!LocalAdmin.Exited) + { + if (_useNewMethod) + ReadByNewMethod(); + else + CurrentInput = Console.ReadLine() ?? string.Empty; + + if (string.IsNullOrEmpty(CurrentInput)) + continue; + + AddToHistory(CurrentInput); + + _commandsQueue.Enqueue(CurrentInput); + CurrentInput = ""; + } + } +} \ No newline at end of file diff --git a/IO/Config.cs b/IO/Config.cs index db7e452..06bd9fe 100644 --- a/IO/Config.cs +++ b/IO/Config.cs @@ -13,6 +13,7 @@ public class Config public bool LaLiveViewUseUtc; public bool LaShowStdoutStderr; + public bool LaEnableNewCommandsInput = !Console.IsInputRedirected; public bool LaNoSetCursor = OperatingSystem.IsLinux(); public bool EnableTrueColor = OperatingSystem.IsLinux(); public bool EnableLaLogs = true; @@ -56,6 +57,9 @@ public string SerializeConfig() sb.Append("la_show_stdout_and_stderr: "); sb.AppendLine(LaShowStdoutStderr.ToString().ToLowerInvariant()); + sb.Append("la_enable_new_commands_input: "); + sb.AppendLine(LaEnableNewCommandsInput.ToString().ToLowerInvariant()); + sb.Append("la_no_set_cursor: "); sb.AppendLine(LaNoSetCursor.ToString().ToLowerInvariant()); @@ -149,6 +153,10 @@ public static Config DeserializeConfig(string[] lines) cfg.LaShowStdoutStderr = b; break; + case "la_enable_new_commands_input" when bool.TryParse(sp[1], out var b): + cfg.LaEnableNewCommandsInput = b; + break; + case "la_no_set_cursor" when bool.TryParse(sp[1], out var b): cfg.LaNoSetCursor = b; break; @@ -249,6 +257,7 @@ public override string ToString() sb.AppendLine($"- LocalAdmin live will use the following timestamp format: {LaLiveViewTimeFormat}"); sb.AppendLine($"- UTC timezone will be displayed as \"{(LaLogsUseZForUtc ? "Z" : "+00:00")}\"."); sb.AppendLine(LaShowStdoutStderr ? "- Standard outputs (that contain a lot of debug information) will be displayed." : "- Standard outputs (that contain a lot of debug information) will NOT be displayed."); + sb.AppendLine(LaEnableNewCommandsInput ? "- LocalAdmin new commands input is ENABLED. If this causes problems in your environment, you can disable it." : "- LocalAdmin new commands input is DISABLED."); sb.AppendLine(LaNoSetCursor ? "- Cursor position management is DISABLED." : "- Cursor position management is ENABLED."); sb.AppendLine(EnableTrueColor ? "- True Color output is ENABLED." : "- True Color output is DISABLED."); sb.AppendLine(EnableLaLogs ? "- LocalAdmin logs are ENABLED." : "- LocalAdmin logs are DISABLED."); diff --git a/IO/ConsoleUtil.cs b/IO/ConsoleUtil.cs index 94baf9a..f5a6480 100644 --- a/IO/ConsoleUtil.cs +++ b/IO/ConsoleUtil.cs @@ -7,10 +7,6 @@ namespace LocalAdmin.V2.IO; public static class ConsoleUtil { - internal const string InputIntro = "> "; - - internal static int InputCursorLeft => InputIntro.Length + Core.LocalAdmin.CurrentInput.Length; - private static readonly char[] ToTrim = { '\n', '\r' }; private static readonly object Lck = new object(); @@ -97,8 +93,6 @@ public static void Write(string content, ConsoleColor color = ConsoleColor.White if (height > 0 && !Core.LocalAdmin.NoSetCursor) Console.CursorTop += height; - ResetLine(); - Console.Write($"{GetLiveViewTimestamp()} {(multiline ? content.Replace("\n", GetLiveViewPadding(), StringComparison.Ordinal) : content)}"); Console.ForegroundColor = ConsoleColor.White; @@ -109,7 +103,7 @@ public static void Write(string content, ConsoleColor color = ConsoleColor.White } } - public static void WriteLine(string? content, ConsoleColor color = ConsoleColor.White, int height = 0, bool log = true, bool display = true, bool inputIntro = true) + public static void WriteLine(string? content, ConsoleColor color = ConsoleColor.White, int height = 0, bool log = true, bool display = true, bool skipInputIntro = false) { lock (Lck) { @@ -130,12 +124,14 @@ public static void WriteLine(string? content, ConsoleColor color = ConsoleColor. if (height > 0 && !Core.LocalAdmin.NoSetCursor) Console.CursorTop += height; - ResetLine(); + ResetCurrentLine(); Console.WriteLine($"{GetLiveViewTimestamp()} {(multiline ? content.Replace("\n", GetLiveViewPadding(), StringComparison.Ordinal) : content)}"); - if(inputIntro) - WriteInputIntro(); + Console.ForegroundColor = ConsoleColor.White; + + if (Core.LocalAdmin.Configuration?.LaEnableNewCommandsInput ?? false && !skipInputIntro) + WriteCommandsInput(); } if (log) @@ -143,24 +139,36 @@ public static void WriteLine(string? content, ConsoleColor color = ConsoleColor. } } - public static void WriteInputIntro() + public static void ResetCurrentLine() + { + lock (Lck) + { + Console.Write("\r"+new string(' ', Console.WindowWidth)+"\r"); + } + } + + internal static void WriteCommandsInput() + { + lock (Lck) + { + Console.Write(CommandsInput.IntroText); + Console.Write(CommandsInput.CurrentInput); + } + } + + internal static void RemoveLastCharacter() { lock (Lck) { - Console.ForegroundColor = ConsoleColor.DarkGray; - Console.Write("> "); - Console.ForegroundColor = ConsoleColor.White; - Console.Write(Core.LocalAdmin.CurrentInput); + Console.Write("\b \b"); } } - public static void ResetLine() + internal static void WriteChar(char value) { lock (Lck) { - Console.CursorLeft = 0; - Console.Write(string.Empty.PadLeft(Console.WindowWidth)); - Console.CursorLeft = 0; + Console.Write(value); } } } \ No newline at end of file From 1f6237ebeabc542878ba26ed43a4feafd6a9f4b9 Mon Sep 17 00:00:00 2001 From: AlexanderK <73738345+AlexanderK666@users.noreply.github.com> Date: Sun, 21 Jul 2024 23:16:41 +0300 Subject: [PATCH 6/6] Added `workflow_dispatch` event for CI. --- .github/workflows/dotnet-core.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dotnet-core.yml b/.github/workflows/dotnet-core.yml index 75bb64d..c6d08e9 100644 --- a/.github/workflows/dotnet-core.yml +++ b/.github/workflows/dotnet-core.yml @@ -1,6 +1,6 @@ name: .NET CI -on: [push, pull_request] +on: [push, pull_request, workflow_dispatch] jobs: build: