diff --git a/TGCommandLine/AdminCommands.cs b/TGCommandLine/AdminCommands.cs index 90c50f0b6e..aedddd15b1 100644 --- a/TGCommandLine/AdminCommands.cs +++ b/TGCommandLine/AdminCommands.cs @@ -4,7 +4,7 @@ using TGServiceInterface.Components; namespace TGCommandLine { - class AdminCommand : RootCommand + class AdminCommand : InstanceRootCommand { public AdminCommand() { diff --git a/TGCommandLine/BYONDCommands.cs b/TGCommandLine/BYONDCommands.cs index 875cca63a1..2666104e44 100644 --- a/TGCommandLine/BYONDCommands.cs +++ b/TGCommandLine/BYONDCommands.cs @@ -6,7 +6,7 @@ using TGServiceInterface.Components; namespace TGCommandLine { - class BYONDCommand : RootCommand + class BYONDCommand : InstanceRootCommand { public BYONDCommand() { diff --git a/TGCommandLine/ChatCommands.cs b/TGCommandLine/ChatCommands.cs index 268fc39d6a..314a8725a1 100644 --- a/TGCommandLine/ChatCommands.cs +++ b/TGCommandLine/ChatCommands.cs @@ -5,7 +5,7 @@ using TGServiceInterface.Components; namespace TGCommandLine { - class IRCCommand : RootCommand + class IRCCommand : InstanceRootCommand { public IRCCommand() { @@ -17,7 +17,7 @@ namespace TGCommandLine return "Manages the IRC bot"; } } - class DiscordCommand : RootCommand + class DiscordCommand : InstanceRootCommand { public DiscordCommand() { diff --git a/TGCommandLine/ConfigCommands.cs b/TGCommandLine/ConfigCommands.cs index d3cdb2d800..0aae903ed4 100644 --- a/TGCommandLine/ConfigCommands.cs +++ b/TGCommandLine/ConfigCommands.cs @@ -6,7 +6,7 @@ using TGServiceInterface.Components; namespace TGCommandLine { - class ConfigCommand : RootCommand + class ConfigCommand : InstanceRootCommand { public ConfigCommand() { diff --git a/TGCommandLine/DDCommands.cs b/TGCommandLine/DDCommands.cs index a0d073b9be..ef518c8911 100644 --- a/TGCommandLine/DDCommands.cs +++ b/TGCommandLine/DDCommands.cs @@ -5,7 +5,7 @@ using TGServiceInterface.Components; namespace TGCommandLine { - class DDCommand : RootCommand + class DDCommand : InstanceRootCommand { public DDCommand() { diff --git a/TGCommandLine/DMCommands.cs b/TGCommandLine/DMCommands.cs index acdd7bf686..cbc1ef6ae2 100644 --- a/TGCommandLine/DMCommands.cs +++ b/TGCommandLine/DMCommands.cs @@ -6,7 +6,7 @@ using TGServiceInterface.Components; namespace TGCommandLine { - class DMCommand : RootCommand + class DMCommand : InstanceRootCommand { public DMCommand() { diff --git a/TGCommandLine/InstanceRootCommand.cs b/TGCommandLine/InstanceRootCommand.cs new file mode 100644 index 0000000000..34685e8395 --- /dev/null +++ b/TGCommandLine/InstanceRootCommand.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using TGServiceInterface; + +namespace TGCommandLine +{ + abstract class InstanceRootCommand : RootCommand + { + public static Interface currentInterface; + public override ExitCode DoRun(IList parameters) + { + if (currentInterface.InstanceName == null) + { + OutputProc("Missing instance!"); + return ExitCode.BadCommand; + } + else + { + var res = currentInterface.ConnectToInstance(); + if (!res.HasFlag(ConnectivityLevel.Connected)) + { + OutputProc("Unable to connect to instance!"); + return ExitCode.ConnectionError; + } + else if (!res.HasFlag(ConnectivityLevel.Authenticated)) + { + OutputProc("The current user is not authorized to use this instance!"); + return ExitCode.ConnectionError; + } + } + return base.DoRun(parameters); + } + } +} diff --git a/TGCommandLine/Program.cs b/TGCommandLine/Program.cs index d3f46d17cd..d396240b4b 100644 --- a/TGCommandLine/Program.cs +++ b/TGCommandLine/Program.cs @@ -9,7 +9,7 @@ namespace TGCommandLine class Program { - static bool interactive = false; + static bool interactive = false, saidSrvVersion = false; static Interface currentInterface; static Command.ExitCode RunCommandLine(IList argsAsList) { @@ -83,6 +83,11 @@ namespace TGCommandLine SentVMMWarning = true; Console.WriteLine(error); } + else if (interactive && !saidSrvVersion) + { + Console.WriteLine("Connectd to service version: " + currentInterface.GetService().Version()); + saidSrvVersion = true; + } try { @@ -97,7 +102,6 @@ namespace TGCommandLine static void ReplaceInterface(Interface I) { - if (!interactive) I.SetBadCertificateHandler((message) => { @@ -110,6 +114,8 @@ namespace TGCommandLine I.SetBadCertificateHandler(BadCertificateInteractive); currentInterface = I; ConsoleCommand.Interface = I; + InstanceRootCommand.currentInterface = I; + saidSrvVersion = false; } public static string ReadLineSecure() @@ -156,21 +162,60 @@ namespace TGCommandLine return false; } + /// + /// Tries to set 's to , outputting appropriate messages + /// + /// The name of the to test + /// If , does not output on success + /// if a was achieved with , otherwise + static bool CheckInstanceConnectivity(string instanceName, bool silentSuccess) + { + var res = currentInterface.ConnectToInstance(instanceName); + if (!res.HasFlag(ConnectivityLevel.Connected)) + Console.WriteLine("Unable to connect to instance! Does it exist?"); + else if (!res.HasFlag(ConnectivityLevel.Authenticated)) + Console.WriteLine("The current user is not authorized to use this instance!"); + else + { + if(!silentSuccess) + Console.WriteLine("Successfully conected to instance!"); + return true; + } + return false; + } + static int Main(string[] args) { ReplaceInterface(new Interface()); Command.OutputProcVar.Value = Console.WriteLine; if (args.Length != 0) - return (int)RunCommandLine(new List(args)); - - Console.WriteLine("Type 'remote' to connect to a remote service"); + { + var argsAsList = new List(args); + for (var I = 0; I < argsAsList.Count - 1; ++I) + if (argsAsList[I].ToLower() == "--instance") + { + if (!CheckInstanceConnectivity(args[I + 1], true)) + return (int)Command.ExitCode.ConnectionError; + argsAsList.RemoveRange(I, 2); + break; + } + return (int)RunCommandLine(argsAsList); + } //interactive mode + + Console.WriteLine("Type 'instance' to connect to a server instance"); + Console.WriteLine("Type 'remote' to connect to a remote service"); + while (true) { Console.Write("Enter command: "); var NextCommand = Console.ReadLine(); switch (NextCommand.ToLower()) { + case "instance": + Console.Write("Enter instance name: "); + CheckInstanceConnectivity(Console.ReadLine(), false); + break; case "remote": SentVMMWarning = false; Console.Write("Enter server address: "); diff --git a/TGCommandLine/RootCommands.cs b/TGCommandLine/RootCommands.cs index 3f35d95518..4e6e3d4b69 100644 --- a/TGCommandLine/RootCommands.cs +++ b/TGCommandLine/RootCommands.cs @@ -12,6 +12,8 @@ namespace TGCommandLine var tmp = new List { new UpdateCommand(), new TestmergeCommand(), new RepoCommand(), new BYONDCommand(), new DMCommand(), new DDCommand(), new ConfigCommand(), new IRCCommand(), new DiscordCommand(), new AutoUpdateCommand(), new SetAutoUpdateCommand() }; if (I.ConnectToInstance().HasFlag(ConnectivityLevel.Administrator)) tmp.Add(new AdminCommand()); + if (I.ConnectionStatus().HasFlag(ConnectivityLevel.Administrator)) + tmp.Add(new ServiceCommand()); Children = tmp.ToArray(); } diff --git a/TGCommandLine/ServiceCommands.cs b/TGCommandLine/ServiceCommands.cs new file mode 100644 index 0000000000..efbc628e7d --- /dev/null +++ b/TGCommandLine/ServiceCommands.cs @@ -0,0 +1,433 @@ +using System; +using System.Collections.Generic; +using TGServiceInterface; + +namespace TGCommandLine +{ + /// + /// Used for managing the + /// + class ServiceCommand : RootCommand + { + /// + /// Construct a + /// + public ServiceCommand() + { + Keyword = "service"; + Children = new Command[] { new ServiceCreateInstanceCommand(), new ServiceDetachInstanceCommand(), new ServiceEnableInstanceCommand(), new ServiceImportInstanceCommand(), new ServiceListInstancesCommand(), new ServicePythonPathCommand(), new ServiceSetPythonPathCommand(), new ServiceSetRemoteAccessPortCommand(), new ServiceRemoteAccessPortCommand(), new ServiceDisableInstanceCommand(), new ServiceRenameInstanceCommand() }; + } + + /// + public override string GetHelpText() + { + return "Manage service wide settings"; + } + } + + /// + /// Command for calling + /// + class ServiceCreateInstanceCommand : ConsoleCommand + { + /// + /// Construct a + /// + public ServiceCreateInstanceCommand() + { + Keyword = "create-instance"; + RequiredParameters = 2; + } + + /// + public override string GetHelpText() + { + return "Creates a new instance at the given path"; + } + + /// + public override string GetArgumentString() + { + return " "; + } + + /// + protected override ExitCode Run(IList parameters) + { + var res = Interface.GetService().CreateInstance(parameters[0], parameters[1]); + if (res != null) + { + OutputProc(res); + return ExitCode.ServerError; + } + return ExitCode.Normal; + } + } + + /// + /// Command for calling + /// + class ServiceListInstancesCommand : ConsoleCommand + { + /// + /// Construct a + /// + public ServiceListInstancesCommand() + { + Keyword = "list-instances"; + } + + /// + public override string GetHelpText() + { + return "Lists all instances"; + } + + /// + protected override ExitCode Run(IList parameters) + { + foreach (var I in Interface.GetService().ListInstances()) + OutputProc(String.Format("{0} ({1}):\t{2}{3}", I.Name, I.Path, I.Enabled ? "Online" : "Offline", I.Enabled ? String.Format(" ({0})", I.LoggingID) : "")); + return ExitCode.Normal; + } + } + + /// + /// Command for calling + /// + class ServiceDetachInstanceCommand : ConsoleCommand + { + /// + /// Construct a + /// + public ServiceDetachInstanceCommand() + { + Keyword = "detach-instance"; + RequiredParameters = 1; + } + + /// + public override string GetHelpText() + { + return "Detaches an instance"; + } + + /// + public override string GetArgumentString() + { + return ""; + } + + /// + protected override ExitCode Run(IList parameters) + { + var res = Interface.GetService().DetachInstance(parameters[0]); + if (res != null) + { + OutputProc(res); + return ExitCode.ServerError; + } + return ExitCode.Normal; + } + } + + /// + /// Command for calling + /// + class ServiceImportInstanceCommand : ConsoleCommand + { + /// + /// Construct a + /// + public ServiceImportInstanceCommand() + { + Keyword = "import-instance"; + RequiredParameters = 1; + } + + /// + public override string GetHelpText() + { + return "Imports an instance (Chat settings will be lost if they are from a different windows installation)"; + } + + /// + public override string GetArgumentString() + { + return ""; + } + + /// + protected override ExitCode Run(IList parameters) + { + var res = Interface.GetService().ImportInstance(parameters[0]); + if (res != null) + { + OutputProc(res); + return ExitCode.ServerError; + } + return ExitCode.Normal; + } + } + + /// + /// Command for calling + /// + class ServicePythonPathCommand : ConsoleCommand + { + /// + /// Construct a + /// + public ServicePythonPathCommand() + { + Keyword = "python"; + } + + /// + public override string GetHelpText() + { + return "Displays configured path the service uses for python"; + } + + /// + protected override ExitCode Run(IList parameters) + { + var res = Interface.GetService().PythonPath(); + if (res != null) + { + OutputProc(res); + return ExitCode.ServerError; + } + return ExitCode.Normal; + } + } + + /// + /// Command for calling + /// + class ServiceSetPythonPathCommand : ConsoleCommand + { + /// + /// Construct a + /// + public ServiceSetPythonPathCommand() + { + Keyword = "set-python"; + RequiredParameters = 1; + } + + /// + public override string GetHelpText() + { + return "Sets the path to the python installation"; + } + + /// + public override string GetArgumentString() + { + return ""; + } + + /// + protected override ExitCode Run(IList parameters) + { + Interface.GetService().SetPythonPath(parameters[0]); + return ExitCode.Normal; + } + } + + /// + /// Command for calling with a parameter + /// + class ServiceEnableInstanceCommand : ConsoleCommand + { + /// + /// Construct a + /// + public ServiceEnableInstanceCommand() + { + Keyword = "enable-instance"; + RequiredParameters = 1; + } + + /// + public override string GetHelpText() + { + return "Enables the specified instance"; + } + + /// + public override string GetArgumentString() + { + return ""; + } + + /// + protected override ExitCode Run(IList parameters) + { + var res = Interface.GetService().SetInstanceEnabled(parameters[0], true); + if (res != null) + { + OutputProc(res); + return ExitCode.ServerError; + } + return ExitCode.Normal; + } + } + + /// + /// Command for calling with a parameter + /// + class ServiceDisableInstanceCommand : ConsoleCommand + { + /// + /// Construct a + /// + public ServiceDisableInstanceCommand() + { + Keyword = "disable-instance"; + RequiredParameters = 1; + } + + /// + public override string GetHelpText() + { + return "Disables the specified instance"; + } + + /// + public override string GetArgumentString() + { + return ""; + } + + /// + protected override ExitCode Run(IList parameters) + { + var res = Interface.GetService().SetInstanceEnabled(parameters[0], false); + if (res != null) + { + OutputProc(res); + return ExitCode.ServerError; + } + return ExitCode.Normal; + } + } + + /// + /// Command for calling + /// + class ServiceRemoteAccessPortCommand : ConsoleCommand + { + /// + /// Construct a + /// + public ServiceRemoteAccessPortCommand() + { + Keyword = "port"; + } + + /// + public override string GetHelpText() + { + return "Displays the service's remote access port"; + } + + /// + protected override ExitCode Run(IList parameters) + { + OutputProc(Interface.GetService().RemoteAccessPort().ToString()); + return ExitCode.Normal; + } + } + + /// + /// Command for calling + /// + class ServiceSetRemoteAccessPortCommand : ConsoleCommand + { + /// + /// Construct a + /// + public ServiceSetRemoteAccessPortCommand() + { + Keyword = "set-port"; + RequiredParameters = 1; + } + + /// + public override string GetHelpText() + { + return "Sets the service's remote access port"; + } + + /// + public override string GetArgumentString() + { + return ""; + } + + /// + protected override ExitCode Run(IList parameters) + { + ushort port; + try + { + port = Convert.ToUInt16(parameters[0]); + } + catch + { + OutputProc("Invalid port number!"); + return ExitCode.BadCommand; + } + + var res = Interface.GetService().SetRemoteAccessPort(port); + if (res != null) + { + OutputProc(res); + return ExitCode.ServerError; + } + OutputProc("Change will be applied after service restart"); + return ExitCode.Normal; + } + } + + /// + /// Command for calling + /// + class ServiceRenameInstanceCommand : ConsoleCommand + { + /// + /// Construct a + /// + public ServiceRenameInstanceCommand() + { + Keyword = "rename-instance"; + RequiredParameters = 1; + } + + /// + public override string GetHelpText() + { + return "Renames an instance. Will temporarily disable the instance if it is active"; + } + + /// + public override string GetArgumentString() + { + return " "; + } + + /// + protected override ExitCode Run(IList parameters) + { + var res = Interface.GetService().RenameInstance(parameters[0], parameters[1]); + if (res != null) + { + OutputProc(res); + return ExitCode.ServerError; + } + return ExitCode.Normal; + } + } +} diff --git a/TGCommandLine/TGCommandLine.csproj b/TGCommandLine/TGCommandLine.csproj index dc4522573e..aa3d32e639 100644 --- a/TGCommandLine/TGCommandLine.csproj +++ b/TGCommandLine/TGCommandLine.csproj @@ -48,11 +48,13 @@ + + diff --git a/TGServerService/Service.cs b/TGServerService/Service.cs index 6c6f0c897d..539d44826b 100644 --- a/TGServerService/Service.cs +++ b/TGServerService/Service.cs @@ -265,6 +265,7 @@ namespace TGServerService for (byte I = 1; I < 100; ++I) if (!UsedLoggingIDs.Contains(I)) { + UsedLoggingIDs.Add(I); return I; } } @@ -407,9 +408,21 @@ namespace TGServerService } /// - public IDictionary ListInstances() + public IList ListInstances() { - throw new NotImplementedException(); + var result = new List(); + lock (this) + foreach (var I in Properties.Settings.Default.InstancePaths) { + var ic = InstanceConfig.Load(I); + result.Add(new InstanceMetadata + { + Name = ic.Name, + Path = I, + Enabled = ic.Enabled, + LoggingID = (byte)(ic.Enabled ? ((ServerInstance)hosts[ic.Name].SingletonInstance).LoggingID : 0) + }); + } + return result; } /// @@ -428,6 +441,7 @@ namespace TGServerService try { var ic = new InstanceConfig(path); + ic.Name = Name; Directory.CreateDirectory(path); ic.Save(); Properties.Settings.Default.InstancePaths.Add(path); @@ -593,7 +607,7 @@ namespace TGServerService finally { if (ie) - result = SetInstanceEnabled(new_name, true) + " "+ result; + result = SetInstanceEnabled(new_name, true) + " " + result; } return result; } diff --git a/TGServiceInterface/Command.cs b/TGServiceInterface/Command.cs index c018a9c6b1..5355a0d55e 100644 --- a/TGServiceInterface/Command.cs +++ b/TGServiceInterface/Command.cs @@ -12,7 +12,7 @@ namespace TGServiceInterface /// /// Exit codes for s /// - public enum ExitCode + public enum ExitCode : int { /// /// The ran successfully diff --git a/TGServiceInterface/Components/Service.cs b/TGServiceInterface/Components/Service.cs index 695bae9849..e9057d957c 100644 --- a/TGServiceInterface/Components/Service.cs +++ b/TGServiceInterface/Components/Service.cs @@ -44,7 +44,7 @@ namespace TGServiceInterface.Components /// /// A of instance names relating to their paths [OperationContract] - IDictionary ListInstances(); + IList ListInstances(); /// /// Creates a new server instance diff --git a/TGServiceInterface/Enumerations.cs b/TGServiceInterface/Enumerations.cs index e342014b8b..5f65b463b0 100644 --- a/TGServiceInterface/Enumerations.cs +++ b/TGServiceInterface/Enumerations.cs @@ -19,11 +19,11 @@ namespace TGServiceInterface /// /// The connected user is authenticated /// - Authenticated = 2, + Authenticated = 2 | Connected, /// /// The connected user is an administrator /// - Administrator = 4, + Administrator = 4 | Authenticated, } /// diff --git a/TGServiceInterface/InstanceMetadata.cs b/TGServiceInterface/InstanceMetadata.cs new file mode 100644 index 0000000000..5a08e5b355 --- /dev/null +++ b/TGServiceInterface/InstanceMetadata.cs @@ -0,0 +1,32 @@ +using System.Runtime.Serialization; + +namespace TGServiceInterface +{ + /// + /// Metadata about an + /// + [DataContract] + public sealed class InstanceMetadata + { + /// + /// The name of the + /// + [DataMember] + public string Name { get; set; } + /// + /// The path of the + /// + [DataMember] + public string Path { get; set; } + /// + /// Whether or not the is enabled + /// + [DataMember] + public bool Enabled { get; set; } + /// + /// The logging ID of the . Will be 0 if is + /// + [DataMember] + public byte LoggingID { get; set; } + } +} diff --git a/TGServiceInterface/Interface.cs b/TGServiceInterface/Interface.cs index bda7c09afd..ec96caf32b 100644 --- a/TGServiceInterface/Interface.cs +++ b/TGServiceInterface/Interface.cs @@ -71,9 +71,9 @@ namespace TGServiceInterface readonly string HTTPSPassword; /// - /// Associated list of open s keyed by type. A in this list may close or fault at any time. Must be locked before being accessed + /// Associated list of open s keyed by type name. A in this list may close or fault at any time. Must be locked before being accessed /// - IDictionary ChannelFactoryCache = new Dictionary(); + IDictionary ChannelFactoryCache = new Dictionary(); /// /// Returns a of s that can be used with the service @@ -136,7 +136,7 @@ namespace TGServiceInterface return ConnectivityLevel.None; var prevInstance = InstanceName; if (prevInstance != instanceName) - CloseAllChannels(); + CloseAllChannels(false); InstanceName = instanceName; if (skipAuthChecks) return ConnectivityLevel.Connected; @@ -202,13 +202,28 @@ namespace TGServiceInterface /// /// Closes all s stored in and clears it /// - void CloseAllChannels() + /// If set to , doesn't clear the channels that are used by + void CloseAllChannels(bool includingRoot) { + string[] RootThings = { typeof(ITGSService).Name, 'S' + typeof(ITGConnectivity).Name }; lock (ChannelFactoryCache) { foreach (var I in ChannelFactoryCache) - CloseChannel(I.Value); - ChannelFactoryCache.Clear(); + { + if (RootThings.Contains(I.Key)) + continue; + var cf = I.Value; + try + { + cf.Closed += ChannelFactory_Closed; + cf.Close(); + } + catch + { + cf.Abort(); + } + ChannelFactoryCache.Remove(I); + } } } @@ -231,23 +246,6 @@ namespace TGServiceInterface return false; } - /// - /// Safely shuts down a single - /// - /// The to shutdown - static void CloseChannel(ChannelFactory cf) - { - try - { - cf.Closed += ChannelFactory_Closed; - cf.Close(); - } - catch - { - cf.Abort(); - } - } - /// /// Disposes a closed /// @@ -275,8 +273,10 @@ namespace TGServiceInterface { if (useInstanceName & InstanceName == null) throw new Exception("Instance not selected!"); - - var tot = typeof(T); + var actualToT = typeof(T); + var tot = actualToT.Name; + if (actualToT == typeof(ITGConnectivity) && !useInstanceName) + tot = 'S' + tot; ChannelFactory cf; lock (ChannelFactoryCache) @@ -413,7 +413,7 @@ namespace TGServiceInterface { if (disposing) { - CloseAllChannels(); + CloseAllChannels(true); } // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. diff --git a/TGServiceInterface/TGServiceInterface.csproj b/TGServiceInterface/TGServiceInterface.csproj index 42943a1d0d..ed915f7b4a 100644 --- a/TGServiceInterface/TGServiceInterface.csproj +++ b/TGServiceInterface/TGServiceInterface.csproj @@ -60,6 +60,7 @@ +