2017-12-15 23:36:25 +00:00
using System ;
using System.IO ;
using System.Net ;
using System.Net.Sockets ;
using System.Threading.Tasks ;
2017-12-16 12:16:43 +00:00
using Nibriboard.Client ;
2017-12-15 23:36:25 +00:00
using Nibriboard.RippleSpace ;
namespace Nibriboard
{
public class CommandConsole
{
private NibriboardServer server ;
private TcpListener commandServer ;
private int commandPort ;
public CommandConsole ( NibriboardServer inServer , int inCommandPort )
{
server = inServer ;
commandPort = inCommandPort ;
}
public async Task Start ( )
{
commandServer = new TcpListener ( IPAddress . IPv6Loopback , server . CommandPort ) ;
commandServer . Start ( ) ;
Log . WriteLine ( "[CommandConsole] Listening on {0}." , new IPEndPoint ( IPAddress . IPv6Loopback , server . CommandPort ) ) ;
while ( true )
{
TcpClient nextClient = await commandServer . AcceptTcpClientAsync ( ) ;
StreamReader source = new StreamReader ( nextClient . GetStream ( ) ) ;
StreamWriter destination = new StreamWriter ( nextClient . GetStream ( ) ) { AutoFlush = true } ;
string rawCommand = await source . ReadLineAsync ( ) ;
string [ ] commandParts = rawCommand . Split ( " \t" . ToCharArray ( ) ) ;
Log . WriteLine ( "[CommandConsole] Client executing {0}" , rawCommand ) ;
try
{
await executeCommand ( destination , commandParts ) ;
}
catch ( Exception error )
{
try
{
await destination . WriteLineAsync ( error . ToString ( ) ) ;
}
catch { nextClient . Close ( ) ; } // Make absolutely sure that the command server won't die
}
nextClient . Close ( ) ;
}
}
private async Task executeCommand ( StreamWriter destination , string [ ] commandParts )
{
string commandName = commandParts [ 0 ] . Trim ( ) ;
switch ( commandName )
{
case "help" :
await destination . WriteLineAsync ( "Nibriboard Server Command Console" ) ;
await destination . WriteLineAsync ( "=================================" ) ;
await destination . WriteLineAsync ( "Available commands:" ) ;
2017-12-23 17:24:20 +00:00
await destination . WriteLineAsync ( " help Show this message" ) ;
await destination . WriteLineAsync ( " version Show the version of nibriboard that is currently running" ) ;
await destination . WriteLineAsync ( " save Save the ripplespace to disk" ) ;
await destination . WriteLineAsync ( " plane {subcommand} Interact with planes" ) ;
await destination . WriteLineAsync ( " clients List the currently connected clients" ) ;
break ;
case "version" :
await destination . WriteLineAsync ( $"Nibriboard Server {NibriboardServer.Version}, built on {NibriboardServer.BuildDate.ToString(" R ")}" ) ;
await destination . WriteLineAsync ( "By Starbeamrainbowlabs, licensed under MPL-2.0" ) ;
2017-12-15 23:36:25 +00:00
break ;
case "save" :
await destination . WriteAsync ( "Saving ripple space - " ) ;
await server . PlaneManager . Save ( ) ;
await destination . WriteLineAsync ( "done." ) ;
await destination . WriteLineAsync ( $"Save is now {BytesToString(server.PlaneManager.LastSaveFileSize)} in size." ) ;
break ;
2017-12-15 23:40:38 +00:00
case "plane" :
if ( commandParts . Length < 2 ) {
2017-12-23 17:24:20 +00:00
await destination . WriteLineAsync ( "Nibriboard Server Command Console: plane" ) ;
await destination . WriteLineAsync ( "----------------------------------------" ) ;
await destination . WriteLineAsync ( "Interact with planes." ) ;
await destination . WriteLineAsync ( "Usage:" ) ;
await destination . WriteLineAsync ( " plane {subcommand}" ) ;
await destination . WriteLineAsync ( ) ;
await destination . WriteLineAsync ( "Subcommands:" ) ;
await destination . WriteLineAsync ( " list" ) ;
await destination . WriteLineAsync ( " List all the currently loaded planes" ) ;
await destination . WriteLineAsync ( " create {new-plane-name} [{chunkSize}]" ) ;
await destination . WriteLineAsync ( " Create a new named plane, optionally with the specified chunk size" ) ;
await destination . WriteLineAsync ( " status {plane-name}" ) ;
await destination . WriteLineAsync ( " Show the statistics of the specified plane" ) ;
2017-12-15 23:40:38 +00:00
break ;
}
string subAction = commandParts [ 1 ] . Trim ( ) ;
switch ( subAction )
{
case "list" :
await destination . WriteLineAsync ( "Planes:" ) ;
foreach ( Plane plane in server . PlaneManager . Planes )
2017-12-16 12:01:07 +00:00
await destination . WriteLineAsync ( $" {plane.Name} @ {plane.ChunkSize} ({plane.LoadedChunks} / ~{plane.SoftLoadedChunkLimit} chunks loaded, {plane.UnloadableChunks} inactive, {plane.TotalChunks} total at last save)" ) ;
2017-12-15 23:40:38 +00:00
await destination . WriteLineAsync ( ) ;
await destination . WriteLineAsync ( $"Total {server.PlaneManager.Planes.Count}" ) ;
2017-12-16 11:00:26 +00:00
break ;
case "create" :
if ( commandParts . Length < 3 ) {
await destination . WriteLineAsync ( "Error: No name specified for the new plane!" ) ;
return ;
}
string newPlaneName = commandParts [ 2 ] . Trim ( ) ;
int chunkSize = server . PlaneManager . DefaultChunkSize ;
if ( commandParts . Length > = 4 )
chunkSize = int . Parse ( commandParts [ 3 ] ) ;
server . PlaneManager . CreatePlane ( new PlaneInfo (
newPlaneName ,
chunkSize
) ) ;
await destination . WriteLineAsync ( $"Created plane with name {newPlaneName} and chunk size {chunkSize}." ) ;
2017-12-16 12:01:07 +00:00
break ;
case "status" :
if ( commandParts . Length < 3 ) {
await destination . WriteLineAsync ( "Error: No plane name specified!" ) ;
return ;
}
string targetPlaneName = commandParts [ 2 ] . Trim ( ) ;
Plane targetPlane = server . PlaneManager . GetByName ( targetPlaneName ) ;
if ( targetPlane = = null ) {
await destination . WriteLineAsync ( $"Error: A plane with the name {targetPlaneName} doesn't exist." ) ;
return ;
}
await destination . WriteLineAsync ( $"Name: {targetPlane.Name}" ) ;
await destination . WriteLineAsync ( $"Chunk size: {targetPlane.ChunkSize}" ) ;
await destination . WriteLineAsync ( $"Loaded chunks: {targetPlane.LoadedChunks}" ) ;
await destination . WriteLineAsync ( $"Unloaded chunks: {targetPlane.TotalChunks - targetPlane.LoadedChunks}" ) ;
await destination . WriteLineAsync ( $"Total chunks: {targetPlane.TotalChunks}" ) ;
await destination . WriteLineAsync ( $"Primary chunk area size: {targetPlane.PrimaryChunkAreaSize}" ) ;
await destination . WriteLineAsync ( $"Min unloadeable chunks: {targetPlane.MinUnloadeableChunks}" ) ;
await destination . WriteLineAsync ( $"Soft loaded chunk limit: {targetPlane.SoftLoadedChunkLimit}" ) ;
2017-12-15 23:40:38 +00:00
break ;
default :
await destination . WriteLineAsync ( $"Error: Unknown sub-action {subAction}." ) ;
break ;
}
2017-12-15 23:36:25 +00:00
break ;
2017-12-16 12:16:43 +00:00
case "clients" :
foreach ( NibriClient client in server . AppServer . NibriClients ) {
await destination . WriteLineAsync ( $"{client.Id}: {client.Name} from {client.RemoteEndpoint}, on {client.CurrentPlane.Name} looking at {client.CurrentViewPort}" ) ;
}
await destination . WriteLineAsync ( ) ;
await destination . WriteLineAsync ( $"Total {server.AppServer.ClientCount} clients" ) ;
break ;
2017-12-16 12:01:07 +00:00
/ * case "chunk" :
if ( commandParts . Length < 2 ) {
await destination . WriteLineAsync ( "Error: No sub-action specified." ) ;
break ;
}
string chunkSubAction = commandParts [ 1 ] . Trim ( ) ;
switch ( chunkSubAction )
{
case "list" :
if ( commandParts . Length < 3 ) {
await destination . WriteLineAsync ( "Error: No plane specified to list the chunks of!" ) ;
return ;
}
Plane plane = server . PlaneManager . GetByName ( commandParts [ 2 ] . Trim ( ) ) ;
foreach ( Chunk chunk in plane .
break ;
default :
await destination . WriteLineAsync ( $"Error: Unknown sub-action {chunkSubAction}." ) ;
break ;
}
break ; * /
2017-12-15 23:36:25 +00:00
default :
await destination . WriteLineAsync ( $"Error: Unrecognised command {commandName}" ) ;
break ;
}
}
public static string BytesToString ( long byteCount )
{
string [ ] suf = { "B" , "KB" , "MB" , "GB" , "TB" , "PB" , "EB" } ; // Longs run out around EB
if ( byteCount = = 0 )
return "0" + suf [ 0 ] ;
long bytes = Math . Abs ( byteCount ) ;
int place = ( int ) Math . Floor ( Math . Log ( bytes , 1024 ) ) ;
double num = Math . Round ( bytes / Math . Pow ( 1024 , place ) , 1 ) ;
return ( Math . Sign ( byteCount ) * num ) . ToString ( ) + suf [ place ] ;
}
}
}