Remote execute command.

Had to make everything async for this.
This commit is contained in:
Pieter-Jan Briers
2022-05-16 00:11:55 +02:00
parent 66041028c6
commit 54f3e3affc
7 changed files with 189 additions and 39 deletions

View File

@@ -30,3 +30,8 @@ cmd-list-help = Usage: list [filter]
cmd-list-heading = SIDE NAME DESC{"\u000A"}-------------------------{"\u000A"}
cmd-list-arg-filter = [filter]
## '>' command, aka remote exec
cmd-remoteexec-desc = Executes server-side commands
cmd-remoteexec-help = Usage: > <command> [arg] [arg] [arg...]
Executes a command on the server. This is necessary if a command with the same name exists on the client, as simply running the command would run the client command first.

View File

@@ -38,10 +38,7 @@ internal sealed partial class ClientConsoleHost
if (!RegisteredCommands.TryGetValue(args[0], out var cmd))
return Task.FromResult(CompletionResult.Empty);
if (cmd is ServerDummyCommand)
return DoServerCompletions(args, cancel);
return Task.FromResult(cmd.GetCompletion(LocalShell, args.ToArray()[1..]));
return cmd.GetCompletionAsync(LocalShell, args.ToArray()[1..], cancel).AsTask();
}
private Task<CompletionResult> DoServerCompletions(List<string> args, CancellationToken cancel)

View File

