Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/dotnet-core.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: .NET CI

on: [push, pull_request]
on: [push, pull_request, workflow_dispatch]

jobs:
build:
Expand Down
48 changes: 23 additions & 25 deletions Core/LocalAdmin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ public sealed class LocalAdmin : IDisposable
public const string VersionString = "2.5.15";
private const ushort DefaultPort = 7777;

private static readonly ConcurrentQueue<string> InputQueue = new();
internal static bool Exited => _exit;

private static readonly Stopwatch RestartsStopwatch = new();
private static string? _previousPat;
private static bool _firstRun = true;
Expand Down Expand Up @@ -186,7 +187,7 @@ internal async Task Start(string[] args)
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("", ConsoleColor.Cyan, skipInputIntro: true);
ConsoleUtil.WriteLine("Do you accept the EULA? [yes/no]", ConsoleColor.Cyan);

ReadInput((input) =>
Expand Down Expand Up @@ -226,13 +227,14 @@ internal async Task Start(string[] args)

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);
ConsoleColor.Green, skipInputIntro: true);
Console.WriteLine(string.Empty);
ConsoleUtil.Write($"Port number (default: {DefaultPort}): ", ConsoleColor.Green);

Expand Down Expand Up @@ -299,6 +301,10 @@ internal async Task Start(string[] args)
case 't':
_noTerminalTitle = true;
break;

case 'i':
useOldCommandsInput = true;
break;
}
}
}
Expand Down Expand Up @@ -378,6 +384,10 @@ internal async Task Start(string[] args)
_noTerminalTitle = true;
break;

case "--oldCommandsInput":
useOldCommandsInput = true;
break;

case "--":
capture = CaptureArgs.ArgsPassthrough;
break;
Expand Down Expand Up @@ -495,6 +505,8 @@ internal async Task Start(string[] args)
AutoFlush &= Configuration.LaLogAutoFlush;
EnableLogging &= Configuration.EnableLaLogs;

Configuration.LaEnableNewCommandsInput &= !useOldCommandsInput;

if (Configuration.EnableHeartbeat)
{
EnableGameHeartbeat = true;
Expand All @@ -505,7 +517,7 @@ internal async Task Start(string[] args)
StartHeartbeatMonitoring();
}

InputQueue.Clear();
CommandsInput.ClearQueue();

if (_firstRun)
{
Expand All @@ -525,7 +537,7 @@ internal async Task Start(string[] args)
{
_exit = false;
_firstRun = false;
SetupKeyboardInput();
CommandsInput.Start(Configuration.LaEnableNewCommandsInput);
}

RegisterCommands();
Expand Down Expand Up @@ -668,22 +680,6 @@ private void SetupServer()
Server.Start();
}

private static void SetupKeyboardInput()
{
new Task(() =>
{
while (!_exit)
{
var input = Console.ReadLine();

if (string.IsNullOrWhiteSpace(input))
continue;

InputQueue.Enqueue(input);
}
}).Start();
}

private void SetupReader()
{
async void ReaderTaskMethod()
Expand All @@ -692,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;
Expand All @@ -701,7 +697,7 @@ async void ReaderTaskMethod()
if (string.IsNullOrWhiteSpace(input))
continue;

var currentLineCursor = NoSetCursor ? 0 : Console.CursorTop;
var currentLineCursor = NoSetCursor || Configuration!.LaEnableNewCommandsInput ? 0 : Console.CursorTop;

if (currentLineCursor > 0)
{
Expand Down Expand Up @@ -745,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;
Expand Down Expand Up @@ -1045,7 +1043,7 @@ private void Terminate()
internal static void HandleExitSignal()
{
Console.WriteLine("exit");
InputQueue.Enqueue("exit");
CommandsInput.EnqueueCommand("exit");
}

internal void HandleHeartbeat()
Expand Down
140 changes: 140 additions & 0 deletions IO/CommandsInput.cs
Original file line number Diff line number Diff line change
@@ -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<string> History => _commandsHistory;

public static string CurrentInput
{
get
{
lock (_lock)
{
return _currentInput;
}
}
private set
{
_currentInput = value;
}
}

private static readonly ConcurrentQueue<string> _commandsQueue = new();
private static readonly List<string> _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 = "";
}
}
}
9 changes: 9 additions & 0 deletions IO/Config.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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());

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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.");
Expand Down
42 changes: 40 additions & 2 deletions IO/ConsoleUtil.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Globalization;
using System.IO;
using LocalAdmin.V2.IO.Logging;
Expand Down Expand Up @@ -103,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)
public static void WriteLine(string? content, ConsoleColor color = ConsoleColor.White, int height = 0, bool log = true, bool display = true, bool skipInputIntro = false)
{
lock (Lck)
{
Expand All @@ -124,13 +124,51 @@ public static void WriteLine(string? content, ConsoleColor color = ConsoleColor.
if (height > 0 && !Core.LocalAdmin.NoSetCursor)
Console.CursorTop += height;

ResetCurrentLine();

Console.WriteLine($"{GetLiveViewTimestamp()} {(multiline ? content.Replace("\n", GetLiveViewPadding(), StringComparison.Ordinal) : content)}");

Console.ForegroundColor = ConsoleColor.White;

if (Core.LocalAdmin.Configuration?.LaEnableNewCommandsInput ?? false && !skipInputIntro)
WriteCommandsInput();
}

if (log)
Logger.Log($"{GetLogsTimestamp()} {(multiline ? content.Replace("\n", GetLogsPadding(), StringComparison.Ordinal) : content)}");
}
}

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.Write("\b \b");
}
}

internal static void WriteChar(char value)
{
lock (Lck)
{
Console.Write(value);
}
}
}