@@ -1,10 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Robust.Client.Log;
using Robust.Client.Player;
using Robust.Shared.Configuration;
using Robust.Shared.Console;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
@@ -211,31 +215,61 @@ namespace Robust.Client.Console
_requestedCommands = true;
}
}
/// <summary>
/// These dummies are made purely so list and help can list server-side commands.
/// </summary>
[Reflect(false)]
internal sealed class ServerDummyCommand : IConsoleCommand
{
internal ServerDummyCommand(string command, string help, string description)
/// <summary>
/// These dummies are made purely so list and help can list server-side commands.
/// </summary>
[Reflect(false)]
private sealed class ServerDummyCommand : IConsoleCommand
{
Command = command;
Help = help;
Description = description;
internal ServerDummyCommand(string command, string help, string description)
{
Command = command;
Help = help;
Description = description;
}
public string Command { get; }
public string Description { get; }
public string Help { get; }
// Always forward to server.
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
shell.RemoteExecuteCommand(argStr);
}
public async ValueTask<CompletionResult> GetCompletionAsync(
IConsoleShell shell,
string[] args,
CancellationToken cancel)
{
var host = (ClientConsoleHost)shell.ConsoleHost;
return await host.DoServerCompletions(args.ToList(), cancel);
}
}
public string Command { get; }
public string Description { get; }
public string Help { get; }
// Always forward to server.
public void Execute(IConsoleShell shell, string argStr, string[] args)
private sealed class RemoteExecCommand : IConsoleCommand
{
shell.RemoteExecuteCommand(argStr);
public string Command => ">";
public string Description => Loc.GetString("cmd-remoteexec-desc");
public string Help => Loc.GetString("cmd-remoteexec-help");
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
shell.RemoteExecuteCommand(argStr["> ".Length..]);
}
public async ValueTask<CompletionResult> GetCompletionAsync(
IConsoleShell shell,
string[] args,
CancellationToken cancel)
{
var host = (ClientConsoleHost)shell.ConsoleHost;
return await host.DoServerCompletions(args.ToList(), cancel);
}
}
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Robust.Server.Player;
using Robust.Shared.Console;
using Robust.Shared.IoC;
@@ -73,7 +74,9 @@ namespace Robust.Server.Console
// Logger.Debug($"A: {string.Join(", ", args)}");
#pragma warning disable CA2012
return CalcCompletions(sudoShell, args);
#pragma warning restore CA2012
});
LoadConsoleCommands();
@@ -177,12 +180,12 @@ namespace Robust.Server.Console
return session != null ? $"{session.Name}" : "[HOST]";
}
private void HandleConCompletions(MsgConCompletion message)
private async void HandleConCompletions(MsgConCompletion message)
{
var session = _players.GetSessionByChannel(message.MsgChannel);
var shell = new ConsoleShell(this, session);
var result = CalcCompletions(shell, message.Args);
var result = await CalcCompletions(shell, message.Args);
var msg = new MsgConCompletionResp
{
@@ -190,27 +193,30 @@ namespace Robust.Server.Console
Seq = message.Seq
};
if (!message.MsgChannel.IsConnected)
return;
NetManager.ServerSendMessage(msg, message.MsgChannel);
}
private CompletionResult CalcCompletions(IConsoleShell shell, string[] args)
private ValueTask<CompletionResult> CalcCompletions(IConsoleShell shell, string[] args)
{
// Logger.Debug(string.Join(", ", args));
if (args.Length <= 1)
{
// Typing out command name, handle this ourselves.
return CompletionResult.FromOptions(AvailableCommands.Keys.ToArray());
return ValueTask.FromResult(CompletionResult.FromOptions(AvailableCommands.Keys.ToArray()));
}
var cmdName = args[0];
if (!AvailableCommands.TryGetValue(cmdName, out var cmd))
return CompletionResult.Empty;
return ValueTask.FromResult(CompletionResult.Empty);
if (!ShellCanExecute(shell, cmdName))
return CompletionResult.Empty;
return ValueTask.FromResult(CompletionResult.Empty);
return cmd.GetCompletion(shell, args[1..]);
return cmd.GetCompletionAsync(shell, args[1..], default);
}
private sealed class SudoShell : IConsoleShell

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Robust.Shared.Enums;
using Robust.Shared.IoC;
using Robust.Shared.IoC.Exceptions;
@@ -63,13 +64,39 @@ namespace Robust.Shared.Console
}
}
/// <inheritdoc />
public void RegisterCommand(
string command,
string description,
string help,
ConCommandCallback callback)
{
if (AvailableCommands.ContainsKey(command))
throw new InvalidOperationException($"Command already registered: {command}");
var newCmd = new RegisteredCommand(command, description, help, callback);
AvailableCommands.Add(command, newCmd);
}
public void RegisterCommand(
string command,
string description,
string help,
ConCommandCallback callback,
ConCommandCompletionCallback? completionCallback = null)
ConCommandCompletionCallback completionCallback)
{
if (AvailableCommands.ContainsKey(command))
throw new InvalidOperationException($"Command already registered: {command}");
var newCmd = new RegisteredCommand(command, description, help, callback, completionCallback);
AvailableCommands.Add(command, newCmd);
}
public void RegisterCommand(
string command,
string description,
string help,
ConCommandCallback callback,
ConCommandCompletionAsyncCallback completionCallback)
{
if (AvailableCommands.ContainsKey(command))
throw new InvalidOperationException($"Command already registered: {command}");
@@ -163,6 +190,7 @@ namespace Robust.Shared.Console
{
public ConCommandCallback Callback { get; }
public ConCommandCompletionCallback? CompletionCallback { get; }
public ConCommandCompletionAsyncCallback? CompletionCallbackAsync { get; }
/// <inheritdoc />
public string Command { get; }
@@ -185,26 +213,68 @@ namespace Robust.Shared.Console
string command,
string description,
string help,
ConCommandCallback callback,
ConCommandCompletionCallback? completionCallback = null)
ConCommandCallback callback)
{
Command = command;
// Should these two be localized somehow?
Description = description;
Help = help;
Callback = callback;
}
/// <summary>
/// Constructs a new instance of <see cref="RegisteredCommand"/>.
/// </summary>
/// <param name="command">Name of the command.</param>
/// <param name="description">Short description of the command.</param>
/// <param name="help">Extended description for the command.</param>
/// <param name="callback">Callback function that is ran when the command is executed.</param>
/// <param name="completionCallback">Callback function to get console completions.</param>
public RegisteredCommand(
string command,
string description,
string help,
ConCommandCallback callback,
ConCommandCompletionCallback completionCallback) : this(command, description, help, callback)
{
CompletionCallback = completionCallback;
}
/// <summary>
/// Constructs a new instance of <see cref="RegisteredCommand"/>.
/// </summary>
/// <param name="command">Name of the command.</param>
/// <param name="description">Short description of the command.</param>
/// <param name="help">Extended description for the command.</param>
/// <param name="callback">Callback function that is ran when the command is executed.</param>
/// <param name="completionCallback">Asynchronous callback function to get console completions.</param>
public RegisteredCommand(
string command,
string description,
string help,
ConCommandCallback callback,
ConCommandCompletionAsyncCallback completionCallback)
: this(command, description, help, callback)
{
CompletionCallbackAsync = completionCallback;
}
/// <inheritdoc />
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
Callback(shell, argStr, args);
}
public CompletionResult GetCompletion(IConsoleShell shell, string[] args)
public ValueTask<CompletionResult> GetCompletionAsync(IConsoleShell shell, string[] args)
{
return CompletionCallback?.Invoke(shell, args) ?? CompletionResult.Empty;
if (CompletionCallbackAsync != null)
return CompletionCallbackAsync(shell, args);
if (CompletionCallback != null)
return ValueTask.FromResult(CompletionCallback(shell, args));
return ValueTask.FromResult(CompletionResult.Empty);
}
}
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
namespace Robust.Shared.Console
@@ -43,5 +44,10 @@ namespace Robust.Shared.Console
void Execute(IConsoleShell shell, string argStr, string[] args);
CompletionResult GetCompletion(IConsoleShell shell, string[] args) => CompletionResult.Empty;
ValueTask<CompletionResult> GetCompletionAsync(IConsoleShell shell, string[] args, CancellationToken cancel)
{
return ValueTask.FromResult(GetCompletion(shell, args));
}
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Robust.Shared.Players;
namespace Robust.Shared.Console
@@ -12,6 +13,7 @@ namespace Robust.Shared.Console
/// <param name="args">An array of all the parsed arguments.</param>
public delegate void ConCommandCallback(IConsoleShell shell, string argStr, string[] args);
public delegate CompletionResult ConCommandCompletionCallback(IConsoleShell shell, string[] args);
public delegate ValueTask<CompletionResult> ConCommandCompletionAsyncCallback(IConsoleShell shell, string[] args);
public delegate void ConAnyCommandCallback(IConsoleShell shell, string commandName, string argStr, string[] args);
@@ -53,6 +55,20 @@ namespace Robust.Shared.Console
/// </summary>
void LoadConsoleCommands();
/// <summary>
/// Registers a console command into the console system. This is an alternative to
/// creating an <see cref="IConsoleCommand"/> class.
/// </summary>
/// <param name="command">A string as identifier for this command.</param>
/// <param name="description">Short one sentence description of the command.</param>
/// <param name="help">Command format string.</param>
/// <param name="callback"></param>
void RegisterCommand(
string command,
string description,
string help,
ConCommandCallback callback);
/// <summary>
/// Registers a console command into the console system. This is an alternative to
/// creating an <see cref="IConsoleCommand"/> class.
@@ -67,7 +83,23 @@ namespace Robust.Shared.Console
string description,
string help,
ConCommandCallback callback,
ConCommandCompletionCallback? completionCallback = null);
ConCommandCompletionCallback completionCallback);
/// <summary>
/// Registers a console command into the console system. This is an alternative to
/// creating an <see cref="IConsoleCommand"/> class.
/// </summary>
/// <param name="command">A string as identifier for this command.</param>
/// <param name="description">Short one sentence description of the command.</param>
/// <param name="help">Command format string.</param>
/// <param name="callback"></param>
/// <param name="completionCallback"></param>
void RegisterCommand(
string command,
string description,
string help,
ConCommandCallback callback,
ConCommandCompletionAsyncCallback completionCallback);
/// <summary>
/// Unregisters a console command that has been registered previously with <see cref="RegisterCommand(string,string,string,Robust.Shared.Console.ConCommandCallback)"/>.